Carl Carl - 6 months ago 15
Python Question

conditional nested for loop executing code even when condition not satisfied

I have a nested loop structure to generate a large number of backtests by varying the value of 4 variables, oq, aq, lev, and val. The idea is to execute every combination of the variables below within the ranges provided.

Without a constraint, this loop would therefore execute a total of 5 * 6 * 5 * 5 = 750 times which at ~5-10 seconds each would take several hours. However, there is a constraint, which is that all the weights must sum to exactly 1 (tot_wgt). By adding an if statement, I hoped to simply discard such cases.

if (tot_wgt != 1):
continue


Unfortunately, the code still seems to execute sometimes when tot_wgt does not have a value of 1. This seems to happen every time the val loop has completed a cycle (and presumably also happens when each of the other 3 loops have completed a cycle).

Problem solved: I had an indentation error: my needed to be at the level of the if statement. But see the excellent answer on recognition of floating point numbers.

mom = 0
for oq in [0.3, 0.4, 0.5, 0.6, 0.7]:
for aq in [0.05, 0.1, 0.15, 0.2, 0.25, 0.3]:
for lev in [0.0, 0.05, 0.1, 0.15, 0.2]:
for val in [0.0, 0.05, 0.1, 0.15, 0.2]:

tot_wgt = oq + aq + lev + val + mom

if (tot_wgt != 1): #we only want to backtest where the weights add up to 1. If <1 or >1, simply skip
continue


<MAIN BACKTEST CODE HERE>

Answer

This is caused by the limitations of representing floating-point numbers in computer hardware as base 2 (binary) fractions. Refer to Floating Point Arithmetic: Issues and Limitations for the detailed description.

For instance, in your case,

>>> 0.7 + 0.2 + 0.0 + 0.1 + 0
0.9999999999999999

# more specific
>>> from decimal import Decimal
>>> Decimal(0.7 + 0.2 + 0.0 + 0.1 + 0)
Decimal('0.99999999999999988897769753748434595763683319091796875')

As you can see, this is not equvelent to 1. One simple way to resolve it is just to replace the line if (tot_wgt != 1): with,

if abs(tot_wgt - 1) < 0.0001 : 

Python 3.5 adds the math.isclose for testing approximate equality. For an earlier version, the equivalent function is as follows.

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

Use numpy.isclose to test if two arrays are element-wise equal within a tolerance.

# Usage
numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

# An example
>>> import numpy as np
>>> np.isclose([1e10,1e-7], [1.00001e10,1e-8])
array([True, False])