diff --git a/sanguo_api/routes.py b/sanguo_api/routes.py index abd49ac..29c4c8a 100644 --- a/sanguo_api/routes.py +++ b/sanguo_api/routes.py @@ -75,7 +75,8 @@ async def submit_cta(req: CtaBacktestRequest): params=req.params, start=req.start, end=req.end, - cfg=None + cfg=None, + benchmark=req.benchmark ) return {"task_id": tid} diff --git a/sanguo_backtest/cta_engine.py b/sanguo_backtest/cta_engine.py index 910ddc7..54384eb 100644 --- a/sanguo_backtest/cta_engine.py +++ b/sanguo_backtest/cta_engine.py @@ -47,7 +47,7 @@ def guess_exchange(symbol: str) -> Exchange: return Exchange("SSE") -def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, db_path: str) -> BacktestResult: +def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, db_path: str, benchmark: str = "hs300") -> BacktestResult: """ Run CTA strategy backtest using vnpy_ctastrategy BacktestingEngine. @@ -98,6 +98,9 @@ def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: # Configure vnpy DB β†’ A-share quant_trading.db. Worker process (spawn) # doesn't inherit main-process SETTINGS, so set before engine.load_data. + # _dcfg is also reused by the metrics branch (benchmark data_paths) since the + # cfg param can be None when called via the API. + _dcfg = None try: from vnpy.trader.setting import SETTINGS from sanguo_data.config import load_config, find_config_path @@ -129,16 +132,17 @@ def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: if not isinstance(daily_df.index, pd.DatetimeIndex): daily_df.index = pd.to_datetime(daily_df.index) - # Get benchmark config (default hs300) - benchmark_key = getattr(cfg, "benchmark", "hs300") if hasattr(cfg, "benchmark") else "hs300" - benchmark_code = BENCHMARK_SYMBOL.get(benchmark_key, "sh000300") + # Get benchmark code (default hs300) + a cfg that has data_paths. + # _dcfg is the loaded config; fall back to the passed cfg if loading failed. + benchmark_code = BENCHMARK_SYMBOL.get(benchmark, "sh000300") + bench_cfg = _dcfg if (_dcfg is not None and hasattr(_dcfg, "data_paths")) else cfg # Load benchmark data start_date = start_dt if isinstance(start_dt, datetime) else datetime.strptime(start, "%Y-%m-%d") end_date = end_dt if isinstance(end_dt, datetime) else datetime.strptime(end, "%Y-%m-%d") try: - bench_df = read_index_daily(benchmark_code, start_date, end_date, cfg) + bench_df = read_index_daily(benchmark_code, start_date, end_date, bench_cfg) if bench_df is not None and not bench_df.empty and "close" in bench_df.columns: # Calculate benchmark daily returns bench_df["date"] = pd.to_datetime(bench_df["date"]) diff --git a/sanguo_orchestrator/runner.py b/sanguo_orchestrator/runner.py index aa9c0e8..5148490 100644 --- a/sanguo_orchestrator/runner.py +++ b/sanguo_orchestrator/runner.py @@ -30,7 +30,7 @@ class Orchestrator: await self._on_stage(task_id, stage) async def submit_cta(self, strategy_class, symbol: str, params: dict, - start: str, end: str, cfg) -> str: + start: str, end: str, cfg, benchmark: str = "hs300") -> str: """Submit a CTA backtesting task asynchronously""" task_id = f"cta_{symbol}_{id(params)}" self.pool.submit(task_id, "cta") @@ -40,14 +40,15 @@ class Orchestrator: params=params, start=start, end=end, - cfg=cfg + cfg=cfg, + benchmark=benchmark ) await self._notify_stage(task_id, "ζŽ’ι˜ŸδΈ­") spec = self._pending[task_id] fut: Future = self.pool.submit_work( task_id, _cta_worker, spec["strategy_class"], spec["symbol"], - spec["params"], spec["start"], spec["end"], spec["cfg"], self.db_path + spec["params"], spec["start"], spec["end"], spec["cfg"], spec["benchmark"], self.db_path ) task = self.pool.get_task(task_id) @@ -164,10 +165,10 @@ class Orchestrator: # Module-level worker functions (must be top-level for ProcessPoolExecutor pickle) -def _cta_worker(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, db_path: str) -> any: +def _cta_worker(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, benchmark: str, db_path: str) -> any: """Worker for CTA backtest (lazy import, spawn-friendly)""" from sanguo_backtest.cta_engine import run_cta_backtest - return run_cta_backtest(strategy_class, symbol, params, start, end, cfg, db_path) + return run_cta_backtest(strategy_class, symbol, params, start, end, cfg, db_path, benchmark=benchmark) def _opt_worker(strategy_class, symbol: str, grid: dict, start: str, end: str, cfg, db_path: str) -> any: