]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor!: add predictions smoothing tunable
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 12 Feb 2025 09:08:50 +0000 (10:08 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 12 Feb 2025 09:08:50 +0000 (10:08 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/user_data/config-template.json
quickadapter/user_data/freqaimodels/LightGBMRegressorQuickAdapterV35.py
quickadapter/user_data/freqaimodels/XGBoostRegressorQuickAdapterV35.py
quickadapter/user_data/strategies/QuickAdapterV3.py

index d73e690553482226762d632e55172060a6c48bcd..ba7ba0e931fbee7dbe9052724067283cc8e9528a 100644 (file)
     "identifier": "quickadapter-xgboost",
     // "identifier": "quickadapter-lgbm",
     "fit_live_predictions_candles": 300,
-    "track_performance": false,
     "data_kitchen_thread_count": 6, // set to number of CPU threads / 4
+    "track_performance": false,
+    "predictions_smoothing": "log-sum-exp",
     "outlier_threshold": 0.999,
     "optuna_hyperopt": {
       "enabled": true,
       "DI_value_param2": 0,
       "DI_value_param3": 0,
       "DI_cutoff": 2,
-      "&s-minima_sort_threshold": -2,
-      "&s-maxima_sort_threshold": 2,
+      "&s-minima_threshold": -2,
+      "&s-maxima_threshold": 2,
       "label_period_candles": 100,
       "rmse": 0
     },
index b7cff11d2caeb11c88442ee63ab78224479cccf4..86318b62d542ad36f95f0c08ffd72de9b7cb5c4d 100644 (file)
@@ -166,8 +166,8 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel):
         )
 
         if not warmed_up:
-            dk.data["extra_returns_per_train"]["&s-maxima_sort_threshold"] = 2
-            dk.data["extra_returns_per_train"]["&s-minima_sort_threshold"] = -2
+            dk.data["extra_returns_per_train"]["&s-maxima_threshold"] = 2
+            dk.data["extra_returns_per_train"]["&s-minima_threshold"] = -2
         else:
             if self.__optuna_hyperopt:
                 label_period_candles = self.__optuna_hp.get(pair, {}).get(
@@ -175,13 +175,13 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel):
                 )
             else:
                 label_period_candles = self.ft_params["label_period_candles"]
-            min_pred, max_pred = min_max_pred(
+            min_pred, max_pred = self.min_max_pred(
                 pred_df_full,
                 num_candles,
                 label_period_candles,
             )
-            dk.data["extra_returns_per_train"]["&s-minima_sort_threshold"] = min_pred
-            dk.data["extra_returns_per_train"]["&s-maxima_sort_threshold"] = max_pred
+            dk.data["extra_returns_per_train"]["&s-minima_threshold"] = min_pred
+            dk.data["extra_returns_per_train"]["&s-maxima_threshold"] = max_pred
 
         dk.data["labels_mean"], dk.data["labels_std"] = {}, {}
         for label in dk.label_list + dk.unique_class_list:
@@ -245,8 +245,30 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel):
             )
         return storage
 
+    def min_max_pred(
+        self,
+        pred_df: pd.DataFrame,
+        fit_live_predictions_candles: int,
+        label_period_candles: int,
+    ) -> tuple[float, float]:
+        predictions_smoothing = self.freqai_info.get(
+            "predictions_smoothing", "log-sum-exp"
+        )
+        if predictions_smoothing == "log-sum-exp":
+            return log_sum_exp_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+        elif predictions_smoothing == "mean":
+            return mean_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+        elif predictions_smoothing == "median":
+            return median_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+
 
-def min_max_pred(
+def log_sum_exp_min_max_pred(
     pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
 ) -> tuple[float, float]:
     label_period_frequency: int = int(
@@ -260,7 +282,24 @@ def min_max_pred(
     return min_pred, max_pred
 
 
-def __min_max_pred(
+def mean_min_max_pred(
+    pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
+) -> tuple[float, float]:
+    pred_df_sorted = (
+        pred_df.select_dtypes(exclude=["object"])
+        .copy()
+        .apply(lambda col: col.sort_values(ascending=False, ignore_index=True))
+    )
+
+    label_period_frequency: int = int(
+        fit_live_predictions_candles / label_period_candles
+    )
+    min_pred = pred_df_sorted.iloc[-label_period_frequency:].mean()
+    max_pred = pred_df_sorted.iloc[:label_period_frequency].mean()
+    return min_pred["&s-extrema"], max_pred["&s-extrema"]
+
+
+def median_min_max_pred(
     pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
 ) -> tuple[float, float]:
     pred_df_sorted = (
index 5f8922aa743e52c8713c11199f9b017137950916..36363c87d8230114953683df19781ea6335bc412 100644 (file)
@@ -169,8 +169,8 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel):
         )
 
         if not warmed_up:
-            dk.data["extra_returns_per_train"]["&s-maxima_sort_threshold"] = 2
-            dk.data["extra_returns_per_train"]["&s-minima_sort_threshold"] = -2
+            dk.data["extra_returns_per_train"]["&s-maxima_threshold"] = 2
+            dk.data["extra_returns_per_train"]["&s-minima_threshold"] = -2
         else:
             if self.__optuna_hyperopt:
                 label_period_candles = self.__optuna_hp.get(pair, {}).get(
@@ -178,13 +178,13 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel):
                 )
             else:
                 label_period_candles = self.ft_params["label_period_candles"]
-            min_pred, max_pred = min_max_pred(
+            min_pred, max_pred = self.min_max_pred(
                 pred_df_full,
                 num_candles,
                 label_period_candles,
             )
-            dk.data["extra_returns_per_train"]["&s-minima_sort_threshold"] = min_pred
-            dk.data["extra_returns_per_train"]["&s-maxima_sort_threshold"] = max_pred
+            dk.data["extra_returns_per_train"]["&s-minima_threshold"] = min_pred
+            dk.data["extra_returns_per_train"]["&s-maxima_threshold"] = max_pred
 
         dk.data["labels_mean"], dk.data["labels_std"] = {}, {}
         for label in dk.label_list + dk.unique_class_list:
@@ -248,8 +248,30 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel):
             )
         return storage
 
+    def min_max_pred(
+        self,
+        pred_df: pd.DataFrame,
+        fit_live_predictions_candles: int,
+        label_period_candles: int,
+    ) -> tuple[float, float]:
+        predictions_smoothing = self.freqai_info.get(
+            "predictions_smoothing", "log-sum-exp"
+        )
+        if predictions_smoothing == "log-sum-exp":
+            return log_sum_exp_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+        elif predictions_smoothing == "mean":
+            return mean_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+        elif predictions_smoothing == "median":
+            return median_min_max_pred(
+                pred_df, fit_live_predictions_candles, label_period_candles
+            )
+
 
-def min_max_pred(
+def log_sum_exp_min_max_pred(
     pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
 ) -> tuple[float, float]:
     label_period_frequency: int = int(
@@ -263,7 +285,24 @@ def min_max_pred(
     return min_pred, max_pred
 
 
-def __min_max_pred(
+def mean_min_max_pred(
+    pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
+) -> tuple[float, float]:
+    pred_df_sorted = (
+        pred_df.select_dtypes(exclude=["object"])
+        .copy()
+        .apply(lambda col: col.sort_values(ascending=False, ignore_index=True))
+    )
+
+    label_period_frequency: int = int(
+        fit_live_predictions_candles / label_period_candles
+    )
+    min_pred = pred_df_sorted.iloc[-label_period_frequency:].mean()
+    max_pred = pred_df_sorted.iloc[:label_period_frequency].mean()
+    return min_pred["&s-extrema"], max_pred["&s-extrema"]
+
+
+def median_min_max_pred(
     pred_df: pd.DataFrame, fit_live_predictions_candles: int, label_period_candles: int
 ) -> tuple[float, float]:
     pred_df_sorted = (
index 19f13f2a5cab4f33b4aeb2950d5ce30951eea4ea..f6ffc7b12d040a35f8a9182e10f0f3e8e6f15cbd 100644 (file)
@@ -71,8 +71,8 @@ class QuickAdapterV3(IStrategy):
             "rmse": {"rmse": {"color": "#c28ce3", "type": "line"}},
             "extrema": {
                 "&s-extrema": {"color": "#f53580", "type": "line"},
-                "&s-minima_sort_threshold": {"color": "#4ae747", "type": "line"},
-                "&s-maxima_sort_threshold": {"color": "#5b5e4b", "type": "line"},
+                "&s-minima_threshold": {"color": "#4ae747", "type": "line"},
+                "&s-maxima_threshold": {"color": "#5b5e4b", "type": "line"},
             },
             "min_max": {
                 "maxima": {"color": "#ac7fc", "type": "bar"},
@@ -266,15 +266,15 @@ class QuickAdapterV3(IStrategy):
             1,
         )
 
-        dataframe["minima_sort_threshold"] = dataframe["&s-minima_sort_threshold"]
-        dataframe["maxima_sort_threshold"] = dataframe["&s-maxima_sort_threshold"]
+        dataframe["minima_threshold"] = dataframe["&s-minima_threshold"]
+        dataframe["maxima_threshold"] = dataframe["&s-maxima_threshold"]
         return dataframe
 
     def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
         enter_long_conditions = [
             df["do_predict"] == 1,
             df["DI_catch"] == 1,
-            df["&s-extrema"] < df["minima_sort_threshold"],
+            df["&s-extrema"] < df["minima_threshold"],
         ]
 
         if enter_long_conditions:
@@ -286,7 +286,7 @@ class QuickAdapterV3(IStrategy):
         enter_short_conditions = [
             df["do_predict"] == 1,
             df["DI_catch"] == 1,
-            df["&s-extrema"] > df["maxima_sort_threshold"],
+            df["&s-extrema"] > df["maxima_threshold"],
         ]
 
         if enter_short_conditions:
@@ -329,13 +329,13 @@ class QuickAdapterV3(IStrategy):
             return "outlier_detected"
 
         if (
-            last_candle["&s-extrema"] < last_candle["minima_sort_threshold"]
+            last_candle["&s-extrema"] < last_candle["minima_threshold"]
             and entry_tag == "short"
         ):
             return "minima_detected_short"
 
         if (
-            last_candle["&s-extrema"] > last_candle["maxima_sort_threshold"]
+            last_candle["&s-extrema"] > last_candle["maxima_threshold"]
             and entry_tag == "long"
         ):
             return "maxima_detected_long"