From 6be25bd86af151d641a197944c42d7381c731eee Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 17 Feb 2025 19:40:56 +0100 Subject: [PATCH 1/4] chore: simplify IF condition --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7d70c6336..699d1ae1a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1774,7 +1774,7 @@ class FreqtradeBot(LoggingMixin): if trade.has_open_orders: oo = trade.select_order(side, True) if oo is not None: - if (price == oo.price) and (side == oo.side) and (amount == oo.amount): + if price == oo.price and side == oo.side and amount == oo.amount: logger.info( f"A similar open order was found for {trade.pair}. " f"Keeping existing {trade.exit_side} order. {price=}, {amount=}" From 93e3bbea5a59176b99c618be0a8a266442cebc31 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 17 Feb 2025 19:51:39 +0100 Subject: [PATCH 2/4] fix: only evaluate the same exit-reason once Limit this behavior to if it caused an actual exit. --- freqtrade/freqtradebot.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 699d1ae1a..a427b2e72 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -64,7 +64,7 @@ from freqtrade.rpc.rpc_types import ( ) from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper -from freqtrade.util import FtPrecise, MeasureTime, dt_from_ts +from freqtrade.util import FtPrecise, MeasureTime, PeriodicCache, dt_from_ts, dt_now from freqtrade.util.migrations.binance_mig import migrate_binance_futures_names from freqtrade.wallets import Wallets @@ -154,6 +154,7 @@ class FreqtradeBot(LoggingMixin): # Protect exit-logic from forcesell and vice versa self._exit_lock = Lock() timeframe_secs = timeframe_to_seconds(self.strategy.timeframe) + self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs) LoggingMixin.__init__(self, logger, timeframe_secs) self._schedule = Scheduler() @@ -1374,6 +1375,14 @@ class FreqtradeBot(LoggingMixin): for should_exit in exits: if should_exit.exit_flag: exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None + if trade.has_open_orders: + pc = self._exit_reason_cache.get( + f"{trade.pair}_{trade.id}_{exit_tag1 or should_exit.exit_reason}", None + ) + if pc: + logger.debug(f"Exit reason already seen this candle, first seen at {pc}") + continue + logger.info( f"Exit for {trade.pair} detected. Reason: {should_exit.exit_type}" f"{f' Tag: {exit_tag1}' if exit_tag1 is not None else ''}" @@ -2092,6 +2101,7 @@ class FreqtradeBot(LoggingMixin): self.handle_insufficient_funds(trade) return False + self._exit_reason_cache[f"{trade.pair}_{trade.id}_{exit_reason}"] = dt_now() order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit) order_obj.ft_order_tag = exit_reason trade.orders.append(order_obj) From f287d0ad2499eacdbe251d9b984c45a1b72522ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 17 Feb 2025 20:00:27 +0100 Subject: [PATCH 3/4] chore: improve variable naming --- freqtrade/freqtradebot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a427b2e72..84731117a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1376,11 +1376,12 @@ class FreqtradeBot(LoggingMixin): if should_exit.exit_flag: exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None if trade.has_open_orders: - pc = self._exit_reason_cache.get( + if prev_eval := self._exit_reason_cache.get( f"{trade.pair}_{trade.id}_{exit_tag1 or should_exit.exit_reason}", None - ) - if pc: - logger.debug(f"Exit reason already seen this candle, first seen at {pc}") + ): + logger.debug( + f"Exit reason already seen this candle, first seen at {prev_eval}" + ) continue logger.info( From 89cd46d2e5f6e5edb7b68e1fb485ed0454924c27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 17 Feb 2025 20:00:44 +0100 Subject: [PATCH 4/4] test: adapt test for "evaluate exit signal once once" behavior --- tests/freqtradebot/test_freqtradebot.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 02ffcb2e4..e66e5065c 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -3743,8 +3743,9 @@ def test_trailing_stop_loss_positive( @pytest.mark.parametrize("is_short", [False, True]) def test_disable_ignore_roi_if_entry_signal( - default_conf_usdt, limit_order, limit_order_open, is_short, fee, mocker + default_conf_usdt, limit_order, limit_order_open, is_short, fee, mocker, time_machine ) -> None: + time_machine.move_to("2025-01-10 08:00:16 +00:00") patch_RPCManager(mocker) patch_exchange(mocker) eside = entry_side(is_short) @@ -3773,6 +3774,13 @@ def test_disable_ignore_roi_if_entry_signal( patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short, exit_short=is_short) assert freqtrade.handle_trade(trade) is True + # Test if entry-signal is absent + patch_get_signal(freqtrade) + # Signal was evaluated already - no action. + assert freqtrade.handle_trade(trade) is False + + # Move to after the candle expired + time_machine.shift(timedelta(hours=5)) # Test if entry-signal is absent patch_get_signal(freqtrade) assert freqtrade.handle_trade(trade) is True