From f83a81b525eff0f425316872f621a34b1a605003 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 10 Jun 2025 00:28:50 +0200 Subject: [PATCH] perf(qav3): run pivot labeling optimization to market via a frequency tunable MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../freqaimodels/QuickAdapterRegressorV3.py | 75 ++++++++++++++----- .../user_data/strategies/QuickAdapterV3.py | 22 +++--- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 22fdef7..da4e8ff 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -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) diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 197192a..1ca38c8 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -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, -- 2.43.0