diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ce09e715e..c620e1a84 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -856,14 +856,14 @@ class FreqtradeBot(LoggingMixin): """ Check and execute sell """ - should_sell = self.strategy.should_sell( + should_exit: SellCheckTuple = self.strategy.should_exit( trade, sell_rate, datetime.now(timezone.utc), buy, sell, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) - if should_sell.sell_flag: - logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') - self.execute_sell(trade, sell_rate, should_sell) + if should_exit.sell_flag: + logger.info(f'Executing Sell for {trade.pair}. Reason: {should_exit.sell_type}') + self.execute_sell(trade, sell_rate, should_exit) return True return False diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 100cf6548..c3cd5b114 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -332,12 +332,13 @@ class Backtesting: return sell_row[OPEN_IDX] def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: - # TODO: short exits sell_candle_time = sell_row[DATE_IDX].to_pydatetime() - sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore - sell_candle_time, buy=sell_row[LONG_IDX], - sell=sell_row[ELONG_IDX], - low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]) + sell = self.strategy.should_exit( + trade, sell_row[OPEN_IDX], sell_candle_time, # type: ignore + enter_long=sell_row[LONG_IDX], enter_short=sell_row[SHORT_IDX], + exit_long=sell_row[ELONG_IDX], exit_short=sell_row[ESHORT_IDX], + low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX] + ) if sell.sell_flag: trade.close_date = sell_candle_time diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 63217df68..1aa9d3867 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -614,8 +614,10 @@ class IStrategy(ABC, HyperStrategyMixin): else: return False - def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, - sell: bool, low: float = None, high: float = None, + def should_exit(self, trade: Trade, rate: float, date: datetime, *, + enter_long: bool, enter_short: bool, + exit_long: bool, exit_short: bool, + low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: """ This function evaluates if one of the conditions required to trigger a sell/exit_short @@ -625,6 +627,10 @@ class IStrategy(ABC, HyperStrategyMixin): :param force_stoploss: Externally provided stoploss :return: True if trade should be exited, False otherwise """ + + enter = enter_short if trade.is_short else enter_long + exit_ = exit_short if trade.is_short else exit_long + current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) @@ -639,7 +645,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_profit = trade.calc_profit_ratio(current_rate) # if enter signal and ignore_roi is set, we don't need to evaluate min_roi. - roi_reached = (not (buy and self.ignore_roi_if_buy_signal) + roi_reached = (not (enter and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date)) @@ -652,8 +658,8 @@ class IStrategy(ABC, HyperStrategyMixin): if (self.sell_profit_only and current_profit <= self.sell_profit_offset): # sell_profit_only and profit doesn't reach the offset - ignore sell signal pass - elif self.use_sell_signal and not buy: - if sell: + elif self.use_sell_signal and not enter: + if exit_: sell_signal = SellType.SELL_SIGNAL else: trade_type = "exit_short" if trade.is_short else "sell" @@ -712,10 +718,10 @@ class IStrategy(ABC, HyperStrategyMixin): # Initiate stoploss with open_rate. Does nothing if stoploss is already set. trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) - dir_correct = ( - trade.stop_loss < (low or current_rate) and not trade.is_short or - trade.stop_loss > (low or current_rate) and trade.is_short - ) + dir_correct = (trade.stop_loss < (low or current_rate) + if not trade.is_short else + trade.stop_loss > (high or current_rate) + ) if self.use_custom_stoploss and dir_correct: stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None @@ -735,6 +741,7 @@ class IStrategy(ABC, HyperStrategyMixin): sl_offset = self.trailing_stop_positive_offset # Make sure current_profit is calculated using high for backtesting. + # TODO-lev: Check this function - high / low usage must be inversed for short trades! high_profit = current_profit if not high else trade.calc_profit_ratio(high) # Don't update stoploss if trailing_only_offset_is_reached is true. diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index af603e611..bfdf88dbb 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -452,27 +452,39 @@ def test_custom_sell(default_conf, fee, caplog) -> None: ) now = arrow.utcnow().datetime - res = strategy.should_sell(trade, 1, now, False, False, None, None, 0) + res = strategy.should_exit(trade, 1, now, + enter_long=False, enter_short=False, + exit_long=False, exit_short=False, + low=None, high=None) assert res.sell_flag is False assert res.sell_type == SellType.NONE strategy.custom_sell = MagicMock(return_value=True) - res = strategy.should_sell(trade, 1, now, False, False, None, None, 0) + res = strategy.should_exit(trade, 1, now, + enter_long=False, enter_short=False, + exit_long=False, exit_short=False, + low=None, high=None) assert res.sell_flag is True assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_reason == 'custom_sell' strategy.custom_sell = MagicMock(return_value='hello world') - res = strategy.should_sell(trade, 1, now, False, False, None, None, 0) + res = strategy.should_exit(trade, 1, now, + enter_long=False, enter_short=False, + exit_long=False, exit_short=False, + low=None, high=None) assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_flag is True assert res.sell_reason == 'hello world' caplog.clear() strategy.custom_sell = MagicMock(return_value='h' * 100) - res = strategy.should_sell(trade, 1, now, False, False, None, None, 0) + res = strategy.should_exit(trade, 1, now, + enter_long=False, enter_short=False, + exit_long=False, exit_short=False, + low=None, high=None) assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_flag is True assert res.sell_reason == 'h' * 64