From ef18d0916123bdfe584ccda8c76792645865b692 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 16:50:38 +0200 Subject: [PATCH 1/3] Call custom_exit also when the trade is not in profit and exit_profit_only is set. --- docs/strategy-callbacks.md | 3 ++- docs/strategy_migration.md | 3 +++ freqtrade/strategy/interface.py | 11 ++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 678899cc6..302ffd5fd 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -91,7 +91,8 @@ For example you could implement a 1:2 risk-reward ROI with `custom_exit()`. Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. !!! Note - Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False` or `exit_profit_only=True` while profit is below `exit_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. + Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. + `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False` or if there is an enter signal. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index eb1729ba7..1fe1f0953 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -145,6 +145,9 @@ Please refer to the [Strategy documentation](strategy-customization.md#exit-sign ### `custom_sell` +`custom_sell` has been renamed to `custom_exit`. +It's now also being called for every iteration, independent of current profit and `exit_profit_only` settings. + ``` python hl_lines="2" class AwesomeStrategy(IStrategy): def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index b0ed6e72d..1c53b2e3e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -881,10 +881,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - if (self.exit_profit_only and current_profit <= self.exit_profit_offset): - # exit_profit_only and profit doesn't reach the offset - ignore sell signal - pass - elif self.use_exit_signal and not enter: + if self.use_exit_signal and not enter: if exit_: exit_signal = ExitType.EXIT_SIGNAL else: @@ -902,7 +899,11 @@ class IStrategy(ABC, HyperStrategyMixin): custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH] else: custom_reason = None - if exit_signal in (ExitType.CUSTOM_EXIT, ExitType.EXIT_SIGNAL): + if ( + exit_signal == ExitType.CUSTOM_EXIT + or (exit_signal == ExitType.EXIT_SIGNAL + and (not self.exit_profit_only or current_profit > self.exit_profit_offset)) + ): logger.debug(f"{trade.pair} - Sell signal received. " f"exit_type=ExitType.{exit_signal.name}" + (f", custom_reason={custom_reason}" if custom_reason else "")) From 114591048c9daa227b31b7990fab6be97e0093af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 17:17:49 +0200 Subject: [PATCH 2/3] Always call custom_sell - also when there's a new enter signal --- docs/strategy-callbacks.md | 4 ++-- freqtrade/strategy/interface.py | 4 ++-- tests/test_freqtradebot.py | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 302ffd5fd..bd32f41c3 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -88,11 +88,11 @@ Allows to define custom exit signals, indicating that specified position should For example you could implement a 1:2 risk-reward ROI with `custom_exit()`. -Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. +Using `custom_exit()` signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. !!! Note Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. - `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False` or if there is an enter signal. + `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False`, even if there is a new enter signal. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1c53b2e3e..ebaa6568f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -881,8 +881,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - if self.use_exit_signal and not enter: - if exit_: + if self.use_exit_signal: + if exit_ and not enter: exit_signal = ExitType.EXIT_SIGNAL else: trade_type = "exit_short" if trade.is_short else "sell" diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e4066413e..3737c7c05 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3663,6 +3663,7 @@ def test_exit_profit_only( }) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + freqtrade.strategy.custom_exit = MagicMock(return_value=None) if exit_type == ExitType.EXIT_SIGNAL.value: freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) else: @@ -3671,10 +3672,15 @@ def test_exit_profit_only( freqtrade.enter_positions() trade = Trade.query.first() - trade.is_short = is_short + assert trade.is_short == is_short oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) trade.update_trade(oobj) freqtrade.wallets.update() + if profit_only: + assert freqtrade.handle_trade(trade) is False + # Custom-exit is called + freqtrade.strategy.custom_exit.call_count == 1 + patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short) assert freqtrade.handle_trade(trade) is handle_first From ffff45e76bb1e647f17f5f8711664ba4985f2de2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 08:37:35 +0200 Subject: [PATCH 3/3] simplify exit message --- freqtrade/strategy/interface.py | 3 +-- tests/strategy/test_interface.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ebaa6568f..ba2eb9636 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -885,7 +885,6 @@ class IStrategy(ABC, HyperStrategyMixin): if exit_ and not enter: exit_signal = ExitType.EXIT_SIGNAL else: - trade_type = "exit_short" if trade.is_short else "sell" custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)( pair=trade.pair, trade=trade, current_time=current_time, current_rate=current_rate, current_profit=current_profit) @@ -893,7 +892,7 @@ class IStrategy(ABC, HyperStrategyMixin): exit_signal = ExitType.CUSTOM_EXIT if isinstance(custom_reason, str): if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH: - logger.warning(f'Custom {trade_type} reason returned from ' + logger.warning(f'Custom exit reason returned from ' f'custom_exit is too long and was trimmed' f'to {CUSTOM_EXIT_MAX_LENGTH} characters.') custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH] diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 44a17ac02..a86d69135 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -523,7 +523,7 @@ def test_custom_exit(default_conf, fee, caplog) -> None: assert res.exit_type == ExitType.CUSTOM_EXIT assert res.exit_flag is True assert res.exit_reason == 'h' * 64 - assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog) + assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog) @pytest.mark.parametrize('side', TRADE_SIDES)