""" Optuna 目标函数。 将策略实例化 → 回测 → 提取指标,包装为 Optuna objective。 """ import optuna import pandas as pd from backtest.base import BaseStrategy from backtest.vectorbt.engine import VectorBTEngine from optimizer.space import SearchSpace # 指标提取器:从 BacktestReport 取对应字段 _METRIC_EXTRACTORS = { "sharpe": lambda r: r.sharpe_ratio, "cagr": lambda r: r.cagr, "calmar": lambda r: r.calmar_ratio, "total_return": lambda r: r.total_return, "return_over_dd": lambda r: abs(r.total_return / r.max_drawdown) if r.max_drawdown != 0 else 0.0, "win_rate": lambda r: r.win_rate, "profit_factor": lambda r: r.profit_factor, } class Objective: """ Optuna 目标函数(可调用)。 用法: obj = Objective(SMACrossStrategy, sma_cross_space, price_df, factor_df, metric="sharpe") study = optuna.create_study(direction="maximize") study.optimize(obj, n_trials=100) """ def __init__( self, strategy_class: type[BaseStrategy], search_space: SearchSpace, price_df: pd.DataFrame, factor_df: pd.DataFrame | None = None, bt_engine: VectorBTEngine | None = None, metric: str = "sharpe", ): self.strategy_class = strategy_class self.search_space = search_space self.price_df = price_df self.factor_df = factor_df if factor_df is not None else price_df self.bt_engine = bt_engine or VectorBTEngine() self.metric = metric self._extractor = _METRIC_EXTRACTORS.get(metric) if self._extractor is None: raise ValueError(f"不支持的指标: '{metric}'。可选: {list(_METRIC_EXTRACTORS)}") def __call__(self, trial: optuna.Trial) -> float: params = self.search_space.suggest(trial) try: strategy = self.strategy_class(**params) except TypeError: # 过滤不匹配的参数 valid = {k: v for k, v in params.items() if k in self.strategy_class.__init__.__code__.co_varnames} strategy = self.strategy_class(**valid) report = self.bt_engine.run(strategy, self.price_df, self.factor_df) value = self._extractor(report) # type: ignore # 无效值处理 if value is None or (isinstance(value, float) and (pd.isna(value) or value == float("inf"))): return float("-inf") return float(value)