From b65fa98cee414f0ae7ae753a6093154a66c052a1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Oct 2023 19:45:00 +0200 Subject: [PATCH] Simplify backtesting by using current_time more consequently --- freqtrade/optimize/backtesting.py | 33 ++++++++++--------- tests/optimize/test_backtesting.py | 4 +-- .../test_backtesting_adjust_position.py | 11 ++++--- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 38150be37..4986f7b55 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -525,10 +525,10 @@ class Backtesting: # This should not be reached... return row[OPEN_IDX] - def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple - ) -> LocalTrade: + def _get_adjust_trade_entry_for_candle( + self, trade: LocalTrade, row: Tuple, current_time: datetime + ) -> LocalTrade: current_rate = row[OPEN_IDX] - current_date = row[DATE_IDX].to_pydatetime() current_profit = trade.calc_profit_ratio(current_rate) min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) @@ -536,7 +536,7 @@ class Backtesting: stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None, supress_error=True)( trade=trade, # type: ignore[arg-type] - current_time=current_date, current_rate=current_rate, + current_time=current_time, current_rate=current_rate, current_profit=current_profit, min_stake=min_stake, max_stake=min(max_stake, stake_available), current_entry_rate=current_rate, current_exit_rate=current_rate, @@ -569,10 +569,10 @@ class Backtesting: # Remaining stake is too low to be sold. return trade exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT) - pos_trade = self._get_exit_for_signal(trade, row, exit_, amount) + pos_trade = self._get_exit_for_signal(trade, row, exit_, current_time, amount) if pos_trade is not None: order = pos_trade.orders[-1] - if self._try_close_open_order(order, trade, current_date, row): + if self._try_close_open_order(order, trade, current_time, row): trade.recalc_trade_from_orders() self.wallets.update() return pos_trade @@ -615,11 +615,11 @@ class Backtesting: def _get_exit_for_signal( self, trade: LocalTrade, row: Tuple, exit_: ExitCheckTuple, + current_time: datetime, amount: Optional[float] = None) -> Optional[LocalTrade]: - exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() if exit_.exit_flag: - trade.close_date = exit_candle_time + trade.close_date = current_time exit_reason = exit_.exit_reason amount_ = amount if amount is not None else trade.amount trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) @@ -647,7 +647,7 @@ class Backtesting: default_retval=close_rate)( pair=trade.pair, trade=trade, # type: ignore[arg-type] - current_time=exit_candle_time, + current_time=current_time, proposed_rate=close_rate, current_profit=current_profit, exit_tag=exit_reason) if rate != close_rate: @@ -673,7 +673,7 @@ class Backtesting: time_in_force=time_in_force, sell_reason=exit_reason, # deprecated exit_reason=exit_reason, - current_time=exit_candle_time)): + current_time=current_time)): return None trade.exit_reason = exit_reason @@ -714,8 +714,9 @@ class Backtesting: trade.orders.append(order) return trade - def _check_trade_exit(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: - exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() + def _check_trade_exit( + self, trade: LocalTrade, row: Tuple, current_time: datetime + ) -> Optional[LocalTrade]: if self.trading_mode == TradingMode.FUTURES: trade.set_funding_fees( @@ -724,13 +725,13 @@ class Backtesting: amount=trade.amount, is_short=trade.is_short, open_date=trade.date_last_filled_utc, - close_date=exit_candle_time + close_date=current_time ) ) # Check if we need to adjust our current positions if self.strategy.position_adjustment_enable: - trade = self._get_adjust_trade_entry_for_candle(trade, row) + trade = self._get_adjust_trade_entry_for_candle(trade, row, current_time) enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX] exit_sig = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX] @@ -740,7 +741,7 @@ class Backtesting: low=row[LOW_IDX], high=row[HIGH_IDX] ) for exit_ in exits: - t = self._get_exit_for_signal(trade, row, exit_) + t = self._get_exit_for_signal(trade, row, exit_, current_time) if t: return t return None @@ -1147,7 +1148,7 @@ class Backtesting: # 4. Create exit orders (if any) if not trade.has_open_orders: - self._check_trade_exit(trade, row) # Place exit order if necessary + self._check_trade_exit(trade, row, current_time) # Place exit order if necessary # 5. Process exit orders. order = trade.select_order(trade.exit_side, is_open=True) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ac409bf71..69fb51505 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -665,7 +665,7 @@ def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None: ] # No data available. - res = backtesting._check_trade_exit(trade, row_sell) + res = backtesting._check_trade_exit(trade, row_sell, row_sell[0].to_pydatetime()) assert res is not None assert res.exit_reason == ExitType.ROI.value assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) @@ -678,7 +678,7 @@ def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None: [], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', 'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']) - res = backtesting._check_trade_exit(trade, row) + res = backtesting._check_trade_exit(trade, row, row[0].to_pydatetime()) assert res is None diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 99e1ec812..ad1f31068 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -133,6 +133,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera ] backtesting.strategy.leverage = MagicMock(return_value=leverage) trade = backtesting._enter_trade(pair, row=row, direction='long') + current_time = row[0].to_pydatetime() assert trade assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.amount) == 47.61904762 * leverage @@ -140,7 +141,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera backtesting.strategy.adjust_trade_position = MagicMock(return_value=None) assert pytest.approx(trade.liquidation_price) == (0.10278333 if leverage == 1 else 1.2122249) - trade = backtesting._get_adjust_trade_entry_for_candle(trade, row) + trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.amount) == 47.61904762 * leverage @@ -148,7 +149,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera # Increase position by 100 backtesting.strategy.adjust_trade_position = MagicMock(return_value=100) - trade = backtesting._get_adjust_trade_entry_for_candle(trade, row) + trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 200.0 @@ -159,7 +160,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera # Reduce by more than amount - no change to trade. backtesting.strategy.adjust_trade_position = MagicMock(return_value=-500) - trade = backtesting._get_adjust_trade_entry_for_candle(trade, row) + trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 200.0 @@ -170,7 +171,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera # Reduce position by 50 backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100) - trade = backtesting._get_adjust_trade_entry_for_candle(trade, row) + trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 100.0 @@ -182,7 +183,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera # Adjust below minimum backtesting.strategy.adjust_trade_position = MagicMock(return_value=-99) - trade = backtesting._get_adjust_trade_entry_for_candle(trade, row) + trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 100.0