]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(qav3): ensure pivot labeling optimization run once per candle
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 12 Jun 2025 19:02:29 +0000 (21:02 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 12 Jun 2025 19:02:29 +0000 (21:02 +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
quickadapter/user_data/strategies/Utils.py

index 5468b79b7e3b9c10005e39a69325a2ced633bf6b..baa9fb247763d3dcc9fe237dd25cfb8ba9d1bd6a 100644 (file)
@@ -1,10 +1,10 @@
+import copy
 from enum import IntEnum
 import hashlib
 import logging
 import json
 import random
 from statistics import median
-import threading
 import time
 import numpy as np
 import pandas as pd
@@ -49,7 +49,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     https://github.com/sponsors/robcaulk
     """
 
-    version = "3.7.84"
+    version = "3.7.85"
 
     @cached_property
     def _optuna_config(self) -> dict:
@@ -92,10 +92,6 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             and self._optuna_config.get("enabled")
             and self.data_split_parameters.get("test_size", TEST_SIZE) > 0
         )
-        self._optuna_locks = {
-            "label": threading.RLock(),
-            "throttle": threading.RLock(),
-        }
         self._optuna_hp_value: dict[str, float] = {}
         self._optuna_train_value: dict[str, float] = {}
         self._optuna_label_values: dict[str, list] = {}
@@ -192,7 +188,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     def get_optuna_label_all_candles(self) -> list[int]:
         n_pairs = len(self.pairs)
         label_frequency_candles = max(
-            2, n_pairs, int(self.ft_params.get("label_frequency_candles", 12))
+            2, 2 * n_pairs - 1, int(self.ft_params.get("label_frequency_candles", 12))
         )
         min_offset = -int(label_frequency_candles / 2)
         max_offset = int(label_frequency_candles / 2)
@@ -208,18 +204,31 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             raise RuntimeError("Failed to initialize optuna label candle pool")
 
     def set_optuna_label_candle(self, pair: str) -> None:
-        with self._optuna_locks.get("label"):
-            if len(self._optuna_label_candle_pool) == 0:
-                self.init_optuna_label_candle_pool()
-            self._optuna_label_candle[pair] = self._optuna_label_candle_pool.pop()
-            optuna_label_available_candles = (
-                set(self.get_optuna_label_all_candles())
-                - set(self._optuna_label_candle_pool)
-                - set(self._optuna_label_candle.values())
+        if len(self._optuna_label_candle_pool) == 0:
+            raise RuntimeError(
+                "Optuna label candle pool is empty, cannot set optuna label candle"
             )
-            if len(optuna_label_available_candles) > 0:
-                self._optuna_label_candle_pool.extend(optuna_label_available_candles)
-                random.shuffle(self._optuna_label_candle_pool)
+        optuna_label_candle_pool = copy.deepcopy(self._optuna_label_candle_pool)
+        for p in self.pairs:
+            if p == pair:
+                continue
+            optuna_label_candle = self._optuna_label_candle.get(p)
+            optuna_label_candles = self._optuna_label_candles.get(p)
+            if optuna_label_candle is not None and optuna_label_candles is not None:
+                remaining_candles = optuna_label_candle - optuna_label_candles
+                if remaining_candles in optuna_label_candle_pool:
+                    optuna_label_candle_pool.remove(remaining_candles)
+        optuna_label_candle = optuna_label_candle_pool.pop()
+        self._optuna_label_candle[pair] = optuna_label_candle
+        self._optuna_label_candle_pool.remove(optuna_label_candle)
+        optuna_label_available_candles = (
+            set(self.get_optuna_label_all_candles())
+            - set(self._optuna_label_candle_pool)
+            - set(self._optuna_label_candle.values())
+        )
+        if len(optuna_label_available_candles) > 0:
+            self._optuna_label_candle_pool.extend(optuna_label_available_candles)
+            random.shuffle(self._optuna_label_candle_pool)
 
     def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
         """
@@ -322,23 +331,25 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     ) -> None:
         if namespace != "label":
             raise ValueError(f"Invalid namespace: {namespace}")
-        with self._optuna_locks.get("throttle"):
-            self._optuna_label_candles[pair] += 1
-            if self._optuna_label_candles[pair] >= self._optuna_label_candle[pair]:
-                try:
-                    callback()
-                except Exception as e:
-                    logger.error(
-                        f"Error executing optuna {pair} {namespace} callback: {str(e)}",
-                        exc_info=True,
-                    )
-                finally:
-                    self._optuna_label_candles[pair] = 0
-                    self.set_optuna_label_candle(pair)
-            else:
-                logger.info(
-                    f"Optuna {pair} {namespace} callback throttled, still {self._optuna_label_candle[pair] - self._optuna_label_candles[pair]} candles to go"
+        self._optuna_label_candles[pair] += 1
+        optuna_label_remaining_candles = self._optuna_label_candle.get(
+            pair
+        ) - self._optuna_label_candles.get(pair)
+        if optuna_label_remaining_candles <= 0:
+            try:
+                callback()
+            except Exception as e:
+                logger.error(
+                    f"Error executing optuna {pair} {namespace} callback: {str(e)}",
+                    exc_info=True,
                 )
+            finally:
+                self._optuna_label_candles[pair] = 0
+                self.set_optuna_label_candle(pair)
+        else:
+            logger.info(
+                f"Optuna {pair} {namespace} callback throttled, still {optuna_label_remaining_candles} candles to go"
+            )
 
     def fit_live_predictions(self, dk: FreqaiDataKitchen, pair: str) -> None:
         warmed_up = True
@@ -1230,7 +1241,7 @@ def zigzag(
     natr_ratio: float = 6.0,
 ) -> tuple[list[int], list[float], list[int], list[float]]:
     min_confirmation_window: int = 3
-    max_confirmation_window: int = 6
+    max_confirmation_window: int = 5
     n = len(df)
     if df.empty or n < max(natr_period, 2 * max_confirmation_window + 1):
         return [], [], [], []
index 7a509951b4cab0375716b157c6f25839e3d0c1a1..7b81b3832e605f1ac47351d71bb414855e088168 100644 (file)
@@ -61,7 +61,7 @@ class QuickAdapterV3(IStrategy):
     INTERFACE_VERSION = 3
 
     def version(self) -> str:
-        return "3.3.88"
+        return "3.3.89"
 
     timeframe = "5m"
 
index 5912fa3172860696b000143bd1cf585d04c776f6..a0e379c5767321f8404113e32d8eba4206dad88e 100644 (file)
@@ -423,7 +423,7 @@ def zigzag(
     natr_ratio: float = 6.0,
 ) -> tuple[list[int], list[float], list[int], list[float]]:
     min_confirmation_window: int = 3
-    max_confirmation_window: int = 6
+    max_confirmation_window: int = 5
     n = len(df)
     if df.empty or n < max(natr_period, 2 * max_confirmation_window + 1):
         return [], [], [], []