From d75691e64d892778c68420830fde60a38514b578 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 12 Sep 2026 08:42:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(portfolio):=20run=5Fbacktest=5Fjson?= =?UTF-8?q?=E9=80=8F=E4=BC=A0factor=5Ftopn=E5=9B=9B=E5=8F=82=E2=80=94?= =?UTF-8?q?=E2=80=94=E7=9F=A9=E9=98=B5=E4=B8=89=E8=B7=91=E5=85=A8=E7=A9=BA?= =?UTF-8?q?=E4=BB=93=E6=80=BB=E6=A0=B9=E5=9B=A0:CLI=20--json=E8=B5=B0?= =?UTF-8?q?=E6=9C=AC=E5=87=BD=E6=95=B0=E6=89=8B=E5=B7=A5=E6=8B=BCNamespace?= =?UTF-8?q?=E6=BC=8Ffactor=5Fdir(=E7=BC=BA=E7=9C=81=E7=A9=BA=E4=B8=B2?= =?UTF-8?q?=E2=86=92=E7=AD=96=E7=95=A5=E3=80=8C=E6=9C=AA=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E3=80=8D=E7=9B=B4=E6=8E=A5=E7=A9=BA=E4=BB=9316=E7=A7=92?= =?UTF-8?q?=E5=81=87=E5=BF=AB),=E8=BF=9B=E7=A8=8B=E5=86=85=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E8=B7=AF=E5=BE=84=E5=AE=8C=E6=95=B4=E6=8E=A9=E7=9B=96?= =?UTF-8?q?=E6=AD=A4bug;main=20--json=E5=88=86=E6=94=AFparams=E4=B8=8EName?= =?UTF-8?q?space=E5=8F=8C=E4=BE=A7=E8=A1=A5=E9=BD=90+mock=E6=88=AA?= =?UTF-8?q?=E8=8E=B7Namespace=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95,?= =?UTF-8?q?=E5=85=A8=E9=87=8F1207=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_portfolio/runner_backtest.py | 11 +++++++++++ tests/portfolio/test_factor_topn.py | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/sanguo_portfolio/runner_backtest.py b/sanguo_portfolio/runner_backtest.py index c99b847..18839d4 100644 --- a/sanguo_portfolio/runner_backtest.py +++ b/sanguo_portfolio/runner_backtest.py @@ -494,6 +494,13 @@ def run_backtest_json(params: Dict[str, Any]) -> Dict[str, Any]: stamp_duty_rate=float(params.get("stamp_duty_rate", 0.001)), min_commission=float(params.get("min_commission", 5.0)), slippage=float(params.get("slippage", 0.0)), + # factor_topn 四参数(2026-09-12:漏透传=矩阵三跑全空仓的根因—— + # CLI --json 走本函数手工拼 Namespace,factor_dir 缺省空串使策略 + # 「factor_dir 未配置」直接空仓跑完) + factor_name=params.get("factor_name", "composite_quant12_v2a"), + factor_dir=params.get("factor_dir", ""), + top_n=int(params.get("top_n", 100)), + rebalance_days=int(params.get("rebalance_days", 21)), initial_positions=json.dumps(params["initial_positions"]) if params.get("initial_positions") else "", ) @@ -829,6 +836,10 @@ def main() -> None: "stamp_duty_rate": args.stamp_duty_rate, "min_commission": args.min_commission, "slippage": args.slippage, + "factor_name": getattr(args, "factor_name", ""), + "factor_dir": getattr(args, "factor_dir", ""), + "top_n": getattr(args, "top_n", 100), + "rebalance_days": getattr(args, "rebalance_days", 21), }) print(json.dumps(result, ensure_ascii=False, default=str)) else: diff --git a/tests/portfolio/test_factor_topn.py b/tests/portfolio/test_factor_topn.py index aa34aae..10f676c 100644 --- a/tests/portfolio/test_factor_topn.py +++ b/tests/portfolio/test_factor_topn.py @@ -170,3 +170,27 @@ def test_initialize_registers_daily_schedule(factor_dir): st.broker.run_daily = lambda fn, t: calls.append((fn, t)) st.initialize(_ctx("2024-10-08")) assert calls == [(st.rebalance, "9:30")] + + +def test_run_backtest_json_passes_factor_topn_params(monkeypatch): + """CLI --json 路径(run_backtest_json 手工拼 Namespace)必须透传 factor 四参—— + 2026-09-12 矩阵三跑全空仓根因: 漏透传→factor_dir=""→策略「未配置」直接空仓, + 而进程内 run_backtest(parse_args()) 路径完整(诊断一直成交掩盖此 bug).""" + from sanguo_portfolio import runner_backtest + captured: dict = {} + + def _fake_run_backtest(args): + captured.update({ + k: getattr(args, k, "") + for k in ("factor_name", "factor_dir", "top_n", "rebalance_days") + }) + return {} + + monkeypatch.setattr(runner_backtest, "run_backtest", _fake_run_backtest) + runner_backtest.run_backtest_json({ + "strategy": "factor_topn", + "factor_name": "composite_test", "factor_dir": "/x/y", + "top_n": 50, "rebalance_days": 10, + }) + assert captured == {"factor_name": "composite_test", "factor_dir": "/x/y", + "top_n": 50, "rebalance_days": 10}