fix: Don't allow backtesting to exceed max_open_trades in odd edge-cases

closes #10853
This commit is contained in:
Matthias
2024-10-29 07:13:02 +01:00
parent 56df7ad9fc
commit 26a59e2cc5
2 changed files with 17 additions and 1 deletions

View File

@@ -1102,6 +1102,7 @@ class Backtesting:
fee_close=self.fee,
is_open=True,
enter_tag=entry_tag,
timeframe=self.timeframe_min,
exchange=self._exchange_name,
is_short=is_short,
trading_mode=self.trading_mode,
@@ -1357,7 +1358,7 @@ class Backtesting:
and (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0)
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
):
if self.trade_slot_available(LocalTrade.bt_open_open_trade_count):
if self.trade_slot_available(LocalTrade.bt_open_open_trade_count_candle):
trade = self._enter_trade(pair, row, trade_dir)
if trade:
self.wallets.update()
@@ -1431,6 +1432,10 @@ class Backtesting:
):
if is_first_call:
self.check_abort()
# Reset open trade count for this candle
# Critical to avoid exceeding max_open_trades in backtesting
# when timeframe-detail is used and trades close within the opening candle.
LocalTrade.bt_open_open_trade_count_candle = LocalTrade.bt_open_open_trade_count
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
current_time=current_time
)

View File

@@ -379,6 +379,7 @@ class LocalTrade:
# Copy of trades_open - but indexed by pair
bt_trades_open_pp: dict[str, list["LocalTrade"]] = defaultdict(list)
bt_open_open_trade_count: int = 0
bt_open_open_trade_count_candle: int = 0
bt_total_profit: float = 0
realized_profit: float = 0
@@ -747,6 +748,7 @@ class LocalTrade:
LocalTrade.bt_trades_open = []
LocalTrade.bt_trades_open_pp = defaultdict(list)
LocalTrade.bt_open_open_trade_count = 0
LocalTrade.bt_open_open_trade_count_candle = 0
LocalTrade.bt_total_profit = 0
def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None:
@@ -1442,6 +1444,11 @@ class LocalTrade:
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
if (trade.close_date_utc - trade.open_date_utc) > timedelta(minutes=trade.timeframe):
# Only subtract trades that are open for more than 1 candle
# To avoid exceeding max_open_trades.
# Must be reset at the start of every candle during backesting.
LocalTrade.bt_open_open_trade_count_candle -= 1
LocalTrade.bt_trades.append(trade)
LocalTrade.bt_total_profit += trade.close_profit_abs
@@ -1451,6 +1458,7 @@ class LocalTrade:
LocalTrade.bt_trades_open.append(trade)
LocalTrade.bt_trades_open_pp[trade.pair].append(trade)
LocalTrade.bt_open_open_trade_count += 1
LocalTrade.bt_open_open_trade_count_candle += 1
else:
LocalTrade.bt_trades.append(trade)
@@ -1459,6 +1467,9 @@ class LocalTrade:
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
# TODO: The below may have odd behavior in case of canceled entries
# It might need to be removed so the trade "counts" as open for this candle.
LocalTrade.bt_open_open_trade_count_candle -= 1
@staticmethod
def get_open_trades() -> list[Any]: