Simon
7e369d9680
feat(quant): 单策略引擎也给出买卖理由与因子曲线(口径与组合引擎一致)
「因子组合」/`POST /api/research/backtests` 走的是 `LocalEngine/TopKBacktestRunner`,
上一版只把理由接进了组合引擎,同一件事在两个引擎上就会有两种说法。这次补齐:
- `engine.py` / `qlib_adapter/engine.py`:因子面板**只算一次**
(`build_factor_panels_full`)→ 复合分与「理由里引用的因子原始值」同源同张面板;
复合分口径逐字未变(与 `selection.score_panel_for_factors` 相同)。
- `local_engine.py`:调仓日保留完整排名与合格集,各站点写入结构化理由 ——
买入(按名次建仓 / 顺延成交 / 涨停 / 停牌 / 现金不足 / 不足最低佣金)、
卖出(全量换仓 / 跌出 TopN / 不在候选池 / 停牌顺延 / 跌停顺延);
`Trade.entry_reason/exit_reason` 两端齐全;每个交易日记录持仓市值,
结果填 `factor_curves`(持仓市值加权平均的因子原始值,空仓日不落点)。
- 新增 `SELL_REBALANCE_FULL`(「调仓换仓卖出」):单策略调仓是「先全清再建仓」,
被卖出的股票**可能仍排在 TopN 内**(如 rank=1),这时写「跌出 TopN」就是假解释;
按事实分 code(仍在 TopN 内 → 全量换仓;否则 → 跌出 TopN / 不在候选池)。
- 顺延成交不拿挂单日的旧名次冒充当日名次(rank/total/score=None,因子值/成交价/预算
取成交当日真实值);「候选池不足」的提示记录保持 reason=None(词表里没有对应语义,
硬套就是编理由)。
验证:
- 新增 `tests/test_local_engine_reasons.py` 14 条:理由数字对回面板、涨停比值对回行情与
板块规则、停牌/跌停/现金不足/最低佣金、顺延成交、全量换仓 vs 不在池两个分支、
Trade 两端理由、因子曲线市值加权(手算加权值断言 + 等权平均对不上)、空仓不落点。
后端 524 条全过(510 + 14),ruff clean。
- 强回归:用改前引擎并排跑 9 个场景,`signal_history`(日期/方向/成交/原因文案/价格)、
`trades`、`positions`、`summary`、净值/回撤、`unimplemented` 逐条一致 —— 理由与曲线
是纯新增字段,成交行为零变化。
2026-10-01 18:08:42 +08:00
..
2026-10-01 16:33:32 +08:00
2026-10-01 17:57:04 +08:00
2026-10-01 16:33:32 +08:00
2026-09-20 07:31:04 +08:00
2026-09-20 07:31:04 +08:00
2026-10-01 17:57:00 +08:00
2026-10-01 16:33:32 +08:00
2026-10-01 18:08:42 +08:00
2026-09-06 16:09:10 +08:00
2026-09-20 07:31:04 +08:00