From: Jérôme Benoit Date: Thu, 30 Jul 2026 19:19:51 +0000 (+0200) Subject: fix(quickadapter): honor unlimited trade sentinel (#184) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=93c5704fc8e4088e0df7ebb32a91927fd5c723cc;p=freqai-strategies.git fix(quickadapter): honor unlimited trade sentinel (#184) * fix(quickadapter): honor unlimited trade sentinel * refactor(quickadapter): unify unlimited trade sentinel detection Consolidate the three divergent unlimited max_open_trades predicates into a single _is_unlimited_open_trades helper, honoring both the raw -1 sentinel and its runtime-normalized float("inf") form (matching Freqtrade's own config_validation idiom). Behavior is preserved for every schema-legal max_open_trades value (config schema minimum is -1): the sole formal divergence is that max_open_trades < -1, already rejected by Freqtrade at config load, is no longer treated as unlimited by max_open_trades_per_side. Also makes the confirm_trade_entry guard reject the +inf form explicitly instead of relying on IEEE finite >= inf semantics. * refactor(quickadapter): harmonize unlimited max_open_trades helper Rename _is_unlimited_open_trades to _is_unlimited_max_open_trades (and the local flag) for terminological precision with max_open_trades and max_open_trades_per_side, and widen its parameter annotation to int | float to match the is_trade_duration_valid precedent. No behavior change. --- diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 6cfda83..024aff3 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -270,6 +270,10 @@ class QuickAdapterV3(IStrategy): def _fit_live_predictions_candles(self) -> int: return get_fit_live_predictions_candles(self.config.get("freqai"), logger) + @staticmethod + def _is_unlimited_max_open_trades(max_open_trades: int | float) -> bool: + return max_open_trades == -1 or max_open_trades == math.inf + @cached_property def protections(self) -> list[dict[str, Any]]: fit_live_predictions_candles = self._fit_live_predictions_candles @@ -292,14 +296,24 @@ class QuickAdapterV3(IStrategy): stoploss_stop_duration_candles, fit_live_predictions_candles, ) - max_open_trades = int(self.config.get("max_open_trades", 0)) - stoploss_trade_limit = min( - max( - 2, - int(round(lookback_period_candles / max(1, trade_duration_candles))), - ), - max(2, int(round(max_open_trades * 0.75))), + max_open_trades = self.config.get("max_open_trades", 0) + unlimited_max_open_trades = QuickAdapterV3._is_unlimited_max_open_trades( + max_open_trades + ) + estimated_trade_limit = max( + 2, + int(round(lookback_period_candles / max(1, trade_duration_candles))), ) + if unlimited_max_open_trades: + stoploss_trade_limit = estimated_trade_limit + drawdown_trade_limit = 2 * estimated_trade_limit + else: + max_open_trades = int(max_open_trades) + stoploss_trade_limit = min( + estimated_trade_limit, + max(2, int(round(max_open_trades * 0.75))), + ) + drawdown_trade_limit = 2 * max_open_trades protections_list = [] @@ -317,7 +331,7 @@ class QuickAdapterV3(IStrategy): { "method": "MaxDrawdown", "lookback_period_candles": lookback_period_candles, - "trade_limit": 2 * max_open_trades, + "trade_limit": drawdown_trade_limit, "stop_duration_candles": drawdown_stop_duration_candles, "max_allowed_drawdown": drawdown["max_allowed_drawdown"], } @@ -347,7 +361,7 @@ class QuickAdapterV3(IStrategy): @property def max_open_trades_per_side(self) -> int: max_open_trades = self.config.get("max_open_trades", 0) - if max_open_trades < 0: + if QuickAdapterV3._is_unlimited_max_open_trades(max_open_trades): return -1 if self.is_short_allowed(): if max_open_trades % 2 == 1: @@ -2280,7 +2294,11 @@ class QuickAdapterV3(IStrategy): f"[{pair}] Denied short {QuickAdapterV3._ORDER_ENTRY}: shorting not allowed" ) return False - if Trade.get_open_trade_count() >= self.config.get("max_open_trades", 0): + max_open_trades = self.config.get("max_open_trades", 0) + if ( + not QuickAdapterV3._is_unlimited_max_open_trades(max_open_trades) + and Trade.get_open_trade_count() >= max_open_trades + ): return False max_open_trades_per_side = self.max_open_trades_per_side if max_open_trades_per_side >= 0: