From 6b4b6a7a7dcc4bac4d2f5e33941534192985e775 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 20 Jul 2026 21:44:08 +0200 Subject: [PATCH] fix(quickadapter): bound take-profit stage to filled take-profit exits Derive the take-profit stage from filled take-profit-tagged exit orders clamped to the final full-exit stage, instead of nr_of_successful_exits plus in-flight exit orders. This stops the stage index from overshooting its maximum (e.g. take_profit_*_4 while the final stage is 3) when a final exit order is in flight or a non-take-profit exit fills. Guard custom_exit against re-issuing the final take-profit exit while an order is open by returning None after the model-expiry and reversal safety exits and before the take-profit stage computation, so safety exits still fire while in-flight orders no longer trigger a duplicate take-profit exit. --- .../user_data/strategies/QuickAdapterV3.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 33fd950..1eb0771 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -161,6 +161,8 @@ class QuickAdapterV3(IStrategy): # (natr_multiplier_fraction, stake_percent, color) _FINAL_EXIT_STAGE: Final[tuple[float, float, str]] = (1.0, 1.0, "deepskyblue") + _TAKE_PROFIT_ORDER_TAG_PREFIX: Final[str] = "take_profit_" + minimal_roi = {str(timeframe_minutes * 864): -1} # FreqAI is crashing if minimal_roi is a property @@ -1261,14 +1263,15 @@ class QuickAdapterV3(IStrategy): @staticmethod def get_trade_exit_stage(trade: Trade) -> int: - n_open_orders = 0 - if trade.has_open_orders: - n_open_orders = sum( - 1 - for open_order in trade.open_orders - if open_order.side == ("buy" if trade.is_short else "sell") + n_filled_take_profit_exits = sum( + 1 + for order in trade.select_filled_orders(trade.exit_side) + if (order.ft_order_tag or "").startswith( + QuickAdapterV3._TAKE_PROFIT_ORDER_TAG_PREFIX ) - return trade.nr_of_successful_exits + n_open_orders + ) + final_stage = max(QuickAdapterV3.partial_exit_stages.keys(), default=-1) + 1 + return min(n_filled_take_profit_exits, final_stage) @staticmethod @lru_cache(maxsize=128) @@ -1576,7 +1579,7 @@ class QuickAdapterV3(IStrategy): ) return ( -trade_partial_stake_amount, - f"take_profit_{trade.trade_direction}_{trade_exit_stage}", + f"{QuickAdapterV3._TAKE_PROFIT_ORDER_TAG_PREFIX}{trade.trade_direction}_{trade_exit_stage}", ) return None @@ -2082,6 +2085,9 @@ class QuickAdapterV3(IStrategy): ): return "maxima_detected_long" + if trade.has_open_orders: + return None + trade_exit_stage = QuickAdapterV3.get_trade_exit_stage(trade) if trade_exit_stage in QuickAdapterV3.partial_exit_stages: return None @@ -2187,7 +2193,7 @@ class QuickAdapterV3(IStrategy): ) if trade_exit: - return f"take_profit_{trade.trade_direction}_{trade_exit_stage}" + return f"{QuickAdapterV3._TAKE_PROFIT_ORDER_TAG_PREFIX}{trade.trade_direction}_{trade_exit_stage}" return None -- 2.53.0