From 052fb811928ebe3c558026206089c9eff171bf95 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 29 Jul 2025 21:05:28 +0200 Subject: [PATCH] feat(qav3): add more predictions thresholding methods MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- quickadapter/docker-compose.yml | 4 +- .../freqaimodels/QuickAdapterRegressorV3.py | 101 ++++++++++++++++-- .../user_data/strategies/QuickAdapterV3.py | 2 +- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/quickadapter/docker-compose.yml b/quickadapter/docker-compose.yml index f5e16be..e9fafa9 100644 --- a/quickadapter/docker-compose.yml +++ b/quickadapter/docker-compose.yml @@ -19,9 +19,11 @@ services: FROM freqtradeorg/freqtrade:stable_freqai ARG optuna_version - RUN pip install --user optuna==$${optuna_version} optuna-integration==$${optuna_version} optuna-dashboard + ARG skimage_version + RUN pip install --user optuna==$${optuna_version} optuna-integration==$${optuna_version} optuna-dashboard scikit-image==$${skimage_version} args: - optuna_version=4.4.0 + - skimage_version=0.25.2 restart: unless-stopped container_name: freqtrade-quickadapter environment: diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 64e4a6c..2d13cea 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -11,6 +11,7 @@ import pandas as pd import scipy as sp import optuna import sklearn +import skimage import warnings import talib.abstract as ta @@ -50,7 +51,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): https://github.com/sponsors/robcaulk """ - version = "3.7.99" + version = "3.7.100" @cached_property def _optuna_config(self) -> dict[str, Any]: @@ -530,12 +531,100 @@ class QuickAdapterRegressorV3(BaseRegressionModel): ) ) extrema = pred_df.get(EXTREMA_COLUMN).iloc[-thresholds_candles:] - thresholds_temperature = float( - self.freqai_info.get("prediction_thresholds_temperature", 300.0) + thresholds_smoothing = self.freqai_info.get( + "prediction_thresholds_smoothing", "logsumexp" ) - min_pred = smoothed_min(extrema, temperature=thresholds_temperature) - max_pred = smoothed_max(extrema, temperature=thresholds_temperature) - return min_pred, max_pred + thresholds_smoothing_methods = { + "logsumexp", + "isodata", + "li", + "mean", + "minimum", + "niblack", + "otsu", + "sauvola", + "triangle", + "yen", + } + if thresholds_smoothing == "logsumexp": + thresholds_temperature = float( + self.freqai_info.get("prediction_thresholds_temperature", 300.0) + ) + return QuickAdapterRegressorV3.logsumexp_min_max( + extrema, thresholds_temperature + ) + elif thresholds_smoothing in thresholds_smoothing_methods: + return QuickAdapterRegressorV3.common_min_max( + extrema, + int(label_period_cycles), + thresholds_quantile, + thresholds_smoothing, + ) + else: + raise ValueError( + f"Unsupported thresholds smoothing method: {thresholds_smoothing}. Supported methods are {', '.join(thresholds_smoothing_methods)}" + ) + + @staticmethod + def logsumexp_min_max(series: pd.Series, temperature: float) -> tuple[float, float]: + min_val = smoothed_min(series, temperature=temperature) + max_val = smoothed_max(series, temperature=temperature) + return min_val, max_val + + @staticmethod + def common_min_max( + series: pd.Series, + label_period_cycles: int, + method: str, + ) -> tuple[float, float]: + n_values = min(int(label_period_cycles), len(series)) + if n_values <= 0: + return np.nan, np.nan + + sorted_series = series.sort_values(ascending=True) + min_subset = sorted_series.iloc[:n_values] + max_subset = sorted_series.iloc[-n_values:] + + method_functions = { + "isodata": QuickAdapterRegressorV3.apply_skimage_threshold, + "li": QuickAdapterRegressorV3.apply_skimage_threshold, + "mean": QuickAdapterRegressorV3.apply_skimage_threshold, + "minimum": QuickAdapterRegressorV3.apply_skimage_threshold, + "niblack": QuickAdapterRegressorV3.apply_skimage_threshold, + "otsu": QuickAdapterRegressorV3.apply_skimage_threshold, + "sauvola": QuickAdapterRegressorV3.apply_skimage_threshold, + "triangle": QuickAdapterRegressorV3.apply_skimage_threshold, + "yen": QuickAdapterRegressorV3.apply_skimage_threshold, + } + + if method not in method_functions: + raise ValueError(f"Unsupported method: {method}") + + min_func = method_functions[method] + max_func = method_functions[method] + + try: + threshold_func = getattr(skimage.filters, f"threshold_{method}") + except AttributeError: + raise ValueError(f"Unknown skimage threshold function: threshold_{method}") + + min_val = min_func(min_subset, threshold_func) + max_val = max_func(max_subset, threshold_func) + + return min_val, max_val + + @staticmethod + def apply_skimage_threshold( + series: pd.Series, threshold_func: Callable[[np.ndarray], float] + ) -> float: + values = series.to_numpy() + + if values.size < 2 or np.all(values == values[0]): + return values.mean() if values.size > 0 else np.nan + try: + return threshold_func(values) + except Exception: + return np.median(values) def get_multi_objective_study_best_trial( self, namespace: str, study: optuna.study.Study diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 915d620..3e9e7a8 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.107" + return "3.3.108" timeframe = "5m" -- 2.43.0