From e75328ac86ba99e7a0c75f310b613f9223e4d760 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 7 Nov 2025 21:04:57 +0100 Subject: [PATCH] fix(qav3): make position adjustement works with futures MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../user_data/strategies/QuickAdapterV3.py | 97 ++++++++++++++++--- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index aced8a6..e5cf30c 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -30,6 +30,7 @@ from Utils import ( get_distance, get_label_defaults, get_zl_ma_fn, + midpoint, non_zero_diff, price_retracement_percent, smooth_extrema, @@ -69,7 +70,7 @@ class QuickAdapterV3(IStrategy): INTERFACE_VERSION = 3 def version(self) -> str: - return "3.3.165" + return "3.3.166" timeframe = "5m" @@ -1045,6 +1046,57 @@ class QuickAdapterV3(IStrategy): ) return trade_take_profit_price_history + @staticmethod + def _interp_partial_stake_amount( + stake_amount: float, + percent: float, + min_percent: float, + max_percent: float, + min_stake: float, + max_stake: float, + ) -> float: + max_stake = max_stake if max_stake > 0 else stake_amount + if max_stake < min_stake: + max_stake = min_stake + if max_percent <= min_percent or not np.isfinite(max_percent - min_percent): + return float(midpoint(min_stake, max_stake)) + interp_partial_stake_amount = float( + np.interp(percent, [min_percent, max_percent], [min_stake, max_stake]) + ) + if interp_partial_stake_amount < min_stake: + return float(min_stake) + return ( + float(max_stake) + if interp_partial_stake_amount > max_stake + else interp_partial_stake_amount + ) + + @staticmethod + @lru_cache(maxsize=8) + def get_min_stake_multiplier( + percent: float, + min_percent: float, + max_percent: float, + min_multiplier: float = 1.15, + max_multiplier: float = 1.40, + ) -> float: + if not ( + isinstance(percent, (int, float)) + and isinstance(min_percent, (int, float)) + and isinstance(max_percent, (int, float)) + ): + return min_multiplier + if max_percent <= min_percent or not np.isfinite(max_percent - min_percent): + return min_multiplier + normalized_percent = (percent - min_percent) / (max_percent - min_percent) + if not np.isfinite(normalized_percent): + return min_multiplier + if normalized_percent < 0.0: + normalized_percent = 0.0 + elif normalized_percent > 1.0: + normalized_percent = 1.0 + return min_multiplier + (max_multiplier - min_multiplier) * normalized_percent + def adjust_trade_position( self, trade: Trade, @@ -1091,22 +1143,39 @@ class QuickAdapterV3(IStrategy): ), ) if trade_partial_exit: - trade_stake_percent = self.partial_exit_stages[trade_exit_stage][1] - trade_partial_stake_amount = trade.stake_amount * trade_stake_percent - if min_stake and trade_partial_stake_amount < min_stake: + if min_stake is None: + min_stake = np.finfo(float).eps + percent = self.partial_exit_stages[trade_exit_stage][1] + percent_values = [ + v[1] + for v in self.partial_exit_stages.values() + if isinstance(v[1], (int, float)) + ] + min_percent = min(percent_values) + max_percent = max(percent_values) + trade_partial_stake_amount = QuickAdapterV3._interp_partial_stake_amount( + stake_amount=trade.stake_amount, + percent=percent, + min_percent=min_percent, + max_percent=max_percent, + min_stake=min_stake, + max_stake=max_stake, + ) + safe_min_stake = min_stake * QuickAdapterV3.get_min_stake_multiplier( + percent=percent, + min_percent=min_percent, + max_percent=max_percent, + ) + remaining_stake_amount = trade.stake_amount - trade_partial_stake_amount + if remaining_stake_amount < safe_min_stake: logger.info( - f"Trade {trade.trade_direction} {trade.pair} stage {trade_exit_stage} | " - f"Stake amount {format_number(trade_partial_stake_amount)} < min_stake {format_number(min_stake)}, " - f"clamped to {format_number(min_stake)}" + f"Trade {trade.trade_direction} {trade.pair} stage {trade_exit_stage} | Remaining stake amount {format_number(remaining_stake_amount)} < safe_min_stake {format_number(safe_min_stake)}, closing position" ) - trade_partial_stake_amount = min_stake - elif max_stake and trade_partial_stake_amount > max_stake: - logger.info( - f"Trade {trade.trade_direction} {trade.pair} stage {trade_exit_stage} | " - f"Stake amount {format_number(trade_partial_stake_amount)} > max_stake {format_number(max_stake)}, " - f"clamped to {format_number(max_stake)}" + last_exit_stage = max(self.partial_exit_stages.keys()) + 1 + return ( + -trade.stake_amount, + f"take_profit_{trade.trade_direction}_{last_exit_stage}", ) - trade_partial_stake_amount = max_stake return ( -trade_partial_stake_amount, f"take_profit_{trade.trade_direction}_{trade_exit_stage}", -- 2.43.0