]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(quickadapter): validate Optuna numeric configuration (#190)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 30 Jul 2026 21:41:32 +0000 (23:41 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Jul 2026 21:41:32 +0000 (23:41 +0200)
* fix(quickadapter): validate Optuna numeric configuration

* refactor(quickadapter): reuse shared numeric validator for Optuna config

Route the Optuna numeric configuration validation through a new public
Utils.require_numeric() helper that wraps the canonical _NumericValidator,
instead of reimplementing bool/int/range checks inline. This removes the
duplicated validation logic, harmonizes error messages with the rest of
the module, and drops the unreachable math.isfinite() branch on
space_fraction (non-finite values already fail the range check).

Also align the README seed range notation (int [0, 4294967295]) with the
existing int [2, 10000] style and the emitted error message.

Behavior-preserving: raise/pass outcomes are identical to the previous
implementation across the full option matrix, except unbounded integer
options with values >= 2**64 (n_trials, timeout, n_startup_trials) are now
rejected via the shared finiteness contract (values <= 2**63 unchanged).

* fix(quickadapter): preserve numeric validation contracts

Keep exact built-in type semantics when using the shared helper and treat arbitrary-precision Python integers as finite.

* refactor(quickadapter): reuse shared boolean validator for Optuna config

Route the Optuna boolean option validation through a new public
Utils.require_bool() helper that wraps the canonical _BoolValidator,
mirroring require_numeric(). This removes the last inline validation in
_optuna_config so both boolean and numeric checks now share the framework
validators, closing the residual duplication.

Behavior-preserving: raise/pass outcomes are identical to the previous
inline check across all boolean options (True/False accepted, every other
type rejected).

* docs(quickadapter): justify the int short-circuit in _is_finite_value

The comment stated a Python language truism (integers are arbitrary-precision
and finite) instead of explaining why the branch exists. Reword it to document
the load-bearing rationale: np.isfinite raises TypeError/OverflowError on Python
ints >= 2**64, so routing them through it would misclassify finite values as
non-finite. Comment-only change; no behavior change.

README.md
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/Utils.py

index 5407b913ca9ef3a2463326edd69d668e19d1309f..3120346a353f00473c95f67d4b5e3abf102f0ffd 100644 (file)
--- a/README.md
+++ b/README.md
@@ -139,13 +139,13 @@ docker compose up -d --build
 | 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                                  | 1                             | int >= 1                                                                                                                                                                                                     | Parallel HPO workers. The effective value is capped at `max(1, CPU threads // 4)`; that cap is not the default.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+| freqai.optuna_hyperopt.n_jobs                                  | 1                             | int >= 1                                                                                                                                                                                                     | Parallel HPO workers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
 | freqai.optuna_hyperopt.timeout                                 | 7200                          | int >= 0                                                                                                                                                                                                     | HPO wall-clock timeout in seconds.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
 | freqai.optuna_hyperopt.label_candles_step                      | 1                             | int >= 1                                                                                                                                                                                                     | Step for Zigzag NATR period `label` search space.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
 | freqai.optuna_hyperopt.space_reduction                         | false                         | bool                                                                                                                                                                                                         | Enable/disable `hp` search space reduction based on previous best parameters.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
 | freqai.optuna_hyperopt.space_fraction                          | 0.4                           | float [0,1]                                                                                                                                                                                                  | Fraction of the `hp` search space to use with `space_reduction`. Lower values create narrower search ranges around the best parameters.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
 | freqai.optuna_hyperopt.min_resource                            | 3                             | int >= 1                                                                                                                                                                                                     | Minimum resource per [HyperbandPruner](https://optuna.readthedocs.io/en/stable/reference/generated/optuna.pruners.HyperbandPruner.html) rung.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.optuna_hyperopt.seed                                    | 1                             | int >= 0                                                                                                                                                                                                     | HPO RNG seed used by the Optuna samplers and label-candle shuffling.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
+| freqai.optuna_hyperopt.seed                                    | 1                             | int [0, 4294967295]                                                                                                                                                                                          | HPO RNG seed used by the Optuna samplers and label-candle shuffling.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
 | freqai.optuna_hyperopt.reset_label_study_on_schema_mismatch    | true                          | bool                                                                                                                                                                                                         | Reset a persisted `label` study when its selection schema is missing, invalid, or incompatible. `true` keeps the historical destructive reset, deleting the study before recreating it; `false` preserves its trials and stored metadata, permits caller-managed reuse in memory, and does not persist selected params until the schema is reconciled. Both fail closed: an inspection error, or (under `true`) a deletion error, aborts study creation. Has no effect when `continuous=true` or outside live/dry-run modes, where studies are always reset.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
 | freqai.optuna_hyperopt.vary_model_seed_by_trial                | true                          | bool                                                                                                                                                                                                         | Add `trial.number` to each regressor's configured model seed (or its default seed of `1`) during HPO. `true` samples model randomness across trials and preserves the historical behavior; `false` evaluates every trial and the final fit with the same model seed. This does not change `freqai.optuna_hyperopt.seed`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
 
index 66871b0b47483f9f748807b62d16b0cf3152ce9f..6f378c87b0d53150a337cbc17bb0531a90531e1b 100644 (file)
@@ -105,6 +105,8 @@ from Utils import (
     migrate_config,
     optuna_load_best_params,
     optuna_save_best_params,
+    require_bool,
+    require_numeric,
     sanitize_and_renormalize,
     safe_distribution_fit,
     summarize_label_weight_support,
@@ -449,6 +451,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     OPTUNA_SPACE_REDUCTION_DEFAULT: Final[bool] = False
     OPTUNA_SPACE_FRACTION_DEFAULT: Final[float] = 0.4
     OPTUNA_SEED_DEFAULT: Final[int] = 1
+    _OPTUNA_SEED_MAX: Final[int] = 2**32 - 1
     OPTUNA_RESET_LABEL_STUDY_ON_SCHEMA_MISMATCH_DEFAULT: Final[bool] = True
     OPTUNA_VARY_MODEL_SEED_BY_TRIAL_DEFAULT: Final[bool] = True
 
@@ -461,6 +464,16 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         "vary_model_seed_by_trial",
     )
 
+    _OPTUNA_INT_OPTION_BOUNDS: Final[dict[str, tuple[int, Optional[int]]]] = {
+        "n_jobs": (1, None),
+        "n_startup_trials": (0, None),
+        "n_trials": (1, None),
+        "timeout": (0, None),
+        "label_candles_step": (1, None),
+        "min_resource": (1, None),
+        "seed": (0, _OPTUNA_SEED_MAX),
+    }
+
     _DATA_SPLIT_METHODS: Final[tuple[str, ...]] = (
         "train_test_split",
         "timeseries_split",
@@ -1337,12 +1350,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     def _optuna_config(self) -> dict[str, Any]:
         optuna_default_config = {
             "enabled": False,
-            "n_jobs": min(
-                self.config.get("freqai", {})
-                .get("optuna_hyperopt", {})
-                .get("n_jobs", QuickAdapterRegressorV3.OPTUNA_N_JOBS_DEFAULT),
-                max(int(self.max_system_threads / 4), 1),
-            ),
+            "n_jobs": QuickAdapterRegressorV3.OPTUNA_N_JOBS_DEFAULT,
             "sampler": QuickAdapterRegressorV3._OPTUNA_HPO_SAMPLERS.tpe,
             "storage": QuickAdapterRegressorV3._STORAGE_FILE,
             "continuous": True,
@@ -1369,11 +1377,30 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             **optuna_hyperopt,
         }
         for option in QuickAdapterRegressorV3._OPTUNA_BOOL_OPTIONS:
-            if not isinstance(optuna_config[option], bool):
-                raise ValueError(
-                    f"freqai.optuna_hyperopt.{option} must be a boolean "
-                    f"(got {type(optuna_config[option]).__name__})"
-                )
+            require_bool(
+                optuna_config[option],
+                option,
+                context="freqai.optuna_hyperopt",
+            )
+        for option, (
+            minimum,
+            maximum,
+        ) in QuickAdapterRegressorV3._OPTUNA_INT_OPTION_BOUNDS.items():
+            require_numeric(
+                optuna_config[option],
+                option,
+                context="freqai.optuna_hyperopt",
+                minimum=minimum,
+                maximum=maximum,
+                require_int=True,
+            )
+        require_numeric(
+            optuna_config["space_fraction"],
+            "space_fraction",
+            context="freqai.optuna_hyperopt",
+            minimum=0,
+            maximum=1,
+        )
         return optuna_config
 
     @property
index b1b6fda33d0ec2f27541ac83c78c4b1ccced07f3..7f8815da1b44c88748a686bb89fdd8ffff777e70 100644 (file)
@@ -323,9 +323,13 @@ def safe_log_ratio(
 
 
 def _is_finite_value(value: Any) -> bool:
-    # ``np.isfinite`` raises/returns non-scalar on inputs it cannot reduce to a
-    # single finite float64 (Python ints >= 2**64 -> ``TypeError``/``OverflowError``;
-    # array-likes -> a ``ValueError`` on ``bool()``); treat all of those as non-finite.
+    # Short-circuit Python ints (always finite): ``np.isfinite`` raises on ints
+    # >= 2**64 (``TypeError``/``OverflowError``), so routing them through it
+    # would misclassify finite values as non-finite. ``np.isfinite`` also
+    # raises/returns non-scalar on array-likes (``ValueError`` on ``bool()``);
+    # treat those remaining failures as non-finite.
+    if isinstance(value, int):
+        return True
     try:
         return bool(np.isfinite(value))
     except (TypeError, OverflowError, ValueError):
@@ -489,6 +493,42 @@ def _validate_params(
     return result
 
 
+def require_numeric(
+    value: Any,
+    name: str,
+    *,
+    context: str,
+    minimum: float | None = None,
+    maximum: float | None = None,
+    min_exclusive: bool = False,
+    max_exclusive: bool = False,
+    require_int: bool = False,
+) -> int | float:
+    """Return an exact built-in numeric value that satisfies the requested bounds."""
+    validator = _NumericValidator(
+        min_value=minimum,
+        max_value=maximum,
+        min_exclusive=min_exclusive,
+        max_exclusive=max_exclusive,
+        require_int=require_int,
+    )
+    accepted_types = (int,) if require_int else (int, float)
+    if type(value) not in accepted_types or not validator(value):
+        raise ValueError(
+            f"Invalid {context}.{name} value {value!r}: {validator.message(name)}"
+        )
+    return value
+
+
+def require_bool(value: Any, name: str, *, context: str) -> bool:
+    validator = _BoolValidator()
+    if not validator(value):
+        raise ValueError(
+            f"Invalid {context}.{name} value {value!r}: {validator.message(name)}"
+        )
+    return value
+
+
 def validate_range(
     min_val: float | int,
     max_val: float | int,