From: Jérôme Benoit Date: Tue, 19 Aug 2025 21:55:22 +0000 (+0200) Subject: fix(qav3): ensure safe threshold values X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=3c2576cc34c6bb8bc8234dbf9e77f3df96214ea5;p=freqai-strategies.git fix(qav3): ensure safe threshold values Signed-off-by: Jérôme Benoit --- diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 89e3b46..3b9e7c4 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -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: diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index e75aa1e..6f470b1 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -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 ( diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 033c709..b296804 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -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: