bt icon indicating copy to clipboard operation
bt copied to clipboard

Potentially infinite loop detected with 2 backtests

Open andrew-rosca opened this issue 2 years ago • 1 comments

I have a relatively simple strategy which takes parameters (moving average period). Different parameter values result in a different strategy object below.

When I run separate backtests for the strategy, they run fine. However, when I attempt to run multiple backtests, they fail.

What could cause this? My commissions are 0.

#strat1, strat2 are constructed separately; same logic, just different moving average periods.

backtest1 = bt.Backtest(strat1, data, commissions=None, integer_positions=False)
backtest2 = bt.Backtest(strat2, data, commissions=None, integer_positions=False)
bt.run(backtest1)
#succeeds
bt.run(backtest2)
#succeeds
bt.run(backtest1, backtest2)

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.

Interestingly, If I re-run that last line multiple times, it sometimes succeeds.

What could cause this?

Here's a video recording of the call succeeding after multiple attempts in Jupyter: https://www.loom.com/share/94db73797da5465ea55cd0b4a9270b26

andrew-rosca avatar Jul 18 '22 22:07 andrew-rosca

After experimenting with it some more: it seems to always succeed on the 3rd try.

andrew-rosca avatar Jul 19 '22 01:07 andrew-rosca

Personally what I've found as a fix is to add a tolerance to the np.isclose check in the allocate method of SecurityBase.

TOL = 1e-12

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

                if self.integer_positions:
                    q = math.floor(q)

                full_outlay, _, _, _ = self.outlay(q)

TOL can be an extremely small value. I've not checked how far I can push it.

@timkpaine Do you think this is an acceptable fix?

0xEljh avatar Nov 29 '23 14:11 0xEljh