diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 14823722e..3df926371 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -70,7 +70,7 @@ This loop will be repeated again and again until the bot is stopped. * Determine stake size by calling the `custom_stake_amount()` callback. * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested. * Call `custom_stoploss()` and `custom_exit()` to find custom exit points. - * For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). + * For exits based on exit-signal, custom-exit and partial exits: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). * Generate backtest report output !!! Note diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 8d46f42e1..32ad1b288 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -423,7 +423,7 @@ class AwesomeStrategy(IStrategy): !!! Warning "Backtesting" Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range. Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle. - `custom_exit_price()` is only called for sells of type exit_signal and Custom exit. All other exit-types will use regular backtesting prices. + `custom_exit_price()` is only called for sells of type exit_signal, Custom exit and partial exits. All other exit-types will use regular backtesting prices. ## Custom order timeout rules diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ff30dbc2a..57b272e86 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -554,7 +554,8 @@ class Backtesting: if remaining < min_stake: # Remaining stake is too low to be sold. return trade - pos_trade = self._exit_trade(trade, row, current_rate, amount) + exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT) + pos_trade = self._get_exit_for_signal(trade, row, exit_, amount) if pos_trade is not None: order = pos_trade.orders[-1] if self._get_order_filled(order.price, row): @@ -589,14 +590,15 @@ class Backtesting: return t return None - def _get_exit_for_signal(self, trade: LocalTrade, row: Tuple, - exit_: ExitCheckTuple) -> Optional[LocalTrade]: + def _get_exit_for_signal( + self, trade: LocalTrade, row: Tuple, exit_: ExitCheckTuple, + 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 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) try: close_rate = self._get_close_rate(row, trade, exit_, trade_dur) @@ -605,7 +607,8 @@ class Backtesting: # call the custom exit price,with default value as previous close_rate current_profit = trade.calc_profit_ratio(close_rate) order_type = self.strategy.order_types['exit'] - if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT): + if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT, + ExitType.PARTIAL_EXIT): # Checks and adds an exit tag, after checking that the length of the # row has the length for an exit tag column if ( @@ -633,22 +636,23 @@ class Backtesting: # Confirm trade exit: time_in_force = self.strategy.order_time_in_force['exit'] - if (exit_.exit_type != ExitType.LIQUIDATION and not strategy_safe_wrapper( - self.strategy.confirm_trade_exit, default_retval=True)( - pair=trade.pair, - trade=trade, # type: ignore[arg-type] - order_type=order_type, - amount=trade.amount, - rate=close_rate, - time_in_force=time_in_force, - sell_reason=exit_reason, # deprecated - exit_reason=exit_reason, - current_time=exit_candle_time)): + if (exit_.exit_type not in (ExitType.LIQUIDATION, ExitType.PARTIAL_EXIT) + and not strategy_safe_wrapper( + self.strategy.confirm_trade_exit, default_retval=True)( + pair=trade.pair, + trade=trade, # type: ignore[arg-type] + order_type=order_type, + amount=amount_, + rate=close_rate, + time_in_force=time_in_force, + sell_reason=exit_reason, # deprecated + exit_reason=exit_reason, + current_time=exit_candle_time)): return None trade.exit_reason = exit_reason - return self._exit_trade(trade, row, close_rate, trade.amount) + return self._exit_trade(trade, row, close_rate, amount_) return None def _exit_trade(self, trade: LocalTrade, sell_row: Tuple,