]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor(quickadapter): extract is_finite_number to Utils and reuse it (#122)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 23 Jul 2026 19:25:54 +0000 (21:25 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Jul 2026 19:25:54 +0000 (21:25 +0200)
* refactor(quickadapter): extract is_finite_number to Utils and reuse it

- move the numeric/finite/non-bool scalar guard from a QuickAdapterV3
  static method to a shared Utils.is_finite_number helper
- reuse it across the strategy label getters/setters and the shared
  label_natr_multiplier validation guard
- leave the strict positive-int label_period_candles/label_horizon_candles
  paths unchanged (distinct predicate)

* refactor(quickadapter): harden label-param fallbacks and per-row NATR guard

Address the #112 re-review findings (landed in this PR):
- coerce the per-row label_period_candles series with pandas to_numeric
  before np.isfinite, matching the guarded scalar path (object/str dtype no
  longer raises)
- re-validate the persisted _label_params fallback in the label getters via
  is_finite_number before use, otherwise fall back to config
- generalize the is_trade_runmode comment (it gates both persisted-param
  reuse and per-candle param setting)
- README: drop the inaccurate 'simulated' wording and the runmode-specific
  framing on the label_period_candles/label_natr_multiplier rows

* docs(quickadapter): drop per-candle label-param behavior notes from tunables

The label_period_candles/label_natr_multiplier rows describe labeling
tunables; the per-candle strategy-NATR/exit consumption is an internal
behavior detail (already documented inline in the code), not needed to
configure the tunable. Revert both rows to their base description.

* style(quickadapter): wrap set_label_natr_multiplier guard per ruff format

The committed one-liner exceeded the 88-char line length; apply ruff
format wrapping (behavior unchanged).

* docs(quickadapter): align warm_start tunable row padding

Trim one over-pad space so the warm_start row matches the surrounding table column width (whitespace only).

README.md
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index b2bcc1de84cd3b11103b39d339f0f198f3c605b2..5ac33d919fdb272eafb891e46a27fba3e8c38deb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -100,12 +100,12 @@ docker compose up -d --build
 | freqai.label_pipeline.sigmoid_scale                            | 1.0                           | float > 0                                                                                                                                                                                                    | Scale parameter for `sigmoid` normalization, controls steepness.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
 | freqai.label_pipeline.gamma                                    | 1.0                           | float (0,10]                                                                                                                                                                                                 | Contrast exponent applied to labels after normalization: >1 emphasizes extrema, values between 0 and 1 soften.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
 | _Feature parameters_                                           |                               |                                                                                                                                                                                                              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
-| freqai.feature_parameters.label_period_candles                 | min/max midpoint              | int >= 1                                                                                                                                                                                                     | Zigzag labeling NATR period. When label HPO is enabled, backtest and hyperopt retain the period produced for each simulated candle when calculating strategy NATR.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.feature_parameters.label_period_candles                 | min/max midpoint              | int >= 1                                                                                                                                                                                                     | Zigzag labeling NATR period.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
 | freqai.feature_parameters.label_horizon_candles                | `label_period_candles`        | int >= 1                                                                                                                                                                                                     | Number of candles after a label row before the label is considered known by causal split guards. Recommended: cover the zigzag pivot confirmation lag (the smoothing kernel half-width is added automatically by `set_freqai_targets`). Used by causal split guards and `<label>_known_at_lookahead` metadata. When unset, falls back to `label_period_candles`.                                                                                                                                                                                                                                           |
 | freqai.feature_parameters.causal_mode                          | true                          | bool                                                                                                                                                                                                         | Causal split guard toggle. When `true` (default): rejects `data_split_parameters.shuffle=true`, `shuffle_after_split=true`, `reverse_train_test_order=true`; for `timeseries_split` auto-sets `gap=label_horizon_candles` when unset/`0` (rejects explicit `gap<label_horizon_candles`); for `train_test_split` drops train rows where position `>=first_test_position-label_horizon_candles`; with `<label>_known_at_lookahead` columns, additionally drops rows where `local_position + row-wise max(<label>_known_at_lookahead) >= first_test_position`. `false` is deprecated; acausal baselines only. |
 | freqai.feature_parameters.min_label_period_candles             | 12                            | int >= 1                                                                                                                                                                                                     | Minimum labeling NATR period used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
 | freqai.feature_parameters.max_label_period_candles             | 24                            | int >= 1                                                                                                                                                                                                     | Maximum labeling NATR period used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
-| freqai.feature_parameters.label_natr_multiplier                | min/max midpoint              | float > 0                                                                                                                                                                                                    | Zigzag labeling NATR multiplier. When label HPO is enabled, stop-loss, take-profit, and candle-deviation calculations consume the value produced for the current simulated candle.                                                                                                                                                                                                                                                                                                                                                                                                                             |
+| freqai.feature_parameters.label_natr_multiplier                | min/max midpoint              | float > 0                                                                                                                                                                                                    | Zigzag labeling NATR multiplier.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
 | freqai.feature_parameters.min_label_natr_multiplier            | 9.0                           | float > 0                                                                                                                                                                                                    | Minimum labeling NATR multiplier used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
 | freqai.feature_parameters.max_label_natr_multiplier            | 12.0                          | float > 0                                                                                                                                                                                                    | Maximum labeling NATR multiplier used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
 | freqai.feature_parameters.label_frequency_candles              | `auto`                        | int >= 2 \| `auto`                                                                                                                                                                                           | Reversals labeling frequency. `auto` = max(2, 2 \* number of whitelisted pairs).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
@@ -135,7 +135,7 @@ docker compose up -d --build
 | freqai.optuna_hyperopt.label_sampler                           | `auto`                        | enum {`auto`,`tpe`,`nsgaii`,`nsgaiii`}                                                                                                                                                                       | HPO sampler algorithm for multi-objective `label` namespace. `nsgaii` uses [NSGAIISampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.NSGAIISampler.html), `nsgaiii` uses [NSGAIIISampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.NSGAIIISampler.html).                                                                                                                                                                                                                                                            |
 | freqai.optuna_hyperopt.storage                                 | `file`                        | enum {`file`,`sqlite`}                                                                                                                                                                                       | HPO storage backend.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
 | freqai.optuna_hyperopt.continuous                              | true                          | bool                                                                                                                                                                                                         | Continuous HPO. Forced for both namespaces in backtest and hyperopt, resetting the study on each optimization.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
-| freqai.optuna_hyperopt.warm_start                              | true                          | bool                                                                                                                                                                                                         | Warm start HPO with previous best value(s). Persisted values are loaded and saved only in live and dry-run modes; non-live runs reuse only values produced earlier in the same run.                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.optuna_hyperopt.warm_start                              | true                          | bool                                                                                                                                                                                                         | Warm start HPO with previous best value(s). Persisted values are loaded and saved only in live and dry-run modes; non-live runs reuse only values produced earlier in the same run.                                                                                                                                                                                                                                                                                                                                                                                                                        |
 | freqai.optuna_hyperopt.n_startup_trials                        | 15                            | int >= 0                                                                                                                                                                                                     | HPO startup trials.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
 | freqai.optuna_hyperopt.n_trials                                | 50                            | int >= 1                                                                                                                                                                                                     | Maximum HPO trials.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
 | freqai.optuna_hyperopt.n_jobs                                  | CPU threads / 4               | int >= 1                                                                                                                                                                                                     | Parallel HPO workers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
index 88c09e74161eb59cb495568e911403288cc218af..ee51f65c7746928db7e1d913a91e5d937d76a096 100644 (file)
@@ -29,7 +29,7 @@ from LabelTransformer import (
     WEIGHT_STRATEGIES,
     get_label_column_config,
 )
-from pandas import DataFrame, Series, isna
+from pandas import DataFrame, Series, isna, to_numeric
 from scipy.stats import pearsonr, t
 from technical.pivots_points import pivots_points
 
@@ -60,6 +60,7 @@ from Utils import (
     get_label_smoothing_config,
     get_label_weighting_config,
     get_zl_ma_fn,
+    is_finite_number,
     label_known_at_lookahead_column_name,
     label_weight_column_name,
     migrate_config,
@@ -202,8 +203,8 @@ class QuickAdapterV3(IStrategy):
 
     @cached_property
     def is_trade_runmode(self) -> bool:
-        # Mirror the regressor's ``self.live`` gate (runmode in TRADE_MODES):
-        # persisted label params are reused only in live and dry-run.
+        # True in live and dry-run (runmode in TRADE_MODES), mirroring the
+        # regressor's ``self.live`` gate.
         return self.config.get("runmode") in TRADE_MODES
 
     @property
@@ -861,16 +862,6 @@ class QuickAdapterV3(IStrategy):
         dataframe["%-hour_of_day"] = (dates.dt.hour + 1) / 25
         return dataframe
 
-    @staticmethod
-    def _is_finite_number(value: Any) -> bool:
-        # Reject bool (int(True) == 1) and non-numeric (str/object) before
-        # np.isfinite, which raises on non-numeric input.
-        return (
-            not isinstance(value, bool)
-            and isinstance(value, (int, float, np.integer, np.floating))
-            and bool(np.isfinite(value))
-        )
-
     def get_label_period_candles(
         self,
         pair: str,
@@ -881,20 +872,20 @@ class QuickAdapterV3(IStrategy):
             period_series = dataframe.get("label_period_candles")
             if period_series is not None and not period_series.empty:
                 period = period_series.iloc[candle_idx]
-                if self._is_finite_number(period) and int(period) > 0:
+                if is_finite_number(period) and int(period) > 0:
                     return int(period)
         period = self._label_params.get(pair, {}).get("label_period_candles")
+        if is_finite_number(period) and int(period) > 0:
+            return int(period)
         return int(
-            period
-            if period is not None
-            else self.freqai_info.get("feature_parameters", {}).get(
+            self.freqai_info.get("feature_parameters", {}).get(
                 "label_period_candles",
                 self._label_defaults[0],
             )
         )
 
     def set_label_period_candles(self, pair: str, label_period_candles: Any) -> None:
-        if self._is_finite_number(label_period_candles) and int(label_period_candles) > 0:
+        if is_finite_number(label_period_candles) and int(label_period_candles) > 0:
             self._label_params[pair]["label_period_candles"] = int(label_period_candles)
 
     def get_label_horizon_candles(self, pair: str) -> int:
@@ -916,19 +907,22 @@ class QuickAdapterV3(IStrategy):
             multiplier_series = dataframe.get("label_natr_multiplier")
             if multiplier_series is not None and not multiplier_series.empty:
                 multiplier = multiplier_series.iloc[candle_idx]
-                if self._is_finite_number(multiplier) and float(multiplier) > 0.0:
+                if is_finite_number(multiplier) and float(multiplier) > 0.0:
                     return float(multiplier)
         multiplier = self._label_params.get(pair, {}).get("label_natr_multiplier")
+        if is_finite_number(multiplier) and float(multiplier) > 0.0:
+            return float(multiplier)
         return float(
-            multiplier
-            if multiplier is not None
-            else self.freqai_info.get("feature_parameters", {}).get(
+            self.freqai_info.get("feature_parameters", {}).get(
                 "label_natr_multiplier", self._label_defaults[1]
             )
         )
 
     def set_label_natr_multiplier(self, pair: str, label_natr_multiplier: Any) -> None:
-        if self._is_finite_number(label_natr_multiplier) and float(label_natr_multiplier) > 0.0:
+        if (
+            is_finite_number(label_natr_multiplier)
+            and float(label_natr_multiplier) > 0.0
+        ):
             self._label_params[pair]["label_natr_multiplier"] = float(
                 label_natr_multiplier
             )
@@ -1103,12 +1097,9 @@ class QuickAdapterV3(IStrategy):
             # per-row periods within one column is intentional).
             dataframe["natr_label_period_candles"] = np.nan
             fallback_period = self.get_label_period_candles(pair)
-            valid_periods = np.isfinite(label_period_candles_series) & (
-                label_period_candles_series >= 1
-            )
-            periods = label_period_candles_series.where(
-                valid_periods, fallback_period
-            ).astype(int)
+            numeric_periods = to_numeric(label_period_candles_series, errors="coerce")
+            valid_periods = np.isfinite(numeric_periods) & (numeric_periods >= 1)
+            periods = numeric_periods.where(valid_periods, fallback_period).astype(int)
             for period in periods.unique():
                 period_rows = periods == period
                 period_natr = ta.NATR(dataframe, timeperiod=int(period))
index 01893032bab3e5453cd6c2b583abda89279d4e7c..2537527c6639e6a79fbddbbd4f3a7b4c957eb49d 100644 (file)
@@ -306,6 +306,16 @@ def safe_log_ratio(
     return _safe_numeric_result(np.asarray(result, dtype=float), numerator, denominator)
 
 
+def is_finite_number(value: Any) -> bool:
+    # Reject bool (int(True) == 1) and non-numeric (str/object) before
+    # np.isfinite, which raises on non-numeric input.
+    return (
+        not isinstance(value, bool)
+        and isinstance(value, (int, float, np.integer, np.floating))
+        and bool(np.isfinite(value))
+    )
+
+
 @dataclass(frozen=True, slots=True)
 class _EnumValidator:
     valid_values: tuple[str, ...]
@@ -3858,12 +3868,7 @@ def _validate_optuna_label_best_params(
                 f"label_period_candles={label_period_candles!r} (must be int >= 1)"
             )
         return None
-    if (
-        isinstance(label_natr_multiplier, bool)
-        or not isinstance(label_natr_multiplier, (int, float, np.integer, np.floating))
-        or not np.isfinite(label_natr_multiplier)
-        or label_natr_multiplier <= 0
-    ):
+    if not is_finite_number(label_natr_multiplier) or label_natr_multiplier <= 0:
         if logger is not None:
             logger.warning(
                 f"[{pair}] Ignoring Optuna label best params: invalid "