From: Jérôme Benoit Date: Wed, 22 Oct 2025 11:33:38 +0000 (+0200) Subject: refactor(qav3): use smart heuristic to compute label_frequency_candles X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=276b2a7704dc1c75a22d1f8047cf361044eaccca;p=freqai-strategies.git refactor(qav3): use smart heuristic to compute label_frequency_candles Signed-off-by: Jérôme Benoit --- diff --git a/README.md b/README.md index b91a0e1..e9af9e2 100644 --- 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). | diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index cd33696..a9f0eec 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -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)