Fix backtesting exception when no data is available for a pair (new approach)

This commit is contained in:
mrpabloyeah
2025-11-24 10:43:00 +01:00
parent f98efdbe07
commit 77e8a53572
3 changed files with 19 additions and 10 deletions

View File

@@ -126,6 +126,7 @@ class Backtesting:
self.config["dry_run"] = True
self.price_pair_prec: dict[str, Series] = {}
self.available_pairs: list[str] = []
self.run_ids: dict[str, str] = {}
self.strategylist: list[IStrategy] = []
self.all_bt_content: dict[str, BacktestContentType] = {}
@@ -176,7 +177,8 @@ class Backtesting:
self._validate_pairlists_for_backtesting()
self.dataprovider.add_pairlisthandler(self.pairlists)
self.pairlists.refresh_pairlist()
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
self.pairlists.refresh_pairlist(only_first=self.dynamic_pairlist)
if len(self.pairlists.whitelist) == 0:
raise OperationalException("No pair in whitelist.")
@@ -211,7 +213,6 @@ class Backtesting:
self._can_short = self.trading_mode != TradingMode.SPOT
self._position_stacking: bool = self.config.get("position_stacking", False)
self.enable_protections: bool = self.config.get("enable_protections", False)
self.dynamic_pairlist: bool = self.config.get("enable_dynamic_pairlist", False)
migrate_data(config, self.exchange)
self.init_backtest()
@@ -335,10 +336,12 @@ class Backtesting:
self.progress.set_new_value(1)
self._load_bt_data_detail()
self.price_pair_prec = {}
for pair in self.pairlists.whitelist:
if pair in data:
# Load price precision logic
self.price_pair_prec[pair] = get_tick_size_over_time(data[pair])
self.available_pairs.append(pair)
return data, self.timerange
def _load_bt_data_detail(self) -> None:
@@ -1425,7 +1428,7 @@ class Backtesting:
# Row is treated as "current incomplete candle".
# entry / exit signals are shifted by 1 to compensate for this.
row = data[pair][row_index]
except (IndexError, KeyError):
except IndexError:
# missing Data for one pair at the end.
# Warnings for this are shown during data loading
return None
@@ -1587,7 +1590,7 @@ class Backtesting:
self.check_abort()
if self.dynamic_pairlist and self.pairlists:
self.pairlists.refresh_pairlist()
self.pairlists.refresh_pairlist(pairs=self.available_pairs)
pairs = self.pairlists.whitelist
# Reset open trade count for this candle

View File

@@ -133,7 +133,7 @@ class PairListManager(LoggingMixin):
def _get_cached_tickers(self) -> Tickers:
return self._exchange.get_tickers()
def refresh_pairlist(self) -> None:
def refresh_pairlist(self, only_first: bool = False, pairs: list[str] | None = None) -> None:
"""Run pairlist through all configured Pairlist Handlers."""
# Tickers should be cached to avoid calling the exchange on each call.
tickers: dict = {}
@@ -143,10 +143,16 @@ class PairListManager(LoggingMixin):
# Generate the pairlist with first Pairlist Handler in the chain
pairlist = self._pairlist_handlers[0].gen_pairlist(tickers)
# Process all Pairlist Handlers in the chain
# except for the first one, which is the generator.
for pairlist_handler in self._pairlist_handlers[1:]:
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
if pairs:
for pair in pairlist:
if pair not in pairs:
pairlist.remove(pair)
if not only_first:
# Process all Pairlist Handlers in the chain
# except for the first one, which is the generator.
for pairlist_handler in self._pairlist_handlers[1:]:
pairlist = pairlist_handler.filter_pairlist(pairlist, tickers)
# Validation against blacklist happens after the chain of Pairlist Handlers
# to ensure blacklist is respected.

View File

@@ -2772,7 +2772,7 @@ def test_time_pair_generator_open_trades_first(mocker, default_conf, dynamic_pai
dummy_row = (end_date, 1.0, 1.1, 0.9, 1.0, 0, 0, 0, 0, None, None)
data = {pair: [dummy_row] for pair in pairs}
def mock_refresh(self):
def mock_refresh(self, **kwargs):
# Simulate shuffle
self._whitelist = pairs[::-1] # ['ETH/BTC', 'NEO/BTC', 'LTC/BTC', 'XRP/BTC']