From dc05916f9473f0a27cbd80f41a624b115ab8d0fb Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 30 Jul 2025 01:15:10 +0200 Subject: [PATCH] fix(qav3): default thresholds smoothing between the mean and the max/min MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../freqaimodels/QuickAdapterRegressorV3.py | 68 ++++++++++--------- .../user_data/strategies/QuickAdapterV3.py | 2 +- quickadapter/user_data/strategies/Utils.py | 2 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index e756fbb..0ce97f7 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -51,7 +51,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): https://github.com/sponsors/robcaulk """ - version = "3.7.101" + version = "3.7.102" @cached_property def _optuna_config(self) -> dict[str, Any]: @@ -528,10 +528,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel): ) pred_extrema = pred_df.get(EXTREMA_COLUMN).iloc[-thresholds_candles:] thresholds_smoothing = str( - self.freqai_info.get("prediction_thresholds_smoothing", "logsumexp") + self.freqai_info.get("prediction_thresholds_smoothing", "exp_weighted_mean") ) thresholds_smoothing_methods = { - "logsumexp", + "exp_weighted_mean", "isodata", "li", "mean", @@ -540,12 +540,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel): "triangle", "yen", } - if thresholds_smoothing == "logsumexp": - thresholds_temperature = float( - self.freqai_info.get("prediction_thresholds_temperature", 200.0) + if thresholds_smoothing == "exp_weighted_mean": + thresholds_alpha = float( + self.freqai_info.get("prediction_thresholds_alpha", 0.25) ) - return QuickAdapterRegressorV3.logsumexp_min_max( - pred_extrema, thresholds_temperature + return QuickAdapterRegressorV3.exp_weighted_mean_min_max( + pred_extrema, thresholds_alpha ) elif thresholds_smoothing in thresholds_smoothing_methods: thresholds_ratio = float( @@ -560,11 +560,11 @@ class QuickAdapterRegressorV3(BaseRegressionModel): ) @staticmethod - def logsumexp_min_max( - pred_extrema: pd.Series, temperature: float + def exp_weighted_mean_min_max( + pred_extrema: pd.Series, alpha: float ) -> tuple[float, float]: - min_val = smoothed_min(pred_extrema, temperature=temperature) - max_val = smoothed_max(pred_extrema, temperature=temperature) + min_val = smoothed_min(pred_extrema, alpha=alpha) + max_val = smoothed_max(pred_extrema, alpha=alpha) return min_val, max_val @staticmethod @@ -1530,7 +1530,7 @@ def zigzag( def calculate_slopes_ok_threshold( pos: int, - min_threshold: float = 0.75, + min_threshold: float = 0.85, max_threshold: float = 0.95, ) -> float: volatility_quantile = calculate_volatility_quantile(pos) @@ -1776,26 +1776,30 @@ def label_objective( return np.median(pivots_thresholds), len(pivots_values) -def smoothed_max(series: pd.Series, temperature=1.0) -> float: - data_array = series.to_numpy() - if data_array.size == 0: +def exponential_weighted_mean(series: pd.Series, alpha: float) -> float: + np_array = series.to_numpy() + if np_array.size == 0: return np.nan - if temperature < 0: - raise ValueError("temperature must be non-negative") - if np.isclose(temperature, 0): - return data_array.max() - return sp.special.logsumexp(temperature * data_array) / temperature - - -def smoothed_min(series: pd.Series, temperature=1.0) -> float: - data_array = series.to_numpy() - if data_array.size == 0: - return np.nan - if temperature < 0: - raise ValueError("temperature must be non-negative") - if np.isclose(temperature, 0): - return data_array.min() - return -sp.special.logsumexp(-temperature * data_array) / temperature + if np.isclose(alpha, 0): + return np.mean(np_array) + scaled_data = alpha * np_array + max_scaled_data = np.max(scaled_data) + if np.isinf(max_scaled_data): + return np_array[np.argmax(scaled_data)] + shifted_exponentials = np.exp(scaled_data - max_scaled_data) + numerator = np.sum(np_array * shifted_exponentials) + denominator = np.sum(shifted_exponentials) + if np.isclose(denominator, 0): + return np.max(np_array) + return numerator / denominator + + +def smoothed_max(series: pd.Series, alpha: float = 1.0) -> float: + return exponential_weighted_mean(series, alpha) + + +def smoothed_min(series: pd.Series, alpha: float = 1.0) -> float: + return exponential_weighted_mean(series, -alpha) def round_to_nearest_int(value: float, step: int) -> int: diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 2e199e8..05c10e6 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -65,7 +65,7 @@ class QuickAdapterV3(IStrategy): INTERFACE_VERSION = 3 def version(self) -> str: - return "3.3.109" + return "3.3.110" timeframe = "5m" diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index bd1dc39..9d6bd97 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -451,7 +451,7 @@ def zigzag( def calculate_slopes_ok_threshold( pos: int, - min_threshold: float = 0.75, + min_threshold: float = 0.85, max_threshold: float = 0.95, ) -> float: volatility_quantile = calculate_volatility_quantile(pos) -- 2.43.0