diff --git a/src/backtest/runner.py b/src/backtest/runner.py index e6e8233..2f6b4b4 100644 --- a/src/backtest/runner.py +++ b/src/backtest/runner.py @@ -514,6 +514,26 @@ def run_all(self, only_cached: bool = False) -> list[BestResult]: else: search_space[name] = options + n_params = len(search_space) + dof_multiplier = self.cfg.param_dof_multiplier + min_bars_floor = self.cfg.param_min_bars + min_bars_for_optimization = max(min_bars_floor, dof_multiplier * n_params) + if search_space and len(df) < min_bars_for_optimization: + self.logger.info( + "skipping optimization due to insufficient bars", + collection=col.name, + symbol=symbol, + timeframe=timeframe, + bars=len(df), + min_bars=min_bars_for_optimization, + n_params=n_params, + dof_multiplier=dof_multiplier, + min_bars_floor=min_bars_floor, + strategy=strat.name, + search_method=search_method, + ) + search_space = {} + best_val = -np.inf best_params: dict[str, Any] | None = None best_stats: dict[str, Any] | None = None diff --git a/src/config.py b/src/config.py index 8c390da..d48e53d 100644 --- a/src/config.py +++ b/src/config.py @@ -50,6 +50,8 @@ class Config: engine: str = "pybroker" # pybroker engine param_search: str = "grid" # grid | optuna param_trials: int = 25 + param_dof_multiplier: int = 100 + param_min_bars: int = 2000 max_workers: int = 1 asset_workers: int = 1 param_workers: int = 1 @@ -127,6 +129,8 @@ def load_config(path: str | Path) -> Config: engine=str(raw.get("engine", "pybroker")).lower(), param_search=str(raw.get("param_search", raw.get("param_optimizer", "grid"))).lower(), param_trials=int(raw.get("param_trials", raw.get("opt_trials", 25))), + param_dof_multiplier=int(raw.get("param_dof_multiplier", 100)), + param_min_bars=int(raw.get("param_min_bars", 2000)), max_workers=int(raw.get("max_workers", raw.get("asset_workers", 1))), asset_workers=int(raw.get("asset_workers", raw.get("max_workers", 1))), param_workers=int(raw.get("param_workers", 1)),