feat: extract backtest iteration into generator

This commit is contained in:
Matthias
2024-08-12 10:19:33 +02:00
parent 980b81f009
commit f01e101447
+81 -68
View File
@@ -1388,6 +1388,25 @@ class Backtesting:
self._process_exit_order(order, trade, current_time, row, pair) self._process_exit_order(order, trade, current_time, row, pair)
return open_trade_count_start return open_trade_count_start
def time_pair_generator(
self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str]
):
"""
Backtest time and pair generator
"""
current_time = start_date + increment
self.progress.init_step(
BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
)
while current_time <= end_date:
is_first = True
for pair in pairs:
yield current_time, pair, is_first
is_first = False
self.progress.increment()
current_time += increment
def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]: def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
""" """
Implement backtesting functionality Implement backtesting functionality
@@ -1411,82 +1430,76 @@ class Backtesting:
# Indexes per pair, so some pairs are allowed to have a missing start. # Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = defaultdict(int) indexes: Dict = defaultdict(int)
current_time = start_date + self.timeframe_td
self.progress.init_step(
BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
)
# Loop timerange and get candle for each pair at that point in time # Loop timerange and get candle for each pair at that point in time
while current_time <= end_date: for current_time, pair, is_first in self.time_pair_generator(
open_trade_count_start = LocalTrade.bt_open_open_trade_count start_date, end_date, self.timeframe_td, list(data.keys())
self.check_abort() ):
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)( if is_first:
current_time=current_time open_trade_count_start = LocalTrade.bt_open_open_trade_count
) self.check_abort()
for i, pair in enumerate(data): strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
row_index = indexes[pair] current_time=current_time
row = self.validate_row(data, pair, row_index, current_time) )
if not row: row_index = indexes[pair]
continue row = self.validate_row(data, pair, row_index, current_time)
if not row:
continue
row_index += 1 row_index += 1
indexes[pair] = row_index indexes[pair] = row_index
self.dataprovider._set_dataframe_max_index(self.required_startup + row_index) self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
self.dataprovider._set_dataframe_max_date(current_time) self.dataprovider._set_dataframe_max_date(current_time)
current_detail_time: datetime = row[DATE_IDX].to_pydatetime() current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
trade_dir: Optional[LongShort] = self.check_for_trade_entry(row) trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
if ( if (
(trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0) (trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
and self.timeframe_detail and self.timeframe_detail
and pair in self.detail_data and pair in self.detail_data
): ):
# Spread out into detail timeframe. # Spread out into detail timeframe.
# Should only happen when we are either in a trade for this pair # Should only happen when we are either in a trade for this pair
# or when we got the signal for a new trade. # or when we got the signal for a new trade.
exit_candle_end = current_detail_time + self.timeframe_td exit_candle_end = current_detail_time + self.timeframe_td
detail_data = self.detail_data[pair] detail_data = self.detail_data[pair]
detail_data = detail_data.loc[ detail_data = detail_data.loc[
(detail_data["date"] >= current_detail_time) (detail_data["date"] >= current_detail_time)
& (detail_data["date"] < exit_candle_end) & (detail_data["date"] < exit_candle_end)
].copy() ].copy()
if len(detail_data) == 0: if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle # Fall back to "regular" data if no detail data was found for this candle
open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, open_trade_count_start, trade_dir
)
continue
detail_data.loc[:, "enter_long"] = row[LONG_IDX]
detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
is_first = True
current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist():
self.dataprovider._set_dataframe_max_date(current_time_det)
open_trade_count_start = self.backtest_loop(
det_row,
pair,
current_time_det,
end_date,
open_trade_count_start,
trade_dir,
is_first,
)
current_time_det += self.timeframe_detail_td
is_first = False
else:
self.dataprovider._set_dataframe_max_date(current_time)
open_trade_count_start = self.backtest_loop( open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, open_trade_count_start, trade_dir row, pair, current_time, end_date, open_trade_count_start, trade_dir
) )
continue
# Move time one configured time_interval ahead. detail_data.loc[:, "enter_long"] = row[LONG_IDX]
self.progress.increment() detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
current_time += self.timeframe_td detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
is_first = True
current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist():
self.dataprovider._set_dataframe_max_date(current_time_det)
open_trade_count_start = self.backtest_loop(
det_row,
pair,
current_time_det,
end_date,
open_trade_count_start,
trade_dir,
is_first,
)
current_time_det += self.timeframe_detail_td
is_first = False
else:
self.dataprovider._set_dataframe_max_date(current_time)
open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, open_trade_count_start, trade_dir
)
self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data) self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data)
self.wallets.update() self.wallets.update()