]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor(qav3): use smart heuristic to compute label_frequency_candles
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 22 Oct 2025 11:33:38 +0000 (13:33 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 22 Oct 2025 11:33:38 +0000 (13:33 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
README.md
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py

index b91a0e14c81d6e9fcfd6d237e3e3e7500251c417..e9af9e27e446e63f3a7941441f93f8d3f044a656 100644 (file)
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ docker compose up -d --build
 | freqai.feature_parameters.label_natr_ratio | 9.0              | float > 0 | Zigzag NATR ratio.                                                              |
 | freqai.feature_parameters.min_label_natr_ratio | 9.0              | float > 0 | Minimum NATR ratio bound used by label HPO.                                     |
 | freqai.feature_parameters.max_label_natr_ratio | 12.0             | float > 0 | Maximum NATR ratio bound used by label HPO.                                     |
-| freqai.feature_parameters.label_frequency_candles | 12               | int >= 2 | Reversals labeling frequency.                                                   |
+| freqai.feature_parameters.label_frequency_candles | `auto`            | int >= 2 \| `auto` | Reversals labeling frequency. `auto` = max(2, 2 * number of whitelisted pairs). |
 | freqai.feature_parameters.label_metric | `euclidean`      | string (supported: `euclidean`,`minkowski`,`cityblock`,`chebyshev`,`mahalanobis`,`seuclidean`,`jensenshannon`,`sqeuclidean`,...) | Metric used in distance calculations to ideal point.                            |
 | freqai.feature_parameters.label_weights | [0.5,0.5]        | list[float] | Per-objective weights used in distance calculations to ideal point.             |
 | freqai.feature_parameters.label_p_order | `None`           | float | p-order used by Minkowski / power-mean calculations (optional).                 |
index cd33696ce0559e95026901168a6d16029dfc3852..a9f0eec4096e8a417e6a92f15e85688dc1f05386 100644 (file)
@@ -90,22 +90,66 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             **self.config.get("freqai", {}).get("optuna_hyperopt", {}),
         }
 
-    @property
-    def _optuna_label_candle_pool_full(self) -> list[int]:
-        if not hasattr(self, "pairs") or not self.pairs:
-            raise RuntimeError(
-                "Failed to initialize optuna label candle pool full: pairs property is not defined or empty"
-            )
+    def _get_label_frequency_candles(self) -> int:
+        """
+        Calculate label_frequency_candles.
+
+        Default behavior is 'auto' which equals max(2, 2 * number_of_pairs).
+        User can override with:
+        - "auto" string value
+        - Integer value between 2 and 10000
+
+        Returns:
+            int: The calculated label_frequency_candles value
+
+        Raises:
+            ValueError: If no trading pairs are configured
+        """
         n_pairs = len(self.pairs)
-        label_frequency_candles = max(
-            2,
-            2 * n_pairs,
-            int(
-                self.config.get("feature_parameters", {}).get(
-                    "label_frequency_candles", 12
-                )
-            ),
+        default_label_frequency_candles = max(2, 2 * n_pairs)
+
+        label_frequency_candles = self.config.get("feature_parameters", {}).get(
+            "label_frequency_candles"
         )
+
+        if label_frequency_candles is None:
+            label_frequency_candles = default_label_frequency_candles
+            logger.info(f"{label_frequency_candles=} (default)")
+        elif isinstance(label_frequency_candles, str):
+            if label_frequency_candles == "auto":
+                label_frequency_candles = default_label_frequency_candles
+                logger.info(f"{label_frequency_candles=} (auto)")
+            else:
+                logger.warning(
+                    f"Invalid string value for label_frequency_candles: '{label_frequency_candles}'. "
+                    f"Only 'auto' is supported. Using fallback"
+                )
+                label_frequency_candles = default_label_frequency_candles
+                logger.info(f"{label_frequency_candles=} (fallback)")
+        elif isinstance(label_frequency_candles, (int, float)):
+            if label_frequency_candles >= 2 and label_frequency_candles <= 10000:
+                label_frequency_candles = int(label_frequency_candles)
+                logger.info(f"{label_frequency_candles=} (configuration)")
+            else:
+                logger.warning(
+                    f"Invalid numeric value for label_frequency_candles: {label_frequency_candles}. "
+                    f"Must be between 2 and 10000. Using fallback"
+                )
+                label_frequency_candles = default_label_frequency_candles
+                logger.info(f"{label_frequency_candles=} (fallback)")
+        else:
+            logger.warning(
+                f"Invalid type for label_frequency_candles: {type(label_frequency_candles).__name__}. "
+                f"Expected int, float, or 'auto'. Using fallback"
+            )
+            label_frequency_candles = default_label_frequency_candles
+            logger.info(f"{label_frequency_candles=} (fallback)")
+
+        return label_frequency_candles
+
+    @property
+    def _optuna_label_candle_pool_full(self) -> list[int]:
+        label_frequency_candles = self._get_label_frequency_candles()
         cache_key = label_frequency_candles
         if cache_key not in self._optuna_label_candle_pool_full_cache:
             half_label_frequency_candles = int(label_frequency_candles / 2)