]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(qav3): ensure safe threshold values
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 19 Aug 2025 21:55:22 +0000 (23:55 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 19 Aug 2025 21:55:22 +0000 (23:55 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index 89e3b46b9f987962b7955f8aed050f9fd168b8d2..3b9e7c4ef0d090e116d3f44e16780482986f0eca 100644 (file)
@@ -581,6 +581,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
 
     @staticmethod
     def get_pred_min_max(pred_extrema: pd.Series) -> tuple[pd.Series, pd.Series]:
+        pred_extrema = (
+            pd.to_numeric(pred_extrema, errors="coerce")
+            .where(np.isfinite, np.nan)
+            .dropna()
+        )
+        if pred_extrema.empty:
+            return pd.Series(dtype=float), pd.Series(dtype=float)
         n_pred_minima = max(1, sp.signal.find_peaks(-pred_extrema)[0].size)
         n_pred_maxima = max(1, sp.signal.find_peaks(pred_extrema)[0].size)
 
@@ -589,6 +596,34 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             -n_pred_maxima:
         ]
 
+    @staticmethod
+    def safe_min_pred(pred_extrema: pd.Series) -> float:
+        try:
+            pred_minimum = pred_extrema.min()
+        except Exception:
+            pred_minimum = None
+        if (
+            pred_minimum is not None
+            and isinstance(pred_minimum, (int, float, np.number))
+            and np.isfinite(pred_minimum)
+        ):
+            return pred_minimum
+        return -2.0
+
+    @staticmethod
+    def safe_max_pred(pred_extrema: pd.Series) -> float:
+        try:
+            pred_maximum = pred_extrema.max()
+        except Exception:
+            pred_maximum = None
+        if (
+            pred_maximum is not None
+            and isinstance(pred_maximum, (int, float, np.number))
+            and np.isfinite(pred_maximum)
+        ):
+            return pred_maximum
+        return 2.0
+
     @staticmethod
     def soft_extremum_min_max(
         pred_extrema: pd.Series, alpha: float
@@ -598,9 +633,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_minima, pred_maxima = QuickAdapterRegressorV3.get_pred_min_max(
             pred_extrema
         )
-        return soft_extremum(pred_minima, alpha=-alpha), soft_extremum(
-            pred_maxima, alpha=alpha
-        )
+        soft_minimum = soft_extremum(pred_minima, alpha=-alpha)
+        if not np.isfinite(soft_minimum):
+            soft_minimum = QuickAdapterRegressorV3.safe_min_pred(pred_extrema)
+        soft_maximum = soft_extremum(pred_maxima, alpha=alpha)
+        if not np.isfinite(soft_maximum):
+            soft_maximum = QuickAdapterRegressorV3.safe_max_pred(pred_extrema)
+        return soft_minimum, soft_maximum
 
     @staticmethod
     def skimage_min_max(pred_extrema: pd.Series, method: str) -> tuple[float, float]:
@@ -630,7 +669,11 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             raise ValueError(f"Unknown skimage threshold function: threshold_{method}")
 
         min_val = min_func(pred_minima, threshold_func)
+        if not np.isfinite(min_val):
+            min_val = QuickAdapterRegressorV3.safe_min_pred(pred_extrema)
         max_val = max_func(pred_maxima, threshold_func)
+        if not np.isfinite(max_val):
+            max_val = QuickAdapterRegressorV3.safe_max_pred(pred_extrema)
 
         return min_val, max_val
 
@@ -642,8 +685,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
 
         if values.size == 0:
             return np.nan
-        if values.size == 1 or np.all(np.isclose(values, values[0])):
-            return values.mean()
+        if (
+            values.size == 1
+            or np.unique(values).size < 3
+            or np.allclose(values, values[0])
+        ):
+            return np.median(values)
         try:
             return threshold_func(values)
         except Exception as e:
index e75aa1ef13ecacaf676afaa3b3361f6545e0f8bc..6f470b1b5ecac6e4b04650c3614e166057f889f2 100644 (file)
@@ -1146,6 +1146,10 @@ class QuickAdapterV3(IStrategy):
         """
         if df.empty:
             return False
+        if side not in {"long", "short"}:
+            return False
+        if order not in {"entry", "exit"}:
+            return False
 
         lookback_period = max(0, min(int(lookback_period), len(df) - 1))
         if not (0.0 < decay_ratio <= 1.0):
@@ -1180,7 +1184,7 @@ class QuickAdapterV3(IStrategy):
 
         for k in range(1, lookback_period + 1):
             close_k = df.iloc[-k].get("close")
-            if not np.isfinite(close_k):
+            if not isinstance(close_k, (int, float)) or not np.isfinite(close_k):
                 return current_ok
 
             decayed_min_natr_ratio_percent = max(
@@ -1199,7 +1203,9 @@ class QuickAdapterV3(IStrategy):
                 max_natr_ratio_percent=decayed_max_natr_ratio_percent,
                 candle_idx=-(k + 1),
             )
-            if not np.isfinite(threshold_k):
+            if not isinstance(threshold_k, (int, float)) or not np.isfinite(
+                threshold_k
+            ):
                 return current_ok
 
             if (side == "long" and not (close_k > threshold_k)) or (
index 033c709cf2826527fede499545f3ed5806bf7328..b296804e212e650635cdafa4fb9f667bbb65d4a3 100644 (file)
@@ -471,7 +471,7 @@ def calculate_quantile(values: NDArray[np.float64], value: float) -> float:
         return np.nan
 
     first_value = values[0]
-    if np.all(np.isclose(values, first_value)):
+    if np.allclose(values, first_value):
         return (
             0.5
             if np.isclose(value, first_value)
@@ -1008,6 +1008,8 @@ def round_to_step(value: float | int, step: int) -> int:
     :return: The rounded value.
     :raises ValueError: If step is not a positive integer or value is not finite.
     """
+    if not isinstance(value, (int, float)):
+        raise ValueError("value must be an integer or float")
     if not np.isfinite(value):
         raise ValueError("value must be finite")
     if not isinstance(step, int) or step <= 0: