Search code examples
pythonif-statementfor-loopnested-loops

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>

Solution

  • 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])