From a11d579bc2338fa3087fc5f7d079fa43314cecd5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Oct 2022 16:22:55 +0200 Subject: [PATCH] Verify order fills on "detail" timeframe --- freqtrade/optimize/backtesting.py | 69 ++++++++++++++++--------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4d98f1f5a..b3395a2c3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -688,10 +688,11 @@ class Backtesting: trade.orders.append(order) return trade - def _get_exit_trade_entry(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: + def _get_exit_trade_entry( + self, trade: LocalTrade, row: Tuple, is_first: bool) -> Optional[LocalTrade]: exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() - if self.trading_mode == TradingMode.FUTURES: + if is_first and self.trading_mode == TradingMode.FUTURES: trade.funding_fees = self.exchange.calculate_funding_fees( self.futures_data[trade.pair], amount=trade.amount, @@ -700,32 +701,7 @@ class Backtesting: close_date=exit_candle_time, ) - if self.timeframe_detail and trade.pair in self.detail_data: - exit_candle_end = exit_candle_time + timedelta(minutes=self.timeframe_min) - - detail_data = self.detail_data[trade.pair] - detail_data = detail_data.loc[ - (detail_data['date'] >= exit_candle_time) & - (detail_data['date'] < exit_candle_end) - ].copy() - if len(detail_data) == 0: - # Fall back to "regular" data if no detail data was found for this candle - return self._get_exit_trade_entry_for_candle(trade, row) - 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] - for det_row in detail_data[HEADERS].values.tolist(): - res = self._get_exit_trade_entry_for_candle(trade, det_row) - if res: - return res - - return None - - else: - return self._get_exit_trade_entry_for_candle(trade, row) + return self._get_exit_trade_entry_for_candle(trade, row) def get_valid_price_and_stake( self, pair: str, row: Tuple, propose_rate: float, stake_amount: float, @@ -1070,7 +1046,7 @@ class Backtesting: def backtest_loop( self, row: Tuple, pair: str, current_time: datetime, end_date: datetime, - max_open_trades: int, open_trade_count_start: int) -> int: + max_open_trades: int, open_trade_count_start: int, is_first: bool = True) -> int: """ NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized. @@ -1088,9 +1064,11 @@ class Backtesting: # without positionstacking, we can only have one open trade per pair. # max_open_trades must be respected # don't open on the last row + # We only open trades on the initial candle. trade_dir = self.check_for_trade_entry(row) if ( (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0) + and is_first and self.trade_slot_available(max_open_trades, open_trade_count_start) and current_time != end_date and trade_dir is not None @@ -1116,7 +1094,7 @@ class Backtesting: # 4. Create exit orders (if any) if not trade.open_order_id: - self._get_exit_trade_entry(trade, row) # Place exit order if necessary + self._get_exit_trade_entry(trade, row, is_first) # Place exit order if necessary # 5. Process exit orders. order = trade.select_order(trade.exit_side, is_open=True) @@ -1167,7 +1145,6 @@ class Backtesting: self.progress.init_step(BacktestState.BACKTEST, int( (end_date - start_date) / timedelta(minutes=self.timeframe_min))) - # Loop timerange and get candle for each pair at that point in time while current_time <= end_date: open_trade_count_start = LocalTrade.bt_open_open_trade_count @@ -1181,9 +1158,35 @@ class Backtesting: row_index += 1 indexes[pair] = row_index self.dataprovider._set_dataframe_max_index(row_index) + current_detail_time: datetime = row[DATE_IDX].to_pydatetime() + if self.timeframe_detail and pair in self.detail_data: + exit_candle_end = current_detail_time + timedelta(minutes=self.timeframe_min) - open_trade_count_start = self.backtest_loop( - row, pair, current_time, end_date, max_open_trades, open_trade_count_start) + detail_data = self.detail_data[pair] + detail_data = detail_data.loc[ + (detail_data['date'] >= current_detail_time) & + (detail_data['date'] < exit_candle_end) + ].copy() + if len(detail_data) == 0: + # 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, max_open_trades, + open_trade_count_start) + 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 + for det_row in detail_data[HEADERS].values.tolist(): + open_trade_count_start = self.backtest_loop( + det_row, pair, current_time, end_date, max_open_trades, + open_trade_count_start, is_first) + is_first = False + else: + open_trade_count_start = self.backtest_loop( + row, pair, current_time, end_date, max_open_trades, open_trade_count_start) # Move time one configured time_interval ahead. self.progress.increment()