pmorissette / bt

bt - flexible backtesting for Python
http://pmorissette.github.io/bt
MIT License
2.22k stars 425 forks source link

add relative tolerance to allocate outlay check #430

Closed 0xEljh closed 10 months ago

0xEljh commented 10 months ago

This PR is to address issues #376 #416 and #368 where the use of non-integer positions and floating point errors result in the np.isclose condition of allocate in SecurityBase to fail to be met, resulting in the "Potentially infinite loop detected..." error being thrown.

def allocate(self, amount, update=True):

       ...

        if not q == -self._position:
            full_outlay, _, _, _ = self.outlay(q)

            ...

            while not np.isclose(full_outlay, amount, rtol=TOL) and q != 0:
                dq_wout_considering_tx_costs = (full_outlay - amount) / (self._price * self.multiplier)
                q = q - dq_wout_considering_tx_costs

                ...

                i = i + 1
                if i > 1e4:
                    raise Exception(
                        "Potentially infinite loop detected. This occurred "
                        "while trying to reduce the amount of shares purchased"
                        " to respect the outlay <= amount rule. This is most "
                        "likely due to a commission function that outputs a "
                        "commission that is greater than the amount of cash "
                        "a short sale can raise."
                    )

As described in #368 and #416, setting rtol to a non-zero, minuscule value like rtol=1e-14 addresses this issue, though in my experience with this error, it sometimes needs to be relaxed up to rtol=1e12.

I think setting rtol=TOL where TOL=1e-16 as per its use in is_zero is a good first step towards resolving this issue.

def is_zero(x):
    """
    Test for zero that is robust against floating point precision errors
    """
    return abs(x) < TOL

The global value for TOL = 1e-16 is never used anywhere else and is perhaps best also applied here since they are both floating point issues.