]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor(ReforceXY): harmonize logging messages in reward space analysis
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 26 Dec 2025 17:46:19 +0000 (18:46 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 26 Dec 2025 17:46:19 +0000 (18:46 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
README.md
ReforceXY/reward_space_analysis/reward_space_analysis.py
ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py
ReforceXY/reward_space_analysis/tests/helpers/test_utilities.py
ReforceXY/reward_space_analysis/tests/robustness/test_robustness.py
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py

index ae3662d255f8a7b48c50da0581a5a34ddc9c1568..730d92562cc6775b0583bb73fb4b0c314c149727 100644 (file)
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ docker compose up -d --build
 | freqai.predictions_extrema.threshold_outlier         | 0.999                         | float (0,1)                                                                                                                                | Quantile threshold for predictions outlier filtering.                                                                                                                                                                                                                                                                                                                   |
 | freqai.predictions_extrema.extrema_fraction          | 1.0                           | float (0,1]                                                                                                                                | Fraction of extrema used for thresholds. `1.0` uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`.                                                                                                                                                                                                  |
 | _Optuna / HPO_                                       |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.optuna_hyperopt.enabled                       | true                          | bool                                                                                                                                       | Enables HPO.                                                                                                                                                                                                                                                                                                                                                            |
+| freqai.optuna_hyperopt.enabled                       | false                         | bool                                                                                                                                       | Enables HPO.                                                                                                                                                                                                                                                                                                                                                            |
 | freqai.optuna_hyperopt.sampler                       | `tpe`                         | enum {`tpe`,`auto`}                                                                                                                        | HPO sampler algorithm. `tpe` uses [TPESampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.TPESampler.html) with multivariate and group, `auto` uses [AutoSampler](https://hub.optuna.org/samplers/auto_sampler).                                                                                                              |
 | freqai.optuna_hyperopt.storage                       | `file`                        | enum {`file`,`sqlite`}                                                                                                                     | HPO storage backend.                                                                                                                                                                                                                                                                                                                                                    |
 | freqai.optuna_hyperopt.continuous                    | true                          | bool                                                                                                                                       | Continuous HPO.                                                                                                                                                                                                                                                                                                                                                         |
index 55a1795ec0264bcdc15315dd5920b17d6c2a9197..414c99c26eaf32b3345546a212d75051d9799fd0 100644 (file)
@@ -793,7 +793,7 @@ def _compute_time_attenuation_coefficient(
     exit_plateau_grace = _get_float_param(params, "exit_plateau_grace")
     if exit_plateau_grace < 0.0:
         warnings.warn(
-            "exit_plateau_grace < 0; falling back to 0.0",
+            f"exit_plateau_grace={exit_plateau_grace} < 0; falling back to 0.0",
             RewardDiagnosticsWarning,
             stacklevel=2,
         )
@@ -801,7 +801,7 @@ def _compute_time_attenuation_coefficient(
     exit_linear_slope = _get_float_param(params, "exit_linear_slope")
     if exit_linear_slope < 0.0:
         warnings.warn(
-            "exit_linear_slope < 0; falling back to 1.0",
+            f"exit_linear_slope={exit_linear_slope} < 0; falling back to 1.0",
             RewardDiagnosticsWarning,
             stacklevel=2,
         )
@@ -826,7 +826,7 @@ def _compute_time_attenuation_coefficient(
                 alpha = -math.log(tau) / _LOG_2
             else:
                 warnings.warn(
-                    f"exit_power_tau={tau} invalid; falling back to alpha=1.0",
+                    f"exit_power_tau={tau} outside (0,1]; falling back to alpha=1.0",
                     RewardDiagnosticsWarning,
                     stacklevel=2,
                 )
@@ -837,14 +837,14 @@ def _compute_time_attenuation_coefficient(
         hl = _get_float_param(params, "exit_half_life")
         if np.isclose(hl, 0.0):
             warnings.warn(
-                f"exit_half_life={hl} close to 0; falling back to 1.0",
+                f"exit_half_life={hl} <= 0; falling back to 1.0",
                 RewardDiagnosticsWarning,
                 stacklevel=2,
             )
             return 1.0
         if hl < 0.0:
             warnings.warn(
-                f"exit_half_life={hl} negative; falling back to 1.0",
+                f"exit_half_life={hl} < 0; falling back to 1.0",
                 RewardDiagnosticsWarning,
                 stacklevel=2,
             )
@@ -881,7 +881,7 @@ def _compute_time_attenuation_coefficient(
         time_attenuation_coefficient = kernel(effective_dr)
     except Exception as e:
         warnings.warn(
-            f"exit_attenuation_mode '{exit_attenuation_mode}' failed ({e!r}); fallback linear (effective_dr={effective_dr:.5f})",
+            f"exit_attenuation_mode='{exit_attenuation_mode}' failed ({e!r}); falling back to linear",
             RewardDiagnosticsWarning,
             stacklevel=2,
         )
@@ -948,9 +948,7 @@ def _get_exit_factor(
         if exit_factor_threshold > 0 and np.isfinite(exit_factor_threshold):
             if abs(exit_factor) > exit_factor_threshold:
                 warnings.warn(
-                    (
-                        f"_get_exit_factor |exit_factor|={abs(exit_factor):.2f} exceeds threshold {exit_factor_threshold:.2f}"
-                    ),
+                    f"|exit_factor|={abs(exit_factor):.2f} > threshold={exit_factor_threshold:.2f}",
                     RewardDiagnosticsWarning,
                     stacklevel=2,
                 )
@@ -1045,7 +1043,7 @@ def _compute_efficiency_coefficient(
     if efficiency_coefficient < 0.0:
         if _get_bool_param(params, "check_invariants"):
             warnings.warn(
-                f"efficiency_coefficient={efficiency_coefficient:.6f} < 0; clamping to 0.0",
+                f"efficiency_coefficient={efficiency_coefficient:.6f} < 0; falling back to 0.0",
                 RewardDiagnosticsWarning,
                 stacklevel=2,
             )
@@ -2214,7 +2212,7 @@ def load_real_episodes(path: Path, *, enforce_columns: bool = True) -> pd.DataFr
                 skipped += 1
         if skipped:
             warnings.warn(
-                f"Ignored {skipped} episode(s) without 'transitions' when loading '{path}'",
+                f"Skipped {skipped} episode(s) without 'transitions' when loading '{path}'",
                 RuntimeWarning,
                 stacklevel=2,
             )
@@ -2271,10 +2269,7 @@ def load_real_episodes(path: Path, *, enforce_columns: bool = True) -> pd.DataFr
             if coerced > 0:
                 frac = coerced / len(df) if len(df) > 0 else 0.0
                 warnings.warn(
-                    (
-                        f"Column '{col}' contained {coerced} non-numeric value(s) "
-                        f"({frac:.1%}) that were coerced to NaN when loading '{path}'."
-                    ),
+                    f"Coerced {coerced} non-numeric value(s) ({frac:.1%}) in column '{col}' to NaN when loading '{path}'",
                     RuntimeWarning,
                     stacklevel=2,
                 )
@@ -2296,7 +2291,7 @@ def load_real_episodes(path: Path, *, enforce_columns: bool = True) -> pd.DataFr
                 f"Found columns: {sorted(list(df.columns))}."
             )
         warnings.warn(
-            f"Loaded episodes data is missing columns {sorted(missing_required)}; filling with NaN (enforce_columns=False)",
+            f"Missing columns {sorted(missing_required)}; filled with NaN when loading (enforce_columns=False)",
             RuntimeWarning,
             stacklevel=2,
         )
@@ -2313,7 +2308,7 @@ def load_real_episodes(path: Path, *, enforce_columns: bool = True) -> pd.DataFr
     df = df.drop_duplicates()
     if len(df) != before_dupes:
         warnings.warn(
-            f"Removed {before_dupes - len(df)} duplicate transition row(s) while loading '{path}'.",
+            f"Dropped {before_dupes - len(df)} duplicate row(s) when loading '{path}'",
             RuntimeWarning,
             stacklevel=2,
         )
@@ -2630,7 +2625,7 @@ def bootstrap_confidence_intervals(
     min_rec = int(INTERNAL_GUARDS.get("bootstrap_min_recommended", 200))
     if n_bootstrap < min_rec:
         warnings.warn(
-            f"n_bootstrap={n_bootstrap} < recommended minimum {min_rec}; confidence intervals may be unstable",
+            f"n_bootstrap={n_bootstrap} < {min_rec}; confidence intervals may be unstable",
             RewardDiagnosticsWarning,
         )
 
@@ -2719,8 +2714,7 @@ def _validate_bootstrap_results(
                 ci_low, ci_high = lower, upper
             results[metric] = (mean, ci_low, ci_high)
             warnings.warn(
-                f"Degenerate bootstrap CI for {metric} adjusted to maintain positive width;"
-                f" original width={width:.6e}, epsilon={epsilon:.1e}",
+                f"bootstrap_ci for '{metric}' degenerate (width={width:.6e}); adjusted with epsilon={epsilon:.1e}",
                 RewardDiagnosticsWarning,
             )
 
@@ -2806,7 +2800,7 @@ def _validate_distribution_diagnostics(diag: Dict[str, Any], *, strict_diagnosti
                     fallback = INTERNAL_GUARDS.get("distribution_constant_fallback_moment", 0.0)
                     diag[key] = fallback
                     warnings.warn(
-                        f"Replaced undefined {key} (constant distribution) with {fallback}",
+                        f"{key} undefined (constant distribution); falling back to {fallback}",
                         RewardDiagnosticsWarning,
                     )
                 else:
@@ -2821,7 +2815,7 @@ def _validate_distribution_diagnostics(diag: Dict[str, Any], *, strict_diagnosti
                     fallback = INTERNAL_GUARDS.get("distribution_constant_fallback_moment", 0.0)
                     diag[key] = fallback
                     warnings.warn(
-                        f"Replaced undefined Anderson diagnostic {key} (constant distribution) with {fallback}",
+                        f"{key} undefined (constant distribution); falling back to {fallback}",
                         RewardDiagnosticsWarning,
                     )
                     continue
@@ -2833,7 +2827,7 @@ def _validate_distribution_diagnostics(diag: Dict[str, Any], *, strict_diagnosti
                     fallback_r2 = INTERNAL_GUARDS.get("distribution_constant_fallback_qq_r2", 1.0)
                     diag[key] = fallback_r2
                     warnings.warn(
-                        f"Replaced undefined Q-Q R^2 {key} (constant distribution) with {fallback_r2}",
+                        f"{key} undefined (constant distribution); falling back to {fallback_r2}",
                         RewardDiagnosticsWarning,
                     )
                 else:
@@ -2923,7 +2917,7 @@ def _get_potential_gamma(params: RewardParams) -> float:
     gamma = _get_float_param(params, "potential_gamma", np.nan)
     if not np.isfinite(gamma):
         warnings.warn(
-            f"potential_gamma not specified or invalid; defaulting to {POTENTIAL_GAMMA_DEFAULT}",
+            f"potential_gamma not specified; falling back to {POTENTIAL_GAMMA_DEFAULT}",
             RewardDiagnosticsWarning,
             stacklevel=2,
         )
@@ -2933,7 +2927,7 @@ def _get_potential_gamma(params: RewardParams) -> float:
     gamma, reason_parts = _clamp_float_to_bounds("potential_gamma", raw_gamma, strict=False)
     if reason_parts:
         warnings.warn(
-            f"potential_gamma={raw_gamma} outside [0,1]; clamped to {gamma}",
+            f"potential_gamma={raw_gamma} outside [0,1]; falling back to {gamma}",
             RewardDiagnosticsWarning,
             stacklevel=2,
         )
@@ -3131,7 +3125,7 @@ def _compute_exit_potential(prev_potential: float, params: RewardParams) -> floa
         decay = _get_float_param(params, "exit_potential_decay")
         if not np.isfinite(decay) or decay < 0.0:
             warnings.warn(
-                "exit_potential_decay invalid or < 0; falling back to 0.0",
+                f"exit_potential_decay={decay} invalid or < 0; falling back to 0.0",
                 RewardDiagnosticsWarning,
                 stacklevel=2,
             )
@@ -3600,7 +3594,7 @@ def write_complete_statistical_analysis(
     analysis_stats = None
     partial_deps = {}
     if skip_feature_analysis or len(df) < 4:
-        print("Skipping feature analysis: flag set or insufficient samples (<4).")
+        print("Skipping feature analysis: insufficient samples or flag set.")
         # Do NOT create feature_importance.csv when skipped (tests expect absence)
         # Create minimal partial dependence placeholders only if feature analysis was NOT explicitly skipped
         if not skip_feature_analysis and not skip_partial_dependence:
@@ -3633,7 +3627,7 @@ def write_complete_statistical_analysis(
                         f"{feature},partial_dependence\n", encoding="utf-8"
                     )
         except ImportError:
-            print("scikit-learn unavailable; generating placeholder analysis artifacts.")
+            print("Skipping feature analysis: scikit-learn unavailable.")
             (output_dir / "feature_importance.csv").write_text(
                 "feature,importance_mean,importance_std\n", encoding="utf-8"
             )
@@ -4428,11 +4422,11 @@ def main() -> None:
     # Load real episodes if provided
     real_df = None
     if args.real_episodes and args.real_episodes.exists():
-        print(f"Loading real episodes from {args.real_episodes}...")
+        print(f"Loading real episodes from: {args.real_episodes}...")
         real_df = load_real_episodes(args.real_episodes)
 
     # Generate consolidated statistical analysis report (with enhanced tests)
-    print("Generating complete statistical analysis...")
+    print("Generating statistical analysis...")
 
     write_complete_statistical_analysis(
         df,
@@ -4450,7 +4444,7 @@ def main() -> None:
         rf_n_jobs=int(getattr(args, "rf_n_jobs", -1)),
         perm_n_jobs=int(getattr(args, "perm_n_jobs", -1)),
     )
-    print(f"Complete statistical analysis saved to: {args.out_dir / 'statistical_analysis.md'}")
+    print(f"Statistical analysis saved to: {args.out_dir / 'statistical_analysis.md'}")
     # Generate manifest summarizing key metrics
     try:
         manifest_path = args.out_dir / "manifest.json"
@@ -4487,7 +4481,7 @@ def main() -> None:
             manifest["simulation_params"] = sim_params
         with manifest_path.open("w", encoding="utf-8") as mh:
             json.dump(manifest, mh, indent=2)
-        print(f"Manifest written to: {manifest_path}")
+        print(f"Manifest saved to: {manifest_path}")
     except Exception as e:
         print(f"Manifest generation failed: {e}")
 
index 4569cd58ca409e07b642afcc3f0da20d202dbfd6..b50f2e1414e4e2c131b1e616b583658953912edb 100644 (file)
@@ -243,11 +243,11 @@ def run_scenario(
         cmd_str = " ".join(cmd)
         stderr_head_lines = proc.stderr.splitlines()[:3]
         stderr_head = "\n".join(stderr_head_lines)
-        print(f"[error details] command: {cmd_str}")
+        print(f"[error] Command: {cmd_str}")
         if stderr_head:
-            print(f"[error details] stderr head:\n{stderr_head}")
+            print(f"[error] Stderr:\n{stderr_head}")
         else:
-            print("[error details] stderr is empty.")
+            print("[error] Stderr: (empty)")
     combined = proc.stdout.splitlines() + proc.stderr.splitlines()
     warnings = sum(1 for line in combined if _is_warning_header(line))
     if full_logs:
@@ -379,9 +379,8 @@ def main():
         else:
             invalid_params.append(p)
     if invalid_params:
-        msg = f"Warning: ignoring malformed --params entries: {invalid_params}"
+        msg = f"[warning] Ignoring malformed --params entries: {invalid_params}"
         print(msg, file=sys.stderr)
-        print(f"{msg}")
     args.params = valid_params
 
     # Prepare list of (conf, strict)
@@ -528,7 +527,7 @@ def main():
                 os.remove(tmp_path)
             except OSError:
                 pass
-    print("Summary written to", out_dir / SUMMARY_FILENAME)
+    print(f"Summary saved to: {out_dir / SUMMARY_FILENAME}")
     if not interrupted and summary["failures"]:
         print("Failures detected:")
         for f in summary["failures"]:
index c25d04b38b5ad7ee58eb53bd3a90e5c169649410..54f0a8f9273c0ab7ba9e35d25aa9616e8a392437 100644 (file)
@@ -42,9 +42,9 @@ class TestLoadRealEpisodes(RewardSpaceTestBase):
             loaded = load_real_episodes(p)
         self.assertEqual(len(loaded), 2, "Expected duplicate row removal to reduce length")
         msgs = [str(warning.message) for warning in w]
-        dup_msgs = [m for m in msgs if "duplicate transition" in m]
+        dup_msgs = [m for m in msgs if "duplicate" in m.lower()]
         self.assertTrue(
-            any("Removed" in m for m in dup_msgs), f"No duplicate removal warning found in: {msgs}"
+            any("Dropped" in m for m in dup_msgs), f"No duplicate removal warning found in: {msgs}"
         )
 
     def test_missing_multiple_required_columns_single_warning(self):
@@ -63,7 +63,7 @@ class TestLoadRealEpisodes(RewardSpaceTestBase):
             self.assertIn(col, loaded.columns)
             self.assertTrue(loaded[col].isna().all(), f"Column {col} should be all NaN")
         msgs = [str(warning.message) for warning in w]
-        miss_msgs = [m for m in msgs if "missing columns" in m]
+        miss_msgs = [m for m in msgs if "Missing columns" in m]
         self.assertEqual(
             len(miss_msgs), 1, f"Expected single missing columns warning (got {miss_msgs})"
         )
index abf53db81239320c51b78f9dbd1b22bebbb33ee3..6962da7d05cd68ce9ab28608a4332a67b5cd1273 100644 (file)
@@ -233,9 +233,9 @@ class TestRewardRobustnessAndBoundaries(RewardSpaceTestBase):
         self.assertTrue(
             any(
                 (
-                    "exceeded threshold" in str(w.message)
-                    or "exceeds threshold" in str(w.message)
-                    or "|factor|=" in str(w.message)
+                    ">" in str(w.message)
+                    and "threshold" in str(w.message)
+                    or "|exit_factor|=" in str(w.message)
                     for w in runtime_warnings
                 )
             )
@@ -715,7 +715,7 @@ class TestRewardRobustnessAndBoundaries(RewardSpaceTestBase):
             pnl=pnl, trade_duration=50, max_unrealized_profit=0.04, min_unrealized_profit=0.0
         )
         duration_ratio = 0.5
-        with assert_diagnostic_warning(["exit_plateau_grace < 0"]):
+        with assert_diagnostic_warning(["exit_plateau_grace=", "< 0"]):
             f_neg = _get_exit_factor(
                 base_factor,
                 pnl,
@@ -826,7 +826,7 @@ class TestRewardRobustnessAndBoundaries(RewardSpaceTestBase):
                 efficiency_weight=0.0,
                 win_reward_factor=0.0,
             )
-            with assert_diagnostic_warning(["exit_half_life", "close to 0"]):
+            with assert_diagnostic_warning(["exit_half_life=", "<= 0"]):
                 f0 = _get_exit_factor(
                     base_factor,
                     pnl,
index af353020e6cc34ce177bcaeb008d57d8aeda096a..7591d0e23acad7f768cda39ac1eb2b7a89a52e5f 100644 (file)
@@ -1226,6 +1226,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         fit_live_predictions_candles: int,
         label_period_candles: int,
     ) -> tuple[float, float]:
+        if not isinstance(label_period_candles, int) or label_period_candles <= 0:
+            label_period_candles = self.ft_params.get(
+                "label_period_candles", self._default_label_period_candles
+            )
         thresholds_candles = (
             max(2, int(fit_live_predictions_candles / label_period_candles))
             * label_period_candles