]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
perf(qav3): run pivot labeling optimization to market via a frequency tunable
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 9 Jun 2025 22:28:50 +0000 (00:28 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 9 Jun 2025 22:28:50 +0000 (00:28 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py

index 22fdef736ea67205f85a816fc0ea4e6eb780a62a..da4e8ffe986b00f0d0309df3e6c30adb01001847 100644 (file)
@@ -1,3 +1,4 @@
+import datetime
 from enum import IntEnum
 import hashlib
 import logging
@@ -17,6 +18,7 @@ from typing import Any, Callable, Optional
 from pathlib import Path
 from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
 from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
+from freqtrade.exchange import timeframe_to_minutes
 
 
 TEST_SIZE = 0.1
@@ -47,7 +49,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     https://github.com/sponsors/robcaulk
     """
 
-    version = "3.7.80"
+    version = "3.7.81"
 
     @cached_property
     def _optuna_config(self) -> dict:
@@ -122,6 +124,15 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                     ),
                 }
             )
+        self._label_throttle_modulo = max(
+            1,
+            int(
+                (
+                    self.ft_params.get("label_frequency_candles", 12)
+                    * (timeframe_to_minutes(self.config.get("timeframe")) * 60)
+                )
+            ),
+        )
         logger.info(
             f"Initialized {self.__class__.__name__} {self.freqai_info.get('regressor', 'xgboost')} regressor model version {self.version}"
         )
@@ -177,6 +188,11 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         else:
             raise ValueError(f"Invalid namespace: {namespace}")
 
+    def get_throttle_modulo(self, namespace: str) -> int:
+        if namespace != "label":
+            raise ValueError(f"Invalid namespace: {namespace}")
+        return self._label_throttle_modulo
+
     def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
         """
         User sets up the training and test data to fit their desired model here
@@ -197,23 +213,6 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
 
         start = time.time()
         if self._optuna_hyperopt:
-            self.optuna_optimize(
-                pair=dk.pair,
-                namespace="label",
-                objective=lambda trial: label_objective(
-                    trial,
-                    self.data_provider.get_pair_dataframe(
-                        pair=dk.pair, timeframe=self.config.get("timeframe")
-                    ),
-                    self.freqai_info.get("fit_live_predictions_candles", 100),
-                    self._optuna_config.get("candles_step"),
-                ),
-                directions=[
-                    optuna.study.StudyDirection.MAXIMIZE,
-                    optuna.study.StudyDirection.MAXIMIZE,
-                ],
-            )
-
             self.optuna_optimize(
                 pair=dk.pair,
                 namespace="hp",
@@ -287,6 +286,24 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
 
         return model
 
+    def throttle_callback(
+        self,
+        pair: str,
+        namespace: str,
+        callback: Callable[[], None],
+        current_time: Optional[datetime.datetime] = None,
+    ) -> None:
+        if current_time is None:
+            current_time = datetime.datetime.now(datetime.timezone.utc)
+        if hash(pair + str(current_time)) % self.get_throttle_modulo(namespace) == 0:
+            try:
+                callback()
+            except Exception as e:
+                logger.error(
+                    f"Error executing {namespace} callback for {pair}: {str(e)}",
+                    exc_info=True,
+                )
+
     def fit_live_predictions(self, dk: FreqaiDataKitchen, pair: str) -> None:
         warmed_up = True
 
@@ -294,6 +311,28 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             "fit_live_predictions_candles", 100
         )
 
+        if self._optuna_hyperopt:
+            self.throttle_callback(
+                pair=pair,
+                namespace="label",
+                callback=lambda: self.optuna_optimize(
+                    pair=pair,
+                    namespace="label",
+                    objective=lambda trial: label_objective(
+                        trial,
+                        self.data_provider.get_pair_dataframe(
+                            pair=pair, timeframe=self.config.get("timeframe")
+                        ),
+                        fit_live_predictions_candles,
+                        self._optuna_config.get("candles_step"),
+                    ),
+                    directions=[
+                        optuna.study.StudyDirection.MAXIMIZE,
+                        optuna.study.StudyDirection.MAXIMIZE,
+                    ],
+                ),
+            )
+
         if self.live:
             if not hasattr(self, "exchange_candles"):
                 self.exchange_candles = len(self.dd.model_return_values[pair].index)
index 197192a0c92e1f82b089c84afe4d2df7e38bb9c0..1ca38c8b3833f7734a662f6f0569c2313aaaefe0 100644 (file)
@@ -61,7 +61,7 @@ class QuickAdapterV3(IStrategy):
     INTERFACE_VERSION = 3
 
     def version(self) -> str:
-        return "3.3.84"
+        return "3.3.85"
 
     timeframe = "5m"
 
@@ -182,9 +182,11 @@ class QuickAdapterV3(IStrategy):
             )
         self._throttle_modulo = max(
             1,
-            round(
-                (timeframe_to_minutes(self.config.get("timeframe")) * 60)
-                / self.config.get("internals", {}).get("process_throttle_secs", 5)
+            int(
+                round(
+                    (timeframe_to_minutes(self.config.get("timeframe")) * 60)
+                    / self.config.get("internals", {}).get("process_throttle_secs", 5)
+                )
             ),
         )
 
@@ -485,7 +487,7 @@ class QuickAdapterV3(IStrategy):
     def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
         return df
 
-    def get_trade_entry_date(self, trade: Trade) -> datetime:
+    def get_trade_entry_date(self, trade: Trade) -> datetime.datetime:
         return timeframe_to_prev_date(self.config.get("timeframe"), trade.open_date_utc)
 
     def get_trade_duration_candles(self, df: DataFrame, trade: Trade) -> Optional[int]:
@@ -623,10 +625,10 @@ class QuickAdapterV3(IStrategy):
         self,
         pair: str,
         callback: Callable[[], None],
-        current_time: Optional[datetime] = None,
+        current_time: Optional[datetime.datetime] = None,
     ) -> None:
         if current_time is None:
-            current_time = datetime.now(datetime.timezone.utc)
+            current_time = datetime.datetime.now(datetime.timezone.utc)
         if hash(pair + str(current_time)) % self._throttle_modulo == 0:
             try:
                 callback()
@@ -639,7 +641,7 @@ class QuickAdapterV3(IStrategy):
         self,
         pair: str,
         trade: Trade,
-        current_time: datetime,
+        current_time: datetime.datetime,
         current_rate: float,
         current_profit: float,
         **kwargs,
@@ -666,7 +668,7 @@ class QuickAdapterV3(IStrategy):
         self,
         pair: str,
         trade: Trade,
-        current_time: datetime,
+        current_time: datetime.datetime,
         current_rate: float,
         current_profit: float,
         **kwargs,
@@ -727,7 +729,7 @@ class QuickAdapterV3(IStrategy):
         amount: float,
         rate: float,
         time_in_force: str,
-        current_time: datetime,
+        current_time: datetime.datetime,
         entry_tag: Optional[str],
         side: str,
         **kwargs,