]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
feat(qav3): add more predictions thresholding methods
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 29 Jul 2025 19:05:28 +0000 (21:05 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 29 Jul 2025 19:05:28 +0000 (21:05 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/docker-compose.yml
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py

index f5e16be4026d494b3c624c649296a86e2090a1dd..e9fafa96ac38960cd16ad66a3232abd02aa4f7e1 100644 (file)
@@ -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:
index 64e4a6c54d0889955bd841c18c4315e865d25280..2d13cea05112ca0509c4e137a23890c8609de2e9 100644 (file)
@@ -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
index 915d62078607a68071093deb41ee05728ec892f3..3e9e7a8f6e4dec4e6b8b21ec0b54ad9ec464fce3 100644 (file)
@@ -65,7 +65,7 @@ class QuickAdapterV3(IStrategy):
     INTERFACE_VERSION = 3
 
     def version(self) -> str:
-        return "3.3.107"
+        return "3.3.108"
 
     timeframe = "5m"