]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(quickadapter): honor unlimited trade sentinel (#184)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 30 Jul 2026 19:19:51 +0000 (21:19 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Jul 2026 19:19:51 +0000 (21:19 +0200)
* 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.

quickadapter/user_data/strategies/QuickAdapterV3.py

index 6cfda830c50df7b6569fb27f2d51c8e03824737f..024aff38540439231d77ffe00c0060a0c22d2e72 100644 (file)
@@ -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: