diff --git a/AGENTS.md b/AGENTS.md index 705c4dc..68d788d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,341 +57,95 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri ## 开发约定 - **交流中文**;代码/注释/commit message 用**英文**。 -- **Git**:feature 分支 + PR。**PR #1(P0+P1)、#2(P2-1)、#3(P2-2)、#4(进度文档)、#5(P2-3)、#6(进度文档)、#7(P2-4)、#8(进度文档)、#9(P3-1)、#10(进度文档)、#11(P3-2)、#12(P3-3)、#13(进度文档)、#14(P3-4)、#15(进度文档)、#16(P3-5)、#17(进度文档)、#18(P3-6)、#19(P3-7)、#20(进度文档)、#21(P3-8)、#22(进度文档)、#23(P4-1)、#24(进度文档)、#25(P4-2)、#28(tushare 权限/限频探测 + capability registry)、#29(I1–I4 分钟级 intraday pipeline,4 commit 一 PR)、#30(进度文档)、#31(P4-3 因子支撑端点缓存 + 21:00 data updater,2 commit 一 PR)、#33(P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model,4 commit 一 PR)、#35(P-I5b 分钟尾盘执行期 raw stk_limit 涨跌停可行性,4 commit 一 PR)、#37(P-I5c MMP 分钟因子端到端 opt-in alpha)、#39(P-I5d MMP 五分位分组回测 standalone,含 I5c plumbing)、#41(数据层 D1 契约文档 + token 解析去重)、#43(数据层 D2 TushareCache specs/parsers 拆分)、#45(数据层 D3 report-only 数据质量层)、#47(数据层 D3b default-off `data-update` 质量报告钩子)、#49(数据层 D4 coverage ledger 批量写 + 进程内查找缓存)、#51(数据层 D5 opt-in 有界并发 + 全局限频器)、#53(P-I5e MMP 五分位 CSI300 独立泛化检验,负结果)、#55(P-I5f 分钟尾盘执行流动性/容量诊断,report-only)、#57(数据层 schema 注册表 + default-off drift 守卫)、#59(数据层 全A 增量 auto-warm + systemd 定时,default-off)、#61(数据层 全A 历史回填 `data-backfill`,分块/续跑/失败隔离)、#62(进度文档)、#63(因子评估契约层 `analytics/eval/` + StandardFactorEvaluator + dashboard + PR-C 因子,4 commit 一 PR)、#64–#73(PR-D…PR-M 十个分钟因子复现,**一因子一分支一 PR**)、#74(因子评估契约 v0.8 + v0.9 判定门修复)、#75(尾盘执行价 VWAP + 分钟持有期收益复权)、#76(报告披露修正 + I5f 名义按真实规模重测)、#77(`adj_factor` 下降质量检查)、#78(#76 的 follow-up:扫描式正则守卫被七种改写实测证否 → 闸门描述改单一来源)、#79(十一因子改 14:51 VWAP exec-to-exec 基准,**评审未返回、靠自查合并**)、#82(闸门/成交价描述全仓清扫,12 处;#78 守卫射程不足的修正)、#80(进度文档)、#84(因子层重构 D0 纸面契约:availability policy 常量 + 预指派表 + 性质编目)、#85(进度文档)、#86(因子层重构 D1:registry + PanelField requires + FactorSpec 契约 v1.0)、#87(因子层重构 D1 面板冻结:14 因子 raw 基线 + manifest)、#88(进度文档)、#89(因子层重构 D2:minute primitives + ops + 11 因子迁移,与冻结基线逐位一致)、#90(进度文档)、#91(因子层重构 D3:store 层——稀疏值存储 + key/指纹 + tail-recompute + R5 fina 视界统一)、#92(进度文档)、#93(因子层重构 D4:FactorService + decision-view materializer + pooled 饱和填充)、#94(进度文档)、#95(因子层重构 D5a:exec 基线冻结 + 11 runner 差异编目 + 可行性探测)、#96(进度文档)、#97(因子层重构 D4b:per-symbol 流式物化)、#98(进度文档)、#99(因子层重构 D5b:评估契约 v1.0 + 诊断通道 + 14:50 可见性守卫;**C4 中止上报**)、#101(因子层重构 D4c:store 去 universe 依赖——(date,symbol) 缺口判定 + store 只存 per-symbol 中间量、截面 combine 移到读出装配 + 调用方 symbol 列表单点归一化)、#103(jump_amount_corr_20 补 14:50 截断 correctness fix——唯一未做截断的分钟因子,exec_to_exec 下信息集越过自己入场锚的真实前视;含 dashboard 布局 5/4 分支修复 + census 守卫前缀匹配修复;单独成 PR 非重构一部分)、#105(因子层重构 D5 C4:统一 exec-only FactorEvalRunner——因子取数全走 factors.service、异构披露唯一家、C5 对账 harness 三模式 + 77/77 硬闸门、warmup 左延具名类登记)、#107(因子层重构 D5 C4b:valley_price_quantile 绑定——combine_daily 声明式引擎扩展 + 平移面板第三条路逐比特等价双侧钉 + binding.py 折进 code_hash + 截面因子 panels 类边界三裁定)均已 merge 到 `main`**;**OPEN 未合并:#38**(见 P-I5d 处置,未获授权关闭)。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 +- **Git**:feature 分支 + PR;commit 用 conventional 格式、**无 attribution**(不加 Co-Authored-By)。**代码 PR 与进度文档 PR 分开**,实现 PR 绝不碰 `CLAUDE.md` / `AGENTS.md`;改完 `CLAUDE.md` 必须 `cp CLAUDE.md AGENTS.md` 并 `cmp` 验证逐字节相同。仓库**无 CI**,合并用 `gh pr merge N --merge`。历史 PR 台账(#1–#107)见 [`docs/progress/pr_ledger.md`](docs/progress/pr_ledger.md);**PR #38 永远保持 OPEN、不许动**。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 -## 当前进度 -- ✅ 7 层骨架 + 架构文档(`main`) -- ✅ **Phase 0 MVP**(PR #1):DemoFeed → PanelStore → StaticUniverse → momentum_20 → zscore → EqualWeightAlpha → TopN 等权 → 月度回测(成本/换手)→ IC/绩效报告,单命令可复现。 -- ✅ **Phase 1 偏差边界**(PR #1,全部真数据实证): - - 前复权(qfq;store 存 raw,内存复权 → batch≡incremental 安全) - - PIT 指数成分(`index_weight` as-of,survivorship-safe;370 天 pre-start 回看 + 90 天分页) - - 可交易过滤(停牌 / ST / 涨跌停;**涨跌停用未复权 raw close** 比 `stk_limit`) - - 财务 `ann_date` 披露日 as-of(绝不按 end_date;500 天 lookback carry forward) - - 行业 + 市值中性化(按 date 截面 OLS 残差;欠定/无自由度截面 → NaN) - - 路径感知降级披露(demo/static vs tushare/index/ann_date,绝不把 demo 当真实验证) -- ✅ 真数据实证(tushare,非 CI):复权除权日 raw−5.74%→qfq+0.99% / CSI300 全年 24 快照 328 名换手 / ann_date Q1 延后至 04-20 / 中性化 corr −0.617→0。详见 `BIAS_AUDIT.md`、`artifacts/reports/phase1_summary.md`。 -- ✅ **Phase 2-1 真实数据可复现基准**(**PR #2 已 merge 到 `main`**):新 run mode `run-phase2-baseline` + `config/phase2_real_baseline.yaml`(上证50 `000016.SH`,2023-07~2024-06)。**复用 P0/P1 全套机器,不扩因子、不调参**。一次真实跑 ~11min(68 成分 / 25 loaded 快照 / in-window distinct 60 / 11 settled 调仓,候选 12 末日跳过),输出 `artifacts/reports/phase2_real_baseline.md`(gitignored):数据窗口 / PIT 成分摘要(loaded vs in-window) / ann_date 覆盖率(100%) / 可交易过滤命中(首命中互斥) / 每期持仓 / 换手成本 / IC(≈0.008) / 绩效(年化−17.6%,**亏损动量基准,非业绩声明**) / 全部 P2 降级。诊断只读、demo 源拒绝、token 不入报告。 -- ✅ **Phase 2-2 执行真实性**(PR #3 已 merge 到 `main`):**拆分 selection(选谁)与 execution feasibility(能否成交)**。 - - 方向感知执行 `runtime/fills.py::simulate_fills`:涨停挡买 / 跌停挡卖 / 停牌·缺收盘双向挡;按 panel flag 实时判定,与选股 toggle 无关。 - - **现金一致 sell-then-buy**:卖在前释放现金、买在后,现金不足按比例部分成交 → **无杠杆**;被挡交易 carry forward;换手/成本只算实际成交;闲置现金按 driver 的 `cash_return` 计息(BT-007)。 - - `universe.min_listing_days` **真实路径已执行**(`stock_basic.list_date` 富化,买入资格过滤,边界 age==min 放行,缺 list_date 保留并披露);demo 无上市日 → 披露 no-op。 - - 回测 `feasibility_log()`:每调仓期 blocked buys/sells/carried/executed turnover/invested;phase2 报告新增 **Execution feasibility** 小节。 - - **保不变量**:demo 无 flag → 全可成交 → P0/P1 数字不变;无未来函数、PIT/ann_date/real-demo 分离不变。 -- ✅ **Phase 2-3 历史 PIT 行业**(**PR #5 已 merge 到 `main`**,默认 SW-L1 可配置):把行业中性化协变量从 `stock_basic.industry` **当前**标签升级为按 trade_date **as-of** 的历史申万行业。 - - `tushare_covariates.pit_sw_intervals(symbols, level)` 读 `index_member_all` 取每股 SW 行业 `in_date`/`out_date` 区间;`data/clean/pit_industry.py::asof_industry` 按 `[in_date,out_date)` 覆盖该日取行业(改分类日新行业生效,PIT-safe;起始日前成分 carry forward 到窗口开始)。 - - **SW 层级可配置** `processing.neutralize.industry_level`(L1/L2/L3,**默认 L1**=31 宽板块,中性化标准 + 小截面自由度更稳)。**关键实证**:旧 tag 年化 −17.6% / SW-L1 −10.2% / SW-L2 −9.3% → **L1≈L2**,大跳的主因是 **tushare→SW 分类切换**(补 PIT 的必然:只有 SW 有 in/out 历史可 PIT 化,旧 tag 无法),**与粒度无关**。(注:曾误以为是粒度、默认 L2,经 L2 实测推翻,改默认 L1。) - - **不静默退回 current**:无 SW 历史的票 → 行业 NaN,被 neutralize 按截面丢弃(neutralize 数学不变,本就丢 NaN 行);每次运行 **实际 level + PIT 覆盖率**进 phase2 报告。 -- ✅ **Phase 2-4 标准分析集成**(**PR #7 已 merge 到 `main`**):alphalens-reloaded + quantstats 接入报告(**report-only cross-check**)。 - - `analytics/alphalens_adapter.py`(IC mean/IR + 分位均值)+ `analytics/quantstats_adapter.py`(CAGR/Sharpe/maxDD/vol)薄 adapter,各带 `backend` 字段。 - - **简版 numpy/pandas 仍权威**(驱动回测 + cross-check);依赖不可用/报错 → 报告显式披露 backend(unavailable/error,只记异常**类型**不记消息),**绝不静默假装用了标准库**。 - - **不改 alpha/portfolio/runtime/fills/universe**:P0/P2 交易数字不变(demo ic 0.96/annual 0.84 不变;实测 alphalens IC=简版 IC 完全吻合),只新增 **Standard analytics** 报告段。 -- ✅ **Phase 3-1 首个真实多因子 baseline**(**PR #9 已 merge 到 `main`**):pipeline 从"只用第一个 enabled factor"升级为**消费全部 enabled factors**;新增 `config/phase3_real_multifactor.yaml`(momentum_20 + roe + netprofit_yoy,SSE50 同窗口,与 phase2 可比)。**不调参、无 learned weights、非收益承诺**。 - - `_build_factors` 全量实例化(配置序;重名报错);财务字段**一次 fetch + 一次 as-of 对齐**(逐字段独立守 `ann_date <= trade_date`,无每因子重复拉取);demo+财务因子仍可读报错。 - - 合成仍 `EqualWeightAlpha`(处理后各列等权平均,不看 forward returns);`drop_missing` 要求该日该票**所有**因子齐备(显式披露)。 - - 报告增强:active factor list / per-factor coverage+IC+分位 / **combo score** 诊断 / 财务 coverage **按字段**披露(TRADED vs diagnostic 角色标注);`output.baseline_report_name` 使 phase3 报告独立于 phase2。 - - **真实结果**(SSE50 2023-07~2024-06,~14min):多因子 annual **−9.05%**(单因子 −10.19%);per-factor IC: momentum_20 0.0083(与 phase2 run 完全一致,跨 run 一致性实证)/ roe 0.0006 / netprofit_yoy 0.0001;combo IC −0.0038;财务两字段 ann_date 覆盖 **100%**;PIT SW-L1 98.53%。财务因子在该小截面短窗口 IC≈0,照实披露——这是 plumbing 验证。 - - **回归不破**:phase2 单因子真实 rerun annual −10.19% / IC 0.0083 不变;demo ic 0.96/annual 0.84 不变。 -- ✅ **Phase 3-2 walk-forward IC 加权 alpha**(**PR #11 已 merge 到 `main`**):新增 `alpha/ic_weight.py::RollingICWeightAlpha`(`alpha.model: ic_weighted`);EqualWeightAlpha 仍默认 + 回归基线。**不调参、非收益声明**。 - - **Lookahead 边界(测试锁定)**:训练严格 walk-forward——(factor[t], fwd_h[t]) 只有**已实现**(交易日序 `t+h <= d`)才进日 d 的权重;**扰动未实现 forward returns 权重不变**(扰动测试)+ `t+h` 切片精确边界测试。forward returns 由 pipeline 在 alpha 边界计算、只传 `alpha.fit`,factors 层照旧绝不接触(不变量 #1)。 - - rolling(默认保守,窗口=60 交易日,min_periods=20)/ expanding 可配;历史不足 → 该日**退回等权**(与 EqualWeightAlpha 逐 bit 一致)并计数披露;权重 L1 归一化、保留符号(负 IC 因子负权重)。 - - 报告新增 **Alpha model** 必含小节:active model / 超参 / 训练覆盖率 + fallback 次数 / 每调仓日生效权重表(fallback 行标注)/ 非调参声明 + 等权基线对比指引。 - - `config/phase3_real_ic_weighted.yaml`:与 phase3_real_multifactor **唯一差异是 alpha.model**(universe/window/因子/中性化全同,直接可比)。 - - **真实结果**(SSE50 2023-07~2024-06,~14min):annual **−3.57%**(等权 −9.05% / 单因子 −10.19%),maxDD −12.93%,训练覆盖 **201/221**(20 个 fallback 全在窗口攒满前,90.95%)。⚠️ 优于等权**不是**业绩声明——单年窗口 + 权重逐期翻号(如 momentum_20 从 −0.58 到 +0.36)正是小样本不稳定的体现,照实披露。 - - **回归不破**:phase3 等权真实 rerun annual −9.05% / IC 0.0083 不变;demo equal_weight ic 0.96/annual 0.84 不变(测试锁定)。 -- ✅ **Phase 3-3 OOS 稳定性验证**(**PR #12 已 merge 到 `main`**,含 review 两 HIGH+一 LOW 修复):**报告型验证层**,不加新 alpha 复杂度、不改 portfolio/execution/factor math。新 run mode `run-phase3-oos`(`qt/oos_stability.py`)+ `config/phase3_real_oos_stability.yaml`(SSE50 扩到 **2 年** 2022-07~2024-06,split 2023-07-01 → train 1y / test 1y,test 年=旧 baseline 窗口可对照)。 - - **一次数据加载、同一 processed 因子面板、两次回测**(equal_weight vs ic_weighted);所有诊断按 split 切段(子段 nav 重新归一,绝不跨段串味)。 - - **边界语义(测试锁定)**:walk-forward(rolling subperiod)——任何日期的权重只用该日已实现观测(`t+h <= d`);**扰动 split 后全部 forward returns,train 期所有日期权重逐 bit 不变**(split 无泄漏测试);不用 freeze-at-split(那是新 alpha 模式,超范围)。**绩效切片按持有窗口**(train 行持有期 end≤split、test 行 start≥split,跨界调仓从两段排除并披露;IC 按实现日 t+h 切)——绝不按 signal date 单切(review HIGH 修复:旧切法把跨界持有期的 test 收益记进 train);runner 强制 `alpha.model: ic_weighted`(否则假对比,可读报错)。 - - 报告 `phase3_oos_stability.md`:split 边界+跨界行披露/分期绩效(annual/vol/sharpe/maxDD/turnover)/逐序列 IC 分期(mean/IR/hit rate/sign consistency)/权重稳定性(每期权重含 train-test 标注、trained 行 sign flips、fallback 次数+原因)/小样本 caveat。 - - **真实结果(关键发现,~16min,77 成分/2 年,持有窗口切片)**:三个原始因子 train→test **IC 全部翻号**(momentum −0.023→+0.006 / roe −0.029→+0.007 / np_yoy −0.011→+0.005,sign consistency 全 NO),hit rate 46~53%≈抛硬币;权重 23 期 sign flips 7/3/4;绩效 eq train −11.92%/test −5.27%,ic train −8.31%/test −2.70%(跨界行 2023-06-30 排除;修切片前 train 被 test 期收益污染到 −6.81%/−1.69%,修正幅度本身就是边界 bug 的实证)。**结论:ic_weighted 两段都略好但 IC≈0 且翻号——P3-2 单年跑赢不可外推,这正是本验证层要拿到的证据;非收益声明。** - - **回归不破**:phase3 equal_weight rerun −9.05%/0.0083、ic_weighted rerun −3.57% 均不变;demo 0.96/0.84 不变;secret scan 报告 0 处 token/config.json。 -- ✅ **Phase 3-4 robustness matrix**(**PR #14 已 merge 到 `main`**,含 review MEDIUM 修复:矩阵 DOWNGRADES 全 universe 披露):把 P3-3 OOS 检验批量跑到 **universe × window 矩阵**上,回答 P3-2/P3-3 结论是否 SSE50 小样本偶然。不加因子/alpha、不改 portfolio/execution/factor math。 - - 新 run mode `run-phase3-robustness`(`qt/robustness.py`)+ `config/phase3_real_robustness_matrix.yaml`(SSE50+CSI300 × 2020-2022/2022-2024 两 fold;`skip_cells` 显式跳过 CSI300×2020-2022——runtime 预算,报告披露,绝不静默缩覆盖)。 - - **每个 cell 逐字复用 P3-3 cell 核心**(`_run_oos_cell` 重构抽出,单 run 行为不变;持有窗口切片/实现日 IC 切片/walk-forward/ic_weighted guard 全继承);cell config 仅替换 universe/窗口/split/output_name(重过全套 pydantic 验证,parquet 不互覆盖);跨 cell 汇总严格按 cell 归属(测试锁定不串数)。 - - **真实矩阵结果**(3 cells,总 2h:960s/934s/5301s,CSI300 399 名): - - **P3-3 不漂移 smoke ✓**:SSE50|2022-2024 cell 逐数复现 P3-3 报告(−11.92%/−5.27%/−8.31%,boundary 2023-06-30)。 - - 原始因子 train→test sign consistency:momentum 1/3 / roe 0/3 / np_yoy 0/3——**IC 翻号跨 cell 普遍,P3-3 结论非偶然**。 - - `combo_ic_weighted` 是唯一跨 cell 稳定序列(test IC 3/3 正 + 3/3 sign consistent)但量级 ≈0.004~0.008 极小。 - - **关键负面发现**:ic_weighted 组合绩效不稳健——train→test 翻车形态出现在 **3 cells 中的 2 个**:SSE50|2020-2022 train **+7.09%→test −7.85%**、CSI300|2022-2024 train **+11.07%→test −17.74%**(sharpe +0.59→−1.01;CSI300 换手 1.2~1.6 成本拖累更重)。**凡 ic_weighted 样本内显著为正,样本外即翻负——典型过拟合签名;P3-2 的"跑赢"不能外推;微弱正 IC ≠ 净值赢。非收益声明。** - - secret scan 报告 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 3-5 factor candidate pack**(**PR #16 已 merge 到 `main`**,**EXPLORATORY**):保守日频 PIT-safe 候选因子组,经 P3-4 矩阵原样复检"弱信号是否只是因子集太窄"。不动 alpha/portfolio/execution/OOS/robustness、不调参。 - - 新因子(`factors/compute/candidates.py`,数学/前导 NaN/未来扰动不变/跨 symbol 隔离/不可变全测试锁定):`reversal_5/20`(=−momentum 复用同机器)/`volatility_20`(滚动收益 std,满窗)/`liquidity_20`(log 滚动均额,非正→NaN)/`overnight_mom_20`(Σlog(open_t/close_{t-1}) 20 日,open 当日开盘已知,prev close 不跨 symbol)/`value_ep`/`value_bp`(daily_basic pe/pb **一次 fetch** 取倒数,≤0→NaN,当日发布 PIT-safe)/`grossprofit_margin`(进 SUPPORTED_FIELDS,走既有 ann_date as-of)。 - - dispatch(registry)扩展;**窗口名/params 不一致 → 可读报错**(绝不静默错标列);旧配置逐 bit 复现(demo 0.96/0.84 不变)。 - - `config/phase3_real_factor_candidates.yaml`:旧三 + 候选八 = 11 因子,同 P3-4 矩阵形状(CSI300×2020-2022 显式 skip 披露);**旧 vs 新一跑读出**(raw IC 按列独立)。 - - **真实矩阵(11 因子版 3 cells / 2h;旧三因子+前轮候选 per-cell IC 逐数不变=不漂移 smoke ✓;secret scan 0;一次 CSI300 网络故障后整 run 重跑成功)**: - - **"因子集太窄"假设获得支持**:`value_ep`/`value_bp` test IC **3/3 正、0.037~0.056**;`volatility_20` **3/3 负、−0.044~−0.079**(低波方向稳定)——比旧三因子(|IC|≤0.015)高一个量级且方向跨 cell 一致。liquidity 3/3 负但量级小;`overnight_mom_20` test IC 2/3 正(+0.008/+0.016,2020-22 为负)中等偏弱;reversal/grossprofit_margin 翻号无信号。 - - `combo_ic_weighted` test IC **3/3 正 + 3/3 sign consistent**(0.0253/0.0012/0.0395)——walk-forward IC 加权吃到了新信号;11 因子**等权** combo 反被翻号因子稀释(IC 0/3 正,CSI300 test **−25.2%**)——等权对因子集质量敏感。 - - 组合绩效:ic test −2.21%/−5.02%/−2.76%(**3/3 cells 跑赢等权**;CSI300 train +23.11%→test −2.76%,train→test 衰减仍显著)。 - - ⚠️ **EXPLORATORY,非收益声明**:value/低波在 2020-2024 A 股是已知强势 regime;三 cells 窗口重叠非独立样本;未调参、未做成本敏感性。下一步候选:value+lowvol 子集独立复检。 -- ✅ **Phase 3-6 value+lowvol 子集复检 + 成本敏感性**(**PR #18 已 merge 到 `main`**,**EXPLORATORY**):把 P3-5 发现的 value/低波信号做**组间对照**(同一矩阵、同 footing),并补三档交易成本场景。**report-only:不加 alpha、不调参、不动 portfolio/execution/OOS 切片/robustness 聚合;`_run_oos_cell` 一字未动**。 - - 新 run mode `run-phase3-subset`(`qt/subset_validation.py`)+ `config/phase3_real_subset_costs.yaml`:4 组(legacy_trio / full_pack=11 因子 / value_lowvol=value_ep+value_bp+volatility_20 / value_lowvol_liq=+liquidity_20,/goal 的"可选"直接实测)× 3 成本场景(base ×1 / 2x ×2 / high_cost ×4,**必须有 multiplier=1.0 base 锚**,config 校验强制)× P3-4 矩阵 3 cells(CSI300×2020-2022 skip 照旧披露)。 - - **机制(测试锁定)**:每 cell 一次共享加载 + raw factor panel(与 OOS cell 同调用序)→ 每组从 raw panel **独立重处理**(`drop_missing` 按组——切 processed 列会错因为 drop_missing 跨列;全列组与旧处理 bitwise 一致)→ 每组 eq vs walk-forward ic_weighted × 每场景回测。成本场景只乘 `cost.fee_rate`:scores/fills 不见 fee → **trades/turnover/gross 跨场景严格不变,只动成本线**;`_run_backtest_for` 增加 default-preserving `fee_rate` 参数(None==旧行为,bitwise 测试锁定)。 - - **三层不漂移对账(真实 run 全过)**:① raw 因子 IC **66/66 行**(11 因子×train/test×3 cells)与 P3-5 报告逐数一致;② `legacy_trio`@base 逐数复现 P3-3/P3-4(ic test −7.85%/−2.70%/−17.74%,train +7.09%/−8.31%/+11.07%);③ `full_pack`@base 逐数复现 P3-5(ic test −2.21%/−5.02%/−2.76%,combo IC 0.0253/0.0012/0.0395,eq test −4.30%/−6.83%/−25.16%)——按组重处理 ≡ 把该组当配置因子集单跑。 - - **真实结果(3 cells×4 组×3 场景,~2.1h)**: - - **子集假设在净值层不成立**:value_lowvol ic test base **−4.49%/−8.66%/−5.84%** vs full_pack **−2.21%/−5.02%/−2.76%**——full_pack 在 **3/3 cells × 全部场景**净值更优。IC 层子集略稳:value_lowvol combo_ic test IC 3/3 正(0.0245/**0.0107**/0.0387),量级与 full_pack 相当且中间 cell 远离 0(full_pack 0.0012≈0)——**IC 稳定性与净值排序不同向**,照实披露。 - - value_lowvol_liq(+liquidity_20):CSI300 IC 最高(0.0427)、SSE50|2020-2022 单 cell 最佳(−2.48%),但 2022-2024 两 cell 均劣于 value_lowvol——**加 liquidity 非免费午餐**,"可选"问题用数据回答。 - - 等权稀释复确认:full_pack eq CSI300 −25.16% vs value_lowvol eq −14.97%(子集等权耐稀释);ic beats eq(base):trio 2/3、full_pack 3/3、value_lowvol 3/3、liq 2/3。 - - **成本敏感性(新维度)**:base→2x→high_cost **全组全 cell 单调恶化**(无例外);4× fee 退化 1.7~4.4pp 年化——legacy_trio CSI300 最重(−17.74%→−22.13%,高换手代价),value_lowvol 低换手(0.41~0.74/月)退化 ~1.8~2.4pp;算术年化 drag base ~0.5-0.9% / high_cost ~1.4-4.8%;turnover 跨场景不变实测 ✓。 - - ⚠️ **全组 test annual 均为负——非收益声明**;**POST-HOC 选择已披露**(子集是在同一批窗口上看完 P3-5 结果后选的,本 run 只量化相对稳健性+成本敏感性,**不是独立确认**——独立确认需真正新窗口/新 universe)。 - - secret scan 报告+日志 0 处(token 值/"token"/".config.json");demo 0.96/0.84 不变。 -- ✅ **Phase 3-7 独立样本验证**(**PR #19 已 merge 到 `main`**,曾 stacked 在 P3-6 分支上、PR #18 先合后顺序合入。**EXPLORATORY**,含 review 2 HIGH 修复):把 value/低波发现从"同窗口 POST-HOC 对照"推进到真正独立 holdout。不加因子/alpha、不调参;P3-6 group/cost 逻辑与配置不变(旧 27 测试原样通过)。 - - **review 2 HIGH 修复**:P3-7 报告曾沿用 P3-6 标题/"not independent confirmation"开场白/"SAME overlapping windows"收尾 caveat,与自身 verdict 节矛盾 → `render_subset_validation` 标题/框架/caveat 与 `_subset_downgrades` 按是否含 independent cells 分支(P3-6 配置原文不变,回归测试锁定,+3 测试);artifact 按用户指示**不重跑矩阵**,以确定性脚本把 5 处与数据无关的静态文案行替换为新渲染器的精确输出(新字符串从新代码路径提取、非手敲;diff 仅 5 行、全部数字未动、secret scan 0)。 - - **机制(测试锁定)**:独立性是**人的声明**——`subset_validation.independent_cells` 显式列 cell(必须引用矩阵已声明 cell、**禁止与 skip_cells 冲突**——"声明独立验证却不跑"是配置错误;未声明一律默认 screened,保守);`hypotheses`(因子→期望 IC 符号)**run 前固定**于 config(value_ep+/value_bp+/volatility_20−,源自 P3-5 筛选);`min_rebalances`(默认 8)不足 → **INSUFFICIENT-DATA**(样本量必披露,绝不静默通过)。verdict=事实性符号检查(HOLDS=期望符号在 holdout **双子期**都成立;SUPPORTED/PARTIAL/NOT SUPPORTED;NaN/缺列不成立);**分类汇总绝不混**(summarize_by_sample 按 sample class 各自聚合;verdict 节只读独立 cells)。 - - 真实 config:screened anchor SSE50|2022-2024(必须逐数复现 P3-6=run 内不漂移锚)+ 独立 SSE50|2024-2026、CSI300|2024-2026(**2024-07-01→2026-05-31 后于全部筛选**,split 2025-07-01;数据实测到 2026-06);CSI300|2022-2024 skip 披露。 - - **运维实证**:两次真实 run 死于瞬时 ConnectionError(旧默认 3 次重试≈3s 容忍)→ 默认重试预算 **3→6**(≈23s 容忍,成功路径不变,测试锁定)→ 第三次 ~2.2h 跑通。⚠️ P3-7 报告与 P3-6 同名(`phase3_subset_validation.md`),后跑覆盖前跑(regenerable,数字都在进度文档)。 - - **anchor 不漂移 ✓**:raw IC 22/22 ≡ P3-5 矩阵报告;组级 base 年化逐数 ≡ P3-6。 - - **真实结果(3 cells/~2.2h,独立结论只来自 2 个 holdout cells)**: - - **独立 verdict:2/2 cells SUPPORTED**(各 21 settled rebalances vs min 8)——value_ep IC +0.0322/+0.0134(SSE50 holdout train/test)与 +0.0245/+0.0072(CSI300);value_bp +0.0379/+0.0033 与 +0.0310/+0.0041;volatility_20 −0.0320/−0.0120 与 −0.0373/−0.0164。**P3-5 的符号假设在未参与筛选的样本上全部成立**。 - - ⚠️ **量级显著衰减**:筛选期 |IC| 0.037~0.079 → holdout 后段 0.003~0.016(value_bp 后段≈0)——符号存活、强度衰减(部分向零回归),照实披露。 - - combo_ic_weighted 独立 test IC **8/8 正**(4 组×2 cells,0.0060~0.0238)——walk-forward IC 加权在 holdout 上仍有效。 - - **组合净值仍未确立**:独立 base ic test——legacy_trio **+1.15%/+8.13%**(2/2 正,恰是此前实证"无信号"的组!)、full_pack +7.30%/−1.93%、value_lowvol +1.72%/−4.68%、liq +3.53%/−0.39%——组间排名跨 cell 翻转,~21 rebalances 小样本,**IC 符号确认 ≠ 组合盈利**;成本阶梯仍全单调。非收益声明。 - - secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 3-8 CSI500 独立泛化检验**(**PR #21 已 merge 到 `main`**,**EXPLORATORY**):P3-7 的符号级结论是否泛化到筛选 universe 之外——新增独立 cell **CSI500(000905.SH)|2024-2026**(universe+时间双独立)。**机器零改动**复用 P3-7 层(同组/同成本场景/同假设,不加因子不调参);唯一代码变更 `output.subset_report_name`(沿 `baseline_report_name` 先例,默认 None 保持旧文件名 bitwise,测试锁定)——P3-8 报告独立成文件,**不再覆盖已验收的 P3-7 artifact**。 - - cell 设计:SSE50|2022-2024(screened 锚,须 ≡P3-6/P3-7)+ SSE50|2024-2026(independent 锚,须 ≡P3-7 verdict 数字)+ CSI500|2024-2026(主问题);CSI500|2022-2024 skip 披露(~645 名×2y 再加 ~3h 不值)。数据可行性先实测:index_weight 月度 500 名快照到 2026-05-29 含 2024-06-28 pre-start 锚。 - - **真实 run(3 cells/~3.55h,CSI500 735 distinct 名主导;一次跑通)双锚对账 ✓**:screened raw IC 22/22 ≡ P3-5 报告;independent SSE50 verdict IC 逐数 ≡ P3-7(+0.0322/+0.0134、+0.0379/+0.0033、−0.0320/−0.0120)——复现性二度确认。 - - **CSI500 verdict:SUPPORTED**(21 settled vs min 8):value_ep **+0.0083/+0.0145**(train/test)、value_bp **+0.0230/+0.0127**、volatility_20 **−0.0350/−0.0272**——三假设双子期全保持。**且衰减更小**:CSI500 test 子期量级(0.0127~0.0272)明显高于 SSE50/CSI300 holdout 的后段(0.003~0.016)——value/低波信号在中盘股上更强,**P3-7 结论泛化成立(GENERALIZES,未减弱)**。 - - combo_ic_weighted CSI500 test IC 全 4 组正(0.0243/0.0294/0.0286/0.0285,为三个 holdout cells 最高);**组合净值 CSI500 base 全组正且 4× 高成本仍全正**(trio +17.80%→+10.57% / full_pack +7.83%→+2.95% / value_lowvol +4.19%→+1.14% / liq +4.02%→+0.84%)——首个全成本阶梯为正的 cell。⚠️ 仍然诚实:trio +17.80% 又是组间排名跨 cell 翻转的例证(中盘动量 regime);单窗口 ~21 调仓小样本;**非收益声明**。 - - **报告标题配置化**(`output.subset_report_title`,沿 `subset_report_name` 先例):P3-8 报告 H1 自报 study 名「Phase 3-8 — CSI500 Independent Generalization Check」而非机器默认 P3-7 标签(review 同类 stale-wording 问题修复,测试断言首行非 P3-7;P3-6/P3-7 配置不设此项、保持 sample-aware 默认,回归锁定);文案/分区检查全过(无 stale P3-6 措辞、verdict 节只含独立 cells);secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 4-1 持久化 Tushare 行情缓存**(**PR #23 已 merge 到 `main`**,daily + adj_factor):feed 之下的 **endpoint 级 raw 缓存**——真实 run 不再每次重抓全量日线+复权因子。**默认 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后行情走 read-through,只有未覆盖的日期区间打 API。 - - **缓存只存 raw**(未复权 OHLCV/amount + 原始 adj_factor),绝不存 qfq、绝不存任何 secret;`front_adjust` 仍在内存跑,公式/时机零改动;`PanelStore` 仍是 per-run artifact,**不是**缓存 SoT。 - - `data/cache/`:`intervals`(闭区间日历算法做 gap 规划——按"日历区间是否抓过"而非"行是否存在"判定,正确区分"未抓"vs"源本无行")/ `parquet_store`(按 `(endpoint,symbol)` 分文件存 `symbol_prefix` 分片,原子 upsert 按 `(date,symbol)` 去重,latest 胜)/ `coverage`(append-only ledger,11 列含 status ok/empty/failed,**只 ok/empty 算覆盖**,failed 留待重试)/ `tushare_cache`(read-through:只抓 gap、upsert、记 coverage 含空返回、再从缓存读全区间;`refresh_recent_days` 重抓近端 tail;`force_refresh` 整端点重拉;per-run 抓取计数 stats 日志)。 - - `TushareFeed.get_bars`:`cache=None` → 旧直抓路径**逐字不变**;`cache` 注入 → read-through 后用**与直抓完全相同的 join+select**,故 `front_adjust` 前面板逐字节一致(qfq 等价单测锁定);per-symbol 限流/重试仍留在 feed 的 `_call` 闭包里。`_build_market_cache` 仅在 `data.cache.enabled` 时接线。 - - **配置**:`data.cache`(enabled=False / root_dir=`artifacts/cache/tushare/v1`(gitignored)/ refresh_recent_days=14 / force_refresh=[]);校验拒绝负刷新窗/空 root/未知键;全部旧配置仍 validate。 - - **真实 smoke(phase2 baseline;非缓存参照 ref / 缓存冷 cold / 缓存暖 warm 三轮)**: - - **三轮 report 指标完全一致** ✓(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%,REF==COLD==WARM)——qfq 等价单测已证 cached==direct 数据层逐字节一致,真实 run 三轮一致是端到端实证。 - - **warm 轮零市场端点调用**:cold 后 coverage ledger = 68 market_daily + 68 adj_factor 行(68 成分全 ok);warm 后**仍 68+68 不变**(零新 coverage 行 = 零 gap-fetch = 零 daily/adj 调用)。 - - wall:ref 998s / cold 1025s / warm **734s**(暖跑省 ~290s = 行情抓取部分;index_weight/财务/covariates 仍 live——P4-1 只缓存行情,诚实标注)。 - - secret scan:缓存 parquet + ledger 0 处 token / `.config.json`;ledger 列只有端点元数据。 - - **缓存命中直接可见(review follow-up)**:`TushareFeed.cache_stats()` 暴露各端点 gap-fetch 计数,`_load_panel` 经 run-scoped logger 打 `data cache: market_daily_gap_fetches=N adj_factor_gap_fetches=M`——冷跑非零、暖跑 0/0(warm rerun 实证 run_phase2_baseline.log 含 `0/0`,secret scan 0)。 -- **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report 全不动;`artifacts/data/{output_name}.parquet` 不当 SoT。范围克制:P4-1 只 market_daily+adj_factor,其余端点(index_weight/daily_basic/fina_indicator/...)P4-2/P4-3 再缓存。 -- ✅ **Phase 4-2 持久化 Tushare universe + tradability 缓存**(**PR #25 已 merge 到 `main`**,index_weight + suspend_d + namechange + stk_limit + stock_basic):把 P4-1 端点级 raw 缓存扩到 universe/可交易性端点——真实 run 不再每次重抓成分股/停牌/ST/涨跌停/上市日。**默认仍 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后这五端点走 read-through。 - - **三种规划形态共用一引擎**:① dense per-symbol 日期区间(`suspend_d`/`stk_limit`,复用 P4-1 gap + recent-tail);② index_code 维日期区间(`index_weight`,coverage key=index_code,gap 内仍 **90 天分页**,raw 快照入库);③ snapshot 维度(`namechange` per-symbol、`stock_basic` 全局 sentinel),用 `refresh_dimension_days`(默认 30)staleness + force_refresh(`CoverageLedger.snapshot_fetched_at` 给新鲜度判定)。 - - **语义全保**:`index_weight` 仍 PIT/as-of(370 天 pre-start lookback 进缓存请求区间,latest-snapshot-on-or-before 仍在 feed/universe);`stk_limit` 仍 **raw price**(限价检查在 front-adjust 前,不碰 qfq);`stock_basic` 只取 list_date 供 `min_listing_days`(缺失仍 kept+披露),current-tag `industry` 绝不入缓存/中性化;`suspend_d`/`namechange`/ST 区间形状不变;**缓存只存 raw 端点事实,不存派生 flag 作 SoT**。 - - **三 feed 接线**:`IndexConstituentsFeed`/`TushareFlagsFeed`/`TushareCovariatesFeed` 各加 `cache=None` 注入;cache present → read-through + **共享 finalizer**(cached==direct,限价 frame `assert_frame_equal`、ST 区间集合相等、suspend set / listing dict 相等单测锁定);`cache=None` → 直抓路径**逐字不变**(旧 feed 测试原样过)。per-symbol 限流/重试仍在各 feed 的 `_call` 闭包里(缓存 transport-agnostic)。 - - **coverage ledger 复用 + 扩展**:11 列不变;**empty 算覆盖、failed 不算**(fetch 抛错则不记 coverage、留待重试,测试锁定);ledger 列只有端点元数据,无 token/secret。 - - **单一共享 cache 贯穿 4 runner**(run_phase0/phase2/oos/subset):`_build_cache(cfg)` 建一个实例线穿 `_build_universe`/`_load_panel`/`_enrich_tradability`/`_maybe_enrich_listing`,跑完所有缓存端点后 `_log_run_cache_stats` 打**一行** 7 端点统计;P4-1 的 market 行前缀保留(旧统计测试过)。 - - **配置**:`data.cache.refresh_dimension_days`(默认 30,>=0 校验);旧配置全 validate(含未知键拒绝)。 - - **真实 smoke(phase2 baseline,fresh temp root,cold→warm;fresh root 不动已 merge 的 v1 缓存)**: - - **cold 行**:`market_daily=68 adj_factor=68 index_weight=9 suspend_d=68 namechange=68 stk_limit=68 stock_basic=1`(全非零);coverage = market/adj/namechange/stk_limit 各 68 ok + index_weight **1 ok**(整 gap 一行,内部 9 窗分页)+ stock_basic 1 ok + **suspend_d 68 empty**(SSE50 大盘股该窗口无停牌 → 空返回算覆盖,warm 不重抓)。 - - **warm 行:7 端点全 0**;coverage ledger **零新行**;report 指标与 cold/P4-1 cached baseline **逐数一致**(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%)——cached==direct 二度端到端实证。 - - wall:cold **960s** / warm **366s**(暖跑省 ~594s = universe+tradability+market 抓取;`daily_basic`(market_cap)/`index_member_all`(pit_sw) 仍 live——P4-2 不缓存这俩,P4-3 再说,诚实标注)。 - - secret scan:缓存 parquet + ledger + 日志 + 报告 0 处 token 值 / `.config.json`;ledger 无 token 列。 - - **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report/`front_adjust` 全不动;`artifacts/data/*.parquet` 不当 SoT。范围克制:`daily_basic`/`fina_indicator`/`index_member_all` 仍留 P4-3。 -- ✅ **分钟级 intraday pipeline I1–I4**(**PR #29 已 merge 到 `main`**,4 commit 一 PR;**全程与日频链路解耦——`factors`/`alpha`/`portfolio`、日频 `runtime/backtest`、日频 `TushareFeed`/`TushareCache`、全部 `config/` 零改动**,Phase 0/2/3 数字不变)。本地验收文档在 `tmp/context/intraday_pit_checkpoints/stage_i{1,2,3,4}_acceptance.md`(gitignored)。 - - **核心架构决策**:raw intraday SoT = **stk_mins `1min` only**;5/15/30/60min 是从缓存 1min **派生**的视图,不作独立 raw 上游产品抓取;日频 `D` 仍独立保留(不被分钟重采样替代)。三时间戳贯穿全程严格分离:**signal cutoff**(T 14:50,特征只用 `available_time<=cutoff`)/ **execution timestamp**(T 14:51)/ **holding period**(exec→next-exec,绝不 close-to-close)。 - - **I1(feat data)** 1min raw feed + PIT schema:`data/clean/intraday_schema.py`(独立 `MultiIndex(time,symbol)`,分钟精度不归零;`bar_end=trade_time`/`bar_start=bar_end-freq`/`available_time=bar_end+data_lag`;`RAW_INTRADAY_FREQ`+`ensure_raw_intraday_freq` 钉死 1min)+ `data/feed/tushare_intraday.py`(`TushareIntradayFeed.get_minutes`,`vol→volume`/`ts_code→symbol`/`trade_time→time/bar_end`,非 1min 在建 SDK client 前即拒,token 不入库不打印)。 - - **I2(feat cache)** stk_mins 1min read-through cache:`data/cache/intraday_coverage.py`(**timestamp-interval ledger**,`raw_freq/start_time/end_time`,不复用日频 date 语义;ok/empty 算覆盖 failed 不算)+ `intraday_parquet_store.py`(**月分区** `stk_mins_1min/freq=1min/symbol_prefix/symbol/year/month.parquet`,原子幂等 upsert by `(symbol,freq,bar_end)`,只存 raw)+ `intraday_cache.py`(`TushareIntradayCache.stk_mins_1min`:交易日 gap 规划复用 `intervals.py`,≤23 日窗分页 <8000 行 cap,1min-only guard)。接入 `TushareIntradayFeed` 可选 cache path(直抓路径字节级不变)。**冷写/暖零调用/partial 只补缺口/empty 记录/failed 重试/cached==direct 规范化后逐字节等价** 全测试锁定;**日频 `TushareCache` 零改动**(独立类)。 - - **I3(feat data)** 分钟→日频 PIT 聚合:`data/clean/intraday_aggregate.py::asof_daily_features`(默认 `decision_time=14:50:00`;**先 `available_time<=cutoff` 逐 bar 过滤再按日 groupby**,分钟时间戳绝不先归零进日频)。cutoff 编码列名 `intraday_ret_0930_1450`/`intraday_realized_vol_0930_1450`/`intraday_vwap_0930_1450`/`intraday_last30m_ret_1420_1450`。`resample_intraday_bars` 派生粗 bar `available_time=max(source_1min.available_time)`。泄漏测试(扰动 14:50 后 bar → 特征不变)+ 可见性排除测试锁定。 - - **I4(feat runtime)** 尾盘调仓 execution 骨架:`runtime/intraday_execution.py::simulate_tail_rebalance`(`next_minute_close` 模型:14:51 或窗口内最早 bar 成交;持有期收益 `exec(T)→exec(T_next)` 非收盘;缺 bar/NaN/无窗口 bar → 可解释 blocked,**不静默用日收盘替代**;只读分钟 bar 无 EOD 泄漏)。**独立函数,未接 config/pipeline**;日频 `close_to_next_period` 不变。`tail_vwap`/`closing_call_proxy` config 层拒绝(注明 future,后者需 `stk_auction_*` 权限当前无)。 - - **下一步**:执行真实性先于研究因子 → I5b 已补执行期涨跌停可行性(见下);之后 I5c 再把 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine(报告披露 cutoff/lag/execution_model/window),日频回归不破。 -- ✅ **P4-3 因子支撑端点缓存 + 21:00 data updater**(**PR #31 已 merge 到 `main`**,daily_basic + fina_indicator + index_member_all 进既有日频 `TushareCache`;新增独立 `data-update` CLI 增量暖跑,**不跑 factor/alpha/portfolio/backtest、不写 PanelStore**,日频回测仍自走 read-through 补缺口)。本地验收:`tmp/context/intraday_pit_checkpoints/stage_p4_3_data_updater_acceptance.md` + `tmp/context/session_handoff_20260613/p4_3_codex_acceptance.md`(codex 复核 PASS)。 - - **daily_basic**(dense,pe/pb/total_mv,一次缓存调用喂 market_cap+value_ratios);**index_member_all**(per-symbol SW in/out 维度,staleness 刷新)。 - - **fina_indicator 字段集无关**(codex acceptance blocker 修复):cache **永远存 canonical `FINA_FIELDS` superset**(roe/netprofit_yoy/grossprofit_margin),feed 读时选子集——一个配置 warm 不会阻塞另一配置(光加 fields_hash 会在 upsert 时互相覆盖);drift 测试守 `FINA_FIELDS ⊇ financial.SUPPORTED_FIELDS`;coverage 仍按 report-period end_date + 长 trailing tail 抓晚披露,ann_date 作 raw 不当覆盖轴。 - - **not_ready pending window**:今日(`not_ready_days`)空返回记 `not_ready`(非 coverage) 次跑重试,跨界 gap 拆分;`not_ready_days=0`(默认) 行为逐字不变。per-endpoint trailing tail + summary 计数。 - - cached==direct 实证(market_cap/value_ratios/pit_sw/fina-as-of);`cache=None`→direct 逐字不变;缓存只存 raw(无 qfq/因子/token);日频/分钟各自 ledger/store。**日频回测数学/factor/alpha/portfolio/runtime 零改动,Phase 0/2/3 不变**。 -- ✅ **P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model**(**PR #33 已 merge 到 `main`**,架构/框架 PR,非研究结果;含 review 两修复):把回测层重构成**共享事件驱动核心**,日频 close-to-close 与分钟尾盘成为**同一 achieved-book ledger 上的两个 event model**,零重复 fill/cash/settle。本地验收:`tmp/context/session_handoff_20260615/stage_i5a_acceptance.md`。 - - **核心**(`runtime/backtest/`):`events.py`(`HoldingPeriod` 显式时间基 + 共享月度日历)/ `engine.py`(`BacktestEngine` 单循环:universe→scores→构建→可行成交 via `SimExecution`/`simulate_fills`→settle→NAV/feasibility/holdings/**event** 日志)/ `event_models.py`(`DailyCloseEventModel` 逐字节复刻日频 + `IntradayTailEventModel` 14:50 决策/14:51 成交/exec-to-exec,复用 `intraday_execution.build_execution_prices`)。`BacktestDriver` 改为 engine+DailyCloseEventModel 的**薄 wrapper**(−237/+167),`pipeline`/`oos`/`phase2` 三 runner 一行未改。 - - **config**:opt-in `intraday`(enabled/decision_time/data_lag/session_open/execution_model/execution_window/require_cache_coverage/missing_execution)+ `backtest.event_order` Literal;`intraday_tail_rebalance` 必须 `intraday.enabled=true`(root validator),旧配置全 validate,非法 model/window 可读报错。 - - **分钟尾盘语义**:决策只用 `available_time ≤ 14:50` 的 bar;成交取执行窗口 `[14:51,14:56:59]` 内最早 1min 收盘;持有期收益 `exec(T_next)/exec(T)−1`(**绝非 close-to-close**);**缺/NaN 执行 bar = 显式 block,绝不退回日收盘**;turnover/cost/holdings 按 achieved book;闲置现金 `cash_return`。事件时间戳全程可审计。 - - **新 CLI `run-phase-i5a-intraday`** + `config/phase_i5a_intraday_tail_framework.yaml`(SSE50 SH/SZ smoke):日频面板/universe 走既有 P4 缓存;分钟 bar **只读既有 intraday 缓存**(缺即 hard blocker,绝不静默暖跑 → **零 `stk_mins` live call**);PIT-safe 分数 = I3 `intraday_ret_0930_1450` 特征(纯框架验证,非业绩声明)。 - - **真实 smoke(SSE50,2026-03-03→06-12,~180s)**:`periods=3 / covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;event 表显示 14:50 决策 / 14:51 执行 / **actual exec bar 范围** / 真实 exit 执行锚;secret scan 0。 - - **review 两修复**:① `require_cache_coverage=true` 下**任一** symbol 缺即 loud fail(原仅全缺才失败 → 静默丢名引入样本覆盖偏差);② `HoldingPeriod.exit_execution_ts` 显式化,event_log + 报告显示 **planned vs ACTUAL 执行 bar 时间**(14:51 缺、14:52 成交可见),末期不再 NaT。 - - **不变量守住**:daily close-to-close 行为不漂移(engine==driver 黄金测试 + phase0 `ic 0.9600/annual 0.8408` 不变);无重复 ledger;缺 bar 绝不日收盘兜底;turnover/cost 按 achieved。 -- ✅ **P-I5b 分钟尾盘执行期价格涨跌停可行性**(**PR #35 已 merge 到 `main`**,执行真实性硬化 PR,非研究 alpha;含 review LOW 修复):补 P-I5a 显式 deferred 的限制——在「有效 1min 执行 bar」规则之上,用 raw `stk_limit` 对**选定执行分钟的 raw 1min 收盘**做方向感知闸门。本地验收:`tmp/context/session_handoff_20260615/stage_i5b_acceptance.md`。 - - **语义(测试锁定)**:`exec ≥ up_limit − tol → can_buy=False`(涨停**只挡买**)/ `exec ≤ down_limit + tol → can_sell=False`(跌停**只挡卖**)/ 缺·NaN 执行 bar 仍**先于**限价逻辑挡两方向;**RAW-vs-RAW**——raw 1min 收盘(intraday 缓存存未复权)vs raw `stk_limit`,绝不碰 qfq/日收盘/`at_up_limit·at_down_limit` 日收盘派生 flag。`can_buy/can_sell` 喂既有 `simulate_fills` achieved-book ledger(turnover/cost/holdings 按实际成交)。 - - **缺限价行绝不静默当通过**:严格 `require_price_limit_coverage=true` 在**模型构造期 raise**(出结果前列缺失 pair);宽松 `false` 计数披露 unchecked 并退回 bar-exists 规则。诊断(涨停挡买/跌停挡卖/unchecked/coverage)按 `(date,symbol)` 幂等,重复 `feasibility()` 不重计。 - - **接线**:`IntradayCfg.{price_limit_check(默认 false)/require_price_limit_coverage/limit_tolerance(≥0 校验)}` + `OutputCfg.intraday_report_name`(I5b 报告独立成文件,不覆盖已验收 I5a artifact,沿 baseline_report_name 先例);runner 经 `TushareFlagsFeed(cache=cache).limits()` 走既有 P4 read-through 缓存取 raw 限价(**不加新端点**),报告 H1/intro 按 check 自报 I5b(修同类 stale-title)。 - - **不变量守住**:`BacktestEngine`/`BacktestDriver`/`DailyCloseEventModel`/`fills`/`execution` **零改动**(限价 map 仅 check 开时构建,默认 false → `feasibility()` 逐字退回 I5a bar-exists 规则);日频 close-to-close 不漂移。 - - **真实 smoke(SSE50 2026-03-03→06-12,3 次跑 NAV 全 `0.976448` 确定性)**:`covered=58/58 / stk_mins_live_calls=0`;限价覆盖 **174/174**;**1 笔涨停挡买 / 0 笔跌停挡卖** → NAV 由 I5a 的 0.952207 变到 **0.976448**(2026-04-30 一只 14:51 封涨停的票被正确挡买,该期只持 9 只——I5a 只查 bar 存在会「买」进涨停板,**这正是 I5b 补的执行真实性,非业绩声明**);`stk_limit` gap-fetches=58(冷暖都 58:窗口尾端距今 3 天落在 `refresh_recent_days=14` 内,P4 按策略重抓 tail,据实报告,非 minute fetch);secret scan 报告+日志+58 缓存 parquet **0 处**。 -- ✅ **P-I5c MMP 分钟因子端到端 opt-in alpha**(**PR #37 已 merge 到 `main`**,**EXPLORATORY**,非业绩声明):把首个 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine,日频回归不破。 - - **MMP(Minute Microstructure Pressure)**(`data/clean/intraday_aggregate.py`,公式/PIT/前导 NaN/跨 symbol 隔离全测试锁定):逐 1min bar `mid=(high+low)/2`、`S=(close−mid)/mid`、`V=√(volume/median(vol[t-20:t]))`、`B=|close−open|/(high−low+eps)`、`R=(high−low)/(mean(hl[t-20:t])+eps)`、`MMP_t=S·V·B·R`(eps=1e-6);日频分数 = 在场 bar `[session_open, decision_time]` 等权均值,rolling baseline 用前 20 个在场 bar(首 20 NaN)。**先 `available_time≤cutoff` 过滤再聚合**(PIT-safe,绝不先归零)。 - - **可配置 `intraday.score_feature`**(默认沿用,`mmp_ew` opt-in)+ 可配置报告标题(修同类 stale-title);`config/phase_i5c_mmp_minute_factor.yaml`。decision feature→alpha→portfolio→event engine 全程复用既有机器,报告披露 cutoff/lag/execution_model/window。**日频 close-to-close 零漂移**(phase0 `0.9600/0.8408` 不变)。 -- ✅ **P-I5d MMP 五分位分组回测**(**PR #39 已 merge 到 `main`**,**standalone against `main`**,**EXPLORATORY,非业绩声明、非调参**):把 I5c MMP 日频分数 `intraday_mmp20_ew_0930_1450` 做 5 等额分位分组回测——每月调仓日按 PIT-safe 分数横截面排名切 `analytics.quantiles=5` 等数桶(Q1 最低 / Q5 最高),每组作独立 long-only 等权组合走**同一** `BacktestEngine`+`IntradayTailEventModel`+`SimExecution(fee_rate=0.001)`,I5b raw `stk_limit` 执行期涨跌停 ON。引擎/执行可行性/MMP 因子数学全不变,仅新增分组 + per-group 编排。本地验收:`tmp/context/session_handoff_20260615/stage_i5d_acceptance.md`。 - - **新增**(机器零改动复用 I5a/I5b/I5c):`qt/intraday_groups.py`(等额 rank 桶 / `GroupScores` / `EqualWeightAll`)、`qt/intraday_group_backtest.py`(cache-only anchor-date-sliced 分钟加载、一份共享 exec-price 矩阵跨 N 个 fresh per-group 模型、QN−Q1 合成 spread)、`qt/intraday_group_figures.py`/`intraday_group_report.py`(NAV/spread/metric 图 + 报告)、`config/phase_i5d_mmp_quintile_5y.yaml`;唯一 runtime 改动 `IntradayTailEventModel` 可选 `precomputed_prices`(默认 None → I5a/I5b 逐字节不变)。 - - **真实 run(CSI500 `000905.SH`,2021-06-01→2026-05-31,59 月调仓,~95min)**:`covered=892/995`(**103 只分钟未覆盖成分被 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only)/ `fee_rate=0.001`;final NAV Q1→Q5 **0.9822 / 1.0423 / 1.0495 / 1.1275 / 1.1577**(**单调 Spearman 1.0000**,等额 rank 桶对 MMP 肥尾稳健);合成 Q5−Q1 spread +0.29%/期、累计 **+17.15%**;限价覆盖 52,584/52,584(严格 `require_price_limit_coverage=true` 过)。 - - ⚠️ **caveat(保留,不移除)**:单因子/单重叠 5 年窗口/单 universe,无调参/无 robustness/无 learned 权重;`covered=892/995`(103 drop);`stk_mins_live_calls=0`;**Q5−Q1 是合成 long-only 腿差,非单独执行的 dollar-neutral 组合**;执行模型仍缺 partial-fill / liquidity / volume-cap;**Q5>Q1 不作业绩声明**。这是项目首个正向 intraday 信号,下一步需独立泛化(CSI300/中证1000 或 disjoint 窗口,机器冻结)。 - - **PR 处置**:#37(I5c)随 #39 落地后其 head 成 `main` 祖先,GitHub **自动 merged**(非本次手动 merge);#38(i5d stacked on i5c,base 非 main)已被 #39 **完全取代**(零 remaining commit/file diff vs main),保持 **OPEN 待清理**(未获显式授权关闭)。 -- ✅ **P-I5e MMP 五分位 CSI300 独立泛化检验**(**PR #53 已 merge 到 `main`**,**EXPLORATORY,非业绩声明、非调参;负结果**):冻结已验收的 I5d MMP 五分位机器,在**独立 universe** CSI300(`000300.SH`)同窗口(2021-06-01→2026-05-31)复跑,检验 I5d 在 CSI500 上的 Q1→Q5 单调性是否归因于因子而非 universe/regime。**仅加一个 config(`config/phase_i5e_mmp_quintile_csi300_generalization.yaml`),零代码/机器改动**——MMP 数学 / 等额 rank 桶(Q1 最低 Q5 最高)/ I5a/I5b 尾盘 event model(14:50 决策 / 14:51 执行 / exec-to-exec)/ raw `stk_limit` 执行可行性 / cache-only anchor-date 分钟加载全冻结;report 名/标题已 config 驱动故无需改代码。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5d_independent_generalization_20260620/`。 - - **Preflight 选 CSI300(非 fallback CSI1000)**:全窗口 481 distinct 成分,分钟全窗口覆盖 **448/481=93.1%**(≥ CSI500 89.6%)→ 够切 5 等额组,窗口不缩短;CSI300 日频成分 ledger 诚实仅覆盖近 1 年(无谎报、无静默截断),缺的经 P4 日频 read-through 补(21 次 index_weight,允许,同 I5d)。 - - **真实 run(CSI300,59 月调仓,~30min)**:`covered=448/481`(**33 只分钟未覆盖成分 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only,缺分钟即 hard miss 绝不暖)/ `fee_rate=0.001`;限价覆盖 26386/26386 全过。 - - **结论:FAILS / 不泛化**。final NAV Q1→Q5 **0.8809 / 0.7377 / 0.9418 / 0.7573 / 0.6665**;年化 **−2.55% / −6.00% / −1.21% / −5.50% / −7.92%**;**单调 Spearman −0.5000**(vs CSI500 +1.0000);合成 Q5−Q1 累计 **−26.77%**(vs CSI500 +17.15%)。**Q5(高 MMP)在 CSI300 上最差**——高 MMP 组不跑赢低 MMP 组,I5d 的 CSI500 单调性在独立 universe 上**反转**,强烈提示其为 universe/regime 特异、非稳健因子属性。这正是独立检验要拿到的证据。 - - ⚠️ **caveat(保留)**:单因子/单重叠 5 年窗口/单 universe;Q5−Q1 是合成 long-only 腿差非 dollar-neutral 执行;丢弃未覆盖分钟名的 coverage bias;执行模型仍缺 partial-fill/liquidity/volume-cap;无调参/无 learned alpha;**非业绩声明**。CSI500 对照仅作 context,不作任何组业绩声明。 - - **回归不破 + 范围克制**:phase0 `0.9600/0.8408` 不变;`pytest` 678 passed(677 基线 + `test_cache_config.py` 的 `config/*.yaml` glob 参数化自动覆盖新 config 的 +1,无新测试文件);**不动 progress docs 以外的代码/config/test**,不碰 schema registry / D6;**PR #38 未动**。 -- ✅ **P-I5f 分钟尾盘执行流动性诊断**(**PR #55 已 merge 到 `main`**,**执行真实性诊断、非因子研究结果;report-only**):给分钟尾盘链路加 opt-in、**report-only** 执行流动性诊断——对每笔 desired trade `|target_w − current_w|`,用**选定执行分钟 1min bar 的成交额 `amount`(RMB)**在参与率上限下估容量,报 capacity ratio(`>=1` 覆盖、`<1` 潜在受限)。新增 default-off `intraday.liquidity_diagnostics`(`enabled` 默认 false → 现存配置字节级不变;启用时 `portfolio_notional`>0、`max_participation_rate`∈(0,1]、仅 `mode=report_only`,各可读报错)。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5f_intraday_liquidity_diagnostics_retry_20260620/`。 - - **report-only 不变量(测试锁定 + 真实 smoke 双证)**:不改 fills / can_buy·can_sell / blocked reason / target 权重 / achieved holdings / turnover / cost / NAV / 因子分数 / MMP 分组 / alpha / 组合构建。容量**只用选定执行分钟 1min `amount`**,绝不用未来 bar、日频 amount/volume、日收盘、或 EOD 代理。`BacktestEngine` 加 additive、default-off `record_rebalance_plan`(默认 False → 循环字节级不变)捕获引擎真实用的 (target,current);诊断在 settle 后从已产出 log 构建,只读不写。既有 missing-bar/missing-price/raw stk_limit 阻挡**保留原 reason,绝不重分类为流动性阻挡**;missing/NaN/≤0 amount 报 missing capacity;零 desired trade 跳过防除零。 - - **真实 smoke(SSE50,2026-03-03→06-12,3 期,机器冻结)**:`covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;**`final_nav=0.976448` 与已验收 I5b 逐字节一致** → 启用诊断未动账面(report-only 端到端证明)。诊断:**43** desired trades;**1** 被既有 I5b feasibility 挡(2026-04-30 涨停那笔,原 reason 保留);**0** missing-capacity;**42** 有可用 capacity ratio;**20** below 1.0x;ratio min/p10/median/p90 = **0.122 / 0.222 / 1.104 / 2.730**;最紧 trade `2026-04-30 601658.SH sell` ratio 0.122。 - - ⚠️ **解读(诚实)**:10M RMB 诊断名义 + 5% 参与率下,约一半被检 SSE50 单分钟 desired trade 容量不足——**支持后续做 size-aware 执行建模,但不证明 alpha/业绩/可交易性,也不外推到其他名义**。**非业绩声明、非策略改进**;partial-fill / volume-cap 强制不在本阶段(那是将来 I5g,须单独显式 goal)。I5f 与 MMP 正交,MMP 仍暂缓(I5e 负结果)。 -- ✅ **数据层 D1 — cache/store 契约文档化 + token 解析去重**(**PR #41 已 merge 到 `main`**,行为保持型重构,非研究/非数据语义变更):数据层第一步低风险清理,只做两件事——把 `cache` vs `store` 边界写成可提交契约,并把重复的 token 解析收敛到既有 `data/feed/secret.py::read_token`。 - - **契约文档** `docs/data/data_layer_contracts.md`:`data/cache` = 可复用 raw endpoint 缓存 / SoT;`data/store/PanelStore` = per-run canonical 面板 artifact(非 raw lake,`artifacts/data/*.parquet` 不当缓存权威);缓存只存 raw;`front_adjust` / PIT 成分 / PIT SW 行业 / `ann_date` 财务对齐 / raw 涨跌停检查 / factor·alpha·portfolio·runtime 数学全在下游;`data-update` 只暖缓存不跑研究;data-quality + 并发是后续阶段(不声称已实现)。 - - **token 去重**:`tushare_feed.py` 删掉重复的 module-level `_lookup_dotted` + 内联 json/Path 解析(`_read_token` 变 `read_token(...)` 一行 wrapper);`index_feed.py` 不再从 `tushare_feed` 导入私有 `_lookup_dotted`,`_client` 直接走 `read_token`。`_lookup_dotted` 全仓清零;6 个 tushare feed(feed/index/flags/fina/intraday/covariates)统一走 `secret.read_token`。构造签名 / `__repr__` token 安全 / fetch+cache 路径全不变;+2 窄测试锁定 index feed 真实 token 路径(fake config、不碰网络、不泄漏)。 - - **行为保持**:phase0 `ic 0.9600 / annual 0.8408` 不变;无数据抓取/缓存语义/factor/alpha/portfolio/runtime/report 变更。**显式非目标(留给 D2+)**:`TushareCache` 内部拆分 / endpoint schema registry / data-quality validator / 并发 / `CoverageLedger` 存储变更 / `PanelStore` append·partition。 -- ✅ **数据层 D2 — TushareCache endpoint specs/parsers 拆分**(**PR #43 已 merge 到 `main`**,行为保持型内部重构,**公开缓存语义零变更**,非性能/并发/data-quality 层):把 814 行的 `data/cache/tushare_cache.py` 内部拆成小文件,让 endpoint 元数据与 raw 解析器可单独审阅,`TushareCache` 仍是公开门面。 - - **拆分**(解析器逐字搬移,行为靠既有 cache 测试锁定):`tushare_specs.py`(89 行,endpoint ids/`ALL_ENDPOINTS`/`_GLOBAL_KEY`/列集+自然键/`FINA_FIELDS`/`_INDEX_WINDOW_DAYS`/rename maps,纯常量)、`tushare_parsers.py`(216 行,10 个 `_parse_*`)、`tushare_planning.py`(22 行,叶子 helper `_fields_hash`/`_compact`);`tushare_cache.py` **814→596 行**,保留 `TushareCache` 门面 + read-through 引擎(gap 规划 / index_weight 90 天分页 / snapshot staleness / `refresh_recent_days`·`recent_tail_overrides`·`not_ready_days`·`force_refresh` / ok·empty·not_ready·failed coverage / 逐自然键 upsert / `stats`·`update_summary`)+ `FetchOne`/`FetchSnapshot`。 - - **向后兼容导入保持**:门面 re-export endpoint ids + `FINA_FIELDS` + `ALL_ENDPOINTS`,`from data.cache.tushare_cache import DAILY_BASIC/FINA_FIELDS/INDEX_WEIGHT/...`(被 `tushare_fina.py` + cache 测试用)全照常;新增 5 个窄测试锁定 re-export identity + 模块边界。**公开方法/签名/内部规划/coverage 语义全不变**;phase0 `0.9600/0.8408` 不变。**非目标(D3+)**:schema registry / data-quality / 并发 / `CoverageLedger` 存储 / `PanelStore` append·partition / intraday 缓存重构 / 限频重试,均未做。 -- ✅ **数据层 D3 — report-only 数据质量层**(**PR #45 已 merge 到 `main`**,含 review 一修复;纯库 + 测试,**report-only 非 cleaning**):新增独立 `data/quality/` 包,在接入处附近**只报告**可疑上游数据,**绝不**过滤行/修复值/改 qfq/改 cache coverage/动 feed·factor·alpha·portfolio·runtime。 - - **包**(纯函数,输入 DataFrame→findings,输入永不被改):`report.py`(`QualityFinding` + `make_finding`(样本限 5、清洗)+ `findings_to_frame`(稳定确定序)+ `render_report`(确定性 Markdown,clean→"No findings")+ secret redaction);`market.py`(日频:dup(date,symbol)/非正 OHLC/high1` 且多 symbol → **只有日频密集 per-symbol 取数**走有界 `ThreadPoolExecutor`,**store upsert + ledger 写仍在主线程按计划序**(成功 gap 先 durable 再抛首个失败,失败 gap 不记覆盖、可重试;store/ledger 内容与取数完成序无关)。snapshot / `index_weight` 90 天分页 / intraday 在 D5 仍串行(并发开启时仍共享同一限频器)。 - - **updater 接线**:`run_data_update` 仅 `max_workers>1` 才建**一个** `GlobalRateLimiter`(用 `rate_limit_per_min`),经 `_build_feeds` 共享给全部 feed(6 个 feed 加 `scheduler=None` 默认不变参);`UpdateResult`+`format_summary` 暴露 `max_workers`/全局 `rate_limit_per_min`(非密)。 - - **测试**(`tests/test_scheduler.py` + `tests/test_data_update_concurrency.py`,network-free,21):全局时隙在争用下逐次升 sleep(fake clock)/ **8 线程实测全局非按线程**(`next_allowed==N*interval`)/ 重试逐次 acquire / scheduler 模式不做 per-call sleep / 失败 secret-safe(仅异常类型)/ feed 转发其 scheduler / config 校验(`<1` 拒;默认 1)/ **serial==concurrent** 帧·ledger·请求计数 / 失败留 gap 未覆盖可重试而成功 durable / empty·not_ready 不变 / 单 symbol 仍串行。真实 token 扫描 **0 hits**;**未跑 live `data-update`**(network-free 验收)。 -- ✅ **数据层 schema 注册表 + default-off drift 守卫**(**PR #57 已 merge 到 `main`**,行为保持型、默认关闭、opt-in):把缓存已存却无人校验的 `fields_hash` 升级成显式 schema drift 守卫。**核心洞察**:每个 `_parse_*` 硬编码 canonical 列集 + 防御性 `fillna`,故存储侧 `fields_hash` 对 tushare 改列免疫——真风险在 **raw 输入侧**(tushare 删/改 parser 依赖的源列 → 静默 NaN 入缓存零告警),守卫在 **parse 之前**查 raw frame。 - - 新 `data/cache/schema_registry.py`:每端点声明式 `EndpointSchema`(required/optional/known_extra 源列 + canonical 列 + 自然键 + 期望 canonical hash,从 `tushare_specs.py` **派生**=单一真相源)+ 有状态 `SchemaGuard`。**4 检查**:#1 缺 required 源列(HARD)/ #2 真未知额外源列(WARNING,被 `known_extra_columns` 目录收窄,只对真新列响)/ #3 parsed canonical ≠ 注册表(HARD)/ #4 存储 `fields_hash` ≠ ledger 历史(HARD,每端点每 run 去重一次,迁移检测器)。 - - **模式**:`report_only`(启用默认,只记 findings + 一行 secret-free summary 日志)/ `strict`(opt-in,HARD 在 upsert **前** raise → 该 gap 不记覆盖、可重试,沿"failed fetch 不记覆盖"语义)。接进 `qt/pipeline.py::_build_cache`(仅 enabled 时建,默认 None);`data/cache/coverage.py` 加只读 `last_fields_hash(endpoint)` 访问器。 - - **不变量**:`data.cache.schema_guard.enabled=false` 默认 → `_guarded_parse(None)` 精确直通、既有 cache 测试全过、factor/alpha/portfolio/runtime/qfq/coverage 语义不变、phase0 `0.9600/0.8408` 不变;findings/日志/异常只含 endpoint+列名+16 字 SHA1,**无 token / `.config.json` / 数据值**;**仅日频 `TushareCache`**,分钟 schema 守卫 defer。 - - **review 两轮**(cc 接替外部 reviewer 验收):security PASS;code-review 首轮 BLOCK 修两 HIGH——`index_member_all` 误把 SW 级名标 required(strict 下缺级名永久 raise,直连路径 `tushare_covariates.py:155` 已 guard 证明 tushare 真省略)→ required 收敛到 `{in_date}`、级名入 optional(WARNING);check#2 噪声 → 加 `known_extra_columns` 目录只对真未知列告警;另:module `assert`→`raise`(`-O` 安全)+ 反向 droppability lock 测试 + check#3/#4 secret-absence 断言。**独立重跑 758 passed / ruff clean / 19 配置 / phase0 0.9600·0.8408 / `-O` registry-sync 守卫成立 / secret 0 新增**。 - - **已知 follow-up(本 PR 范围外)**:守卫只接 pipeline/回测缓存路径,**未接 live `data-update` 暖跑(`qt/data_updater.py`)**——那是下一步最该开 drift 检测处。 -- ✅ **数据层 盘后全A 自动拉取 —— PR-1 全A 增量 auto-warm + systemd 定时**(**PR #59 已 merge 到 `main`**,行为保持型、默认关闭、装不 enable;**2-PR 之 PR-1**,历史回填是 PR-2):先做数据正确性审计(D3 质量层真跑 80GB 真实缓存:daily 全扫结构 nan/dup/high today` 守卫**:逆序窗口本会被 `subtract_intervals` 当"零缺口=已覆盖"→ 未来 start typo 静默 no-op;故加 loud `ValueError`(仿 `DataCfg._check_date_order`)。config `data_update.backfill: {start, chunk_size, include_minute}`(全默认 → 现存配置照常 validate);`config/data_update_all_a.yaml` 加 `backfill:` 块 + 诚实成本注(冷全A 分钟回填数小时~多日,可续跑)。ops doc 加"历史回填"节(先回填再 enable 夜间 timer)。 - - **不变量**:增量 `run_data_update` 路径**字节级不变**——唯一共享改动是 `_build_scheduler`/`_build_caches` verbatim 抽取(same kwargs/顺序);49 个既有 data_update 测试原样过(main-vs-branch pytest 对照 **771→793 恰好 +22、0 既有测试被动**)。phase0 `0.9600/0.8408` 不变;缓存仍 raw-only;无 token。 - - **review 两轮**(cc 接替外部 reviewer 验收):security CLEAN(失败/进度日志只 counts+异常类型);code-review 无 CRIT/HIGH(共享重构 verbatim 验证),修 2 MEDIUM(`start>today` 静默 no-op 守卫 + 失败×续跑联合测)+ LOW(summary 显并发模式)。独立重跑 **793 passed / ruff / 20 配置 / phase0 0.9600·0.8408**。**建设侧完成;live rollout(先 `data-backfill` 补史 → 手动增量 warm → enable timer)= 用户 Stage-1 driver**。 -- ✅ **因子评估契约层 `analytics/eval/` + 十一个分钟因子复现**(**PR #63–#73 已 merge 到 `main`**,**EXPLORATORY,非业绩声明**):研报 → 复现循环(10/10 收官)与支撑它的判定契约层。全部因子同一数据面:CSI500 `000905.SH` 2021-07-01→2026-06-30 日频、cache-only(`stk_mins_live_calls=0`)、每因子 no_book / with_book 两跑(因子簿 = 已确认的 value_ep + value_bp + volatility_20)。 - - **契约层(PR #63,v0.1→v0.7 迭代后 frozen)**:`FactorSpec`(Factor 必带 classattr、**预注册 `expected_ic_sign`**、PIT 契约、intraday 块校验)+ `FactorEvaluator` ABC(8 个必填 section,缺一即 raise)+ `FactorEvalReport`(确定性 md/json,secret-redacting renderer)+ **三轴判决 Predictive × Incremental × Tradable → Adopt / Watch / Reject / INSUFFICIENT-DATA**。三条判定纪律:**非对称门**(hard Reject 先于样本量门)、**unknown never convicts**(判不了绝不定罪)、**PASS 一律读 N_eff 置信区间下界**(`N_eff = N/(1+2Σρ)`,Geyer 扩展;点估计不得买 PASS)。v0.7:增量轴独立门 `min_incremental_abs_icir=0.15`(正交化收缩残差尺度,沿用原始 0.30 是类别错误)+ 单调性作**方向门**(默认 0.0,非强度门)。exploratory 因子**封顶 Watch**(Tradable 轴全程 NOT_ASSESSED)。一次向量化 IR 喂全部 section(与朴素逐期循环等价性测试锁定)+ 单 PNG dashboard,带**强制 FACTOR DEFINITION 带**——不说明怎么算就不许展示因子。 - - **十一因子(下表 verdict 为当前 v0.9 close-to-close 口径,非各 PR 当时口径;出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` 参照表 + `tmp/Quantitative_Research_Report/factors/ledger.md`)**: +## 当前状态(截面写于 2026-07-30,均为实跑验证) - | PR | 因子 | 家族 | sign | IC | ICIR | 换手 | 净1× | no_book / with_book | - |---|---|---|---|---|---|---|---|---| - | #63 | `jump_amount_corr` | 跳跃×成交额相关 | −1 | −0.0298 | −0.401 | 0.415 | −0.000834 | Watch / Watch | - | #64 | `minute_ideal_amplitude` | 振幅 | −1 | −0.0291 | −0.464 | 0.990 | −0.001981 | Watch / Watch | - | #65 | `amp_marginal_anomaly_vol` | 振幅波动 | **+1** | −0.0412 | −0.425 | 0.285 | −0.000504 | Reject / Reject(documented negative:预注册 +1 未迁移,**实测负 IC ≠ sign=−1**) | - | #66 | `volume_peak_count` | 计数 | +1 | +0.0177 | +0.210 | 0.304 | **+0.000071** | Reject / Reject(符号迁移成功但 ICIR 下界 0.151 < 0.30 预注册门) | - | #67 | `intraday_amp_cut` | 振幅 | −1 | −0.0347 | −0.435 | 0.578 | −0.000260 | **INSUFFICIENT-DATA** / Watch | - | #68 | `peak_interval_kurtosis` | 时序 | +1 | +0.0002 | +0.006 | 0.576 | −0.000463 | Reject / Reject(诚实空结果;研报称 RankIC +7.19%/ICIR 4.63 → 本循环最大复现落差) | - | #69 | `valley_relative_vwap` | 价格比值 | +1 | +0.0327 | **+0.506** | 0.473 | −0.000085 | Watch / Watch | - | #70 | `valley_ridge_vwap_ratio` | 价格比值 | +1 | +0.0339 | +0.425 | **2.101** | −0.001705 | Watch / Watch(对齐单调 CI 下界仅 **+0.0010**,全表最薄) | - | #71 | `ridge_minute_return` | 收益 | −1 | −0.0312 | −0.381 | **2.108** | −0.001983 | **INSUFFICIENT-DATA** / Reject(增量轴与因子簿冗余) | - | #72 | `valley_price_quantile` | 价格位置 | +1 | +0.0265 | +0.414 | 0.679 | **+0.000048** | Watch / Watch(按日单调 +0.1064 全表最高) | - | #73 | `peak_ridge_amount_ratio` | 成交额 | +1 | **+0.0401** | +0.488 | **2.127** | −0.001402 | Watch / Watch(IC 全场最高,零价格含量) | +| 项 | 值 | +|---|---| +| `main` | `c9cc12e`(PR #109) | +| `pytest -p no:warnings` | **2537 passed**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | +| `ruff check .` | clean | +| phase0 锚(`run-phase0 --config config/example.yaml`) | **ic 0.9600 / annual 0.8408**(自 P0 起从未变过,任何改动都不许动它) | +| `qt.exec_baseline_freeze --verify` | **77/77 @ `45c14aa`** | +| `validate-config` | 32/32 | +| 唯一 OPEN 的 PR | #38(历史遗留,保持 OPEN) | + +**当前主线 = 因子层深度重构(设计 v3.2,D0–D7),走到 D5 C5(四腿全量对账)。** 全量跑已完成并暴露五类失败 F1–F5,全部已根因定位并裁定,**修复代码尚未开始**。 + +> **接手必读(唯一入口)**:[`tmp/design/HANDOFF_2026-07-30_claude_code.md`](tmp/design/HANDOFF_2026-07-30_claude_code.md) +> ——含当前截面、F1–F5 裁定速查表、六步路线、派发/评审/限额的具体操作法、陷阱清单。 +> 伴侣文档 `tmp/context/cc_handoff_20260730_d5_c5/HANDOFF.md`(**动手修 F1–F5 前必须整读其 §2**,逐格实测证据在那里)。 +> 设计权威 `tmp/design/factor_refactor_design_v3.md`(红线 #1–#11、四腿对账、D0–D7、实施期修订 A1/A2/A3)。 +> ⚠️ **`tmp/` 被 `.gitignore` 排除**——这三份文档只在本机,不在 git 里。 + +## 常设授权(owner 明确给过,不必逐项再确认) + +- **git 自主推进**(2026-07-27):commit / push / 开 PR / merge 不必逐项确认,推进到重构完成或新指令为止。 +- **14:50 截断**(2026-07-19):全日数据的分钟因子一律截断到 14:50,授权直接改,不必逐因子再问。 +- **C6 退役重生成能力**(2026-07-28):删旧 runner 的同时把 `panel_freeze` / `panel_reconcile` / `hand_anchors_engine_values` 改为 verify-only,D1 基线 frozen-forever。 + +## 工作制度:双轨验收 - - **三条循环级结论**:① **家族图谱**——计数弱正 / 时序归零 / 收益 Reject / **价格比值×2 + 价格位置 + 成交额 = 四个双轴 PASS**;峰岭谷行为学划分本身有效,且 **不止价格信息有效**(PR-M 零价格含量却 IC 最高)。② **IC 强 ≠ 可交易(贯穿证据)**——11 个因子里只有 PR-F(+0.71bp)与 PR-L(+0.48bp)在 1× 费率下净多空为正,且 PR-L 扛不住 2×;毛价差常相近,差距**几乎全部来自换手**(PR-I/L 的 0.47/0.68 vs PR-J/K/M 的 2.1+)。③ **假说证伪记录(不得作为已确立规律引用)**——"秩IC/Pearson 背离比预测单调性"被 PR-M 证伪(3.86×→0.60 与 PR-J 4.22×→0.90 交叉),仅两个极端处有不确定关联。 - - **方法论(本轮建立,已作常设规则)**:① **不可能失败的测试**——三个实例:PR-L 的旗舰反泄漏测试(全截面统一扰动是带截距 OLS 残差不可见的仿射变换)、`compare_postmerge.py`(拿新结果和自己比,见下条 #74 重跑对账)、I5b fixture 给每根 bar 定价使 **VWAP ≡ close**(对"闸门读哪个"结构性失明)。**任何不变性测试都必须有实际跑过的 mutation 证据,且 mutation 要打中所主张的那个具体性质**。② **行为变了而措辞没变**(#76/#78 实证)——正确的修法**不是**"断言不存在第 N+1 处副本"(#76 就是这么做的,用扫描式正则,被七种改写全部绕过),而是 **把主张只写一遍、其余每一处都去组合它**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以**。 -- ✅ **因子评估契约 v0.8 + v0.9**(**PR #74 已 merge 到 `main`**,`1ae555e`):十一个因子在 `analytics/eval/` **严格 no-touch** 下跑完后暴露的两项 frozen 缺陷,本 PR 是**对该 frozen 契约的首次授权修改**。 - - **缺陷一:对齐净价差把成本加了回去(判定门缺陷,非仅展示)**。四处写成 `sign * net`(`net` 已扣成本),`sign=−1` 下展开为 **`−gross + cost`**——因子付的手续费被当利润返还。正确语义 `aligned = sign*gross − cost`(先按假设翻腿、再永远扣成本)。**其中两处在 `verdict.py`(`_base_spread` / `_all_spreads_negative`),直接喂 Tradable 轴 PASS 条件**,历来未触发仅因该轴全程 NOT_ASSESSED。手算实证:`gross 0.000125 / net 1× −0.001983 → cost 0.002108 → aligned −0.002233`(缺陷式给 **+0.001983**)。`sign=+1` 逐比特不变。年化展示值修正:jump_amount_corr **+23.14%→−0.01%**、minute_ideal_amplitude **+64.07%→−0.27%**、intraday_amp_cut **+6.30%→−20.52%**、ridge_minute_return **+64.28%→−43.25%**(**旧值全是虚高**)。 - - **缺陷二:用量级敏感的池化统计量给秩基轴把门**。`monotonicity_spearman` 是"桶序号 vs 跨日等权算术均值"的相关,无界,少数极端收益日集中于某桶即可翻号,而每日封顶的秩 IC 几乎不动。新增 `monotonicity_spearman_by_date`(逐日 Spearman,先封顶 [−1,1] 再平均,与秩 IC 同构)并把门移过去;池化版保留为报告字段 + 回退披露。 - - **v0.9:新统计量被日内噪声衰减,裸 0.0 门落在噪声带内**。实测分位阶梯完美的因子按日只有 **0.045–0.106**,门却仍在裸 0.0 且无离散度估计,两个因子相差 0.021 就分生死。改为读该按日序列的 **N_eff 置信区间**(复用既有 `mean_ci`),三值:对齐 CI 下界 > bar → **HOLDS**;对齐 CI 上界 < 0 → **CONTRADICTED**(FAIL);跨零 → **UNKNOWN**(既不定罪也不放行)。实测 CI 半宽 **0.0291–0.0448** → 裸 0.0 实为一道约 0.035 的隐式门槛。**最要害的路由规则:UNKNOWN 绝不救本就不合格的因子**——单调性已从点信号布尔链中摘出(三值事实塞进 `and` 链必然坍缩),ICIR / NW-t / win / OOS 任一不清关仍走 FAIL,UNKNOWN 只在其余全清关时把预测轴落到 INSUFFICIENT_DATA。 - - **3 处 verdict 变化**(全部归因于门的变化,并如此陈述):`intraday_amp_cut`(no_book) Reject→INSUFFICIENT-DATA(对齐 CI [−0.0353,+0.0343] 跨零)、`intraday_amp_cut`(with_book) **Reject→Watch**(同上 + 增量轴本就 PASS)、`ridge_minute_return`(no_book) Watch→INSUFFICIENT-DATA(对齐 CI [−0.0151,+0.0558] 跨零)。`ridge_minute_return` 跨三版本轨迹 **v0.7 Reject → v0.8 Watch → v0.9 INSUFFICIENT-DATA**:前两次都是在噪声上判决、只是方向不同,第三次才承认判不了。 - - **不变量**:IC / ICIR / NW-t / win / 增量 ICIR / 两个单调点值 **全部逐比特不变**(修复未泄漏到 IC 路径与增量路径)。**路由自检**:`amp_marginal_anomaly_vol`(OOS 符号翻转,第一步即 FAIL)与 `peak_interval_kurtosis`(OOS 不一致 + ICIR 0.006)单调方向均为 UNKNOWN 但仍 Reject,无渗漏。 - - **合并后十一因子干净全量重跑(`main` @ `1ae555e`,11/11 rc=0,~87min)**:`repro_reconcile.py` 11 因子 × 20 字段 = 220 项,**容差 0.0**(精确浮点相等,不用 `isclose`、不留 epsilon —— 1e-6 是"可能动"的下限,用恰好为零来测才能让任何 ≥ 该下限的变化浮出来)→ **0 处差异**;`repro_compare.py` 对 22 个 JSON artifact 做展平叶子深比 → **5,756 叶、0 处不同**,外加 **22/22 Markdown 逐字节相同**。防空过:22 个 JSON 确实被重写(mtime 落在各自 run 窗口 09:24:33→10:43:54)、前后 HEAD 都是 `1ae555e`、11 个 rc=0、全程 `stk_mins_live_calls=0`。 - - ⚠️ **记一次自己的失误:写了一个不可能失败的对账脚本**。第三个脚本 `scratchpad/compare_postmerge.py` 曾被跑并报出结论,**但它是空的,其输出什么也不证明**:它执行 `_baseline_table.py` 取新值,而那个脚本第 123 行**会写** `baseline_rows.json`,随后"旧值"读的正是刚被覆盖的同一个文件 → `old == new` 由构造成立,哪怕 artifact 错得离谱也会打印"11 个全部精确复现"(已在沙箱里用 baseline `ic=1.0` vs fresh `ic=999.0` 复现该假通过)。它还顺带毁掉了 `baseline_rows.json`。**这正是整轮重跑要消灭的失效模式,而我们亲手造了一个**(同 P2-3「曾误以为是粒度」、P3-4「过拟合签名」,照实记录)。上面的结论只站在不碰该文件的两个脚本上(其中一个跑在覆盖之前)。**权威基线是 `scratchpad/baseline_reports/`(22 文件,冻结于 run 前);复核必须比它,不是 `baseline_rows.json`。** -- ✅ **尾盘执行价改 VWAP + 分钟持有期收益复权**(**PR #75 已 merge 到 `main`**):两项改动都源于"14:50 就知道的信号为什么按 15:00 形成的价格计分"。 - - **改动 1:成交价 = 执行 bar 的 VWAP**(`amount/volume`,整分钟成交量加权均价)而非收盘 tick。`IntradayCfg.execution_price_basis: "bar_vwap"|"bar_close"`,默认 `bar_vwap`;`bar_close` **逐比特复现旧行为**——用独立 worktree 跑 main 版本、**字节比对价格矩阵与完整 fills 日志**(含 `exec_time` 与 block `reason`)验证,非断言。VWAP 未定义(volume≤0 / amount≤0 / 非有限)→ 走**显式阻挡**路径,**绝不退回 bar 收盘价、更不退回日收盘**。 - - **闸门改比实际成交价**:涨停分钟只有两种形态——**LOCKED 封死**(全部成交在涨停价 → VWAP = 涨停价)与 **OPENED 盘中打开**(有成交低于涨停 → VWAP < 涨停价),恰好对应"能不能买到";按收盘价判两头都错。这是**有意的行为变化,且被计数不被静默**:`opened_limit_up_minutes()` / `opened_limit_down_minutes()` 报出与收盘价闸门的每一处分歧。真实数据取证:`601858.SH 2023-05-15 14:51`,close = up_limit 46.13、VWAP 46.048583(差 0.18%,远高于舍入量级)。 - - **容差保持 `1e-6`**:实测 13,699 根 14:51 bar(149 根收在限价),LOCKED 偏离 ≤0.0021%、OPENED 0.013–0.017%(两总体不重叠)。1e-6 下 100% 的 OPENED 正确放行,代价是 2.1% 的 LOCKED 被误判为打开(≈全样本 0.02%)。**残差偏乐观且有界**,已写入 docstring。0.001 在本样本双向为 0%,但那是在 149 个观测上重标定阈值 = 后验调参,不为 0.02% 的效应破例。 - - **改动 2:分钟持有期收益复权(先前缺陷)**。`holding_returns` 原在 **raw 未复权价**上算 `exec(exit)/exec(entry)−1`,而日频模型用 qfq 面板 → 两条路径不对称,除权除息的机械性下跌被读成亏损。改为 `(raw_exit·af_exit)/(raw_entry·af_entry)−1`(qfq 锚在比值中相消,无需另存复权序列)。缺 `adj_factor` **不静默按 1.0**(那会把刚去掉的除权偏差放回来),走既有丢弃路径 + `missing_adj_factor_pairs()` 计数。复权**不渗进 I5b 限价闸门**(保持 raw-vs-raw),有专测钉住。 - - **实测发生率(月频调仓)**:I5d CSI500 52,923 个 (symbol, 持有期) 对中 **4,450 (8.41%)** 跨 `adj_factor` 变化,修正均值/中位 **+3.92% / +1.07%**,92.9% 为正;I5e CSI300 26,314 对中 **2,566 (9.75%)**,**+3.74% / +1.31%**,94.5% 为正。均值远高于中位 = 右尾**送转股**(af 翻倍 → raw 价腰斩 → 未复权读成 −50% 假亏损)。 -- ✅ **报告披露修正 + I5f 名义按真实规模重测**(**PR #76 已 merge 到 `main`**,`runtime/` 行为未变):**报告曾谎报自己做的检查**——PR #75 把闸门从 bar 收盘价移到实际成交价,却漏改了两个 report writer 里**描述闸门本身**的那句,此后每次跑出的报告都在描述一个它没有执行的检查。在"绝不静默降级"是常设红线的项目里,报告写错自己的方法比不出报告更糟。 - - **三个一直在算却被丢弃的计数器进报告**:`opened_limit_up_minutes` / `opened_limit_down_minutes`(本闸门与收盘价闸门的**全部分歧集合**——什么能不能成交变了,绝不许静默)、`missing_adj_factor_pairs`(因锚点 `adj_factor` 不可用而丢掉的持有期;非零即已测覆盖率短少)。尾盘报告另行声明收益是复权的并给出恒等式("exec-to-exec"一词已不足以描述它们)。 - - 措辞抽成 `limit_basis_lines()` 并由三个测试钉住;**mutation evidence 实跑**:把措辞改回收盘口径 → 2 个测试 FAIL;从分组表里删掉新计数器 → 第 3 个 FAIL。**旧 I5b 测试为何没抓到**:其 fixture 给每根执行 bar `volume=1.0, amount=close` → **VWAP ≡ close**,使所有断言在两个基准上都成立,对"闸门读哪个"结构性失明;已写进 module docstring 防止重建同一盲点。 - - ⚠️ 本 PR 的守卫办法**随后被证明不够**——见下条 **PR #78**。 -- ✅ **`adj_factor` 下降质量检查**(**PR #77 已 merge 到 `main`**,D3 层新检查,WARNING、report-only):`data/quality/market.py::check_decreasing_adj_factor` 接进 `run_adj_factor_checks`。tushare 的累计复权因子随每次公司行为**只增不减**,`front_adjust` 拿它乘 raw 价,一个发布出来的下降会直穿到每一个下游收益;而真实实例是**手工**发现的(见下条审计)。 - - **阈值 1%(相对,非绝对)由全缓存扫描定**(扫全部 5562 只已缓存票的每一个负步进:7898 个事件、涉及 4479 只票): +- lead 只做调度与统筹(任务书 / 派发 / 裁决 / 合并 / 写进度文档);**实现与评审派不同的 subagent**,评审是**对抗性**的(自写 fixture、自跑 mutation,**不复核对方的自述**)。这套制度累计抓出 **6+ 个真 HIGH**,多数是"测试全绿但结论错"。 +- **验收双轨**:评审裁定 **+ lead 独立重跑 gates**;**绝不采信 subagent 自报的数字**。 +- 评审的裁定只覆盖它实际看到的 commit;后落的 commit 要么补一轮评审,要么在 PR 正文里**显式声明未经评审**。 +- 具体派发形态、限额中断处理、worktree 陷阱见 handoff §4/§5。 - | 阈值 | 触发事件数 | 触发的**非已知缺陷**票数 | - |---|---|---| - | 0.01% | 3653 | 2429(纯浮点噪声) | - | 0.10% | 179 | 2 | - | 0.50% | 178 | 1(`603081.SH`) | - | **1.00%** | **177** | **0** | - | 2.00% | 176 | 0 | +## 方法论(比任何单条代码都值钱) - 低于 1% 会点名状态**判不了**的票:`603081.SH` 最坏步 −0.776%,对应 raw close −1.810%,而真实公司行为需要 +0.782%——有缺陷的**形状**,但在该量级上普通日波动淹没隐含值,价格检验没有功效,结论不可解释。高于 1% 则漏掉一个真事件(1% 触发 177、2% 只触发 176,多出的那个属已知缺陷票)。1% 同时让最小的**已确认**缺陷(`000998.SZ` −2.636%)站在门槛的 2.6× 而非 1.3×——report-only 层宁可误报(代价一行 WARNING)也不可漏报(代价是被污染的价格静默通过)。 - - **相对而非绝对**(factor 跨缓存 ~1…~500):mutation 实证 `pct_change()`→`diff()` 让原六个测试**全部通过**(fixture 都落在 factor ~1–11 两种切法重合处),补两个别的尺度才有牙(`0.200→0.195` 是真 −2.5% 断裂而绝对门看成 −0.005 放行;`500.0→499.97` 是 −0.006% 舍入而绝对门 −0.03 误报)。 - - **本条 WARNING 的语义与 `check_extreme_returns` 不同**:本模块所有 HARD 检查都是零参数的代数不可能(重复键 / 非正 OHLC / high50% 的收盘跳变确实可能是拆股或复牌,而累计复权因子**没有任何合法的变小方式**。severity 反映的是阈值标定风险,不是对主张本身的怀疑。 -- ✅ **闸门描述改单一来源**(**PR #78 已 merge 到 `main`**,`ecd8db2`;#76 的结构性 follow-up):#76 加了一道扫描式正则守卫,并在 commit message 里声称"再多一处错误描述就会失败而不是发出去"。**review 用实测证否了这个声称**:写了七种自然改写("its last print"、"the final tick"、"1-minute" 代替 "1min" 等)跑真正的正则,**七种全部逃逸**——这是拿词法守卫去守语义主张的普遍下场。 - - **修法是结构性的,不是再加一条正则**:`limit_basis_phrase()` 成为**唯一被撰写的那句话**,feasibility 段、分组报告、报告的 Limitations 条目三处**组合它**而非各写各的;`_load_price_limits` 的 docstring 改为指向它而不再复述。本 PR 只改"有多少处可以各自出错"。正则守卫**保留**,但 docstring 缩到它真能做的事:抓**字面 revert**(坏 merge / rebase 把旧句子带回来),而这正是真实发生过的那次失效。 - - ⚠️ **"渲染文本逐字节不变"这个声称一开始是错的**(合并前修掉,`5b4d1ec`):一次**渲染探针**(在 main 与分支上各自构造同样那几行、比较字符串)查出**三个宿主里有两个丢了收尾的破折号**,渲染成 `...on the `bar_vwap` basis to the raw `stk_limit` band` 这样**破折号不成对**的病句——即一次以"没有输出变化"名义发出去的真实输出变化。**review 在探针跑之前恰好问过这个问题**("在一处读起来通顺的片段,换个上下文可能不合语法"),答案是它已经发生了。两处修复后重跑探针:所有组合出的行与 main **字符串完全相同**,并加测试钉住"片段被嵌进句中时,起头的破折号必须在其后被闭合"。 - - **代价面照记**:把一个片段组合进多个句子,是用**"三处可以各自把事实说错"换成"一处可以把标点打错"**——仍是划算的交易,**但它是一笔交易,不是白拿**。 - - **可带走的规则(已写进上面的方法论 ②)**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以。** mutation 证据用的正是 review 自己那批能逃逸的措辞——它们**通过**正则守卫、**失败**于结构性测试(Limitations 条目改写成"the execution minute's final tick"、分组报告改写成"the selected bar's last print",两者都被结构测试判死)。 - - gates(`5b4d1ec` 修复后):`pytest` **1742 passed**、`ruff` clean、phase0 `0.9600/0.8408` 不变。 -- ✅ **日频除权正确性审计**(用户点名最高优先级;调查记录 `tmp/design/FINDING_adj_factor_seam_2026-07-21.md`,全程只读 `artifacts/cache/tushare/v1`,无独立 PR):**结论 GENUINE——日频 qfq 路径本来就是对的,没有 vintage 接缝**;十一因子的 close-to-close 前瞻收益一直是除权正确的。 - - **接缝假设被证否**(双向检验:重述接缝既可能是降也可能是升):CSI500 池 996 票、979 票的 `adj_factor` 来自 >1 次 fetch、960 对相邻交易日跨 vintage 边界,其中因子**真的变化的只有 12 对**——全部为正、全部落在同一个边界(2026-05-29→06-01,A股年度除权高峰首个交易日,12/996 = 1.2% 属正常季节性),且大的都伴随 raw 价格的对应跳变(`002837.SZ` Δaf +30.24% / raw close −27.41% / 隐含 −23.22%)。**任何 fetch 边界上都不存在负向因子变化。** - - **`−57.27%` 是北交所占位符,不是分布尾部**:`920627.BJ` 一只票占全部 −2% 以下 176 行中的 171 行,因子在 `2.3404 ↔ 1.0000` 之间反复跳(175 行正好等于 `1.0000` = "无调整"占位符),其数据来自**单次 fetch**(一次 API 响应内不可能有跨 vintage 接缝),且**连一行 `market_daily` 收盘都没有**(永远进不了价格面板)。`1/(1−0.572723)−1 = +134.04%` —— 最初报的 `+134%` 上尾与 `−57.3%` 下尾是**同一只票的一正一负**。 - - **评估池内暴露:603,258 个 (symbol, date) 对里 1 对**(`000998.SZ` 2023-06-01,PIT 确认的 CSI500 成分:af −2.636% 而 raw close **+1.228%**、当日**开盘 −0.41% 没有任何机械缺口** → 判 **AMBIGUOUS**,倾向 tushare 自身发布缺陷而非缓存 artifact);**CSI300 零**。传播后:单日分位桶均值畸变 0.0208pp、摊到 1,211 个评估日 **0.0000172pp**、多空价差 ≤0.0000344pp——比任何能动 verdict 的量级低四个数量级,**不重述任何因子结果**,只作披露。 - - **污染的均值 vs 干净的均值(独立复测)**:全 CSI500 评估窗口日频 1,154,642 对中 6,906(**0.5981%**)跨因子变化,均值修正 **+2.28%**、中位 **+0.52%**、82% 为正,摊到全样本 **+1.37bp/日 ≈ +3.4%/年**(正是 A股股息率量级)。与最初被污染的测量对照:**发生率(0.604% vs 0.5981%)与中位(+0.45% vs +0.52%)一直是对的,错的只有均值(+4.38% vs +2.28%)与尾部(+134%)**——单个极端污染样本的典型签名(中位稳健、均值不稳健)。**经验:当均值与其中位差一个数量级,先去找那一行坏数据,别先给分布编故事。** - - 与 PR #75 的关系:**那是另一个 bug**——*分钟* `exec_to_exec` 路径用未复权价算持有期收益,日频路径从来不受影响。BSE `920xxx.BJ` 的 `adj_factor` 一律按不可信处理(零成本:无研究 universe 含它们,且它们没有日线价格行)。 -- ✅ **I5d / I5e / I5f 在修正引擎上重跑**(出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md`;**EXPLORATORY,非业绩声明**)。⚠️ **读数前提(不许混谈)**:三个 run 的 NAV 全线上移**全部来自 bug 修复**(把除权除息还回去),**不是业绩改善**;VWAP 成交基准本身是**略微不利**的(−0.18pp)。 - - **I5f 四档归因**(SSE50 2026-03-03→06-12,3 期,`covered=58/58`,`stk_mins_live_calls=0`,每档同窗口实跑、确定性): +1. **每个 mutation 都要先断言它真的改变了目标。** 本项目累计 **9 例"不可能失败的测试"**,其中三次出在同一天同一份工作里,**没有一次是被测试抓住的,全是被"与预期矛盾的观察"抓住的**。评审提出的建议本身也需要 mutation 证据。 +2. **正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以。** 措辞/事实只写一遍(author-once),其余每处都去组合它;扫描守卫只是网,且**守卫的射程必须写进它自己的 docstring**。一个只扫你已经修过的地方的守卫,永远只能确认你已经知道的事。 +3. **失配摧毁意义 → 拦;失配只带来已披露、方向已知的偏 → 记。** +4. **未编目的差异算失败**(设计 §六.5);**性质测试跑不过时修引擎、不改测试**(§六.16)。 +5. **"预期全绿却没绿"时先怀疑预测本身**——已有两次源头是上一份交接的预测写错了,不是引擎 bug。 +6. **从一个样本推总体会造出假警报**;**均值与中位差一个数量级时,先去找那一行坏数据,别先给分布编故事**。 +7. **"报告没说出关于它自己出处的该说的话"与"声明了它没做的检查"是同一类缺陷**——已出现三次。 +8. **卡住就停下上报,别夹带。** 正因如此,一个已发布结论的更正才没被混进重构 PR。 +9. 研究侧:**IC 强 ≠ 可交易**(11 个分钟因子里 1× 费率下净多空为正的只有 2 个,差距几乎全部来自换手);**非业绩声明**是所有因子结论的默认前缀。 - | 引擎配置 | final NAV | delta | - |---|---|---| - | 已验收 I5b/I5f:`bar_close` 成交 + **未复权**收益 | `0.976448` | — | - | `bar_close` 成交 + **复权**收益 | `1.021137` | **+4.47pp** | - | `bar_vwap` 成交 + 复权收益(**新默认**) | `1.019318` | **−0.18pp** | +## 绝不可覆盖的 artifact - 复权修正 ≈ 全部位移(该窗口正跨 A股年度除权高峰,暴露处于季节最大值);**更诚实的成交价反而差 0.18pp**(单根收盘 tick 比整分钟成交量加权价"更走运",方向符合预期)。闸门没有放松任何东西:`opened_limit_up_minutes`=**0**、`opened_limit_down_minutes`=**0**(这正是本闸门与收盘价闸门可能分歧的**全集**,本窗口为空),涨停挡买仍 1 笔、跌停挡卖 0 笔;限价覆盖 174/174;`missing_adj_factor_pairs`=**0**(修正测在全样本上)。 - - **I5f 容量按真实规模重测**:`portfolio_notional` 10,000,000 → **1,000,000 RMB**(用户明示资金上限),`max_participation_rate` 0.05 不变。43 笔 desired trade / 1 笔已被既有 feasibility 挡 / 42 笔有可用 ratio:**below 1.0x 由 20 笔变 0 笔**,ratio min/p10/median/p90 由 `0.122/0.222/1.104/2.730` 变 **`1.217/2.219/11.037/27.296`**。**此前"约半数单分钟 desired trade 容量不足"描述的是一个比本项目会跑的任何组合大一个数量级的规模**;真实规模下单分钟容量在该 universe/窗口**不构成约束**(最紧 1.22×)。这**不取消** partial-fill / volume-cap(可能的 I5g),只是重新界定其动机;仍是诊断,**非可交易性/业绩声明**,不外推到其他 universe / 名义 / 窗口。 - - **I5d(CSI500 五分位,2021-06→2026-05,59 期):原结论不成立**。Q1→Q5 终值 `0.9822/1.0423/1.0495/1.1275/1.1577` → **`1.1758/1.2310/1.2313/1.3090/1.2712`**;**单调 Spearman +1.0000 → +0.9000**、合成 Q5−Q1 累计 **+17.15% → +8.65%**(腰斩)、**Q5 不再是最优组**(Q4 1.3090 > Q5 1.2712)——I5d 的全部主张就是那条完美的 Q1→Q5 排序,它断了。分钟覆盖 892/995 → 900/995;`stk_mins_live_calls=0`;限价覆盖 52836/52836;各组 `returns dropped: no adj_factor` 均为 **0**。 - **四档归因(四件事同时变,实跑中间配置而非猜)**:① 老引擎 + **新缓存**(900 票)Spearman 仍 **+1.0000** → **样本变化被排除**;② `bar_close` 成交 + 闸门不变、**只把分红除回去**即已 Spearman **+0.9000**、价差 **+8.83%** → **复权修正是全部效应**;③ 再换 `bar_vwap` Spearman 不变、价差 −0.18pp → **VWAP 基准近乎惰性**。**即 I5d 的完美单调性实质上是"把除权下跌记成亏损"的产物**:高 MMP 的 Q5 篮子除权暴露低于 Q4,错扣分红反而抬高了 Q5 相对 Q4;把账算对,Q4 就超过它。74 个 (date,symbol) 对收在限价却盘中打开、现在获准成交(opened limit-up 22/14/10/10/18、limit-down 3/2/1/2/4),真实且已披露,但按 ②→③ 不是移动结果的原因。 - - **I5e(CSI300 独立泛化):负结果不变且更强**。Q1→Q5 `0.8809/0.7377/0.9418/0.7573/0.6665` → `1.0919/0.8777/1.1575/0.9223/`**`0.7850`**;**Spearman −0.5000 完全不变**,合成 Q5−Q1 累计 **−26.77% → −31.82%**(更负);Q5 仍是最差组(年化 −4.81%、maxDD −47.79%);覆盖 448→450/481;opened limit-up 21 对(远小于 I5d 的 74)。**这正是该问的问题:若光靠复权修正就能翻转 I5e,那个负结果就有一部分是"把分红记成亏损"的假象。它没翻——泛化失败是因子的属性,不是执行假设的产物。** - - **MMP 合并读数**:唯一的正面证据在修正引擎上**退化**,独立 universe 上的负面证据**加强** → **暂缓 MMP 的理由比过去更强,而不是不变**。 -- ✅ **十一因子改用 14:51 VWAP 的 exec-to-exec 基准**(**PR #79 已 merge 到 `main`**,`b04c966`;⚠️ **合并方式见下条**;数字出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` + PR #79 正文):每个因子**只算一次、在两个基准上各评估一次**;既有 `{stem}_{no,with}_book` artifact 原样不动,新增的是 `{stem}_exec_{no,with}_book`。**why**:分钟因子在 14:50 截止时就已定死,而本框架**能建模的唯一成交**是 14:51 开始的那根 1min bar;按 `close(t+h)/close(t)` 计分等于把一个**本项目模拟不了的收盘集合竞价**算进因子功劳(`closing_call_proxy` 需要 `stk_auction_*`,数据方案不含)。 - - **回归先行**:rebasis 分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 个叶子、0 处不同** → 本次是**加了一个基准,不是移动了一个**(两个目录由两次独立文件读取比对,不经任何共享中间产物——正是上面那次空对账的教训)。 - - **22 格(11 因子 × no_book/with_book)中 20 格不变;移动的 2 格都朝"更没把握"走**: - - `valley_ridge_vwap_ratio`(no_book)Watch → **INSUFFICIENT-DATA**:对齐按日单调 CI `[+0.00100,+0.07110]` → `[−0.00535,+0.06804]`。它的 Watch 本就建立在 +0.0010 这个全表最薄的下界上(**跑之前就已在参照表里标注**),CI 稍宽即跨零 → 方向 UNKNOWN → 预测轴 INSUFFICIENT_DATA。其 with_book 仍是 Watch,但**改由增量轴独扛**(正交 ICIR 下界 +0.232 > 0.15)——同一个标签靠的是不同证据,reasons 里写明。 - - `ridge_minute_return`(with_book)Reject → **INSUFFICIENT-DATA**:close 基准上增量轴 FAIL(正交 ICIR −0.141≈0,与因子簿冗余);exec 基准上点估计 −0.151 方向仍对,但对齐下界 +0.084 够不到 0.15 门 → 既不通过也不否证。**这是把握变弱,不是翻案**:INSUFFICIENT-DATA 的语义是"既没证明也没证否",且它到不了正面主张。 - - **机制只有一个**:点估计几乎全面**变强**(|ICIR| 11 个里 10 个上升;唯一例外 `peak_interval_kurtosis` 0.0058→0.0008,本就是空因子),但 **N_eff 11 个里 9 个下降**(1187.7→1116.0、1009.5→933.3、1122.9→1055.8)——exec-to-exec 的 IC 序列比 close-to-close 带更多自相关,于是每个 CI 都变宽。**更强的点估计落在更宽的区间里**,恰是 v0.9 那道门被造出来裁的情形,而它裁得保守。 - - **新基准的诚实代价(两个分母刻意分开,因为 `1,541` 在两种口径下都会出现)**:**按锚点**(面板网格 1,158,912 个 (date, symbol) 对)——`ok` 1,156,368(99.78%)/ `no_bar` 执行窗口内无 1min bar **1,734(0.150%)**/ `bad_vwap` 有 bar 但 VWAP 未定义 810(0.070%)/ `bad_adj_factor` **0**。**按前瞻收益对**(每因子各自的评估网格)——比 close_to_close 少 3,170–3,274 对(**0.278%–0.283%**),涉及 175–180 只票,`bad_adj_factor` 十一个因子**全为 0**,`stk_mins_live_calls` 全为 0。**VWAP 定价比 bar-close 基准多挡 1,541 对(占收盘基准可测的 1,156,183 的 0.133%),因为那一分钟根本没有成交——这部分排除与流动性相关、有方向、非随机**(被排掉的正是最不流动的分钟):**这是有偏的样本限制,不是舍入损失;百分比小不等于随机,不许当随机报**(每份 exec 报告都带 `coverage_bias_bad_vwap` 原话,且 sanity 报告把这句话放在覆盖率表**之上**,使读者不会先看到百分比;有测试钉住措辞)。N_eff 在 9/11 上缩水是更真实基准**付出的统计功效代价**,是真实借方而非记账层面的假象。 - - **VWAP 与 bar close 在无条件分布层面几乎同一序列**:均值差 **+1.0e-07**(SE 1.3e-06,t = 0.074)、中位差 0、逐日相关中位 **0.998** → 改基准买到的是**执行锚点的真实性,不是另一个信号**,与 I5d 归因(VWAP 什么都没动、复权动了全部)一致。**close-to-close 结果保留作并排对照**,exec_to_exec 为操作基准。 - - **正确性验收(CSI500 全规模,996 票 / 1,210 日,cache-only,十个日频/universe gap-fetch 计数器全 0)**:① 与 close_to_close 的逐日截面相关中位 **0.9890**(p10 0.9821 / p90 0.9933,Spearman 中位 0.9814),下限 0.90 → PASS(**低于下限时 builder 直接 raise 而不是继续跑**);② 量级同阶(exec mean 0.000429 / std 0.027718 vs c2c 0.000451 / 0.028050);③ **5 行从 raw 1min parquet 用普通算术手算复核**(不 import 任何执行层 helper,故不会继承其 bug),最坏绝对差 **0.0 精确**;④ 除权持有期两种口径并列(如 `000564.SZ` 2021-12-29→12-31:未复权 −0.598581 → 复权 **−0.045367**)。gates(PR 正文):`pytest` **1774 passed**(= 当时 `main` 的 1740 + 34,恰为两个新测试文件、0 个既有测试被挤掉)、`ruff` clean、phase0 `0.9600/0.8408`、31 配置、secret scan 0。 - - ⚠️ **它是怎么合并的:派出的 code review 约 1.7 小时未返回,本 PR 靠自查而非评审通过而合并**(PR 正文下有一条 comment 如实说明,以免日后有人以为它被评审签过字)。**合并前独立核过的**:① 既有路径没动——分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 叶、0 处不同**(两个目录分别读取、无共享中间产物),与 diff **+2595/−0 纯新增**一致;② **`h` 走的是因子自己的评估网格而非价格面板的行**(`exec_forward_returns` 先 `isin(dates)` 再 `groupby(symbol).shift(-h)`,正是 `analytics/eval/ir.py::_resolve_forward_returns` 标注为 close 路径隐患的那条性质;非正分母掩成 NaN);③ 因子信息集**无前视**(日 d 的入场锚是 d 当天 14:51 的执行 bar,因子 14:50 已知,因果成立);④ 共享 artifact 的 **cache key 完整**(含 schema 版本 / universe 类型 + 指数代码 / 起止 / 缓存根 / `price_adjust` / 全部执行参数 / **精确 symbol 集合与精确日期网格各自的 sha1**)——陈旧 artifact 无法被喂给另一份配置,那正是"悄悄拿错收益给一族因子打分"的失效模式;⑤ 覆盖率成因**按构造互斥划分**(先归入场锚、否则归出场锚)。**未独立核、因此仍对后续检查开放的**:五条 mutation 声称(只读了、未重跑)、十一个 runner 改动是否机械等同、`EvalContext.execution_capacity` 透传是否在作者自带测试之外仍保持 Tradable 轴 `NOT_ASSESSED`。**若日后评审在这个未核集合里查出问题,要重述的是 exec 基准的数字——不是 close-to-close,后者未被触碰且仍是已验证的参照。** -- ✅ **闸门/成交价描述全仓清扫**(**PR #82 已 merge 到 `main`**,`f432dc5`;#78 的 review follow-up):#78 的守卫**只扫它刚修过的那两个 report 模块**,于是同一缺陷的**六处存活实例原则上就够不到**——`qt/config.py:343` 加 **五个 I5b/c/d/e/f config YAML**。后者是操作者启用 `price_limit_check` 前会读的文件,**读者层级高于 docstring**。 - - **教训(本条是全轮最值得留下的一条)**:**一个只扫你已经修过的文件的守卫,永远只能确认你已经知道的事。** 把扫描扩到整个特性面后**立刻又抓出六处**——不是同一个声明,是同一次 PR #75 行为变更留下的另一批:`执行价 = 窗口内第一根有效 1min **收盘价**`(`qt/intraday_tail_framework.py` module docstring + I5a/I5b/I5f config),而 #75 之后成交价是 bar VWAP。**共 12 处:7 处描述闸门,5 处描述成交价。** - - **两处因导入环无法组合短语,改为指针并写明原因**:`runtime/backtest/event_models.py` 与 `qt/config.py` 都在 `qt.intraday_tail_framework` 的**上游**(是它 import 这两个),反向 import 必成环 → 不再复述,改指向 `limit_basis_phrase`,扫描作为这两处的兜底。`event_models.py` 的 docstring 明写这一点,免得下一个人再去试那个 import。 - - **刻意不扫、逐条核过的**:`RUNBOOK.md` / `CLAUDE.md` 的「涨跌停用未复权 raw close 比 `stk_limit`」是**日频** universe 可交易性过滤(`universe/filters.py` + `data/clean/tradability.py`),另一套更老的机制,本来就该比日收盘——**粗心的 sed 会把真话改成假话**。 - - **守卫的射程如实写进它自己的 docstring**:它抓的是"close"写在"execution minute"附近,**抓不住换词改写**(review 实测七种逃逸)。**组合(author once)才是真答案,扫描只是导入环挡住组合的那两处下面的网。** review 独立复核:对 merge-base 与本 PR tip 各跑一遍 → **修前 12 命中 / 修后 0 命中,行号逐一对上**;另用更宽松模式扫全仓 `.py/.yaml/.md` 共 54 处命中**逐条读过**,无新增真缺陷。 - - **顺手把 #79 的 `qt/exec_basis_*.py` / `qt/exec_forward_returns.py` 提前纳入扫描**(今天是对的,但同属"执行基准"领地)——等它们变陈旧再加,就是把刚吃过的亏再吃一遍。 -- ✅ **因子层深度重构 —— 设计定稿 v3.2 + D0 纸面契约**(**PR #84 已 merge 到 `main`** @ `703bd3e`;重构工作流启动,迁移计划 D0–D7):全面重构因子层(registry/requires/两视图/store/统一 eval runner/服务层),设计经三轮迭代 + 对抗性 agent-team 审查定稿。 - - **五项拍板决策**(2026-07-23 用户逐项确认):① 全线深度(bitwise 验收退役 → 语义对账四腿;旧 demo 锚保到 D1 末 → D2 冻 provisional → D6 转正);② 运营形态 = **日频 14:50 决策 / 14:51 执行**(当前侧重投研;实时 = 决策点批处理 + 同一 cache,不建流式引擎);③ 存量机器全部迁移(真实目标 = 杀第二条因子取数路径,编排/分析逻辑留代码);④ 收官全量重评估 **只跑 exec**(close 口径从评估契约退役,decision-view 因子簿关闭 with_book close(d) 混用活缺陷);⑤ 爆炸半径 = 因子侧全重写,data 层与 runtime 事件引擎/fills 与 `analytics/eval` 统计裁决核**保留**。设计文档 `tmp/design/factor_refactor_design_v3.md`(v3.2,SER8 同步)。 - - **对抗性审查**(8 角色 Opus 4.8 max effort + 20 kill agent,~3.07M tokens):53 发现,20 HIGH/CRIT 经交叉证伪 **1 存活 / 6 削弱 / 13 被杀**;24 条修订 R1–R25 折入 v3.2(综合报告 `tmp/design/review_round3_v32_revision_list.md`,含被杀档案**禁翻烧饼**)。唯一完整存活的 HIGH 是设计文档自身的事实错误:「峰/岭/谷分类 ≥5 处重复」与代码相反——实为 author-once 单点定义(`intraday_volume_prv.py::peak_mask_for_symbol`,6 模块 import 复用),已更正并撤回 #78 援引;primitives 层理由重述为 surface 蔓延 + 延迟承重(实测 11 因子朴素逐读 ≈58s vs 共享读 ≈17.6s)。审查另发现当前 `main` 潜在缺陷:**回测 `_build_cache` 不传 fina 400 天 `recent_tail_overrides`**(仅 data-update 路径有),晚披露财务在纯回测 read-through 下可能漏抓——修复排入 D3 前置。 - - **D0(PR #84,纯附加 +1148/−0,无引擎代码)**:`data/availability_policy.py`(§1.3 policy 表代码常量单一来源:**字段级 market_daily**——decision 视图仅 `open` 当日合法;3 处 DOMAIN ASSUMPTION 显式标注;修订视界从调用方传入的 config 值派生、**禁硬编码 14/400**、fina 缺 override 即 raise;端点封闭性 `require_known_source`;视图×基准配对合法性;`adjustment`/`overnight_boundary` 三值枚举单一来源)+ 30 测试(含对 `data/cache` 端点常量的真 drift guard、frozen 真赋值断言、bool-混入-int 守卫的 mutation 有牙实证)+ `docs/factors/refactor_d0_contract.md`(收官 14 因子 adjustment/overnight_boundary **预指派表**带 file:line 证据——9 RETURNS_INVARIANT / 5 NONE / 0 PRICE_LEVEL,`valley_price_quantile` = CROSSED_DISCLOSED,另发现 `minute_ideal_amplitude` 以 raw close 跨日池化排序 → CROSSED_DISCLOSED 候选、偏差测量义务留 D2;11 类性质**双向编目**,107 个既有测试引用逐一核实存在,NET-NEW 性质钉 D 格:adjustment→D3、overnight_boundary→D2、单点填≡批量填→D4、视图隔离→D1/D3/D4)。 - - **D1(PR #86 + #87,整步已关账)**:① registry + requires(PR #86,+1751/−69)——`factors/registry/`(register/build/requirements + 显式 builtin 列表 + 三时点命名校验)、`PanelField`(endpoint-only,构造期 `require_known_source`)、FactorSpec **契约 v1.0**(三个必填声明维 requires/adjustment/overnight_boundary,值域引用 D0 枚举;既有 metaclass 双检/预注册/property-spec 全不削弱)、18 个 Factor 类全部落声明(14 收官因子照 D0 表逐行、21 行参数化测试锁死;日频因子逐类带证据派生)、`qt/pipeline._build_factors` if/elif 链退役为纯 `registry.build()`。**因子数学零改动三重证**:compute 文件删除行仅 4 条相同 import / 评审逐 hunk 扫 `def compute` 零命中 / phase0 锚逐字不变。评审 APPROVE-WITH-NITS(1 MEDIUM 经裁定为 diff 基线假象——分支切在 #85 之前,三点 diff 证实未碰 AGENTS/CLAUDE;1 LOW 前瞻记录)。两处披露的行为差异:unknown 因子名提前到 config 解析期报错;旧链对每因子 eager 算 `int(params.get("window",20))` 的 accidental ValueError 不再复现(实证无依赖)。gates:1879 passed / ruff / 31 configs / 锚不变 / secret 0。 - - ② **面板冻结**(PR #87,v3.2 §五第 4 腿/R11,D5 逐格对账的唯一基线):`qt/panel_freeze.py`(**零公式重抄**——分钟因子调各 runner 私有 `_load_*_panel`、常数从 runner 模块对象引用、书因子走 `_build_book_factors`;canonical content hash = sha256(排序索引串行化+float64 原始字节),NaN payload 归一、±0.0 保留区分;原子写;`--resume` 从严——读回文件也重过 process+对账+重算哈希)+ 23 测试(NaN payload mutation 证据、写中途失败原子性实测)+ manifest 入 git(producing SHA 钉 `3669c90`——**先跑后 commit** 使 run 时 tracked 树逐位 = main;provenance 规则原文在案)。真实 run:14/14 面板(网格 1,158,912/1,159,263 行,CSI500 996 成分 1210 交易日,2.79 亿行 raw 1min),`stk_mins_live_calls=0`,**写盘前对账 11/11**(mismatch 即 raise 不落盘;书因子无覆盖字段可对账、照实披露不假装,证据换双跑+跨 run 同哈希+全网格行数恒等),确定性双跑 3/3 跨进程一致;两段 run(会话限额 60min 杀首段 → resume)与首段日志丢失(`_make_logger` truncate)均如实披露。评审 **APPROVE 零发现(含 LOW,本轮首次)**:逐参数镜像比对调用点、14/14 行 doc↔json 程序化核对、3 面板盘上重算哈希、独立 secret 扫描 17 文件 0 命中。gates:**1902 passed** / ruff / 锚不变 / 31 configs。 - - 验收:独立对抗评审 **APPROVE-WITH-NITS**(0 CRIT/HIGH/MED,1 LOW 当场修 + mutation 证据 rc=1/rc=0);lead 独立重跑 gates:pytest **1806 passed**(= 1776 + 30)、`ruff` clean、`validate-config` 31/31、phase0 锚 **0.9600/0.8408 不变**、diff secret scan 0。执行流程:实现与验收均由 subagent 承担、lead 把控(含 5h 窗 <10% 不开新 subagent 的节流规则)。 - - **D2(PR #89,整步已关账——首个动数学步骤,实测逐位一致)**:`factors/ops/` 7 算子(momentum/volatility/liquidity/overnight_mom 改写其上,naive-oracle 等值锁定)+ `factors/compute/minute/`(primitives 吸收 `peak_mask_for_symbol` 单点定义 + 11 因子一因子一文件,数学+surface+声明同文件)+ R14 粒度 shim 化(10 纯模块 + intraday_derived AST 锁纯度;aggregate 通用核留真实代码;共享常量安家 intraday_schema——live import 测试证留 aggregate 成真环)。**验收三条独立路径互证**:① 逐格对账 vs D1 冻结基线 **14/14 `max_rel_diff=0.0`、canonical hash 全等(逐位一致)**;② 88 行分层手算锚 0 失配(脚本运行时守卫禁 import 引擎,曾自抓 3 个口径错);③ mutation 证据实跑 rc=1/rc=0 三组(评审活体复跑其二)。I5 四 run 值级锚全命中(I5b/I5f 1.019318;I5d Spearman +0.9;I5e −0.5)。PR-D 测量债清偿(4.58% 窗含真 ex-date,评审从冻结 manifest 独立复算,维持 CROSSED_DISCLOSED)。provisional 锚全精度值入测试(PROVISIONAL,D6 转正)。评审 APPROVE-WITH-NITS(1 LOW:guard_boundary 静默跳过无机器可读披露——当场修,skip 显式落 JSON)。留 D6 两注记:shim 期 aggregate→factors.minute 方向(披露的临时例外,D6d 日落);`asof_daily_features` 的 mmp_ew hook 届时需新家。gates:**1955 passed** / ruff / 31 configs / phase0 锚不变 / secret 0。 - - **D3(PR #91,整步已关账——store 层,纯附加未接线)**:`factors/store/`(hashing/code_hash/fingerprint/keys/values/run_registry/incremental,全 <800 行)+ **R5 前置修复**(`fina_tail_days` 单一真相迁至 `data.cache.fina_tail_days`,回测 `_build_cache` 与 data-update 两 builder 统一传 fina 400 尾 override——回测 read-through 现会补 fina 尾窗,行为变更已披露;`data_update.fina_tail_days` 删除对 repo 外旧 YAML 属 breaking,仓内两 config 已迁)。key = params_hash + code_hash(自身模块 + 枚举共享集{minute primitives/ops/base/spec} + **one-hop 直接项目内 import 折叠**:AST 自动派生、无手工清单、不做传递闭包——评审 MEDIUM 实测 `reversal_20` 可 build 且值=−momentum 而 momentum.py 取值面不入 key = 陈旧复用洞,据此修;改 momentum → candidates 系 key 变、financial/分钟系不变,无全局过度失效)+ view + 数据指纹(schema-version 维 = data/clean PIT 承重模块内容 hash;`price_level` 另掺 per-symbol adj_factor 事件表 hash 含锚值,收官集零成员不豁免 fixture 测试)。存储:单文件 parquet date 排序 / 原子写 tmp+`os.replace` / per-artifact O_CREAT|O_EXCL 锁(两进程 gap-fill 零丢行实测;锁 neuter mutation 证明锁对原子写也承重)/ JSONL append-only 运行注册表(no-secret:sanitize_text 过自由文本,redaction 测试钉住)/ 损坏文件=miss / **不建 coverage ledger**(值确定性可重算)/ 启动一致性断言(registry 条目必须命中代码注册表)。tail-recompute:`w_overlap=max(W_dep, 端点修订视界)`——W_dep=传递 lookback 深度经 **`FactorSpec.lookback_depth`(optional 附加字段,contract v1.1)**声明、store 消费点 HARD-REQUIRE(14 真实因子未声明 → loud 报错,声明属预注册动作留 D4);视界经 `revision_horizon()` 从 live config 派生(调大 refresh_recent_days → 重叠窗变宽 mutation 实证,无硬编码 14/400);重叠 NaN-aware bitwise,失配按 adjustment 分类(returns_invariant→loud 上游回改记录 / price_level→预期);schema 指纹失配 → schema_void_full 全量重算不误报 revision;§六.18 teeth(headline 深度欠采 → 假 mismatch)测试钉住。#adjustment NET-NEW 性质测试钉本格:扰动 qfq 锚 → returns_invariant 真因子(日频 volatility_20 + 分钟 valley_relative_vwap)逐 bit 不变,批量 census 9/5/0 ≡ D0 表。评审 APPROVE-WITH-NITS(0 CRIT/HIGH,1 MEDIUM+3 LOW+1 NIT;MEDIUM/LOW-1/LOW-3 合并前修于 `fd906f4` 并经 delta 复核三项 CLOSED——评审亲手重跑 momentum on-disk mutation 5/5、allowlist 注入、锁 neuter,并猎杀「不可能失败的测试」零发现)。留 D4 注记:materializer 必须传 live `data.cache.fina_tail_days` 勿靠 400 默认;two-hop 组合链是 one-hop 折叠的已知边界(今日无实例,设计明令不做传递遍历器);`availability_policy` 编辑会 over-invalidate 其 importer 的 store(安全方向、D4/D5 churn 期真实重算成本);单点填≡批量填(P8)是 D4 验收项。gates:**2031 passed**(=1955+76)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 - - **D4(PR #93,整步已关账——FactorService + materializer;四轮评审揪出两个 HIGH + 一项设计修正)**:`factors/service.py`(frozen `DecisionPoint` + `cross_section`/`panel` **都 read-through 读 D3 store**,miss 触发唯一引擎,配对合法性复用 D0 `require_legal_pairing` 构造期强制)+ decision-view materializer(R18 **全** policy-lag:日线非-open ≤ d−1、**open[d] 字段级例外从 policy 表派生**、daily_basic/fina ≤ d−1、分钟 ≤14:50;ex-date 只暴露**布尔**的类型边界;`CacheHorizonConfig` 从 live config 构造缺属性即 raise)+ process 编排下沉 `factors/process/orchestrate.py`(`_process_factors` 薄委托逐字行为保持)+ 14 因子 `lookback_depth` 预注册(参数化派生+测试钉死)+ 热路径计时 smoke(真实缓存 cache-only 40 票共享读 ≈2.0x,诚实标注不外推)。**评审故事(P8 单点填≡批量填连揪三层真缺陷)**:①【设计修正】valid-day **pooled** 因子(8/11 分钟因子按滚动机制码检划分,非按观察到分歧的 3 个——#82 教训)trailing N 个**有效日**的日历深度数据依赖、**无界** → **证否 v3.2 §3.3 R4「W_dep=各 lookback 之和」假设**,固定 40 日 trim 使单点填 vs 批量填 finite↔NaN 分歧;②首版不动点判据(值稳定+nbars 稳定)被评审构造 **>1 chunk 停牌 gap 反例**击穿(finite-vs-finite 静默错值)→ 改**结构性判据**(锁定单调性:丢窗口头部 baseline_days 交易日其余分类 FINAL;锁定区 output-date 计数 ≥ lookback_days,gap-immune)+ provider 显式 `earliest_available`(nbars 推断删除,中段 gap 不许当 data start),上版「divergence is gone」overclaim 在 commit message 开篇更正;③判据循环只遍历**有输出的** symbol → 长停牌票**无否决权被静默丢出截面**(撞 I5a 红线)→ 改遍历**请求的** symbol 列表,评审 fixture(600519 丢名复现 + 锁定偏移 416.0 vs 438.0 静默错值)全部落成 committed 测试 + 源码级 mutation 双 witness(rc=1/rc=0,评审亲手用自己未改动的脚本独立复跑确认)。`_MAX_SATURATION_CHUNKS` 耗尽改 loud raise(红线 #9)+ raise 分支有测试。**代价如实入 docstring**:凑不满 lookback 有效日的票拖全 universe 扩到声明 floor;per-symbol 饱和起点是 D5 优化**且非纯优化**(`intraday_amp_cut` 截面 z-score 耦合)。**早期区域量化(D5 面板腿归因名目)**:分钟缓存真实起点 **2015-01-05** → D1 冻结基线(锚 2021-07-01)对 pooled 因子属 **anchor 截断**,饱和加载在 2021-07→10 五票上 ridge 有 157/184 cell 不同(79 NaN→finite,按月递减至零)——D5 对账须具名归因「saturation vs anchor truncation」。JC1 已裁定:P8 逐 bit 对滚动因子降为 ≤1e-12 可归因浮点重排(实测 floor ~3.5e-18,真缺行效应 ≥7 个数量级于容差;NaN mask 必须精确)。留 D5/D6:声明 floor 应从分钟 coverage ledger 派生 per-symbol(现为硬编码常量,声明偏晚会静默截断);`valley_price_quantile` 分钟绑定 DEFERRED(loud 报错,已声明 pooled,D5 绑定继承饱和路径**不许**固定 trim 绕过);零输出否决语义已被测试+mutation 钉住,D5 为性能回退它=放回 HIGH;blanket shift 依赖「因子输入面板只含端点列」的 D6 wiring 契约。gates:**2098 passed**(=2031+67)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 - - **D4b(PR #97,实施期修订 A1 新插入的迁移步——per-symbol 流式物化;改因子取值路径,自带四腿验收)**:materializer 改「逐票读 → 立刻聚合到日频 → 丢弃」,**切口落在截面 combine 之前**。**规模(真实评估面 CSI500 995 票 × 5 年,cache-only `live_calls=0`)**:名义最大单帧 **122.2 GB → 0.066 GB**、峰值 RSS **~115 GB → 0.964 GB**(可用 56 GB,占 1.7%);三种加载几何各跑一因子(pooled×截面 / pooled / bounded)全部 995/995 票、**每次请求 1 票**;峰值稳定 0.73–0.96 GB,**由「一票 bars + 日频面板」决定而非 universe 决定**;扩到 floor 的代价只落在那 84 只 late 票(40 次调用/14.95s),不再拖着 911 只陪跑(dense 票 1.0 次/0.59s)。**四腿**:① D4 既有 31 测试原样通过(P8 四 regime / 锁定偏移 / 零输出否决 / exhaustion loud raise),评审证其**真在测新路径**(单文件即覆盖 `materialize.py` 86%,per-symbol helper 体均被覆盖);② 手算锚 18 行**零项目 import**(运行时守卫断言 `sys.modules` 无 factors/qt/data/runtime/alpha,653 模块 0 命中),`volume_peak_count` 9/9 精确 0.0、`intraday_amp_cut` ≤1.998e-15;③ 逐格对账真实缓存 40 票×6 月 vs 整 universe 单帧(3,711,400 行):10/10 index 相同、**NaN 集合差异全 0**、5 个逐位相同、最差残差 3.275e-14,`intraday_amp_cut` 在 **4,720** 个真实 cell 上对上(评审以 **main 真引擎**为参照复跑得 **max|abs|=0.000e+00**,比文档的 2.5e-14 更干净——因两侧加载几何相同,已写进文档);④ 规模证明如上,且 `--mode reconcile` 使 §6.1 那张最承重的表**可从仓库复跑**(评审亲跑:10 行×4 列与文档逐格一致,且**非空对账**——参照侧完全不经 `materialize_range`,只在被检验的加载几何上不同)。**评审带来的头号反转**:实现方把 per-symbol trim 描述为「更保守的语义细化」,评审证明它在**修 D4 的真缺陷**——同一稀疏 fixture 跨引擎对照,3 个 bounded 因子各动 1 cell,**三个都是 D4b == 几何无关真值、D4 ≠ 真值**(`jump` 0.0901 vs 0.1571;另两个 D4 给 **NaN** 而 D4b 给正确 finite);union trim 给稀疏票喂的自有交易日不足 `lookback_depth` = 红线 #6 的 load-geometry 依赖。且四个旧 runner **逐票读全窗口、根本没有 union trim** ⇒ per-symbol trim 是**朝 D1 冻结基线靠拢**——实现方给 D5 的预警「稀疏票对不上先看这里」**方向反了**(反在安全侧),已订正为指向饱和深度/anchor 截断。超集性质经评审证严(子集第 k 大 ≤ 超集第 k 大),反例构造失败。**MED-1(D4b 新引入的唯一真引擎缺陷)**:请求含重复 symbol 时逐票流式会独立处理每个重复项——48→52 行、4 个重复 (date,symbol),`intraday_amp_cut` 更因重复票**两次进入截面**而 **48/48 共享 cell 全部变值**(D4 免疫是靠 provider 的 `isin` 幂等吸收)→ 改**保序去重**(而非 loud raise:去重**精确复现单帧引擎对同一调用的返回**、保持「只改内存剖面」,raise 会新拒绝前一引擎接受过的输入);评审跨引擎复核修后 0 差异。**另两条「说法与事实相反」**:MED-2 一处 docstring 称旧 mutation 写法「cannot fail」,评审原样跑在 D4b 上是 **FAIL**——它其实是 **cannot pass**(性质保住、只换写法,属 §六.16 而非偷改测试;计数 70→**72**=70 单点填+2 批量填);MED-3 可行性文档声明了一条「本步绝不允许 finite↔NaN 翻转」的政策,而 per-symbol trim **能且实测确实产生**(3 个 bounded 里 2 个 NaN→finite),40 票样本只是没触发 → 改为如实陈述。**lead 合并前补的闭环**:派生 id 列表只守住了「加了因子却没进对账」一个方向;评审实测另一方向——把某因子从**两张 binding 表都删掉**,该文件 53 passed、全量 2169 passed **全绿**,因子从每条 parametrize 里静默消失。故新增闭包测试,真相源改为**`factors/compute/minute` 下定义的全部 `Factor` 子类**,未分类即红(mutation witness:删 `PeakIntervalKurtosisFactor` occurrences 4→0 先断言落地 → 转红;还原后 `binding.py` 逐字节相同、58 passed)。**A1 点名的致命陷阱未踩**:`factors/service.py` diff **0 行**(不许在 service 层按 symbol 分批——`_ensure_coverage` 只按日期判缺口会静默丢名)。gates:**2174 passed / 1 skipped**(= 2117 + 57,**node-ID 集合证 0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 head 未变 / 冻结面板 mtime 未变。留 D5:扩张步长(late 票 40 次重读,纯性能但仍是加载几何改动、欠自己的对账,**刻意没夹带**);声明 floor 仍是 universe 级硬编码常量未按票从 coverage ledger 派生;`valley_price_quantile` 仍 DEFERRED,D5 绑定**继承流式路径不许固定 trim 绕过**;10 个因子只跑了 3 个的全规模证明(几何全覆盖,评审证未测因子的加载参数与已测者相同或被支配);日频路径仍整 universe 单帧(995×1210 行,量级小,未动)。 - - **D5b(PR #99,D5 后四块只交付了 C3——**C4 中止、C5/C6 未做、什么也没删**;中止本身是本步最重要的产出)**:**发现并(两方独立)确认一个真实前视缺陷**:`jump_amount_corr_20` 是十一个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 **0**,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它、全程无补救。**exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP**,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测(两方各用不同票/不同窗口,前置断言两腿齐全):实现方 12 票/2021H2 **1,477/1,477 格全移动**(max|diff| 1.276)、其余 9 个 bound 因子 **0 格**;评审另 12 票/2023H1 **1,373/1,373**(max|diff| 1.054)、**其余全部 11 条 0 格**(并补测了实现方漏掉的 `valley_price_quantile_stats` 与 `mmp_ew`);评审再加一刀**只扰动 `bar_end >= 14:51`**(执行 bar 本身及其后)→ **1,363/1,363 移动**——**信号看到了自己成交的那一分钟及其后 9 分钟**。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`/`return_basis="exec_to_exec"`/`execution_window="[14:51:00,14:56:59]"`,verdict **Watch**、predictive 轴 **PASS**(|NW-t| 15.19)——**报告声明了它没有执行的检查**。**既有测试为何没抓到**:`test_pit_perturbing_future_bars_does_not_change_factor_at_d` 守的是**跨日**边界,其 fixture session 只有 09:31–09:36 六根 bar、**根本不存在 14:50 边界**;日内边界只在 **PR #79** 把入场锚从 `close(d)` 移到 `14:51(d)` 之后才承重,而 #79 评审未返回、这**恰在其「未独立核」清单射程内**。**裁定(设计文档实施期修订 A3)**:用户 2026-07-19 常设授权明写「全日数据因子一律截断到 14:50……授权直接截断,不必逐因子再问」,而该因子**建于此授权之前** ⇒ 加截断是**补用既有授权**非新研究决策;**但评审的程序性修正已采纳**——从重构视角它仍是定义变更、而授权来自研究侧不来自重构 ⇒ **必须单独成 PR、显式标注 correctness fix,不许进 D5 任何 commit**(已另派)。幅度实测 pearson **r=0.9993**、mean|diff| 0.0042——**缺陷是结构性的必须撤回,但修正代价大概率很小;verdict 存活与否必须由真正的重跑给出、不许由相关系数推定**。**交付的 C3(评估契约 v1.0)**:`EvalConfig.view`/`return_basis` 构造期经 D0 `require_legal_pairing` 强制配对;provenance 增 requires/adjustment/overnight_boundary/lookback_depth + `eval_contract_version`;跨口径汇总**缺 view/basis 列拒渲染**(声明了列却留空同样拒);**判定门一个数没动**(评审证 6 个文件 md5 与 main 逐字节相同,8 个门值逐值钉死)。**评审两轮 APPROVE-WITH-NITS,2 MEDIUM 全关**:**MEDIUM-1「守卫守不住自己」**——声称钉住 identity 修复的测试是对**函数源码做子串匹配**,评审把赋值掏空、两个 token 留**注释**里 → 每份 exec artifact 都会继承调用方 close 身份而实际按 exec 收益打分,**52 passed 全仓无一抓住**(#78 教训的翻版,且放行的恰是同一 commit 刚修的缺陷)→ 抽 `exec_identity()` 改**行为断言**,评审原样重跑逃逸 **4 failed**,并验了**假阳性**方向(重构进 helper 不误报,58 passed)。**MEDIUM-2「假声明上升一层」**——exec 路径无条件声明 `view=decision`,但 with_book 的因子簿是 close(d)+当晚 daily_basic(**§1.1 自己列为「已经咬过人的活缺陷」**),且 jump 连 no_book 格都不成立;而该字段定义是「**因子值**取自哪个信息集」,with_book 有**两个**信息集、一个字段表达不了 → **这正是该字段被造出来防的那种缺陷、只是上升一层**。**现在修免费**(还没 artifact 带它),C5 一跑就写进 22 份。修法:**先拆两个事实**(②单独不成立——登记例外并不能让 `view=decision` 在 with_book 上变成真话)→ 新增 `EvalConfig.book_view`(唯一知情方声明)+ `subject_view(factor_id)` **从事实派生**(deny list `NOT_DECISION_CUTOFF_SAFE` 放**因子侧**——「它是关于**值**的事实」,由可见性测试**测量**而非声明,本地副本已删)。**刻意不校验 `book_view`×basis 配对**,评审的裁定值得作通则记下:**失配摧毁意义 → 拦;失配只带来已披露、方向已知的偏 → 记**(close-view 书是 Incremental 轴的控制变量、偏的方向已预注册;拦它不会让信息集不混,只会让 artifact 说不出它混了)。**并落一个有意的响亮阻断**:jump 现在派生出非法配对 → 其 exec 评估 loud raise(截断修正落地即解除,删 deny list 一条)——在它被修正前**不存在**它的诚实 exec artifact,阻断优于假声明(红线 #9)。**实现方又一次自查出同形缺口**(单测说不了调用方怎么接线:with_book 传 `book_view=None` → **28 测试全绿**)并照实上报、补端到端断言读**两份实际写出的 JSON**。**另三个前提问题已裁定并立项**:(a) 3/11 因子的覆盖率披露无法由 store 供给(每次披露必重算,已写成行为并测试锁定);(b) `valley_price_quantile` 会被 materializer 二次滞后成 close_{d-2},评审实测第三条路**逐比特等价**(`max|diff|=0.000e+00`、`Series.equals=True`,破坏对照 7.3e-2 证非空过)⇒ 属实现变更可授权,落地须**两侧都钉**等价性测试 + 左端多一天历史;(c) **store key 无 universe 维(存量 CRITICAL,对 D6/D7)**——评审拆成**两个独立失效模式**:① `_ensure_coverage` 缺口判定**只按日期不按 (date,symbol)** ⇒ 第二批「无缺口」直接返回、那批票**静默从截面消失**,影响**全部 11 因子**(**讽刺**:A1 否决「按票分批」出路的理由一字不差就是这条,**红线写在文档里而服务层身上就带着它**);② `intraday_amp_cut` 的 z-score 在物化期发生 ⇒ 存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、24 行而非 48、存活 24 格全错** max|Δ| 0.159–0.186)。**「给 key 加 universe 维」被否**(过度失效 10 个 universe 无关因子;**PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来**;跨指数存储爆炸),改为 **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(`MinuteStreamBinding` 本就是 per_symbol+combine 两段,切在两段之间,正合红线 #7)⇒ 立为 **D4c,D5 C4 的前置**(设计文档修订 A2)。(d) 运维陷阱:真实评估 universe **996** 而冻结分钟面板 **995**(`300114.SZ` 无分钟 bar;**冻结面板自证**——三个日频书因子面板各 996 含它、十个分钟面板全 995 不含它),而 `saturation_probe` 默认 universe 从 995 面板派生。**编目 §七之二 已在任何 artifact 重生成之前登记漂移**(`eval_config` +3 键 / 顶层 +1 / Markdown +4 行全为新增 ⇒ C5 **逐字节对账已知不成立**、能成立的是值级 + 结构性断言;另登记 exec 侧 no_book 与 with_book 的 `eval_config` **首次合法地不同**、不得当回归;jump 的 loud raise 属有意阻断及解除方式)。gates:**2227 passed + 1 skipped**(2228 收集 = main 2175 +46 +7,**0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa` / 冻结面板 mtime 未变。**方法论新增两条**:① **「失配摧毁意义 → 拦;失配只带偏 → 记」**(见上);② **lead 与评审共用同一 worktree 时,测量与变异会打架**——本轮 lead 跑 gates 时 `git status` 一度显示 1 个改动(评审正在做 mutation),故合并前在评审收工后重跑了一遍干净 gates。 - - **D4c(PR #101,实施期修订 A2 插入的迁移步,晚于 D5b 合并——store 去 universe 依赖,D5 C4 的前置)**:修 D5b 评审立项的**两个独立失效模式**。① **`factors/service.py::_ensure_coverage` 缺口判定原只按日期不按 (date,symbol)**——第二批 symbol「无缺口」直接返回、**静默从截面消失**(影响全部 11 分钟因子)→ 改按 (date,symbol) 判定。② **store key 无 universe 维**:`intraday_amp_cut` 的截面 z-score 发生在物化期、store 之前,存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、返回 24 行而非 48、存活 24 格全错**)→ **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(切在 `MinuteStreamBinding` 的 per_symbol/combine 两段之间,正合红线 #7)。**「给 store key 加 universe 维」明确否决、不重开**(三条理由:10 个 universe 无关因子过度失效;PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来;跨指数存储爆炸)。**调用方 symbol 列表单点归一化** `materialize.requested_universe` + 入口面 probe 守卫(其射程已写进守卫自己的 docstring:四种逃逸路径实测);**per-symbol floor 契约测试钉住**(`test_a_max_form_floor_breaks_the_premise`,docstring 写明引擎逐票硬化时它变红是预期信号、届时删除/反转)。**评审**:对抗性评审 APPROVE(HIGH-1/MEDIUM-1/NIT-1/floor 契约全 CLOSED);末 commit `131467e` 纯 docstring,经二轮评审 + 真实 mutation(引擎逐票硬化 → 该测试精确变红 9.322e-03→0.0,还原干净)。**gates(lead 独立重跑)**:pytest **2277 passed / 1 skipped** / ruff / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa`。**留 D5 C4 注记**(PR #105 部分处置:NaN 足迹误诊已修(filled_after_footprint 类)+ 中间量具名拒绝;完整接线仍留 21:00 故事):`tail_recompute` 对两列 payload loud raise 未接线 + D4c 的 NaN 足迹会被重叠校验误诊为 upstream revision(**高优先**);**派生 per-symbol floor 只能 per-symbol 或 min-over-symbols,绝不能 max 形**——实测 max 形偏差 9.3e-03~1.45e-02,会静默打破「中间量 universe 无关」的立论前提。 - - **jump_amount_corr 14:50 截断 correctness fix(PR #103,设计文档实施期修订 A3——单独成 PR、显式 correctness fix,非因子层重构一部分)**:修 D5b 发现并(两方独立)确认的真实前视缺陷。`jump_amount_corr_20` 是 11 个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 0,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它;exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测:扰动 cutoff 后 bar → 该因子 1,477/1,477 与 1,373/1,373 格全移动、其余 11 条 0 格;只扰动 `bar_end >= 14:51`(执行 bar 本身及其后)→ 1,363/1,363 移动。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`、verdict **Watch**、predictive 轴 PASS——**报告声明了它没有执行的检查**。**定性**:2026-07-19 owner 常设授权「全日数据因子一律截断到 14:50」建于此因子**之后** ⇒ 补截断是**补用既有授权**非新研究决策;但它是已发布因子的定义变更 ⇒ **单独成 PR、不许进重构任何 commit**。**内容**:截断 + 通用日内边界守卫(覆盖 11 分钟因子 + mmp_ew,目录遍历闭包防新增因子漏网)+ 结构化更正承载(JSON 顶层 `corrections` 恰好 1 处、`CORRECTION_FIELD_MAX_CHARS=2000` 超长 raise 不裁剪)+ 评估契约 v1.0→v1.1(`spec.version` 单独可判别 artifact 存档在更正哪一侧)+ 解除 exec 评估 loud 阻断(D5b 的跨 PR 互锁测试曾逐字给出该做的两个动作)。**评审 BLOCK 两条必修已修**:① dashboard 布局 `DEFINITION_MAX_LINES` 量错分支——`meta_y=0.20/0.10` ⇒ daily 容量 5 行 / intraday 4 行,而 dashboard 实际走 intraday 分支 → exec 形态 9/11 叠字;改 `DEFINITION_MAX_LINES_DAILY=5`/`_INTRADAY=4` + 布局守卫参数化扩到 exec 形态(mutation 证据:`_INTRADAY` 改回 5 → 11 测试红);② `test_the_correction_carrier_is_never_truncated_on_disk` 恒真(`MUST_NEVER_TRUNCATE` 集合与 `_leaves()` 叶子路径交集恒空)→ 改前缀匹配 `corrections.` + mutation(截断 `corrections.superseded` → 红且报对字段,旧逻辑在同语料上仍绿)。**重述结果(真跑出来的,非 pearson r=0.9993 推定)**:exec 与 close 两基准 verdict **Watch/Watch 不变**(exec IC −0.030840→−0.030539,N_eff 1162→1178);**不追认旧 artifact**——每个 emitted cell 都被污染过,聚合秩 IC 动得小是**稀释**(污染 bar 占 20 日池化窗 ~4.5% 且秩 IC 逐日封顶)不是无害。**评审自己撤回的两条(记录防失真)**:① `verdict.reasons` 无长度上限(走 `sanitize_text`;盘上最长未截断 note 1263 字符,「距上限 16 字符」是单样本推总体的假警报;真被截的是六个 payload 字段,真正临界的只有 `coverage_bias_bad_vwap` 158/200);② PNG 叠字「既有缺陷」与「本 PR 回归」两说皆对——renderer 一直如此(`figures.py` 此前只有 1 个 commit),jump 描述 323→702 字符曾是本 PR 引入的回归、又被结构化承载拖出(现 352 字符)。**预期下游信号(预登记非回归)**:`hand_anchors_d2.json` 将报 `all_ok_frozen14: False`(70 行 5 处失配全部且仅有 jump:手算已截断 vs 冻结 panels_d2 未截断);其余 9 因子 exec dashboard 叠字是 renderer 既有缺陷,C5 全量重跑时重渲染。**gates(lead 独立重跑)**:pytest **2389 passed / 5 skipped**(4 个 census 语料测试在 worktree 无语料 skip、已对真实语料验 4/4 PASS;1 个 gitignored 基线字节 skip)/ ruff clean / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret scan 0 / `exec_baseline --verify` 77/77 @ `45c14aa`;jump 4 份 artifact 经 git archive 导出重生成(cache-only,`stk_mins_live_calls=0`)——9 份 JSON/MD 逐字节不变、PNG 不变(jump 3 行描述在两个 cap 下都放得下,本就不叠字),exec dashboard 目视无叠字。 - - **D5 C4(PR #105,统一 exec-only FactorEvalRunner——决策③"杀第二条因子取数路径"的实质落地)**:四块。① `qt/factor_eval_providers.py`(CacheMinuteProvider 从 hotpath_smoke 提升为单一来源、DailyEvalPanelProvider 给 close-view 未滞后面板、build_eval_service 复用 pipeline 同调用序)+ `incremental.py` 足迹误诊修复(stored-NaN→fresh-finite 单立 `filled_after_footprint` 类,不再报 upstream REVISION)+ 中间量 payload 具名拒绝 `IntermediatePayloadNotWiredError`(deliberate deferral);② `qt/factor_eval_runner.py` + `qt/factor_eval_disclosures.py`(四份 coverage 披露 8/8 VERBATIM 搬入唯一家、旧 runner re-export、`book_mode` decision/close 双模式——decision 模式关闭 §1.1 close(d) 书活缺陷、add-Section 后 mandatory+verdict 逐位不变、新 stem `factor_eval_*` 与旧 `eval_*` 命名隔离、编目 BUG 3/5/6 闭环);③ `qt/factor_eval_reconcile.py`(panels/reports/anchors 三模式 + 入口 77/77 硬闸门,基线不可达=硬错误、jump 路由 pr_c_cutoff_fix 参照物);④ 判据补全(见下)。 - - **首轮真实对账失败→根因调查→判据补全**(本轮最重要的方法论事件,照实记):bounded 因子预期≈0 差异却出 4554 unclassified + 2 锚 FAILED → 独立调查定位主因是 **warmup 左延**(旧 runner 左端锚 `data.start` 不扩窗、冻结面板前 w−1 日欠 warmup;materializer 左延到真实缓存起点 2015-01-05),加两个浮点尾部(rolling 求和顺序 rel≤2.9e-12;rolling-σ 噪声×整数成交量阈值翻转 ±1 count)。**认知修正:handoff §3①"bounded vs D1 baseline 预期零差异"的预测错了——旧 runner 的"全窗口"左端同样是 anchor 截断;首轮不绿的源头是预测错、不是引擎 bug。** 新具名类(机器可判、类外仍非零退出):`warmup_left_extension`(bounded 前 w−1 日全方向 / pooled 早区窗+按月非递增)、`float_reordering_tail`(rel≤5e-12 且 ≤101 cell)、`threshold_flip_tail`(±1 count、≤25 cell)、MD 按 key 配对、JSON 登记项。编目新增 §七之三。 - - **真实对账 9/9 绿**(cache-only `stk_mins_live_calls=0`):minute_ideal_amp warmup=8,198 / jump warmup=17,289+float_tail=101 / volume_peak warmup=34,441(按月 19,977→14,464 单调)+threshold_flip=20,三因子 unclassified 全 0;jump 非 warmup 区 3 锚变绿(rel ~1e-15,#103 截断修正被新路径携带确认)。 - - 评审 APPROVE-WITH-NITS(0 CRIT/HIGH/MEDIUM;64 个自造 mutation probe 全过、verbatim 8/8 复核、panels 模式独立重跑逐格一致);登记 LOW-1(bounded warmup 类无量级/cell 数上限,~1.6% 网格,**C5 加 cell 数 ceiling**)与 LOW-2(reports 腿 digit OR 规则散文理由可变义,verdict 标签仍严格);NIT-1/2 装饰性。 - - gates(lead 独立重跑):pytest 2492 passed(node-ID 差分 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret scan 2 处 benign(新 config 的 external_secret_file 路径 + key 名,与 main 上 11 份 eval config 同形,无 token 值)、exec_baseline --verify 77/77 @ 45c14aa。 - - 刻意不做:删旧 runner(C6)、11 因子全量四腿(C5)、vpq 绑定 + binding 折 code_hash(PR-C4b,lead 已裁定批准)、mmp_ew(D6c)、run registry(D7)。 - - **D5 C4b(PR #107,vpq 绑定 + code_hash 折叠——11 分钟因子全部绑定完成)**: - - **vpq 绑定**:`valley_price_quantile` 的 residualize 是逐日截面 OLS(值依赖 universe)⇒ 按 D4c 架构存 per-symbol `raw_qbar` 中间量、截面 combine 移到读出装配;combine 需日频 close ⇒ 唯一引擎扩展 `DailyCombineInput`/`combine_daily` **声明式**槽位(红线 #5 禁 isinstance 守住;顺带删掉了旧的 `isinstance(factor, ValleyPriceQuantileFactor)` 分发);`combine_daily_panel` 用 decision-lagged close + 左端 22 个 lagged 交易日 warmup(欠暖钳位有一行披露)。 - - **平移面板第三条路**:因子内部 T-1 会被 decision 视图二次滞后成 close_{d−2} ⇒ 传平移面板 + 关内部 T-1,**逐比特等价双侧钉**(binding 侧 `Series.equals` + 破坏对照 ~8.5e-2;runner 侧 fixture 240/240 max|diff|=0.0 + 左端 22 日硬断言;评审用不同 fixture + 新扰动独立复验)。 - - **binding.py 折进 code_hash**(lead 裁定 Q2):承重语义文件此前在 key 射程外;mutation 实证改 binding → 18/18 factor key 全变、改无关模块 → 0 变;dev-only `artifacts/factor_store/` 一次性失效已披露。 - - runner 启用 vpq + NeutralizationCoverage 机制 B(经新 `service.stored_payload` 回读 raw 中间量,同一 key/fingerprint/coverage 引擎、非第二取数路径)+ **anchors 模式修截面因子全 universe 服务**(5 锚 symbol < min_cross_section=10 必全 NaN——C5 对 intraday_amp_cut 本也会踩)。 - - **panels 类边界三裁定**(首个 FAIL 100% 分解后 lead 裁定):① pooled 月单调豁免锚到**结构性部分月**(冻结面板首个 finite 值所在月;初版位置性实现被评审 probe 绕过 → 修复 + 回退 mutation 证据);② 新具名类 `threshold_flip_contamination`(窗口 [2023-06-01,2023-07-14]、600623.SH ≤2e-04、其余 ≤1e-06、≤20,000 cell——PRV 临界 bar 翻转×逐日 OLS 污染族,输入侧变化已排除:ledger 零写入 + qbar 双几何逐位一致 1.1e-16,评审独立复算 1,241/1,241 格);③ float 尾加 abs 下限 1e-12 + cap 分档(bars-only 101 / 截面 1,000)。 - - **真实对账(cache-only `stk_mins_live_calls=0`,评审独立重跑逐数一致)**:panels rc=0(warmup=24,568 按月 902→19,980→3,667→19 豁免后单调 / float_tail=883 / flip_contamination=19,177 / unclassified=0)、reports 4/4 OK、anchors 5/5;vpq verdict Watch/Watch 与冻结基线同(数值微移全在登记 warmup 聚合效应内)。 - - 评审两轮:APPROVE-WITH-NITS(LOW-1 位置性豁免可绕过 + NIT-1 静默钳位)→ 修复 → **delta 复核双双 CLOSED 可合并**(评审回退修复自证测试有牙)。 - - gates(lead 独立重跑):pytest 2533 passed(node-ID 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret 0、exec_baseline --verify 77/77 @ 45c14aa。 - - **D5a(PR #95,D5 的前三块——冻结/编目/探测;探测结论迫使插入新迁移步 D4b)**:**C1 冻结 exec 对账基线**(`qt/exec_baseline_freeze.py`)——22 份 exec artifact 是后续一切对账的参照,却 **gitignored 只存在于本机**,且设计假定的耐久副本 `scratchpad/baseline_reports/` **早已不存在**;新 runner 沿用同名输出路径,**跑一次就毁掉唯一基线**,随后的「对账」会比对刚被自己覆盖的文件(= 本仓在案的 `compare_postmerge.py` 空对账形态)。实冻 **77 个文件**(不是交办的 44——同族还有 11 份 `_exec_basis_sanity.md` + 22 张 dashboard,同样单份不可再生),**sha256 manifest 入 git**(字节留 gitignored,哈希只能经评审 diff 变动),每次读都验哈希,读取器结构性拒绝 live 路径(直连/symlink 别名/`..` 三种绕过全拒)。**provenance 按证据强度分级**:观察到的是 77 文件共享毫秒级 mtime(批量拷贝签名),对照 44 份 close 基准的 11 个递进 mtime 与 #74 重跑窗口吻合;「字节出自 #79 run」标注为**推断非观察**,字段名故意叫 `frozen_at_git_head` 而非 `producing_sha`。**C2 编目 11 runner 差异**(§六.5「未编目的差异算失败」)——用 **AST 级比对**而非抽读证明完整性(`_build_book_factors`/`_write_report`/`_section_payload` 11 份逐字符相同;其余 helper 剥掉字符串常量后坍缩成 1 组),另记 **7 个既有 bug**(只标不修)与 `FactorSpec` 16→20 键漂移(来自 #86/#91,须预登记否则对账时会被当 D5 回归去追)。**探测(`qt/saturation_probe.py`,可复跑工具非一次性脚本):D4 的 materializer 在真实评估面上跑不了**——995 票中 **84 票**首根 1min bar ≥ emit_start ⇒ pooled 饱和**永远无法提前终止、必打到 floor**(D4 已披露的代价在全规模是**构造性**兑现);磁盘 8.18/16.43 GB,名义内存 52.7/122.2 GB,而 **frame:峰值RSS 实测 2.2–2.45×**(`minute_bars` 逐票读完 `pd.concat`,parts 与结果同时在内存)⇒ 评估窗真实峰值 **≈115 GB vs 可用 56 GB**;**不止 pooled**——bounded 路径同样整 universe 单帧(`materialize.py:262`),光评估窗就超可用内存约一倍。**三条更便宜的出路全部证否**:按票分批会因 `service._ensure_coverage` 的缺口判定**只按日期不按 (date,symbol)** 而**静默丢名**(I5a 红线);按时间分块只救 3 个 bounded 因子;缩短窗口不动 pooled 加载左端(锚在 floor)。**关门的是 `intraday_amp_cut`**(**同时 pooled × 截面**)——按票分批毁其截面、按时间分块管不住其深度,两轴都绕不开。切口位置已实测:纯因子逐票 ≡ 整 universe **逐位相同**(728/1524 个可比 cell `max|diff|=0.0`),`intraday_amp_cut` 逐票 **1860 finite → 0** ⇒ **切口必须落在截面 combine 之前**。**暴露的设计层缺口(照实记)**:§八只给**决策热路径**(20 日尾窗)定过规模预算,**从未给评估路径定规模**,所以「整 universe 单帧」在设计评审里没人拦;而旧 runner docstring 白纸黑字写着 "the multi-year all-symbol minute panel is NEVER materialized"——**D4 的加载几何相对被替换对象是回归**,只是 D4 验收跑在 40 票 smoke 上看不见。lead 提的「复用 `panel_freeze.py` 的加载形态代替 D4b」**已证否**:它没有自己的加载形态,是 import 11 个 runner 调其私有 `_load_*_panel`——「复用」等于保留决策③要杀的第二条取数路径;可复用的是**形状**不是调用路径。⇒ **插入迁移步 D4b(per-symbol 流式物化)**,设计文档记为实施期修订 A1;C3–C6 刻意未开始(修 materializer 是改取值路径,须自带四腿验收,混进 D5 当「让 run 跑起来」正是本项目反复抓到的形态)。**评审两轮**:首轮 BLOCK 1 HIGH——被文档称作「幂等」的再冻结命令**静默销毁 provenance**(未改动树上跑,输出 `copied: 0 / already ok: 77` 报纯成功,git 里却清空 `source_note` 并把 `frozen_at_git_head` 改指向重跑者 = **主动的假声明**,而 **16 个测试全程绿灯**)→ 改为 provenance 继承 + 内容不变则一个字节不写 + **恒跑断言**(`source_note` 非空 / head ^[0-9a-f]{40}$ / sha256 合法 hex(原只验长度)/ `size_bytes`>0),评审逐条 mutation 四条全红;另修 2 MEDIUM(**本 PR 自己 suite 里的空 mutation**——那条 git-tracking 测试全程不碰 git,注释掉 `.gitignore` 仍通过 → 改查 **git 索引** `git ls-files -- `;**C6 爆炸半径漏编目 3 个 runner importer**,含**生产 D1 冻结基线的 `panel_freeze.py` 本身**,其重跑流程是「旧 SHA + 当前分支文件」的混合式、两种 C6 处置下都会失效)。**方法论(三个空 mutation,全部照实记)**:实现方两次(tamper 测试替换了文件里**不存在**的字节串;git 索引 mutation 用 `git add -f` 被 symlink 拒绝执行 → 什么也没改、测试**为错误的理由通过**),评审一次(它建议的 `git ls-files --error-unmatch` 对「未跟踪」与「路径不存在」**返回相同 rc=1**,据此的断言会在**最需要它的机器上空过**)——**三次都不是被测试抓住的,而是被「与预期矛盾的观察」抓住的;评审建议本身也需要 mutation 证据**。**顺带更正一条长期误诊**:「本机 pytest 摘要行被环境吞掉」是假的——`pyproject.toml:53` 已有 `addopts="-q"`,我们再传 `-q` 就是 `-qq`;去掉即出现 `N passed` 摘要行(点计数仍是有用的交叉校验,本轮就抓到 2113 vs 2114 之差,但它从来不是因为摘要不可得才必须存在)。gates:**2117 collected**(2116 passed + 1 skipped——跳的是字节绑定测试,其字节 gitignored;manifest 自洽测试**无 skipif 恒跑**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `--verify` 77/77 且 head 仍 `45c14aa` / 冻结面板 mtime 未变。 -- ✅ 质量门(**当前 `main` @ `f432dc5` 实测**:`python -m pytest -p no:warnings` **1776 passed**、`ruff` clean、`validate-config` **31/31 配置逐个 OK**、`run-phase0`(demo)**ic 0.9600 / annual 0.8408 不变**)。构成对得上:`aa19968` 实测 **1740** → #78 加 2(含钉住破折号闭合的那条)= **1742** → #79 加 34(两个新测试文件,0 个既有测试被挤掉)= **1776**。新增测试分组(`--collect-only` 实数,`aa19968` 口径):**因子评估契约层 contract=337 + standard=113 + figures=7 / 十一因子(factor + eval runner,21 文件)=421 / PR#75 exec-vwap-basis=46 / D3 market quality 15→21(含 #77 `decreasing_adj_factor` 及其相对-vs-绝对尺度 mutation 测试)**。历史分组(旧 793 基线):`pytest` **793 passed**(P0=97 / P1=78 / P2-1=22 / P2-2=22 / P2-3=14 / P2-4=8 / P3-1=10 / P3-2=18 / P3-3=16 / P3-4=15 / P3-5=22 / P3-6=27 / P3-7=25+1 throttle / P3-8=8 / P4-1=28 / P4-2=17 / **intraday I1 schema=14 + feed=9 / I2 cache=10 / I3 aggregate=13 / I4 execution=10 / P4-3 cache=10 + updater=7 / P-I5a event-backtest=19 / P-I5b exec-feasibility=16 / P-I5c mmp-minute=18 / P-I5d mmp-quintile=19 / P-I5e 仅 config(无新测试,`test_cache_config` 的 config glob 自动 +1)/ P-I5f intraday-liquidity=17(+ `test_cache_config` config glob 自动 +1)/ D1 token-dedup index-feed +2 / D2 cache-modularization +5 / D3 data-quality market=15 + intraday=12 + report=14 / D3b data-update-quality=18 / D4 coverage-ledger-scaling=13 / D5 scheduler=9 + concurrency=12 / schema-registry-guard=62 / all-A-incremental=12 / all-A-backfill=22**);当时 `ruff` clean、`validate-config` 20 配置(data_update + phase_i5a…phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 自 P0 起从未变过**)。 -- ⚠️ 剩余(已显式披露):日线 only、demo 路径非真数据、旧三因子无信号(P3-3/P3-4 实证;但其组合在 2024-2026 holdout 上 SSE50/CSI300/CSI500 全正、CSI500 高达 +17.8%——小样本/regime 翻转的持续例证);value/低波信号获得**独立样本符号级确认**(P3-7 SSE50/CSI300 量级衰减;P3-8 CSI500 泛化成立且更强),组合级盈利能力仍未确立(排名跨 cell 翻转);subset 报告文件名已可配置(P3-8 起不再互覆盖)。**分钟因子侧新增披露**:十一个复现因子全部 EXPLORATORY、**封顶 Watch,无一到 Adopt**,1× 费率下净多空为正的只有 2 个(PR-F/PR-L,且 PR-L 扛不住 2×)——**IC 强 ≠ 可交易**;MMP 的唯一正面证据(I5d)在修正引擎上退化、I5e 负结果加强;I5f 的容量结论已按**真实 100 万名义**重写(1000 万口径的"约半数不足"作废);**日频除权审计已完成**(GENUINE、无 vintage 接缝、评估池暴露 1/603,258,不重述任何因子结果),**分钟路径的复权缺陷已在 PR #75 修**;exec-to-exec 改基准已随 **PR #79 合并**,close-to-close 与 exec 两套 artifact 并存;⚠️ **#79 的 code review 未返回、靠自查合并**,其"未独立核"清单(五条 mutation 声称 / 十一个 runner 改动是否机械等同 / `execution_capacity` 透传)仍待后续检查——若查出问题,重述 exec 基准数字而非 close-to-close;schema 守卫**仍未接** live `data-update` 暖跑路径;HTML compendium(`artifacts/reports/factor_compendium.html`)**还读 v0.8 结果,待按最终结果修订**。 -- 路线图下一步:**MMP 暂缓推进——理由在修正引擎上比过去更强,不是不变**(I5e 独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转** 且**在修正引擎上更负**:Spearman **−0.5000 不变**、Q5 高 MMP 仍最差、合成 Q5−Q1 累计 **−26.77% → −31.82%**;而 I5d 的 CSI500 完美单调性在修正引擎上**退化**(Spearman **+1.0000 → +0.9000**、Q5−Q1 **+17.15% → +8.65%**、Q5 让位 Q4),四档归因实测**归到复权修正本身**——原结论实质是"把除权下跌记成亏损"的产物 → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断;**按用户真实规模 100 万 RMB / 5% 参与率重测:below-1.0x 由 20 笔变 0 笔、最紧 1.22×——单分钟容量在该 universe/窗口不构成约束**;此前"10M RMB 下约半数不足"的说法**已作废**,它描述的组合比本项目会跑的大一个数量级;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**盘后全A auto-fetch**(数据正确性审计 PASS——D3 质量层实测 80GB 真实缓存结构干净、ann_date PIT 轴 100%、跨 shard schema 无漂移;PR #59 = PR-1 全A 增量 auto-warm + systemd @21:00 default-off 已落地;**PR #61 = PR-2 全A 历史回填**——分块/断点续跑/分钟全窗口 + per-symbol 失败隔离 + `start>today` 守卫,已落地。**建设侧完成,剩 live rollout = 用户 Stage-1**:先 `data-backfill` 补全A 历史(可续跑)→ 手动跑增量 warm 观察 → enable systemd timer);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / data-update summary 落 cache-stats artifact);**数据层 D6**(D1+D2+D3+D3b+D4+D5 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分 + report-only `data/quality/` 质量层 + default-off `data-update` 质量报告钩子 + coverage ledger `record_many` 批量写 + 进程内查找缓存 + opt-in 有界并发 + 全局限频器 + **schema 注册表/default-off drift 守卫(PR #57)**;剩余数据层 follow-up:schema 守卫接 live `data-update` 暖跑路径(当前仅接 pipeline/回测) + 分钟 schema 守卫;**D6**=`PanelStore` append/partition / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);**因子层收尾**(① `feat/exec-to-exec-rebasis` 两基准对照已跑完并随 **PR #79 合并**——22 格中 20 格不变、2 格朝更保守走;**补一次 #79 的 code review**(原评审未返回,未独立核的三项见上);② HTML compendium 按最终结果修订;③ 十一因子**无一到 Adopt、净多空几乎全负** → 下一步优先做**换手/成本敏感**的组合层验证与更长 holdout,而不是继续堆新因子);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +`artifacts/refactor_baseline/{panels, panels_d2, exec_baseline, pr_c_cutoff_fix}` —— 后续一切对账的唯一参照物,**无法从当前代码再生**,只读。所有真实 run 一律 **cache-only**(日志须 `stk_mins_live_calls=0`)。 + +## 进度档案(完整历史;**按需 grep,不要整读**) + +本文件曾内联全部阶段日志(~78k tokens,每个会话都被完整加载)。历史已逐字搬进 `docs/progress/`,**内容一字未改**。 + +> **新阶段的详细记录写进 `docs/progress/`(PR 号追加进 `pr_ledger.md`),不要写回本文件。** +> 本文件只维护三处随进度变化的内容:**当前状态**表、**当前有效的披露与限制**、**路线图**。 + +| 档案 | 覆盖 | +|---|---| +| [`01_phase0_3_daily_pipeline.md`](docs/progress/01_phase0_3_daily_pipeline.md) | Phase 0 MVP / Phase 1 偏差边界 / Phase 2 执行真实性 + PIT 行业 / Phase 3 多因子·IC 加权·OOS·robustness·候选因子·子集·独立样本·CSI500(PR #1–#21) | +| [`02_phase4_cache_and_intraday_i1_i4.md`](docs/progress/02_phase4_cache_and_intraday_i1_i4.md) | 端点级持久化缓存 P4-1/4-2/4-3 + 分钟 pipeline I1–I4(PR #23–#31) | +| [`03_intraday_i5.md`](docs/progress/03_intraday_i5.md) | 事件驱动回测 / 执行期涨跌停 / MMP 因子与五分位 / CSI300 泛化负结果 / 流动性诊断(PR #33–#55) | +| [`04_data_layer.md`](docs/progress/04_data_layer.md) | 数据层 D1–D5、schema 注册表与 drift 守卫、全A auto-warm 与历史回填(PR #41–#61) | +| [`05_minute_factors_eval_contract.md`](docs/progress/05_minute_factors_eval_contract.md) | `analytics/eval/` 三轴判决契约 v0.1→v0.9 + 十一因子复现总表与结论(PR #63–#74) | +| [`06_execution_basis_and_corrections.md`](docs/progress/06_execution_basis_and_corrections.md) | VWAP 成交 / 分钟收益复权 / 披露修正 / `adj_factor` 检查 / 日频除权审计 / exec-to-exec 改基准(PR #75–#82) | +| [`07_factor_layer_refactor.md`](docs/progress/07_factor_layer_refactor.md) | 因子层重构 D0–D5 C4b 全部细节与评审发现(PR #84–#107) | +| [`08_gates_disclosures_roadmap.md`](docs/progress/08_gates_disclosures_roadmap.md) | 归档时点的质量门快照 / 完整披露清单 / 路线图原文 | + +其它权威文档:`docs/data/data_layer_contracts.md`(cache vs store 契约)、`docs/factors/`(D0 契约 / D1 面板冻结 manifest / D5 差异编目 / 性质测试映射表 / C5 审计报告骨架)、`docs/ops/data_update_schedule.md`。 + +## 当前有效的披露与限制 + +- **研究侧结论一律 EXPLORATORY、非业绩声明。** 日线 only;demo 路径非真数据。 +- 旧三因子(momentum_20 / roe / netprofit_yoy)**无信号**;**value/低波**获得独立样本**符号级**确认(P3-7 SSE50/CSI300 量级衰减、P3-8 CSI500 泛化更强),但**组合级盈利能力仍未确立**(组间排名跨 cell 翻转)。 +- 十一个分钟因子**全部封顶 Watch,无一到 Adopt**;1× 费率下净多空为正的只有 2 个(其一扛不住 2×)。 +- **MMP 暂缓推进,理由在修正引擎上比过去更强**:I5d 的 CSI500 完美单调性退化(Spearman +1.0→+0.9,Q5 让位 Q4,四档归因把效应归到**复权修正本身**——原结论实质是"把除权下跌记成亏损"的产物);I5e 的 CSI300 负结果不变且更负。**在出现 disjoint 窗口 / 第三 universe 的正向证据前,不当稳健信号推进。** +- I5f 容量诊断已按**真实 100 万 RMB 名义**重写(below-1.0x 由 20 笔变 **0** 笔,最紧 1.22×);此前"10M 名义下约半数不足"的说法**已作废**。执行模型仍无 partial-fill / volume-cap。 +- **日频除权审计已完成:GENUINE,无 vintage 接缝**(评估池暴露 1/603,258,不重述任何因子结果);分钟路径的复权缺陷已在 PR #75 修。 +- ⚠️ **PR #79 的 code review 未返回、靠自查合并**,其"未独立核"清单(五条 mutation 声称 / 十一个 runner 改动是否机械等同 / `execution_capacity` 透传)仍待补检——若查出问题,重述的是 **exec 基准**数字,close-to-close 未被触碰。 +- schema drift 守卫**仍未接** live `data-update` 暖跑路径(当前只接 pipeline/回测);分钟 schema 守卫未做。 +- HTML compendium(`artifacts/reports/factor_compendium.html`)**还读 v0.8 结果**,待按最终结果修订。 +- 全A 盘后自动拉取:**建设侧完成**(增量 auto-warm + systemd default-off + 历史回填),**live rollout 是用户 Stage-1**(先 `data-backfill` → 手动观察一次增量 warm → 才 enable timer)。 + +## 路线图 + +1. **因子层重构收尾(当前主线)**:F1 残桶 correctness PR → C5 audit PR(F2/F3/F4 扩类 + F5 修复 + 真实重跑 + 填审计报告)→ C5 进度文档 → C6 删 11 个旧 runner → D6a–d 存量收口 → D7 收官全量重评估(**exec-only**)。 +2. **因子研究**:十一因子无一到 Adopt ⇒ 下一步优先做**换手/成本敏感的组合层验证**与更长 holdout,而不是继续堆新因子;补 PR #79 的 review;修订 HTML compendium。 +3. **可选、须单独显式 goal**:I5g(强制 partial-fill / volume-cap);数据层 D6(`PanelStore` append/partition,仅当因子研究需要可复用派生面板时才启动);schema 守卫接 live `data-update`。 diff --git a/CLAUDE.md b/CLAUDE.md index 705c4dc..68d788d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,341 +57,95 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri ## 开发约定 - **交流中文**;代码/注释/commit message 用**英文**。 -- **Git**:feature 分支 + PR。**PR #1(P0+P1)、#2(P2-1)、#3(P2-2)、#4(进度文档)、#5(P2-3)、#6(进度文档)、#7(P2-4)、#8(进度文档)、#9(P3-1)、#10(进度文档)、#11(P3-2)、#12(P3-3)、#13(进度文档)、#14(P3-4)、#15(进度文档)、#16(P3-5)、#17(进度文档)、#18(P3-6)、#19(P3-7)、#20(进度文档)、#21(P3-8)、#22(进度文档)、#23(P4-1)、#24(进度文档)、#25(P4-2)、#28(tushare 权限/限频探测 + capability registry)、#29(I1–I4 分钟级 intraday pipeline,4 commit 一 PR)、#30(进度文档)、#31(P4-3 因子支撑端点缓存 + 21:00 data updater,2 commit 一 PR)、#33(P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model,4 commit 一 PR)、#35(P-I5b 分钟尾盘执行期 raw stk_limit 涨跌停可行性,4 commit 一 PR)、#37(P-I5c MMP 分钟因子端到端 opt-in alpha)、#39(P-I5d MMP 五分位分组回测 standalone,含 I5c plumbing)、#41(数据层 D1 契约文档 + token 解析去重)、#43(数据层 D2 TushareCache specs/parsers 拆分)、#45(数据层 D3 report-only 数据质量层)、#47(数据层 D3b default-off `data-update` 质量报告钩子)、#49(数据层 D4 coverage ledger 批量写 + 进程内查找缓存)、#51(数据层 D5 opt-in 有界并发 + 全局限频器)、#53(P-I5e MMP 五分位 CSI300 独立泛化检验,负结果)、#55(P-I5f 分钟尾盘执行流动性/容量诊断,report-only)、#57(数据层 schema 注册表 + default-off drift 守卫)、#59(数据层 全A 增量 auto-warm + systemd 定时,default-off)、#61(数据层 全A 历史回填 `data-backfill`,分块/续跑/失败隔离)、#62(进度文档)、#63(因子评估契约层 `analytics/eval/` + StandardFactorEvaluator + dashboard + PR-C 因子,4 commit 一 PR)、#64–#73(PR-D…PR-M 十个分钟因子复现,**一因子一分支一 PR**)、#74(因子评估契约 v0.8 + v0.9 判定门修复)、#75(尾盘执行价 VWAP + 分钟持有期收益复权)、#76(报告披露修正 + I5f 名义按真实规模重测)、#77(`adj_factor` 下降质量检查)、#78(#76 的 follow-up:扫描式正则守卫被七种改写实测证否 → 闸门描述改单一来源)、#79(十一因子改 14:51 VWAP exec-to-exec 基准,**评审未返回、靠自查合并**)、#82(闸门/成交价描述全仓清扫,12 处;#78 守卫射程不足的修正)、#80(进度文档)、#84(因子层重构 D0 纸面契约:availability policy 常量 + 预指派表 + 性质编目)、#85(进度文档)、#86(因子层重构 D1:registry + PanelField requires + FactorSpec 契约 v1.0)、#87(因子层重构 D1 面板冻结:14 因子 raw 基线 + manifest)、#88(进度文档)、#89(因子层重构 D2:minute primitives + ops + 11 因子迁移,与冻结基线逐位一致)、#90(进度文档)、#91(因子层重构 D3:store 层——稀疏值存储 + key/指纹 + tail-recompute + R5 fina 视界统一)、#92(进度文档)、#93(因子层重构 D4:FactorService + decision-view materializer + pooled 饱和填充)、#94(进度文档)、#95(因子层重构 D5a:exec 基线冻结 + 11 runner 差异编目 + 可行性探测)、#96(进度文档)、#97(因子层重构 D4b:per-symbol 流式物化)、#98(进度文档)、#99(因子层重构 D5b:评估契约 v1.0 + 诊断通道 + 14:50 可见性守卫;**C4 中止上报**)、#101(因子层重构 D4c:store 去 universe 依赖——(date,symbol) 缺口判定 + store 只存 per-symbol 中间量、截面 combine 移到读出装配 + 调用方 symbol 列表单点归一化)、#103(jump_amount_corr_20 补 14:50 截断 correctness fix——唯一未做截断的分钟因子,exec_to_exec 下信息集越过自己入场锚的真实前视;含 dashboard 布局 5/4 分支修复 + census 守卫前缀匹配修复;单独成 PR 非重构一部分)、#105(因子层重构 D5 C4:统一 exec-only FactorEvalRunner——因子取数全走 factors.service、异构披露唯一家、C5 对账 harness 三模式 + 77/77 硬闸门、warmup 左延具名类登记)、#107(因子层重构 D5 C4b:valley_price_quantile 绑定——combine_daily 声明式引擎扩展 + 平移面板第三条路逐比特等价双侧钉 + binding.py 折进 code_hash + 截面因子 panels 类边界三裁定)均已 merge 到 `main`**;**OPEN 未合并:#38**(见 P-I5d 处置,未获授权关闭)。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 +- **Git**:feature 分支 + PR;commit 用 conventional 格式、**无 attribution**(不加 Co-Authored-By)。**代码 PR 与进度文档 PR 分开**,实现 PR 绝不碰 `CLAUDE.md` / `AGENTS.md`;改完 `CLAUDE.md` 必须 `cp CLAUDE.md AGENTS.md` 并 `cmp` 验证逐字节相同。仓库**无 CI**,合并用 `gh pr merge N --merge`。历史 PR 台账(#1–#107)见 [`docs/progress/pr_ledger.md`](docs/progress/pr_ledger.md);**PR #38 永远保持 OPEN、不许动**。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 -## 当前进度 -- ✅ 7 层骨架 + 架构文档(`main`) -- ✅ **Phase 0 MVP**(PR #1):DemoFeed → PanelStore → StaticUniverse → momentum_20 → zscore → EqualWeightAlpha → TopN 等权 → 月度回测(成本/换手)→ IC/绩效报告,单命令可复现。 -- ✅ **Phase 1 偏差边界**(PR #1,全部真数据实证): - - 前复权(qfq;store 存 raw,内存复权 → batch≡incremental 安全) - - PIT 指数成分(`index_weight` as-of,survivorship-safe;370 天 pre-start 回看 + 90 天分页) - - 可交易过滤(停牌 / ST / 涨跌停;**涨跌停用未复权 raw close** 比 `stk_limit`) - - 财务 `ann_date` 披露日 as-of(绝不按 end_date;500 天 lookback carry forward) - - 行业 + 市值中性化(按 date 截面 OLS 残差;欠定/无自由度截面 → NaN) - - 路径感知降级披露(demo/static vs tushare/index/ann_date,绝不把 demo 当真实验证) -- ✅ 真数据实证(tushare,非 CI):复权除权日 raw−5.74%→qfq+0.99% / CSI300 全年 24 快照 328 名换手 / ann_date Q1 延后至 04-20 / 中性化 corr −0.617→0。详见 `BIAS_AUDIT.md`、`artifacts/reports/phase1_summary.md`。 -- ✅ **Phase 2-1 真实数据可复现基准**(**PR #2 已 merge 到 `main`**):新 run mode `run-phase2-baseline` + `config/phase2_real_baseline.yaml`(上证50 `000016.SH`,2023-07~2024-06)。**复用 P0/P1 全套机器,不扩因子、不调参**。一次真实跑 ~11min(68 成分 / 25 loaded 快照 / in-window distinct 60 / 11 settled 调仓,候选 12 末日跳过),输出 `artifacts/reports/phase2_real_baseline.md`(gitignored):数据窗口 / PIT 成分摘要(loaded vs in-window) / ann_date 覆盖率(100%) / 可交易过滤命中(首命中互斥) / 每期持仓 / 换手成本 / IC(≈0.008) / 绩效(年化−17.6%,**亏损动量基准,非业绩声明**) / 全部 P2 降级。诊断只读、demo 源拒绝、token 不入报告。 -- ✅ **Phase 2-2 执行真实性**(PR #3 已 merge 到 `main`):**拆分 selection(选谁)与 execution feasibility(能否成交)**。 - - 方向感知执行 `runtime/fills.py::simulate_fills`:涨停挡买 / 跌停挡卖 / 停牌·缺收盘双向挡;按 panel flag 实时判定,与选股 toggle 无关。 - - **现金一致 sell-then-buy**:卖在前释放现金、买在后,现金不足按比例部分成交 → **无杠杆**;被挡交易 carry forward;换手/成本只算实际成交;闲置现金按 driver 的 `cash_return` 计息(BT-007)。 - - `universe.min_listing_days` **真实路径已执行**(`stock_basic.list_date` 富化,买入资格过滤,边界 age==min 放行,缺 list_date 保留并披露);demo 无上市日 → 披露 no-op。 - - 回测 `feasibility_log()`:每调仓期 blocked buys/sells/carried/executed turnover/invested;phase2 报告新增 **Execution feasibility** 小节。 - - **保不变量**:demo 无 flag → 全可成交 → P0/P1 数字不变;无未来函数、PIT/ann_date/real-demo 分离不变。 -- ✅ **Phase 2-3 历史 PIT 行业**(**PR #5 已 merge 到 `main`**,默认 SW-L1 可配置):把行业中性化协变量从 `stock_basic.industry` **当前**标签升级为按 trade_date **as-of** 的历史申万行业。 - - `tushare_covariates.pit_sw_intervals(symbols, level)` 读 `index_member_all` 取每股 SW 行业 `in_date`/`out_date` 区间;`data/clean/pit_industry.py::asof_industry` 按 `[in_date,out_date)` 覆盖该日取行业(改分类日新行业生效,PIT-safe;起始日前成分 carry forward 到窗口开始)。 - - **SW 层级可配置** `processing.neutralize.industry_level`(L1/L2/L3,**默认 L1**=31 宽板块,中性化标准 + 小截面自由度更稳)。**关键实证**:旧 tag 年化 −17.6% / SW-L1 −10.2% / SW-L2 −9.3% → **L1≈L2**,大跳的主因是 **tushare→SW 分类切换**(补 PIT 的必然:只有 SW 有 in/out 历史可 PIT 化,旧 tag 无法),**与粒度无关**。(注:曾误以为是粒度、默认 L2,经 L2 实测推翻,改默认 L1。) - - **不静默退回 current**:无 SW 历史的票 → 行业 NaN,被 neutralize 按截面丢弃(neutralize 数学不变,本就丢 NaN 行);每次运行 **实际 level + PIT 覆盖率**进 phase2 报告。 -- ✅ **Phase 2-4 标准分析集成**(**PR #7 已 merge 到 `main`**):alphalens-reloaded + quantstats 接入报告(**report-only cross-check**)。 - - `analytics/alphalens_adapter.py`(IC mean/IR + 分位均值)+ `analytics/quantstats_adapter.py`(CAGR/Sharpe/maxDD/vol)薄 adapter,各带 `backend` 字段。 - - **简版 numpy/pandas 仍权威**(驱动回测 + cross-check);依赖不可用/报错 → 报告显式披露 backend(unavailable/error,只记异常**类型**不记消息),**绝不静默假装用了标准库**。 - - **不改 alpha/portfolio/runtime/fills/universe**:P0/P2 交易数字不变(demo ic 0.96/annual 0.84 不变;实测 alphalens IC=简版 IC 完全吻合),只新增 **Standard analytics** 报告段。 -- ✅ **Phase 3-1 首个真实多因子 baseline**(**PR #9 已 merge 到 `main`**):pipeline 从"只用第一个 enabled factor"升级为**消费全部 enabled factors**;新增 `config/phase3_real_multifactor.yaml`(momentum_20 + roe + netprofit_yoy,SSE50 同窗口,与 phase2 可比)。**不调参、无 learned weights、非收益承诺**。 - - `_build_factors` 全量实例化(配置序;重名报错);财务字段**一次 fetch + 一次 as-of 对齐**(逐字段独立守 `ann_date <= trade_date`,无每因子重复拉取);demo+财务因子仍可读报错。 - - 合成仍 `EqualWeightAlpha`(处理后各列等权平均,不看 forward returns);`drop_missing` 要求该日该票**所有**因子齐备(显式披露)。 - - 报告增强:active factor list / per-factor coverage+IC+分位 / **combo score** 诊断 / 财务 coverage **按字段**披露(TRADED vs diagnostic 角色标注);`output.baseline_report_name` 使 phase3 报告独立于 phase2。 - - **真实结果**(SSE50 2023-07~2024-06,~14min):多因子 annual **−9.05%**(单因子 −10.19%);per-factor IC: momentum_20 0.0083(与 phase2 run 完全一致,跨 run 一致性实证)/ roe 0.0006 / netprofit_yoy 0.0001;combo IC −0.0038;财务两字段 ann_date 覆盖 **100%**;PIT SW-L1 98.53%。财务因子在该小截面短窗口 IC≈0,照实披露——这是 plumbing 验证。 - - **回归不破**:phase2 单因子真实 rerun annual −10.19% / IC 0.0083 不变;demo ic 0.96/annual 0.84 不变。 -- ✅ **Phase 3-2 walk-forward IC 加权 alpha**(**PR #11 已 merge 到 `main`**):新增 `alpha/ic_weight.py::RollingICWeightAlpha`(`alpha.model: ic_weighted`);EqualWeightAlpha 仍默认 + 回归基线。**不调参、非收益声明**。 - - **Lookahead 边界(测试锁定)**:训练严格 walk-forward——(factor[t], fwd_h[t]) 只有**已实现**(交易日序 `t+h <= d`)才进日 d 的权重;**扰动未实现 forward returns 权重不变**(扰动测试)+ `t+h` 切片精确边界测试。forward returns 由 pipeline 在 alpha 边界计算、只传 `alpha.fit`,factors 层照旧绝不接触(不变量 #1)。 - - rolling(默认保守,窗口=60 交易日,min_periods=20)/ expanding 可配;历史不足 → 该日**退回等权**(与 EqualWeightAlpha 逐 bit 一致)并计数披露;权重 L1 归一化、保留符号(负 IC 因子负权重)。 - - 报告新增 **Alpha model** 必含小节:active model / 超参 / 训练覆盖率 + fallback 次数 / 每调仓日生效权重表(fallback 行标注)/ 非调参声明 + 等权基线对比指引。 - - `config/phase3_real_ic_weighted.yaml`:与 phase3_real_multifactor **唯一差异是 alpha.model**(universe/window/因子/中性化全同,直接可比)。 - - **真实结果**(SSE50 2023-07~2024-06,~14min):annual **−3.57%**(等权 −9.05% / 单因子 −10.19%),maxDD −12.93%,训练覆盖 **201/221**(20 个 fallback 全在窗口攒满前,90.95%)。⚠️ 优于等权**不是**业绩声明——单年窗口 + 权重逐期翻号(如 momentum_20 从 −0.58 到 +0.36)正是小样本不稳定的体现,照实披露。 - - **回归不破**:phase3 等权真实 rerun annual −9.05% / IC 0.0083 不变;demo equal_weight ic 0.96/annual 0.84 不变(测试锁定)。 -- ✅ **Phase 3-3 OOS 稳定性验证**(**PR #12 已 merge 到 `main`**,含 review 两 HIGH+一 LOW 修复):**报告型验证层**,不加新 alpha 复杂度、不改 portfolio/execution/factor math。新 run mode `run-phase3-oos`(`qt/oos_stability.py`)+ `config/phase3_real_oos_stability.yaml`(SSE50 扩到 **2 年** 2022-07~2024-06,split 2023-07-01 → train 1y / test 1y,test 年=旧 baseline 窗口可对照)。 - - **一次数据加载、同一 processed 因子面板、两次回测**(equal_weight vs ic_weighted);所有诊断按 split 切段(子段 nav 重新归一,绝不跨段串味)。 - - **边界语义(测试锁定)**:walk-forward(rolling subperiod)——任何日期的权重只用该日已实现观测(`t+h <= d`);**扰动 split 后全部 forward returns,train 期所有日期权重逐 bit 不变**(split 无泄漏测试);不用 freeze-at-split(那是新 alpha 模式,超范围)。**绩效切片按持有窗口**(train 行持有期 end≤split、test 行 start≥split,跨界调仓从两段排除并披露;IC 按实现日 t+h 切)——绝不按 signal date 单切(review HIGH 修复:旧切法把跨界持有期的 test 收益记进 train);runner 强制 `alpha.model: ic_weighted`(否则假对比,可读报错)。 - - 报告 `phase3_oos_stability.md`:split 边界+跨界行披露/分期绩效(annual/vol/sharpe/maxDD/turnover)/逐序列 IC 分期(mean/IR/hit rate/sign consistency)/权重稳定性(每期权重含 train-test 标注、trained 行 sign flips、fallback 次数+原因)/小样本 caveat。 - - **真实结果(关键发现,~16min,77 成分/2 年,持有窗口切片)**:三个原始因子 train→test **IC 全部翻号**(momentum −0.023→+0.006 / roe −0.029→+0.007 / np_yoy −0.011→+0.005,sign consistency 全 NO),hit rate 46~53%≈抛硬币;权重 23 期 sign flips 7/3/4;绩效 eq train −11.92%/test −5.27%,ic train −8.31%/test −2.70%(跨界行 2023-06-30 排除;修切片前 train 被 test 期收益污染到 −6.81%/−1.69%,修正幅度本身就是边界 bug 的实证)。**结论:ic_weighted 两段都略好但 IC≈0 且翻号——P3-2 单年跑赢不可外推,这正是本验证层要拿到的证据;非收益声明。** - - **回归不破**:phase3 equal_weight rerun −9.05%/0.0083、ic_weighted rerun −3.57% 均不变;demo 0.96/0.84 不变;secret scan 报告 0 处 token/config.json。 -- ✅ **Phase 3-4 robustness matrix**(**PR #14 已 merge 到 `main`**,含 review MEDIUM 修复:矩阵 DOWNGRADES 全 universe 披露):把 P3-3 OOS 检验批量跑到 **universe × window 矩阵**上,回答 P3-2/P3-3 结论是否 SSE50 小样本偶然。不加因子/alpha、不改 portfolio/execution/factor math。 - - 新 run mode `run-phase3-robustness`(`qt/robustness.py`)+ `config/phase3_real_robustness_matrix.yaml`(SSE50+CSI300 × 2020-2022/2022-2024 两 fold;`skip_cells` 显式跳过 CSI300×2020-2022——runtime 预算,报告披露,绝不静默缩覆盖)。 - - **每个 cell 逐字复用 P3-3 cell 核心**(`_run_oos_cell` 重构抽出,单 run 行为不变;持有窗口切片/实现日 IC 切片/walk-forward/ic_weighted guard 全继承);cell config 仅替换 universe/窗口/split/output_name(重过全套 pydantic 验证,parquet 不互覆盖);跨 cell 汇总严格按 cell 归属(测试锁定不串数)。 - - **真实矩阵结果**(3 cells,总 2h:960s/934s/5301s,CSI300 399 名): - - **P3-3 不漂移 smoke ✓**:SSE50|2022-2024 cell 逐数复现 P3-3 报告(−11.92%/−5.27%/−8.31%,boundary 2023-06-30)。 - - 原始因子 train→test sign consistency:momentum 1/3 / roe 0/3 / np_yoy 0/3——**IC 翻号跨 cell 普遍,P3-3 结论非偶然**。 - - `combo_ic_weighted` 是唯一跨 cell 稳定序列(test IC 3/3 正 + 3/3 sign consistent)但量级 ≈0.004~0.008 极小。 - - **关键负面发现**:ic_weighted 组合绩效不稳健——train→test 翻车形态出现在 **3 cells 中的 2 个**:SSE50|2020-2022 train **+7.09%→test −7.85%**、CSI300|2022-2024 train **+11.07%→test −17.74%**(sharpe +0.59→−1.01;CSI300 换手 1.2~1.6 成本拖累更重)。**凡 ic_weighted 样本内显著为正,样本外即翻负——典型过拟合签名;P3-2 的"跑赢"不能外推;微弱正 IC ≠ 净值赢。非收益声明。** - - secret scan 报告 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 3-5 factor candidate pack**(**PR #16 已 merge 到 `main`**,**EXPLORATORY**):保守日频 PIT-safe 候选因子组,经 P3-4 矩阵原样复检"弱信号是否只是因子集太窄"。不动 alpha/portfolio/execution/OOS/robustness、不调参。 - - 新因子(`factors/compute/candidates.py`,数学/前导 NaN/未来扰动不变/跨 symbol 隔离/不可变全测试锁定):`reversal_5/20`(=−momentum 复用同机器)/`volatility_20`(滚动收益 std,满窗)/`liquidity_20`(log 滚动均额,非正→NaN)/`overnight_mom_20`(Σlog(open_t/close_{t-1}) 20 日,open 当日开盘已知,prev close 不跨 symbol)/`value_ep`/`value_bp`(daily_basic pe/pb **一次 fetch** 取倒数,≤0→NaN,当日发布 PIT-safe)/`grossprofit_margin`(进 SUPPORTED_FIELDS,走既有 ann_date as-of)。 - - dispatch(registry)扩展;**窗口名/params 不一致 → 可读报错**(绝不静默错标列);旧配置逐 bit 复现(demo 0.96/0.84 不变)。 - - `config/phase3_real_factor_candidates.yaml`:旧三 + 候选八 = 11 因子,同 P3-4 矩阵形状(CSI300×2020-2022 显式 skip 披露);**旧 vs 新一跑读出**(raw IC 按列独立)。 - - **真实矩阵(11 因子版 3 cells / 2h;旧三因子+前轮候选 per-cell IC 逐数不变=不漂移 smoke ✓;secret scan 0;一次 CSI300 网络故障后整 run 重跑成功)**: - - **"因子集太窄"假设获得支持**:`value_ep`/`value_bp` test IC **3/3 正、0.037~0.056**;`volatility_20` **3/3 负、−0.044~−0.079**(低波方向稳定)——比旧三因子(|IC|≤0.015)高一个量级且方向跨 cell 一致。liquidity 3/3 负但量级小;`overnight_mom_20` test IC 2/3 正(+0.008/+0.016,2020-22 为负)中等偏弱;reversal/grossprofit_margin 翻号无信号。 - - `combo_ic_weighted` test IC **3/3 正 + 3/3 sign consistent**(0.0253/0.0012/0.0395)——walk-forward IC 加权吃到了新信号;11 因子**等权** combo 反被翻号因子稀释(IC 0/3 正,CSI300 test **−25.2%**)——等权对因子集质量敏感。 - - 组合绩效:ic test −2.21%/−5.02%/−2.76%(**3/3 cells 跑赢等权**;CSI300 train +23.11%→test −2.76%,train→test 衰减仍显著)。 - - ⚠️ **EXPLORATORY,非收益声明**:value/低波在 2020-2024 A 股是已知强势 regime;三 cells 窗口重叠非独立样本;未调参、未做成本敏感性。下一步候选:value+lowvol 子集独立复检。 -- ✅ **Phase 3-6 value+lowvol 子集复检 + 成本敏感性**(**PR #18 已 merge 到 `main`**,**EXPLORATORY**):把 P3-5 发现的 value/低波信号做**组间对照**(同一矩阵、同 footing),并补三档交易成本场景。**report-only:不加 alpha、不调参、不动 portfolio/execution/OOS 切片/robustness 聚合;`_run_oos_cell` 一字未动**。 - - 新 run mode `run-phase3-subset`(`qt/subset_validation.py`)+ `config/phase3_real_subset_costs.yaml`:4 组(legacy_trio / full_pack=11 因子 / value_lowvol=value_ep+value_bp+volatility_20 / value_lowvol_liq=+liquidity_20,/goal 的"可选"直接实测)× 3 成本场景(base ×1 / 2x ×2 / high_cost ×4,**必须有 multiplier=1.0 base 锚**,config 校验强制)× P3-4 矩阵 3 cells(CSI300×2020-2022 skip 照旧披露)。 - - **机制(测试锁定)**:每 cell 一次共享加载 + raw factor panel(与 OOS cell 同调用序)→ 每组从 raw panel **独立重处理**(`drop_missing` 按组——切 processed 列会错因为 drop_missing 跨列;全列组与旧处理 bitwise 一致)→ 每组 eq vs walk-forward ic_weighted × 每场景回测。成本场景只乘 `cost.fee_rate`:scores/fills 不见 fee → **trades/turnover/gross 跨场景严格不变,只动成本线**;`_run_backtest_for` 增加 default-preserving `fee_rate` 参数(None==旧行为,bitwise 测试锁定)。 - - **三层不漂移对账(真实 run 全过)**:① raw 因子 IC **66/66 行**(11 因子×train/test×3 cells)与 P3-5 报告逐数一致;② `legacy_trio`@base 逐数复现 P3-3/P3-4(ic test −7.85%/−2.70%/−17.74%,train +7.09%/−8.31%/+11.07%);③ `full_pack`@base 逐数复现 P3-5(ic test −2.21%/−5.02%/−2.76%,combo IC 0.0253/0.0012/0.0395,eq test −4.30%/−6.83%/−25.16%)——按组重处理 ≡ 把该组当配置因子集单跑。 - - **真实结果(3 cells×4 组×3 场景,~2.1h)**: - - **子集假设在净值层不成立**:value_lowvol ic test base **−4.49%/−8.66%/−5.84%** vs full_pack **−2.21%/−5.02%/−2.76%**——full_pack 在 **3/3 cells × 全部场景**净值更优。IC 层子集略稳:value_lowvol combo_ic test IC 3/3 正(0.0245/**0.0107**/0.0387),量级与 full_pack 相当且中间 cell 远离 0(full_pack 0.0012≈0)——**IC 稳定性与净值排序不同向**,照实披露。 - - value_lowvol_liq(+liquidity_20):CSI300 IC 最高(0.0427)、SSE50|2020-2022 单 cell 最佳(−2.48%),但 2022-2024 两 cell 均劣于 value_lowvol——**加 liquidity 非免费午餐**,"可选"问题用数据回答。 - - 等权稀释复确认:full_pack eq CSI300 −25.16% vs value_lowvol eq −14.97%(子集等权耐稀释);ic beats eq(base):trio 2/3、full_pack 3/3、value_lowvol 3/3、liq 2/3。 - - **成本敏感性(新维度)**:base→2x→high_cost **全组全 cell 单调恶化**(无例外);4× fee 退化 1.7~4.4pp 年化——legacy_trio CSI300 最重(−17.74%→−22.13%,高换手代价),value_lowvol 低换手(0.41~0.74/月)退化 ~1.8~2.4pp;算术年化 drag base ~0.5-0.9% / high_cost ~1.4-4.8%;turnover 跨场景不变实测 ✓。 - - ⚠️ **全组 test annual 均为负——非收益声明**;**POST-HOC 选择已披露**(子集是在同一批窗口上看完 P3-5 结果后选的,本 run 只量化相对稳健性+成本敏感性,**不是独立确认**——独立确认需真正新窗口/新 universe)。 - - secret scan 报告+日志 0 处(token 值/"token"/".config.json");demo 0.96/0.84 不变。 -- ✅ **Phase 3-7 独立样本验证**(**PR #19 已 merge 到 `main`**,曾 stacked 在 P3-6 分支上、PR #18 先合后顺序合入。**EXPLORATORY**,含 review 2 HIGH 修复):把 value/低波发现从"同窗口 POST-HOC 对照"推进到真正独立 holdout。不加因子/alpha、不调参;P3-6 group/cost 逻辑与配置不变(旧 27 测试原样通过)。 - - **review 2 HIGH 修复**:P3-7 报告曾沿用 P3-6 标题/"not independent confirmation"开场白/"SAME overlapping windows"收尾 caveat,与自身 verdict 节矛盾 → `render_subset_validation` 标题/框架/caveat 与 `_subset_downgrades` 按是否含 independent cells 分支(P3-6 配置原文不变,回归测试锁定,+3 测试);artifact 按用户指示**不重跑矩阵**,以确定性脚本把 5 处与数据无关的静态文案行替换为新渲染器的精确输出(新字符串从新代码路径提取、非手敲;diff 仅 5 行、全部数字未动、secret scan 0)。 - - **机制(测试锁定)**:独立性是**人的声明**——`subset_validation.independent_cells` 显式列 cell(必须引用矩阵已声明 cell、**禁止与 skip_cells 冲突**——"声明独立验证却不跑"是配置错误;未声明一律默认 screened,保守);`hypotheses`(因子→期望 IC 符号)**run 前固定**于 config(value_ep+/value_bp+/volatility_20−,源自 P3-5 筛选);`min_rebalances`(默认 8)不足 → **INSUFFICIENT-DATA**(样本量必披露,绝不静默通过)。verdict=事实性符号检查(HOLDS=期望符号在 holdout **双子期**都成立;SUPPORTED/PARTIAL/NOT SUPPORTED;NaN/缺列不成立);**分类汇总绝不混**(summarize_by_sample 按 sample class 各自聚合;verdict 节只读独立 cells)。 - - 真实 config:screened anchor SSE50|2022-2024(必须逐数复现 P3-6=run 内不漂移锚)+ 独立 SSE50|2024-2026、CSI300|2024-2026(**2024-07-01→2026-05-31 后于全部筛选**,split 2025-07-01;数据实测到 2026-06);CSI300|2022-2024 skip 披露。 - - **运维实证**:两次真实 run 死于瞬时 ConnectionError(旧默认 3 次重试≈3s 容忍)→ 默认重试预算 **3→6**(≈23s 容忍,成功路径不变,测试锁定)→ 第三次 ~2.2h 跑通。⚠️ P3-7 报告与 P3-6 同名(`phase3_subset_validation.md`),后跑覆盖前跑(regenerable,数字都在进度文档)。 - - **anchor 不漂移 ✓**:raw IC 22/22 ≡ P3-5 矩阵报告;组级 base 年化逐数 ≡ P3-6。 - - **真实结果(3 cells/~2.2h,独立结论只来自 2 个 holdout cells)**: - - **独立 verdict:2/2 cells SUPPORTED**(各 21 settled rebalances vs min 8)——value_ep IC +0.0322/+0.0134(SSE50 holdout train/test)与 +0.0245/+0.0072(CSI300);value_bp +0.0379/+0.0033 与 +0.0310/+0.0041;volatility_20 −0.0320/−0.0120 与 −0.0373/−0.0164。**P3-5 的符号假设在未参与筛选的样本上全部成立**。 - - ⚠️ **量级显著衰减**:筛选期 |IC| 0.037~0.079 → holdout 后段 0.003~0.016(value_bp 后段≈0)——符号存活、强度衰减(部分向零回归),照实披露。 - - combo_ic_weighted 独立 test IC **8/8 正**(4 组×2 cells,0.0060~0.0238)——walk-forward IC 加权在 holdout 上仍有效。 - - **组合净值仍未确立**:独立 base ic test——legacy_trio **+1.15%/+8.13%**(2/2 正,恰是此前实证"无信号"的组!)、full_pack +7.30%/−1.93%、value_lowvol +1.72%/−4.68%、liq +3.53%/−0.39%——组间排名跨 cell 翻转,~21 rebalances 小样本,**IC 符号确认 ≠ 组合盈利**;成本阶梯仍全单调。非收益声明。 - - secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 3-8 CSI500 独立泛化检验**(**PR #21 已 merge 到 `main`**,**EXPLORATORY**):P3-7 的符号级结论是否泛化到筛选 universe 之外——新增独立 cell **CSI500(000905.SH)|2024-2026**(universe+时间双独立)。**机器零改动**复用 P3-7 层(同组/同成本场景/同假设,不加因子不调参);唯一代码变更 `output.subset_report_name`(沿 `baseline_report_name` 先例,默认 None 保持旧文件名 bitwise,测试锁定)——P3-8 报告独立成文件,**不再覆盖已验收的 P3-7 artifact**。 - - cell 设计:SSE50|2022-2024(screened 锚,须 ≡P3-6/P3-7)+ SSE50|2024-2026(independent 锚,须 ≡P3-7 verdict 数字)+ CSI500|2024-2026(主问题);CSI500|2022-2024 skip 披露(~645 名×2y 再加 ~3h 不值)。数据可行性先实测:index_weight 月度 500 名快照到 2026-05-29 含 2024-06-28 pre-start 锚。 - - **真实 run(3 cells/~3.55h,CSI500 735 distinct 名主导;一次跑通)双锚对账 ✓**:screened raw IC 22/22 ≡ P3-5 报告;independent SSE50 verdict IC 逐数 ≡ P3-7(+0.0322/+0.0134、+0.0379/+0.0033、−0.0320/−0.0120)——复现性二度确认。 - - **CSI500 verdict:SUPPORTED**(21 settled vs min 8):value_ep **+0.0083/+0.0145**(train/test)、value_bp **+0.0230/+0.0127**、volatility_20 **−0.0350/−0.0272**——三假设双子期全保持。**且衰减更小**:CSI500 test 子期量级(0.0127~0.0272)明显高于 SSE50/CSI300 holdout 的后段(0.003~0.016)——value/低波信号在中盘股上更强,**P3-7 结论泛化成立(GENERALIZES,未减弱)**。 - - combo_ic_weighted CSI500 test IC 全 4 组正(0.0243/0.0294/0.0286/0.0285,为三个 holdout cells 最高);**组合净值 CSI500 base 全组正且 4× 高成本仍全正**(trio +17.80%→+10.57% / full_pack +7.83%→+2.95% / value_lowvol +4.19%→+1.14% / liq +4.02%→+0.84%)——首个全成本阶梯为正的 cell。⚠️ 仍然诚实:trio +17.80% 又是组间排名跨 cell 翻转的例证(中盘动量 regime);单窗口 ~21 调仓小样本;**非收益声明**。 - - **报告标题配置化**(`output.subset_report_title`,沿 `subset_report_name` 先例):P3-8 报告 H1 自报 study 名「Phase 3-8 — CSI500 Independent Generalization Check」而非机器默认 P3-7 标签(review 同类 stale-wording 问题修复,测试断言首行非 P3-7;P3-6/P3-7 配置不设此项、保持 sample-aware 默认,回归锁定);文案/分区检查全过(无 stale P3-6 措辞、verdict 节只含独立 cells);secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 -- ✅ **Phase 4-1 持久化 Tushare 行情缓存**(**PR #23 已 merge 到 `main`**,daily + adj_factor):feed 之下的 **endpoint 级 raw 缓存**——真实 run 不再每次重抓全量日线+复权因子。**默认 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后行情走 read-through,只有未覆盖的日期区间打 API。 - - **缓存只存 raw**(未复权 OHLCV/amount + 原始 adj_factor),绝不存 qfq、绝不存任何 secret;`front_adjust` 仍在内存跑,公式/时机零改动;`PanelStore` 仍是 per-run artifact,**不是**缓存 SoT。 - - `data/cache/`:`intervals`(闭区间日历算法做 gap 规划——按"日历区间是否抓过"而非"行是否存在"判定,正确区分"未抓"vs"源本无行")/ `parquet_store`(按 `(endpoint,symbol)` 分文件存 `symbol_prefix` 分片,原子 upsert 按 `(date,symbol)` 去重,latest 胜)/ `coverage`(append-only ledger,11 列含 status ok/empty/failed,**只 ok/empty 算覆盖**,failed 留待重试)/ `tushare_cache`(read-through:只抓 gap、upsert、记 coverage 含空返回、再从缓存读全区间;`refresh_recent_days` 重抓近端 tail;`force_refresh` 整端点重拉;per-run 抓取计数 stats 日志)。 - - `TushareFeed.get_bars`:`cache=None` → 旧直抓路径**逐字不变**;`cache` 注入 → read-through 后用**与直抓完全相同的 join+select**,故 `front_adjust` 前面板逐字节一致(qfq 等价单测锁定);per-symbol 限流/重试仍留在 feed 的 `_call` 闭包里。`_build_market_cache` 仅在 `data.cache.enabled` 时接线。 - - **配置**:`data.cache`(enabled=False / root_dir=`artifacts/cache/tushare/v1`(gitignored)/ refresh_recent_days=14 / force_refresh=[]);校验拒绝负刷新窗/空 root/未知键;全部旧配置仍 validate。 - - **真实 smoke(phase2 baseline;非缓存参照 ref / 缓存冷 cold / 缓存暖 warm 三轮)**: - - **三轮 report 指标完全一致** ✓(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%,REF==COLD==WARM)——qfq 等价单测已证 cached==direct 数据层逐字节一致,真实 run 三轮一致是端到端实证。 - - **warm 轮零市场端点调用**:cold 后 coverage ledger = 68 market_daily + 68 adj_factor 行(68 成分全 ok);warm 后**仍 68+68 不变**(零新 coverage 行 = 零 gap-fetch = 零 daily/adj 调用)。 - - wall:ref 998s / cold 1025s / warm **734s**(暖跑省 ~290s = 行情抓取部分;index_weight/财务/covariates 仍 live——P4-1 只缓存行情,诚实标注)。 - - secret scan:缓存 parquet + ledger 0 处 token / `.config.json`;ledger 列只有端点元数据。 - - **缓存命中直接可见(review follow-up)**:`TushareFeed.cache_stats()` 暴露各端点 gap-fetch 计数,`_load_panel` 经 run-scoped logger 打 `data cache: market_daily_gap_fetches=N adj_factor_gap_fetches=M`——冷跑非零、暖跑 0/0(warm rerun 实证 run_phase2_baseline.log 含 `0/0`,secret scan 0)。 -- **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report 全不动;`artifacts/data/{output_name}.parquet` 不当 SoT。范围克制:P4-1 只 market_daily+adj_factor,其余端点(index_weight/daily_basic/fina_indicator/...)P4-2/P4-3 再缓存。 -- ✅ **Phase 4-2 持久化 Tushare universe + tradability 缓存**(**PR #25 已 merge 到 `main`**,index_weight + suspend_d + namechange + stk_limit + stock_basic):把 P4-1 端点级 raw 缓存扩到 universe/可交易性端点——真实 run 不再每次重抓成分股/停牌/ST/涨跌停/上市日。**默认仍 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后这五端点走 read-through。 - - **三种规划形态共用一引擎**:① dense per-symbol 日期区间(`suspend_d`/`stk_limit`,复用 P4-1 gap + recent-tail);② index_code 维日期区间(`index_weight`,coverage key=index_code,gap 内仍 **90 天分页**,raw 快照入库);③ snapshot 维度(`namechange` per-symbol、`stock_basic` 全局 sentinel),用 `refresh_dimension_days`(默认 30)staleness + force_refresh(`CoverageLedger.snapshot_fetched_at` 给新鲜度判定)。 - - **语义全保**:`index_weight` 仍 PIT/as-of(370 天 pre-start lookback 进缓存请求区间,latest-snapshot-on-or-before 仍在 feed/universe);`stk_limit` 仍 **raw price**(限价检查在 front-adjust 前,不碰 qfq);`stock_basic` 只取 list_date 供 `min_listing_days`(缺失仍 kept+披露),current-tag `industry` 绝不入缓存/中性化;`suspend_d`/`namechange`/ST 区间形状不变;**缓存只存 raw 端点事实,不存派生 flag 作 SoT**。 - - **三 feed 接线**:`IndexConstituentsFeed`/`TushareFlagsFeed`/`TushareCovariatesFeed` 各加 `cache=None` 注入;cache present → read-through + **共享 finalizer**(cached==direct,限价 frame `assert_frame_equal`、ST 区间集合相等、suspend set / listing dict 相等单测锁定);`cache=None` → 直抓路径**逐字不变**(旧 feed 测试原样过)。per-symbol 限流/重试仍在各 feed 的 `_call` 闭包里(缓存 transport-agnostic)。 - - **coverage ledger 复用 + 扩展**:11 列不变;**empty 算覆盖、failed 不算**(fetch 抛错则不记 coverage、留待重试,测试锁定);ledger 列只有端点元数据,无 token/secret。 - - **单一共享 cache 贯穿 4 runner**(run_phase0/phase2/oos/subset):`_build_cache(cfg)` 建一个实例线穿 `_build_universe`/`_load_panel`/`_enrich_tradability`/`_maybe_enrich_listing`,跑完所有缓存端点后 `_log_run_cache_stats` 打**一行** 7 端点统计;P4-1 的 market 行前缀保留(旧统计测试过)。 - - **配置**:`data.cache.refresh_dimension_days`(默认 30,>=0 校验);旧配置全 validate(含未知键拒绝)。 - - **真实 smoke(phase2 baseline,fresh temp root,cold→warm;fresh root 不动已 merge 的 v1 缓存)**: - - **cold 行**:`market_daily=68 adj_factor=68 index_weight=9 suspend_d=68 namechange=68 stk_limit=68 stock_basic=1`(全非零);coverage = market/adj/namechange/stk_limit 各 68 ok + index_weight **1 ok**(整 gap 一行,内部 9 窗分页)+ stock_basic 1 ok + **suspend_d 68 empty**(SSE50 大盘股该窗口无停牌 → 空返回算覆盖,warm 不重抓)。 - - **warm 行:7 端点全 0**;coverage ledger **零新行**;report 指标与 cold/P4-1 cached baseline **逐数一致**(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%)——cached==direct 二度端到端实证。 - - wall:cold **960s** / warm **366s**(暖跑省 ~594s = universe+tradability+market 抓取;`daily_basic`(market_cap)/`index_member_all`(pit_sw) 仍 live——P4-2 不缓存这俩,P4-3 再说,诚实标注)。 - - secret scan:缓存 parquet + ledger + 日志 + 报告 0 处 token 值 / `.config.json`;ledger 无 token 列。 - - **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report/`front_adjust` 全不动;`artifacts/data/*.parquet` 不当 SoT。范围克制:`daily_basic`/`fina_indicator`/`index_member_all` 仍留 P4-3。 -- ✅ **分钟级 intraday pipeline I1–I4**(**PR #29 已 merge 到 `main`**,4 commit 一 PR;**全程与日频链路解耦——`factors`/`alpha`/`portfolio`、日频 `runtime/backtest`、日频 `TushareFeed`/`TushareCache`、全部 `config/` 零改动**,Phase 0/2/3 数字不变)。本地验收文档在 `tmp/context/intraday_pit_checkpoints/stage_i{1,2,3,4}_acceptance.md`(gitignored)。 - - **核心架构决策**:raw intraday SoT = **stk_mins `1min` only**;5/15/30/60min 是从缓存 1min **派生**的视图,不作独立 raw 上游产品抓取;日频 `D` 仍独立保留(不被分钟重采样替代)。三时间戳贯穿全程严格分离:**signal cutoff**(T 14:50,特征只用 `available_time<=cutoff`)/ **execution timestamp**(T 14:51)/ **holding period**(exec→next-exec,绝不 close-to-close)。 - - **I1(feat data)** 1min raw feed + PIT schema:`data/clean/intraday_schema.py`(独立 `MultiIndex(time,symbol)`,分钟精度不归零;`bar_end=trade_time`/`bar_start=bar_end-freq`/`available_time=bar_end+data_lag`;`RAW_INTRADAY_FREQ`+`ensure_raw_intraday_freq` 钉死 1min)+ `data/feed/tushare_intraday.py`(`TushareIntradayFeed.get_minutes`,`vol→volume`/`ts_code→symbol`/`trade_time→time/bar_end`,非 1min 在建 SDK client 前即拒,token 不入库不打印)。 - - **I2(feat cache)** stk_mins 1min read-through cache:`data/cache/intraday_coverage.py`(**timestamp-interval ledger**,`raw_freq/start_time/end_time`,不复用日频 date 语义;ok/empty 算覆盖 failed 不算)+ `intraday_parquet_store.py`(**月分区** `stk_mins_1min/freq=1min/symbol_prefix/symbol/year/month.parquet`,原子幂等 upsert by `(symbol,freq,bar_end)`,只存 raw)+ `intraday_cache.py`(`TushareIntradayCache.stk_mins_1min`:交易日 gap 规划复用 `intervals.py`,≤23 日窗分页 <8000 行 cap,1min-only guard)。接入 `TushareIntradayFeed` 可选 cache path(直抓路径字节级不变)。**冷写/暖零调用/partial 只补缺口/empty 记录/failed 重试/cached==direct 规范化后逐字节等价** 全测试锁定;**日频 `TushareCache` 零改动**(独立类)。 - - **I3(feat data)** 分钟→日频 PIT 聚合:`data/clean/intraday_aggregate.py::asof_daily_features`(默认 `decision_time=14:50:00`;**先 `available_time<=cutoff` 逐 bar 过滤再按日 groupby**,分钟时间戳绝不先归零进日频)。cutoff 编码列名 `intraday_ret_0930_1450`/`intraday_realized_vol_0930_1450`/`intraday_vwap_0930_1450`/`intraday_last30m_ret_1420_1450`。`resample_intraday_bars` 派生粗 bar `available_time=max(source_1min.available_time)`。泄漏测试(扰动 14:50 后 bar → 特征不变)+ 可见性排除测试锁定。 - - **I4(feat runtime)** 尾盘调仓 execution 骨架:`runtime/intraday_execution.py::simulate_tail_rebalance`(`next_minute_close` 模型:14:51 或窗口内最早 bar 成交;持有期收益 `exec(T)→exec(T_next)` 非收盘;缺 bar/NaN/无窗口 bar → 可解释 blocked,**不静默用日收盘替代**;只读分钟 bar 无 EOD 泄漏)。**独立函数,未接 config/pipeline**;日频 `close_to_next_period` 不变。`tail_vwap`/`closing_call_proxy` config 层拒绝(注明 future,后者需 `stk_auction_*` 权限当前无)。 - - **下一步**:执行真实性先于研究因子 → I5b 已补执行期涨跌停可行性(见下);之后 I5c 再把 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine(报告披露 cutoff/lag/execution_model/window),日频回归不破。 -- ✅ **P4-3 因子支撑端点缓存 + 21:00 data updater**(**PR #31 已 merge 到 `main`**,daily_basic + fina_indicator + index_member_all 进既有日频 `TushareCache`;新增独立 `data-update` CLI 增量暖跑,**不跑 factor/alpha/portfolio/backtest、不写 PanelStore**,日频回测仍自走 read-through 补缺口)。本地验收:`tmp/context/intraday_pit_checkpoints/stage_p4_3_data_updater_acceptance.md` + `tmp/context/session_handoff_20260613/p4_3_codex_acceptance.md`(codex 复核 PASS)。 - - **daily_basic**(dense,pe/pb/total_mv,一次缓存调用喂 market_cap+value_ratios);**index_member_all**(per-symbol SW in/out 维度,staleness 刷新)。 - - **fina_indicator 字段集无关**(codex acceptance blocker 修复):cache **永远存 canonical `FINA_FIELDS` superset**(roe/netprofit_yoy/grossprofit_margin),feed 读时选子集——一个配置 warm 不会阻塞另一配置(光加 fields_hash 会在 upsert 时互相覆盖);drift 测试守 `FINA_FIELDS ⊇ financial.SUPPORTED_FIELDS`;coverage 仍按 report-period end_date + 长 trailing tail 抓晚披露,ann_date 作 raw 不当覆盖轴。 - - **not_ready pending window**:今日(`not_ready_days`)空返回记 `not_ready`(非 coverage) 次跑重试,跨界 gap 拆分;`not_ready_days=0`(默认) 行为逐字不变。per-endpoint trailing tail + summary 计数。 - - cached==direct 实证(market_cap/value_ratios/pit_sw/fina-as-of);`cache=None`→direct 逐字不变;缓存只存 raw(无 qfq/因子/token);日频/分钟各自 ledger/store。**日频回测数学/factor/alpha/portfolio/runtime 零改动,Phase 0/2/3 不变**。 -- ✅ **P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model**(**PR #33 已 merge 到 `main`**,架构/框架 PR,非研究结果;含 review 两修复):把回测层重构成**共享事件驱动核心**,日频 close-to-close 与分钟尾盘成为**同一 achieved-book ledger 上的两个 event model**,零重复 fill/cash/settle。本地验收:`tmp/context/session_handoff_20260615/stage_i5a_acceptance.md`。 - - **核心**(`runtime/backtest/`):`events.py`(`HoldingPeriod` 显式时间基 + 共享月度日历)/ `engine.py`(`BacktestEngine` 单循环:universe→scores→构建→可行成交 via `SimExecution`/`simulate_fills`→settle→NAV/feasibility/holdings/**event** 日志)/ `event_models.py`(`DailyCloseEventModel` 逐字节复刻日频 + `IntradayTailEventModel` 14:50 决策/14:51 成交/exec-to-exec,复用 `intraday_execution.build_execution_prices`)。`BacktestDriver` 改为 engine+DailyCloseEventModel 的**薄 wrapper**(−237/+167),`pipeline`/`oos`/`phase2` 三 runner 一行未改。 - - **config**:opt-in `intraday`(enabled/decision_time/data_lag/session_open/execution_model/execution_window/require_cache_coverage/missing_execution)+ `backtest.event_order` Literal;`intraday_tail_rebalance` 必须 `intraday.enabled=true`(root validator),旧配置全 validate,非法 model/window 可读报错。 - - **分钟尾盘语义**:决策只用 `available_time ≤ 14:50` 的 bar;成交取执行窗口 `[14:51,14:56:59]` 内最早 1min 收盘;持有期收益 `exec(T_next)/exec(T)−1`(**绝非 close-to-close**);**缺/NaN 执行 bar = 显式 block,绝不退回日收盘**;turnover/cost/holdings 按 achieved book;闲置现金 `cash_return`。事件时间戳全程可审计。 - - **新 CLI `run-phase-i5a-intraday`** + `config/phase_i5a_intraday_tail_framework.yaml`(SSE50 SH/SZ smoke):日频面板/universe 走既有 P4 缓存;分钟 bar **只读既有 intraday 缓存**(缺即 hard blocker,绝不静默暖跑 → **零 `stk_mins` live call**);PIT-safe 分数 = I3 `intraday_ret_0930_1450` 特征(纯框架验证,非业绩声明)。 - - **真实 smoke(SSE50,2026-03-03→06-12,~180s)**:`periods=3 / covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;event 表显示 14:50 决策 / 14:51 执行 / **actual exec bar 范围** / 真实 exit 执行锚;secret scan 0。 - - **review 两修复**:① `require_cache_coverage=true` 下**任一** symbol 缺即 loud fail(原仅全缺才失败 → 静默丢名引入样本覆盖偏差);② `HoldingPeriod.exit_execution_ts` 显式化,event_log + 报告显示 **planned vs ACTUAL 执行 bar 时间**(14:51 缺、14:52 成交可见),末期不再 NaT。 - - **不变量守住**:daily close-to-close 行为不漂移(engine==driver 黄金测试 + phase0 `ic 0.9600/annual 0.8408` 不变);无重复 ledger;缺 bar 绝不日收盘兜底;turnover/cost 按 achieved。 -- ✅ **P-I5b 分钟尾盘执行期价格涨跌停可行性**(**PR #35 已 merge 到 `main`**,执行真实性硬化 PR,非研究 alpha;含 review LOW 修复):补 P-I5a 显式 deferred 的限制——在「有效 1min 执行 bar」规则之上,用 raw `stk_limit` 对**选定执行分钟的 raw 1min 收盘**做方向感知闸门。本地验收:`tmp/context/session_handoff_20260615/stage_i5b_acceptance.md`。 - - **语义(测试锁定)**:`exec ≥ up_limit − tol → can_buy=False`(涨停**只挡买**)/ `exec ≤ down_limit + tol → can_sell=False`(跌停**只挡卖**)/ 缺·NaN 执行 bar 仍**先于**限价逻辑挡两方向;**RAW-vs-RAW**——raw 1min 收盘(intraday 缓存存未复权)vs raw `stk_limit`,绝不碰 qfq/日收盘/`at_up_limit·at_down_limit` 日收盘派生 flag。`can_buy/can_sell` 喂既有 `simulate_fills` achieved-book ledger(turnover/cost/holdings 按实际成交)。 - - **缺限价行绝不静默当通过**:严格 `require_price_limit_coverage=true` 在**模型构造期 raise**(出结果前列缺失 pair);宽松 `false` 计数披露 unchecked 并退回 bar-exists 规则。诊断(涨停挡买/跌停挡卖/unchecked/coverage)按 `(date,symbol)` 幂等,重复 `feasibility()` 不重计。 - - **接线**:`IntradayCfg.{price_limit_check(默认 false)/require_price_limit_coverage/limit_tolerance(≥0 校验)}` + `OutputCfg.intraday_report_name`(I5b 报告独立成文件,不覆盖已验收 I5a artifact,沿 baseline_report_name 先例);runner 经 `TushareFlagsFeed(cache=cache).limits()` 走既有 P4 read-through 缓存取 raw 限价(**不加新端点**),报告 H1/intro 按 check 自报 I5b(修同类 stale-title)。 - - **不变量守住**:`BacktestEngine`/`BacktestDriver`/`DailyCloseEventModel`/`fills`/`execution` **零改动**(限价 map 仅 check 开时构建,默认 false → `feasibility()` 逐字退回 I5a bar-exists 规则);日频 close-to-close 不漂移。 - - **真实 smoke(SSE50 2026-03-03→06-12,3 次跑 NAV 全 `0.976448` 确定性)**:`covered=58/58 / stk_mins_live_calls=0`;限价覆盖 **174/174**;**1 笔涨停挡买 / 0 笔跌停挡卖** → NAV 由 I5a 的 0.952207 变到 **0.976448**(2026-04-30 一只 14:51 封涨停的票被正确挡买,该期只持 9 只——I5a 只查 bar 存在会「买」进涨停板,**这正是 I5b 补的执行真实性,非业绩声明**);`stk_limit` gap-fetches=58(冷暖都 58:窗口尾端距今 3 天落在 `refresh_recent_days=14` 内,P4 按策略重抓 tail,据实报告,非 minute fetch);secret scan 报告+日志+58 缓存 parquet **0 处**。 -- ✅ **P-I5c MMP 分钟因子端到端 opt-in alpha**(**PR #37 已 merge 到 `main`**,**EXPLORATORY**,非业绩声明):把首个 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine,日频回归不破。 - - **MMP(Minute Microstructure Pressure)**(`data/clean/intraday_aggregate.py`,公式/PIT/前导 NaN/跨 symbol 隔离全测试锁定):逐 1min bar `mid=(high+low)/2`、`S=(close−mid)/mid`、`V=√(volume/median(vol[t-20:t]))`、`B=|close−open|/(high−low+eps)`、`R=(high−low)/(mean(hl[t-20:t])+eps)`、`MMP_t=S·V·B·R`(eps=1e-6);日频分数 = 在场 bar `[session_open, decision_time]` 等权均值,rolling baseline 用前 20 个在场 bar(首 20 NaN)。**先 `available_time≤cutoff` 过滤再聚合**(PIT-safe,绝不先归零)。 - - **可配置 `intraday.score_feature`**(默认沿用,`mmp_ew` opt-in)+ 可配置报告标题(修同类 stale-title);`config/phase_i5c_mmp_minute_factor.yaml`。decision feature→alpha→portfolio→event engine 全程复用既有机器,报告披露 cutoff/lag/execution_model/window。**日频 close-to-close 零漂移**(phase0 `0.9600/0.8408` 不变)。 -- ✅ **P-I5d MMP 五分位分组回测**(**PR #39 已 merge 到 `main`**,**standalone against `main`**,**EXPLORATORY,非业绩声明、非调参**):把 I5c MMP 日频分数 `intraday_mmp20_ew_0930_1450` 做 5 等额分位分组回测——每月调仓日按 PIT-safe 分数横截面排名切 `analytics.quantiles=5` 等数桶(Q1 最低 / Q5 最高),每组作独立 long-only 等权组合走**同一** `BacktestEngine`+`IntradayTailEventModel`+`SimExecution(fee_rate=0.001)`,I5b raw `stk_limit` 执行期涨跌停 ON。引擎/执行可行性/MMP 因子数学全不变,仅新增分组 + per-group 编排。本地验收:`tmp/context/session_handoff_20260615/stage_i5d_acceptance.md`。 - - **新增**(机器零改动复用 I5a/I5b/I5c):`qt/intraday_groups.py`(等额 rank 桶 / `GroupScores` / `EqualWeightAll`)、`qt/intraday_group_backtest.py`(cache-only anchor-date-sliced 分钟加载、一份共享 exec-price 矩阵跨 N 个 fresh per-group 模型、QN−Q1 合成 spread)、`qt/intraday_group_figures.py`/`intraday_group_report.py`(NAV/spread/metric 图 + 报告)、`config/phase_i5d_mmp_quintile_5y.yaml`;唯一 runtime 改动 `IntradayTailEventModel` 可选 `precomputed_prices`(默认 None → I5a/I5b 逐字节不变)。 - - **真实 run(CSI500 `000905.SH`,2021-06-01→2026-05-31,59 月调仓,~95min)**:`covered=892/995`(**103 只分钟未覆盖成分被 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only)/ `fee_rate=0.001`;final NAV Q1→Q5 **0.9822 / 1.0423 / 1.0495 / 1.1275 / 1.1577**(**单调 Spearman 1.0000**,等额 rank 桶对 MMP 肥尾稳健);合成 Q5−Q1 spread +0.29%/期、累计 **+17.15%**;限价覆盖 52,584/52,584(严格 `require_price_limit_coverage=true` 过)。 - - ⚠️ **caveat(保留,不移除)**:单因子/单重叠 5 年窗口/单 universe,无调参/无 robustness/无 learned 权重;`covered=892/995`(103 drop);`stk_mins_live_calls=0`;**Q5−Q1 是合成 long-only 腿差,非单独执行的 dollar-neutral 组合**;执行模型仍缺 partial-fill / liquidity / volume-cap;**Q5>Q1 不作业绩声明**。这是项目首个正向 intraday 信号,下一步需独立泛化(CSI300/中证1000 或 disjoint 窗口,机器冻结)。 - - **PR 处置**:#37(I5c)随 #39 落地后其 head 成 `main` 祖先,GitHub **自动 merged**(非本次手动 merge);#38(i5d stacked on i5c,base 非 main)已被 #39 **完全取代**(零 remaining commit/file diff vs main),保持 **OPEN 待清理**(未获显式授权关闭)。 -- ✅ **P-I5e MMP 五分位 CSI300 独立泛化检验**(**PR #53 已 merge 到 `main`**,**EXPLORATORY,非业绩声明、非调参;负结果**):冻结已验收的 I5d MMP 五分位机器,在**独立 universe** CSI300(`000300.SH`)同窗口(2021-06-01→2026-05-31)复跑,检验 I5d 在 CSI500 上的 Q1→Q5 单调性是否归因于因子而非 universe/regime。**仅加一个 config(`config/phase_i5e_mmp_quintile_csi300_generalization.yaml`),零代码/机器改动**——MMP 数学 / 等额 rank 桶(Q1 最低 Q5 最高)/ I5a/I5b 尾盘 event model(14:50 决策 / 14:51 执行 / exec-to-exec)/ raw `stk_limit` 执行可行性 / cache-only anchor-date 分钟加载全冻结;report 名/标题已 config 驱动故无需改代码。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5d_independent_generalization_20260620/`。 - - **Preflight 选 CSI300(非 fallback CSI1000)**:全窗口 481 distinct 成分,分钟全窗口覆盖 **448/481=93.1%**(≥ CSI500 89.6%)→ 够切 5 等额组,窗口不缩短;CSI300 日频成分 ledger 诚实仅覆盖近 1 年(无谎报、无静默截断),缺的经 P4 日频 read-through 补(21 次 index_weight,允许,同 I5d)。 - - **真实 run(CSI300,59 月调仓,~30min)**:`covered=448/481`(**33 只分钟未覆盖成分 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only,缺分钟即 hard miss 绝不暖)/ `fee_rate=0.001`;限价覆盖 26386/26386 全过。 - - **结论:FAILS / 不泛化**。final NAV Q1→Q5 **0.8809 / 0.7377 / 0.9418 / 0.7573 / 0.6665**;年化 **−2.55% / −6.00% / −1.21% / −5.50% / −7.92%**;**单调 Spearman −0.5000**(vs CSI500 +1.0000);合成 Q5−Q1 累计 **−26.77%**(vs CSI500 +17.15%)。**Q5(高 MMP)在 CSI300 上最差**——高 MMP 组不跑赢低 MMP 组,I5d 的 CSI500 单调性在独立 universe 上**反转**,强烈提示其为 universe/regime 特异、非稳健因子属性。这正是独立检验要拿到的证据。 - - ⚠️ **caveat(保留)**:单因子/单重叠 5 年窗口/单 universe;Q5−Q1 是合成 long-only 腿差非 dollar-neutral 执行;丢弃未覆盖分钟名的 coverage bias;执行模型仍缺 partial-fill/liquidity/volume-cap;无调参/无 learned alpha;**非业绩声明**。CSI500 对照仅作 context,不作任何组业绩声明。 - - **回归不破 + 范围克制**:phase0 `0.9600/0.8408` 不变;`pytest` 678 passed(677 基线 + `test_cache_config.py` 的 `config/*.yaml` glob 参数化自动覆盖新 config 的 +1,无新测试文件);**不动 progress docs 以外的代码/config/test**,不碰 schema registry / D6;**PR #38 未动**。 -- ✅ **P-I5f 分钟尾盘执行流动性诊断**(**PR #55 已 merge 到 `main`**,**执行真实性诊断、非因子研究结果;report-only**):给分钟尾盘链路加 opt-in、**report-only** 执行流动性诊断——对每笔 desired trade `|target_w − current_w|`,用**选定执行分钟 1min bar 的成交额 `amount`(RMB)**在参与率上限下估容量,报 capacity ratio(`>=1` 覆盖、`<1` 潜在受限)。新增 default-off `intraday.liquidity_diagnostics`(`enabled` 默认 false → 现存配置字节级不变;启用时 `portfolio_notional`>0、`max_participation_rate`∈(0,1]、仅 `mode=report_only`,各可读报错)。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5f_intraday_liquidity_diagnostics_retry_20260620/`。 - - **report-only 不变量(测试锁定 + 真实 smoke 双证)**:不改 fills / can_buy·can_sell / blocked reason / target 权重 / achieved holdings / turnover / cost / NAV / 因子分数 / MMP 分组 / alpha / 组合构建。容量**只用选定执行分钟 1min `amount`**,绝不用未来 bar、日频 amount/volume、日收盘、或 EOD 代理。`BacktestEngine` 加 additive、default-off `record_rebalance_plan`(默认 False → 循环字节级不变)捕获引擎真实用的 (target,current);诊断在 settle 后从已产出 log 构建,只读不写。既有 missing-bar/missing-price/raw stk_limit 阻挡**保留原 reason,绝不重分类为流动性阻挡**;missing/NaN/≤0 amount 报 missing capacity;零 desired trade 跳过防除零。 - - **真实 smoke(SSE50,2026-03-03→06-12,3 期,机器冻结)**:`covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;**`final_nav=0.976448` 与已验收 I5b 逐字节一致** → 启用诊断未动账面(report-only 端到端证明)。诊断:**43** desired trades;**1** 被既有 I5b feasibility 挡(2026-04-30 涨停那笔,原 reason 保留);**0** missing-capacity;**42** 有可用 capacity ratio;**20** below 1.0x;ratio min/p10/median/p90 = **0.122 / 0.222 / 1.104 / 2.730**;最紧 trade `2026-04-30 601658.SH sell` ratio 0.122。 - - ⚠️ **解读(诚实)**:10M RMB 诊断名义 + 5% 参与率下,约一半被检 SSE50 单分钟 desired trade 容量不足——**支持后续做 size-aware 执行建模,但不证明 alpha/业绩/可交易性,也不外推到其他名义**。**非业绩声明、非策略改进**;partial-fill / volume-cap 强制不在本阶段(那是将来 I5g,须单独显式 goal)。I5f 与 MMP 正交,MMP 仍暂缓(I5e 负结果)。 -- ✅ **数据层 D1 — cache/store 契约文档化 + token 解析去重**(**PR #41 已 merge 到 `main`**,行为保持型重构,非研究/非数据语义变更):数据层第一步低风险清理,只做两件事——把 `cache` vs `store` 边界写成可提交契约,并把重复的 token 解析收敛到既有 `data/feed/secret.py::read_token`。 - - **契约文档** `docs/data/data_layer_contracts.md`:`data/cache` = 可复用 raw endpoint 缓存 / SoT;`data/store/PanelStore` = per-run canonical 面板 artifact(非 raw lake,`artifacts/data/*.parquet` 不当缓存权威);缓存只存 raw;`front_adjust` / PIT 成分 / PIT SW 行业 / `ann_date` 财务对齐 / raw 涨跌停检查 / factor·alpha·portfolio·runtime 数学全在下游;`data-update` 只暖缓存不跑研究;data-quality + 并发是后续阶段(不声称已实现)。 - - **token 去重**:`tushare_feed.py` 删掉重复的 module-level `_lookup_dotted` + 内联 json/Path 解析(`_read_token` 变 `read_token(...)` 一行 wrapper);`index_feed.py` 不再从 `tushare_feed` 导入私有 `_lookup_dotted`,`_client` 直接走 `read_token`。`_lookup_dotted` 全仓清零;6 个 tushare feed(feed/index/flags/fina/intraday/covariates)统一走 `secret.read_token`。构造签名 / `__repr__` token 安全 / fetch+cache 路径全不变;+2 窄测试锁定 index feed 真实 token 路径(fake config、不碰网络、不泄漏)。 - - **行为保持**:phase0 `ic 0.9600 / annual 0.8408` 不变;无数据抓取/缓存语义/factor/alpha/portfolio/runtime/report 变更。**显式非目标(留给 D2+)**:`TushareCache` 内部拆分 / endpoint schema registry / data-quality validator / 并发 / `CoverageLedger` 存储变更 / `PanelStore` append·partition。 -- ✅ **数据层 D2 — TushareCache endpoint specs/parsers 拆分**(**PR #43 已 merge 到 `main`**,行为保持型内部重构,**公开缓存语义零变更**,非性能/并发/data-quality 层):把 814 行的 `data/cache/tushare_cache.py` 内部拆成小文件,让 endpoint 元数据与 raw 解析器可单独审阅,`TushareCache` 仍是公开门面。 - - **拆分**(解析器逐字搬移,行为靠既有 cache 测试锁定):`tushare_specs.py`(89 行,endpoint ids/`ALL_ENDPOINTS`/`_GLOBAL_KEY`/列集+自然键/`FINA_FIELDS`/`_INDEX_WINDOW_DAYS`/rename maps,纯常量)、`tushare_parsers.py`(216 行,10 个 `_parse_*`)、`tushare_planning.py`(22 行,叶子 helper `_fields_hash`/`_compact`);`tushare_cache.py` **814→596 行**,保留 `TushareCache` 门面 + read-through 引擎(gap 规划 / index_weight 90 天分页 / snapshot staleness / `refresh_recent_days`·`recent_tail_overrides`·`not_ready_days`·`force_refresh` / ok·empty·not_ready·failed coverage / 逐自然键 upsert / `stats`·`update_summary`)+ `FetchOne`/`FetchSnapshot`。 - - **向后兼容导入保持**:门面 re-export endpoint ids + `FINA_FIELDS` + `ALL_ENDPOINTS`,`from data.cache.tushare_cache import DAILY_BASIC/FINA_FIELDS/INDEX_WEIGHT/...`(被 `tushare_fina.py` + cache 测试用)全照常;新增 5 个窄测试锁定 re-export identity + 模块边界。**公开方法/签名/内部规划/coverage 语义全不变**;phase0 `0.9600/0.8408` 不变。**非目标(D3+)**:schema registry / data-quality / 并发 / `CoverageLedger` 存储 / `PanelStore` append·partition / intraday 缓存重构 / 限频重试,均未做。 -- ✅ **数据层 D3 — report-only 数据质量层**(**PR #45 已 merge 到 `main`**,含 review 一修复;纯库 + 测试,**report-only 非 cleaning**):新增独立 `data/quality/` 包,在接入处附近**只报告**可疑上游数据,**绝不**过滤行/修复值/改 qfq/改 cache coverage/动 feed·factor·alpha·portfolio·runtime。 - - **包**(纯函数,输入 DataFrame→findings,输入永不被改):`report.py`(`QualityFinding` + `make_finding`(样本限 5、清洗)+ `findings_to_frame`(稳定确定序)+ `render_report`(确定性 Markdown,clean→"No findings")+ secret redaction);`market.py`(日频:dup(date,symbol)/非正 OHLC/high1` 且多 symbol → **只有日频密集 per-symbol 取数**走有界 `ThreadPoolExecutor`,**store upsert + ledger 写仍在主线程按计划序**(成功 gap 先 durable 再抛首个失败,失败 gap 不记覆盖、可重试;store/ledger 内容与取数完成序无关)。snapshot / `index_weight` 90 天分页 / intraday 在 D5 仍串行(并发开启时仍共享同一限频器)。 - - **updater 接线**:`run_data_update` 仅 `max_workers>1` 才建**一个** `GlobalRateLimiter`(用 `rate_limit_per_min`),经 `_build_feeds` 共享给全部 feed(6 个 feed 加 `scheduler=None` 默认不变参);`UpdateResult`+`format_summary` 暴露 `max_workers`/全局 `rate_limit_per_min`(非密)。 - - **测试**(`tests/test_scheduler.py` + `tests/test_data_update_concurrency.py`,network-free,21):全局时隙在争用下逐次升 sleep(fake clock)/ **8 线程实测全局非按线程**(`next_allowed==N*interval`)/ 重试逐次 acquire / scheduler 模式不做 per-call sleep / 失败 secret-safe(仅异常类型)/ feed 转发其 scheduler / config 校验(`<1` 拒;默认 1)/ **serial==concurrent** 帧·ledger·请求计数 / 失败留 gap 未覆盖可重试而成功 durable / empty·not_ready 不变 / 单 symbol 仍串行。真实 token 扫描 **0 hits**;**未跑 live `data-update`**(network-free 验收)。 -- ✅ **数据层 schema 注册表 + default-off drift 守卫**(**PR #57 已 merge 到 `main`**,行为保持型、默认关闭、opt-in):把缓存已存却无人校验的 `fields_hash` 升级成显式 schema drift 守卫。**核心洞察**:每个 `_parse_*` 硬编码 canonical 列集 + 防御性 `fillna`,故存储侧 `fields_hash` 对 tushare 改列免疫——真风险在 **raw 输入侧**(tushare 删/改 parser 依赖的源列 → 静默 NaN 入缓存零告警),守卫在 **parse 之前**查 raw frame。 - - 新 `data/cache/schema_registry.py`:每端点声明式 `EndpointSchema`(required/optional/known_extra 源列 + canonical 列 + 自然键 + 期望 canonical hash,从 `tushare_specs.py` **派生**=单一真相源)+ 有状态 `SchemaGuard`。**4 检查**:#1 缺 required 源列(HARD)/ #2 真未知额外源列(WARNING,被 `known_extra_columns` 目录收窄,只对真新列响)/ #3 parsed canonical ≠ 注册表(HARD)/ #4 存储 `fields_hash` ≠ ledger 历史(HARD,每端点每 run 去重一次,迁移检测器)。 - - **模式**:`report_only`(启用默认,只记 findings + 一行 secret-free summary 日志)/ `strict`(opt-in,HARD 在 upsert **前** raise → 该 gap 不记覆盖、可重试,沿"failed fetch 不记覆盖"语义)。接进 `qt/pipeline.py::_build_cache`(仅 enabled 时建,默认 None);`data/cache/coverage.py` 加只读 `last_fields_hash(endpoint)` 访问器。 - - **不变量**:`data.cache.schema_guard.enabled=false` 默认 → `_guarded_parse(None)` 精确直通、既有 cache 测试全过、factor/alpha/portfolio/runtime/qfq/coverage 语义不变、phase0 `0.9600/0.8408` 不变;findings/日志/异常只含 endpoint+列名+16 字 SHA1,**无 token / `.config.json` / 数据值**;**仅日频 `TushareCache`**,分钟 schema 守卫 defer。 - - **review 两轮**(cc 接替外部 reviewer 验收):security PASS;code-review 首轮 BLOCK 修两 HIGH——`index_member_all` 误把 SW 级名标 required(strict 下缺级名永久 raise,直连路径 `tushare_covariates.py:155` 已 guard 证明 tushare 真省略)→ required 收敛到 `{in_date}`、级名入 optional(WARNING);check#2 噪声 → 加 `known_extra_columns` 目录只对真未知列告警;另:module `assert`→`raise`(`-O` 安全)+ 反向 droppability lock 测试 + check#3/#4 secret-absence 断言。**独立重跑 758 passed / ruff clean / 19 配置 / phase0 0.9600·0.8408 / `-O` registry-sync 守卫成立 / secret 0 新增**。 - - **已知 follow-up(本 PR 范围外)**:守卫只接 pipeline/回测缓存路径,**未接 live `data-update` 暖跑(`qt/data_updater.py`)**——那是下一步最该开 drift 检测处。 -- ✅ **数据层 盘后全A 自动拉取 —— PR-1 全A 增量 auto-warm + systemd 定时**(**PR #59 已 merge 到 `main`**,行为保持型、默认关闭、装不 enable;**2-PR 之 PR-1**,历史回填是 PR-2):先做数据正确性审计(D3 质量层真跑 80GB 真实缓存:daily 全扫结构 nan/dup/high today` 守卫**:逆序窗口本会被 `subtract_intervals` 当"零缺口=已覆盖"→ 未来 start typo 静默 no-op;故加 loud `ValueError`(仿 `DataCfg._check_date_order`)。config `data_update.backfill: {start, chunk_size, include_minute}`(全默认 → 现存配置照常 validate);`config/data_update_all_a.yaml` 加 `backfill:` 块 + 诚实成本注(冷全A 分钟回填数小时~多日,可续跑)。ops doc 加"历史回填"节(先回填再 enable 夜间 timer)。 - - **不变量**:增量 `run_data_update` 路径**字节级不变**——唯一共享改动是 `_build_scheduler`/`_build_caches` verbatim 抽取(same kwargs/顺序);49 个既有 data_update 测试原样过(main-vs-branch pytest 对照 **771→793 恰好 +22、0 既有测试被动**)。phase0 `0.9600/0.8408` 不变;缓存仍 raw-only;无 token。 - - **review 两轮**(cc 接替外部 reviewer 验收):security CLEAN(失败/进度日志只 counts+异常类型);code-review 无 CRIT/HIGH(共享重构 verbatim 验证),修 2 MEDIUM(`start>today` 静默 no-op 守卫 + 失败×续跑联合测)+ LOW(summary 显并发模式)。独立重跑 **793 passed / ruff / 20 配置 / phase0 0.9600·0.8408**。**建设侧完成;live rollout(先 `data-backfill` 补史 → 手动增量 warm → enable timer)= 用户 Stage-1 driver**。 -- ✅ **因子评估契约层 `analytics/eval/` + 十一个分钟因子复现**(**PR #63–#73 已 merge 到 `main`**,**EXPLORATORY,非业绩声明**):研报 → 复现循环(10/10 收官)与支撑它的判定契约层。全部因子同一数据面:CSI500 `000905.SH` 2021-07-01→2026-06-30 日频、cache-only(`stk_mins_live_calls=0`)、每因子 no_book / with_book 两跑(因子簿 = 已确认的 value_ep + value_bp + volatility_20)。 - - **契约层(PR #63,v0.1→v0.7 迭代后 frozen)**:`FactorSpec`(Factor 必带 classattr、**预注册 `expected_ic_sign`**、PIT 契约、intraday 块校验)+ `FactorEvaluator` ABC(8 个必填 section,缺一即 raise)+ `FactorEvalReport`(确定性 md/json,secret-redacting renderer)+ **三轴判决 Predictive × Incremental × Tradable → Adopt / Watch / Reject / INSUFFICIENT-DATA**。三条判定纪律:**非对称门**(hard Reject 先于样本量门)、**unknown never convicts**(判不了绝不定罪)、**PASS 一律读 N_eff 置信区间下界**(`N_eff = N/(1+2Σρ)`,Geyer 扩展;点估计不得买 PASS)。v0.7:增量轴独立门 `min_incremental_abs_icir=0.15`(正交化收缩残差尺度,沿用原始 0.30 是类别错误)+ 单调性作**方向门**(默认 0.0,非强度门)。exploratory 因子**封顶 Watch**(Tradable 轴全程 NOT_ASSESSED)。一次向量化 IR 喂全部 section(与朴素逐期循环等价性测试锁定)+ 单 PNG dashboard,带**强制 FACTOR DEFINITION 带**——不说明怎么算就不许展示因子。 - - **十一因子(下表 verdict 为当前 v0.9 close-to-close 口径,非各 PR 当时口径;出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` 参照表 + `tmp/Quantitative_Research_Report/factors/ledger.md`)**: +## 当前状态(截面写于 2026-07-30,均为实跑验证) - | PR | 因子 | 家族 | sign | IC | ICIR | 换手 | 净1× | no_book / with_book | - |---|---|---|---|---|---|---|---|---| - | #63 | `jump_amount_corr` | 跳跃×成交额相关 | −1 | −0.0298 | −0.401 | 0.415 | −0.000834 | Watch / Watch | - | #64 | `minute_ideal_amplitude` | 振幅 | −1 | −0.0291 | −0.464 | 0.990 | −0.001981 | Watch / Watch | - | #65 | `amp_marginal_anomaly_vol` | 振幅波动 | **+1** | −0.0412 | −0.425 | 0.285 | −0.000504 | Reject / Reject(documented negative:预注册 +1 未迁移,**实测负 IC ≠ sign=−1**) | - | #66 | `volume_peak_count` | 计数 | +1 | +0.0177 | +0.210 | 0.304 | **+0.000071** | Reject / Reject(符号迁移成功但 ICIR 下界 0.151 < 0.30 预注册门) | - | #67 | `intraday_amp_cut` | 振幅 | −1 | −0.0347 | −0.435 | 0.578 | −0.000260 | **INSUFFICIENT-DATA** / Watch | - | #68 | `peak_interval_kurtosis` | 时序 | +1 | +0.0002 | +0.006 | 0.576 | −0.000463 | Reject / Reject(诚实空结果;研报称 RankIC +7.19%/ICIR 4.63 → 本循环最大复现落差) | - | #69 | `valley_relative_vwap` | 价格比值 | +1 | +0.0327 | **+0.506** | 0.473 | −0.000085 | Watch / Watch | - | #70 | `valley_ridge_vwap_ratio` | 价格比值 | +1 | +0.0339 | +0.425 | **2.101** | −0.001705 | Watch / Watch(对齐单调 CI 下界仅 **+0.0010**,全表最薄) | - | #71 | `ridge_minute_return` | 收益 | −1 | −0.0312 | −0.381 | **2.108** | −0.001983 | **INSUFFICIENT-DATA** / Reject(增量轴与因子簿冗余) | - | #72 | `valley_price_quantile` | 价格位置 | +1 | +0.0265 | +0.414 | 0.679 | **+0.000048** | Watch / Watch(按日单调 +0.1064 全表最高) | - | #73 | `peak_ridge_amount_ratio` | 成交额 | +1 | **+0.0401** | +0.488 | **2.127** | −0.001402 | Watch / Watch(IC 全场最高,零价格含量) | +| 项 | 值 | +|---|---| +| `main` | `c9cc12e`(PR #109) | +| `pytest -p no:warnings` | **2537 passed**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | +| `ruff check .` | clean | +| phase0 锚(`run-phase0 --config config/example.yaml`) | **ic 0.9600 / annual 0.8408**(自 P0 起从未变过,任何改动都不许动它) | +| `qt.exec_baseline_freeze --verify` | **77/77 @ `45c14aa`** | +| `validate-config` | 32/32 | +| 唯一 OPEN 的 PR | #38(历史遗留,保持 OPEN) | + +**当前主线 = 因子层深度重构(设计 v3.2,D0–D7),走到 D5 C5(四腿全量对账)。** 全量跑已完成并暴露五类失败 F1–F5,全部已根因定位并裁定,**修复代码尚未开始**。 + +> **接手必读(唯一入口)**:[`tmp/design/HANDOFF_2026-07-30_claude_code.md`](tmp/design/HANDOFF_2026-07-30_claude_code.md) +> ——含当前截面、F1–F5 裁定速查表、六步路线、派发/评审/限额的具体操作法、陷阱清单。 +> 伴侣文档 `tmp/context/cc_handoff_20260730_d5_c5/HANDOFF.md`(**动手修 F1–F5 前必须整读其 §2**,逐格实测证据在那里)。 +> 设计权威 `tmp/design/factor_refactor_design_v3.md`(红线 #1–#11、四腿对账、D0–D7、实施期修订 A1/A2/A3)。 +> ⚠️ **`tmp/` 被 `.gitignore` 排除**——这三份文档只在本机,不在 git 里。 + +## 常设授权(owner 明确给过,不必逐项再确认) + +- **git 自主推进**(2026-07-27):commit / push / 开 PR / merge 不必逐项确认,推进到重构完成或新指令为止。 +- **14:50 截断**(2026-07-19):全日数据的分钟因子一律截断到 14:50,授权直接改,不必逐因子再问。 +- **C6 退役重生成能力**(2026-07-28):删旧 runner 的同时把 `panel_freeze` / `panel_reconcile` / `hand_anchors_engine_values` 改为 verify-only,D1 基线 frozen-forever。 + +## 工作制度:双轨验收 - - **三条循环级结论**:① **家族图谱**——计数弱正 / 时序归零 / 收益 Reject / **价格比值×2 + 价格位置 + 成交额 = 四个双轴 PASS**;峰岭谷行为学划分本身有效,且 **不止价格信息有效**(PR-M 零价格含量却 IC 最高)。② **IC 强 ≠ 可交易(贯穿证据)**——11 个因子里只有 PR-F(+0.71bp)与 PR-L(+0.48bp)在 1× 费率下净多空为正,且 PR-L 扛不住 2×;毛价差常相近,差距**几乎全部来自换手**(PR-I/L 的 0.47/0.68 vs PR-J/K/M 的 2.1+)。③ **假说证伪记录(不得作为已确立规律引用)**——"秩IC/Pearson 背离比预测单调性"被 PR-M 证伪(3.86×→0.60 与 PR-J 4.22×→0.90 交叉),仅两个极端处有不确定关联。 - - **方法论(本轮建立,已作常设规则)**:① **不可能失败的测试**——三个实例:PR-L 的旗舰反泄漏测试(全截面统一扰动是带截距 OLS 残差不可见的仿射变换)、`compare_postmerge.py`(拿新结果和自己比,见下条 #74 重跑对账)、I5b fixture 给每根 bar 定价使 **VWAP ≡ close**(对"闸门读哪个"结构性失明)。**任何不变性测试都必须有实际跑过的 mutation 证据,且 mutation 要打中所主张的那个具体性质**。② **行为变了而措辞没变**(#76/#78 实证)——正确的修法**不是**"断言不存在第 N+1 处副本"(#76 就是这么做的,用扫描式正则,被七种改写全部绕过),而是 **把主张只写一遍、其余每一处都去组合它**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以**。 -- ✅ **因子评估契约 v0.8 + v0.9**(**PR #74 已 merge 到 `main`**,`1ae555e`):十一个因子在 `analytics/eval/` **严格 no-touch** 下跑完后暴露的两项 frozen 缺陷,本 PR 是**对该 frozen 契约的首次授权修改**。 - - **缺陷一:对齐净价差把成本加了回去(判定门缺陷,非仅展示)**。四处写成 `sign * net`(`net` 已扣成本),`sign=−1` 下展开为 **`−gross + cost`**——因子付的手续费被当利润返还。正确语义 `aligned = sign*gross − cost`(先按假设翻腿、再永远扣成本)。**其中两处在 `verdict.py`(`_base_spread` / `_all_spreads_negative`),直接喂 Tradable 轴 PASS 条件**,历来未触发仅因该轴全程 NOT_ASSESSED。手算实证:`gross 0.000125 / net 1× −0.001983 → cost 0.002108 → aligned −0.002233`(缺陷式给 **+0.001983**)。`sign=+1` 逐比特不变。年化展示值修正:jump_amount_corr **+23.14%→−0.01%**、minute_ideal_amplitude **+64.07%→−0.27%**、intraday_amp_cut **+6.30%→−20.52%**、ridge_minute_return **+64.28%→−43.25%**(**旧值全是虚高**)。 - - **缺陷二:用量级敏感的池化统计量给秩基轴把门**。`monotonicity_spearman` 是"桶序号 vs 跨日等权算术均值"的相关,无界,少数极端收益日集中于某桶即可翻号,而每日封顶的秩 IC 几乎不动。新增 `monotonicity_spearman_by_date`(逐日 Spearman,先封顶 [−1,1] 再平均,与秩 IC 同构)并把门移过去;池化版保留为报告字段 + 回退披露。 - - **v0.9:新统计量被日内噪声衰减,裸 0.0 门落在噪声带内**。实测分位阶梯完美的因子按日只有 **0.045–0.106**,门却仍在裸 0.0 且无离散度估计,两个因子相差 0.021 就分生死。改为读该按日序列的 **N_eff 置信区间**(复用既有 `mean_ci`),三值:对齐 CI 下界 > bar → **HOLDS**;对齐 CI 上界 < 0 → **CONTRADICTED**(FAIL);跨零 → **UNKNOWN**(既不定罪也不放行)。实测 CI 半宽 **0.0291–0.0448** → 裸 0.0 实为一道约 0.035 的隐式门槛。**最要害的路由规则:UNKNOWN 绝不救本就不合格的因子**——单调性已从点信号布尔链中摘出(三值事实塞进 `and` 链必然坍缩),ICIR / NW-t / win / OOS 任一不清关仍走 FAIL,UNKNOWN 只在其余全清关时把预测轴落到 INSUFFICIENT_DATA。 - - **3 处 verdict 变化**(全部归因于门的变化,并如此陈述):`intraday_amp_cut`(no_book) Reject→INSUFFICIENT-DATA(对齐 CI [−0.0353,+0.0343] 跨零)、`intraday_amp_cut`(with_book) **Reject→Watch**(同上 + 增量轴本就 PASS)、`ridge_minute_return`(no_book) Watch→INSUFFICIENT-DATA(对齐 CI [−0.0151,+0.0558] 跨零)。`ridge_minute_return` 跨三版本轨迹 **v0.7 Reject → v0.8 Watch → v0.9 INSUFFICIENT-DATA**:前两次都是在噪声上判决、只是方向不同,第三次才承认判不了。 - - **不变量**:IC / ICIR / NW-t / win / 增量 ICIR / 两个单调点值 **全部逐比特不变**(修复未泄漏到 IC 路径与增量路径)。**路由自检**:`amp_marginal_anomaly_vol`(OOS 符号翻转,第一步即 FAIL)与 `peak_interval_kurtosis`(OOS 不一致 + ICIR 0.006)单调方向均为 UNKNOWN 但仍 Reject,无渗漏。 - - **合并后十一因子干净全量重跑(`main` @ `1ae555e`,11/11 rc=0,~87min)**:`repro_reconcile.py` 11 因子 × 20 字段 = 220 项,**容差 0.0**(精确浮点相等,不用 `isclose`、不留 epsilon —— 1e-6 是"可能动"的下限,用恰好为零来测才能让任何 ≥ 该下限的变化浮出来)→ **0 处差异**;`repro_compare.py` 对 22 个 JSON artifact 做展平叶子深比 → **5,756 叶、0 处不同**,外加 **22/22 Markdown 逐字节相同**。防空过:22 个 JSON 确实被重写(mtime 落在各自 run 窗口 09:24:33→10:43:54)、前后 HEAD 都是 `1ae555e`、11 个 rc=0、全程 `stk_mins_live_calls=0`。 - - ⚠️ **记一次自己的失误:写了一个不可能失败的对账脚本**。第三个脚本 `scratchpad/compare_postmerge.py` 曾被跑并报出结论,**但它是空的,其输出什么也不证明**:它执行 `_baseline_table.py` 取新值,而那个脚本第 123 行**会写** `baseline_rows.json`,随后"旧值"读的正是刚被覆盖的同一个文件 → `old == new` 由构造成立,哪怕 artifact 错得离谱也会打印"11 个全部精确复现"(已在沙箱里用 baseline `ic=1.0` vs fresh `ic=999.0` 复现该假通过)。它还顺带毁掉了 `baseline_rows.json`。**这正是整轮重跑要消灭的失效模式,而我们亲手造了一个**(同 P2-3「曾误以为是粒度」、P3-4「过拟合签名」,照实记录)。上面的结论只站在不碰该文件的两个脚本上(其中一个跑在覆盖之前)。**权威基线是 `scratchpad/baseline_reports/`(22 文件,冻结于 run 前);复核必须比它,不是 `baseline_rows.json`。** -- ✅ **尾盘执行价改 VWAP + 分钟持有期收益复权**(**PR #75 已 merge 到 `main`**):两项改动都源于"14:50 就知道的信号为什么按 15:00 形成的价格计分"。 - - **改动 1:成交价 = 执行 bar 的 VWAP**(`amount/volume`,整分钟成交量加权均价)而非收盘 tick。`IntradayCfg.execution_price_basis: "bar_vwap"|"bar_close"`,默认 `bar_vwap`;`bar_close` **逐比特复现旧行为**——用独立 worktree 跑 main 版本、**字节比对价格矩阵与完整 fills 日志**(含 `exec_time` 与 block `reason`)验证,非断言。VWAP 未定义(volume≤0 / amount≤0 / 非有限)→ 走**显式阻挡**路径,**绝不退回 bar 收盘价、更不退回日收盘**。 - - **闸门改比实际成交价**:涨停分钟只有两种形态——**LOCKED 封死**(全部成交在涨停价 → VWAP = 涨停价)与 **OPENED 盘中打开**(有成交低于涨停 → VWAP < 涨停价),恰好对应"能不能买到";按收盘价判两头都错。这是**有意的行为变化,且被计数不被静默**:`opened_limit_up_minutes()` / `opened_limit_down_minutes()` 报出与收盘价闸门的每一处分歧。真实数据取证:`601858.SH 2023-05-15 14:51`,close = up_limit 46.13、VWAP 46.048583(差 0.18%,远高于舍入量级)。 - - **容差保持 `1e-6`**:实测 13,699 根 14:51 bar(149 根收在限价),LOCKED 偏离 ≤0.0021%、OPENED 0.013–0.017%(两总体不重叠)。1e-6 下 100% 的 OPENED 正确放行,代价是 2.1% 的 LOCKED 被误判为打开(≈全样本 0.02%)。**残差偏乐观且有界**,已写入 docstring。0.001 在本样本双向为 0%,但那是在 149 个观测上重标定阈值 = 后验调参,不为 0.02% 的效应破例。 - - **改动 2:分钟持有期收益复权(先前缺陷)**。`holding_returns` 原在 **raw 未复权价**上算 `exec(exit)/exec(entry)−1`,而日频模型用 qfq 面板 → 两条路径不对称,除权除息的机械性下跌被读成亏损。改为 `(raw_exit·af_exit)/(raw_entry·af_entry)−1`(qfq 锚在比值中相消,无需另存复权序列)。缺 `adj_factor` **不静默按 1.0**(那会把刚去掉的除权偏差放回来),走既有丢弃路径 + `missing_adj_factor_pairs()` 计数。复权**不渗进 I5b 限价闸门**(保持 raw-vs-raw),有专测钉住。 - - **实测发生率(月频调仓)**:I5d CSI500 52,923 个 (symbol, 持有期) 对中 **4,450 (8.41%)** 跨 `adj_factor` 变化,修正均值/中位 **+3.92% / +1.07%**,92.9% 为正;I5e CSI300 26,314 对中 **2,566 (9.75%)**,**+3.74% / +1.31%**,94.5% 为正。均值远高于中位 = 右尾**送转股**(af 翻倍 → raw 价腰斩 → 未复权读成 −50% 假亏损)。 -- ✅ **报告披露修正 + I5f 名义按真实规模重测**(**PR #76 已 merge 到 `main`**,`runtime/` 行为未变):**报告曾谎报自己做的检查**——PR #75 把闸门从 bar 收盘价移到实际成交价,却漏改了两个 report writer 里**描述闸门本身**的那句,此后每次跑出的报告都在描述一个它没有执行的检查。在"绝不静默降级"是常设红线的项目里,报告写错自己的方法比不出报告更糟。 - - **三个一直在算却被丢弃的计数器进报告**:`opened_limit_up_minutes` / `opened_limit_down_minutes`(本闸门与收盘价闸门的**全部分歧集合**——什么能不能成交变了,绝不许静默)、`missing_adj_factor_pairs`(因锚点 `adj_factor` 不可用而丢掉的持有期;非零即已测覆盖率短少)。尾盘报告另行声明收益是复权的并给出恒等式("exec-to-exec"一词已不足以描述它们)。 - - 措辞抽成 `limit_basis_lines()` 并由三个测试钉住;**mutation evidence 实跑**:把措辞改回收盘口径 → 2 个测试 FAIL;从分组表里删掉新计数器 → 第 3 个 FAIL。**旧 I5b 测试为何没抓到**:其 fixture 给每根执行 bar `volume=1.0, amount=close` → **VWAP ≡ close**,使所有断言在两个基准上都成立,对"闸门读哪个"结构性失明;已写进 module docstring 防止重建同一盲点。 - - ⚠️ 本 PR 的守卫办法**随后被证明不够**——见下条 **PR #78**。 -- ✅ **`adj_factor` 下降质量检查**(**PR #77 已 merge 到 `main`**,D3 层新检查,WARNING、report-only):`data/quality/market.py::check_decreasing_adj_factor` 接进 `run_adj_factor_checks`。tushare 的累计复权因子随每次公司行为**只增不减**,`front_adjust` 拿它乘 raw 价,一个发布出来的下降会直穿到每一个下游收益;而真实实例是**手工**发现的(见下条审计)。 - - **阈值 1%(相对,非绝对)由全缓存扫描定**(扫全部 5562 只已缓存票的每一个负步进:7898 个事件、涉及 4479 只票): +- lead 只做调度与统筹(任务书 / 派发 / 裁决 / 合并 / 写进度文档);**实现与评审派不同的 subagent**,评审是**对抗性**的(自写 fixture、自跑 mutation,**不复核对方的自述**)。这套制度累计抓出 **6+ 个真 HIGH**,多数是"测试全绿但结论错"。 +- **验收双轨**:评审裁定 **+ lead 独立重跑 gates**;**绝不采信 subagent 自报的数字**。 +- 评审的裁定只覆盖它实际看到的 commit;后落的 commit 要么补一轮评审,要么在 PR 正文里**显式声明未经评审**。 +- 具体派发形态、限额中断处理、worktree 陷阱见 handoff §4/§5。 - | 阈值 | 触发事件数 | 触发的**非已知缺陷**票数 | - |---|---|---| - | 0.01% | 3653 | 2429(纯浮点噪声) | - | 0.10% | 179 | 2 | - | 0.50% | 178 | 1(`603081.SH`) | - | **1.00%** | **177** | **0** | - | 2.00% | 176 | 0 | +## 方法论(比任何单条代码都值钱) - 低于 1% 会点名状态**判不了**的票:`603081.SH` 最坏步 −0.776%,对应 raw close −1.810%,而真实公司行为需要 +0.782%——有缺陷的**形状**,但在该量级上普通日波动淹没隐含值,价格检验没有功效,结论不可解释。高于 1% 则漏掉一个真事件(1% 触发 177、2% 只触发 176,多出的那个属已知缺陷票)。1% 同时让最小的**已确认**缺陷(`000998.SZ` −2.636%)站在门槛的 2.6× 而非 1.3×——report-only 层宁可误报(代价一行 WARNING)也不可漏报(代价是被污染的价格静默通过)。 - - **相对而非绝对**(factor 跨缓存 ~1…~500):mutation 实证 `pct_change()`→`diff()` 让原六个测试**全部通过**(fixture 都落在 factor ~1–11 两种切法重合处),补两个别的尺度才有牙(`0.200→0.195` 是真 −2.5% 断裂而绝对门看成 −0.005 放行;`500.0→499.97` 是 −0.006% 舍入而绝对门 −0.03 误报)。 - - **本条 WARNING 的语义与 `check_extreme_returns` 不同**:本模块所有 HARD 检查都是零参数的代数不可能(重复键 / 非正 OHLC / high50% 的收盘跳变确实可能是拆股或复牌,而累计复权因子**没有任何合法的变小方式**。severity 反映的是阈值标定风险,不是对主张本身的怀疑。 -- ✅ **闸门描述改单一来源**(**PR #78 已 merge 到 `main`**,`ecd8db2`;#76 的结构性 follow-up):#76 加了一道扫描式正则守卫,并在 commit message 里声称"再多一处错误描述就会失败而不是发出去"。**review 用实测证否了这个声称**:写了七种自然改写("its last print"、"the final tick"、"1-minute" 代替 "1min" 等)跑真正的正则,**七种全部逃逸**——这是拿词法守卫去守语义主张的普遍下场。 - - **修法是结构性的,不是再加一条正则**:`limit_basis_phrase()` 成为**唯一被撰写的那句话**,feasibility 段、分组报告、报告的 Limitations 条目三处**组合它**而非各写各的;`_load_price_limits` 的 docstring 改为指向它而不再复述。本 PR 只改"有多少处可以各自出错"。正则守卫**保留**,但 docstring 缩到它真能做的事:抓**字面 revert**(坏 merge / rebase 把旧句子带回来),而这正是真实发生过的那次失效。 - - ⚠️ **"渲染文本逐字节不变"这个声称一开始是错的**(合并前修掉,`5b4d1ec`):一次**渲染探针**(在 main 与分支上各自构造同样那几行、比较字符串)查出**三个宿主里有两个丢了收尾的破折号**,渲染成 `...on the `bar_vwap` basis to the raw `stk_limit` band` 这样**破折号不成对**的病句——即一次以"没有输出变化"名义发出去的真实输出变化。**review 在探针跑之前恰好问过这个问题**("在一处读起来通顺的片段,换个上下文可能不合语法"),答案是它已经发生了。两处修复后重跑探针:所有组合出的行与 main **字符串完全相同**,并加测试钉住"片段被嵌进句中时,起头的破折号必须在其后被闭合"。 - - **代价面照记**:把一个片段组合进多个句子,是用**"三处可以各自把事实说错"换成"一处可以把标点打错"**——仍是划算的交易,**但它是一笔交易,不是白拿**。 - - **可带走的规则(已写进上面的方法论 ②)**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以。** mutation 证据用的正是 review 自己那批能逃逸的措辞——它们**通过**正则守卫、**失败**于结构性测试(Limitations 条目改写成"the execution minute's final tick"、分组报告改写成"the selected bar's last print",两者都被结构测试判死)。 - - gates(`5b4d1ec` 修复后):`pytest` **1742 passed**、`ruff` clean、phase0 `0.9600/0.8408` 不变。 -- ✅ **日频除权正确性审计**(用户点名最高优先级;调查记录 `tmp/design/FINDING_adj_factor_seam_2026-07-21.md`,全程只读 `artifacts/cache/tushare/v1`,无独立 PR):**结论 GENUINE——日频 qfq 路径本来就是对的,没有 vintage 接缝**;十一因子的 close-to-close 前瞻收益一直是除权正确的。 - - **接缝假设被证否**(双向检验:重述接缝既可能是降也可能是升):CSI500 池 996 票、979 票的 `adj_factor` 来自 >1 次 fetch、960 对相邻交易日跨 vintage 边界,其中因子**真的变化的只有 12 对**——全部为正、全部落在同一个边界(2026-05-29→06-01,A股年度除权高峰首个交易日,12/996 = 1.2% 属正常季节性),且大的都伴随 raw 价格的对应跳变(`002837.SZ` Δaf +30.24% / raw close −27.41% / 隐含 −23.22%)。**任何 fetch 边界上都不存在负向因子变化。** - - **`−57.27%` 是北交所占位符,不是分布尾部**:`920627.BJ` 一只票占全部 −2% 以下 176 行中的 171 行,因子在 `2.3404 ↔ 1.0000` 之间反复跳(175 行正好等于 `1.0000` = "无调整"占位符),其数据来自**单次 fetch**(一次 API 响应内不可能有跨 vintage 接缝),且**连一行 `market_daily` 收盘都没有**(永远进不了价格面板)。`1/(1−0.572723)−1 = +134.04%` —— 最初报的 `+134%` 上尾与 `−57.3%` 下尾是**同一只票的一正一负**。 - - **评估池内暴露:603,258 个 (symbol, date) 对里 1 对**(`000998.SZ` 2023-06-01,PIT 确认的 CSI500 成分:af −2.636% 而 raw close **+1.228%**、当日**开盘 −0.41% 没有任何机械缺口** → 判 **AMBIGUOUS**,倾向 tushare 自身发布缺陷而非缓存 artifact);**CSI300 零**。传播后:单日分位桶均值畸变 0.0208pp、摊到 1,211 个评估日 **0.0000172pp**、多空价差 ≤0.0000344pp——比任何能动 verdict 的量级低四个数量级,**不重述任何因子结果**,只作披露。 - - **污染的均值 vs 干净的均值(独立复测)**:全 CSI500 评估窗口日频 1,154,642 对中 6,906(**0.5981%**)跨因子变化,均值修正 **+2.28%**、中位 **+0.52%**、82% 为正,摊到全样本 **+1.37bp/日 ≈ +3.4%/年**(正是 A股股息率量级)。与最初被污染的测量对照:**发生率(0.604% vs 0.5981%)与中位(+0.45% vs +0.52%)一直是对的,错的只有均值(+4.38% vs +2.28%)与尾部(+134%)**——单个极端污染样本的典型签名(中位稳健、均值不稳健)。**经验:当均值与其中位差一个数量级,先去找那一行坏数据,别先给分布编故事。** - - 与 PR #75 的关系:**那是另一个 bug**——*分钟* `exec_to_exec` 路径用未复权价算持有期收益,日频路径从来不受影响。BSE `920xxx.BJ` 的 `adj_factor` 一律按不可信处理(零成本:无研究 universe 含它们,且它们没有日线价格行)。 -- ✅ **I5d / I5e / I5f 在修正引擎上重跑**(出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md`;**EXPLORATORY,非业绩声明**)。⚠️ **读数前提(不许混谈)**:三个 run 的 NAV 全线上移**全部来自 bug 修复**(把除权除息还回去),**不是业绩改善**;VWAP 成交基准本身是**略微不利**的(−0.18pp)。 - - **I5f 四档归因**(SSE50 2026-03-03→06-12,3 期,`covered=58/58`,`stk_mins_live_calls=0`,每档同窗口实跑、确定性): +1. **每个 mutation 都要先断言它真的改变了目标。** 本项目累计 **9 例"不可能失败的测试"**,其中三次出在同一天同一份工作里,**没有一次是被测试抓住的,全是被"与预期矛盾的观察"抓住的**。评审提出的建议本身也需要 mutation 证据。 +2. **正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以。** 措辞/事实只写一遍(author-once),其余每处都去组合它;扫描守卫只是网,且**守卫的射程必须写进它自己的 docstring**。一个只扫你已经修过的地方的守卫,永远只能确认你已经知道的事。 +3. **失配摧毁意义 → 拦;失配只带来已披露、方向已知的偏 → 记。** +4. **未编目的差异算失败**(设计 §六.5);**性质测试跑不过时修引擎、不改测试**(§六.16)。 +5. **"预期全绿却没绿"时先怀疑预测本身**——已有两次源头是上一份交接的预测写错了,不是引擎 bug。 +6. **从一个样本推总体会造出假警报**;**均值与中位差一个数量级时,先去找那一行坏数据,别先给分布编故事**。 +7. **"报告没说出关于它自己出处的该说的话"与"声明了它没做的检查"是同一类缺陷**——已出现三次。 +8. **卡住就停下上报,别夹带。** 正因如此,一个已发布结论的更正才没被混进重构 PR。 +9. 研究侧:**IC 强 ≠ 可交易**(11 个分钟因子里 1× 费率下净多空为正的只有 2 个,差距几乎全部来自换手);**非业绩声明**是所有因子结论的默认前缀。 - | 引擎配置 | final NAV | delta | - |---|---|---| - | 已验收 I5b/I5f:`bar_close` 成交 + **未复权**收益 | `0.976448` | — | - | `bar_close` 成交 + **复权**收益 | `1.021137` | **+4.47pp** | - | `bar_vwap` 成交 + 复权收益(**新默认**) | `1.019318` | **−0.18pp** | +## 绝不可覆盖的 artifact - 复权修正 ≈ 全部位移(该窗口正跨 A股年度除权高峰,暴露处于季节最大值);**更诚实的成交价反而差 0.18pp**(单根收盘 tick 比整分钟成交量加权价"更走运",方向符合预期)。闸门没有放松任何东西:`opened_limit_up_minutes`=**0**、`opened_limit_down_minutes`=**0**(这正是本闸门与收盘价闸门可能分歧的**全集**,本窗口为空),涨停挡买仍 1 笔、跌停挡卖 0 笔;限价覆盖 174/174;`missing_adj_factor_pairs`=**0**(修正测在全样本上)。 - - **I5f 容量按真实规模重测**:`portfolio_notional` 10,000,000 → **1,000,000 RMB**(用户明示资金上限),`max_participation_rate` 0.05 不变。43 笔 desired trade / 1 笔已被既有 feasibility 挡 / 42 笔有可用 ratio:**below 1.0x 由 20 笔变 0 笔**,ratio min/p10/median/p90 由 `0.122/0.222/1.104/2.730` 变 **`1.217/2.219/11.037/27.296`**。**此前"约半数单分钟 desired trade 容量不足"描述的是一个比本项目会跑的任何组合大一个数量级的规模**;真实规模下单分钟容量在该 universe/窗口**不构成约束**(最紧 1.22×)。这**不取消** partial-fill / volume-cap(可能的 I5g),只是重新界定其动机;仍是诊断,**非可交易性/业绩声明**,不外推到其他 universe / 名义 / 窗口。 - - **I5d(CSI500 五分位,2021-06→2026-05,59 期):原结论不成立**。Q1→Q5 终值 `0.9822/1.0423/1.0495/1.1275/1.1577` → **`1.1758/1.2310/1.2313/1.3090/1.2712`**;**单调 Spearman +1.0000 → +0.9000**、合成 Q5−Q1 累计 **+17.15% → +8.65%**(腰斩)、**Q5 不再是最优组**(Q4 1.3090 > Q5 1.2712)——I5d 的全部主张就是那条完美的 Q1→Q5 排序,它断了。分钟覆盖 892/995 → 900/995;`stk_mins_live_calls=0`;限价覆盖 52836/52836;各组 `returns dropped: no adj_factor` 均为 **0**。 - **四档归因(四件事同时变,实跑中间配置而非猜)**:① 老引擎 + **新缓存**(900 票)Spearman 仍 **+1.0000** → **样本变化被排除**;② `bar_close` 成交 + 闸门不变、**只把分红除回去**即已 Spearman **+0.9000**、价差 **+8.83%** → **复权修正是全部效应**;③ 再换 `bar_vwap` Spearman 不变、价差 −0.18pp → **VWAP 基准近乎惰性**。**即 I5d 的完美单调性实质上是"把除权下跌记成亏损"的产物**:高 MMP 的 Q5 篮子除权暴露低于 Q4,错扣分红反而抬高了 Q5 相对 Q4;把账算对,Q4 就超过它。74 个 (date,symbol) 对收在限价却盘中打开、现在获准成交(opened limit-up 22/14/10/10/18、limit-down 3/2/1/2/4),真实且已披露,但按 ②→③ 不是移动结果的原因。 - - **I5e(CSI300 独立泛化):负结果不变且更强**。Q1→Q5 `0.8809/0.7377/0.9418/0.7573/0.6665` → `1.0919/0.8777/1.1575/0.9223/`**`0.7850`**;**Spearman −0.5000 完全不变**,合成 Q5−Q1 累计 **−26.77% → −31.82%**(更负);Q5 仍是最差组(年化 −4.81%、maxDD −47.79%);覆盖 448→450/481;opened limit-up 21 对(远小于 I5d 的 74)。**这正是该问的问题:若光靠复权修正就能翻转 I5e,那个负结果就有一部分是"把分红记成亏损"的假象。它没翻——泛化失败是因子的属性,不是执行假设的产物。** - - **MMP 合并读数**:唯一的正面证据在修正引擎上**退化**,独立 universe 上的负面证据**加强** → **暂缓 MMP 的理由比过去更强,而不是不变**。 -- ✅ **十一因子改用 14:51 VWAP 的 exec-to-exec 基准**(**PR #79 已 merge 到 `main`**,`b04c966`;⚠️ **合并方式见下条**;数字出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` + PR #79 正文):每个因子**只算一次、在两个基准上各评估一次**;既有 `{stem}_{no,with}_book` artifact 原样不动,新增的是 `{stem}_exec_{no,with}_book`。**why**:分钟因子在 14:50 截止时就已定死,而本框架**能建模的唯一成交**是 14:51 开始的那根 1min bar;按 `close(t+h)/close(t)` 计分等于把一个**本项目模拟不了的收盘集合竞价**算进因子功劳(`closing_call_proxy` 需要 `stk_auction_*`,数据方案不含)。 - - **回归先行**:rebasis 分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 个叶子、0 处不同** → 本次是**加了一个基准,不是移动了一个**(两个目录由两次独立文件读取比对,不经任何共享中间产物——正是上面那次空对账的教训)。 - - **22 格(11 因子 × no_book/with_book)中 20 格不变;移动的 2 格都朝"更没把握"走**: - - `valley_ridge_vwap_ratio`(no_book)Watch → **INSUFFICIENT-DATA**:对齐按日单调 CI `[+0.00100,+0.07110]` → `[−0.00535,+0.06804]`。它的 Watch 本就建立在 +0.0010 这个全表最薄的下界上(**跑之前就已在参照表里标注**),CI 稍宽即跨零 → 方向 UNKNOWN → 预测轴 INSUFFICIENT_DATA。其 with_book 仍是 Watch,但**改由增量轴独扛**(正交 ICIR 下界 +0.232 > 0.15)——同一个标签靠的是不同证据,reasons 里写明。 - - `ridge_minute_return`(with_book)Reject → **INSUFFICIENT-DATA**:close 基准上增量轴 FAIL(正交 ICIR −0.141≈0,与因子簿冗余);exec 基准上点估计 −0.151 方向仍对,但对齐下界 +0.084 够不到 0.15 门 → 既不通过也不否证。**这是把握变弱,不是翻案**:INSUFFICIENT-DATA 的语义是"既没证明也没证否",且它到不了正面主张。 - - **机制只有一个**:点估计几乎全面**变强**(|ICIR| 11 个里 10 个上升;唯一例外 `peak_interval_kurtosis` 0.0058→0.0008,本就是空因子),但 **N_eff 11 个里 9 个下降**(1187.7→1116.0、1009.5→933.3、1122.9→1055.8)——exec-to-exec 的 IC 序列比 close-to-close 带更多自相关,于是每个 CI 都变宽。**更强的点估计落在更宽的区间里**,恰是 v0.9 那道门被造出来裁的情形,而它裁得保守。 - - **新基准的诚实代价(两个分母刻意分开,因为 `1,541` 在两种口径下都会出现)**:**按锚点**(面板网格 1,158,912 个 (date, symbol) 对)——`ok` 1,156,368(99.78%)/ `no_bar` 执行窗口内无 1min bar **1,734(0.150%)**/ `bad_vwap` 有 bar 但 VWAP 未定义 810(0.070%)/ `bad_adj_factor` **0**。**按前瞻收益对**(每因子各自的评估网格)——比 close_to_close 少 3,170–3,274 对(**0.278%–0.283%**),涉及 175–180 只票,`bad_adj_factor` 十一个因子**全为 0**,`stk_mins_live_calls` 全为 0。**VWAP 定价比 bar-close 基准多挡 1,541 对(占收盘基准可测的 1,156,183 的 0.133%),因为那一分钟根本没有成交——这部分排除与流动性相关、有方向、非随机**(被排掉的正是最不流动的分钟):**这是有偏的样本限制,不是舍入损失;百分比小不等于随机,不许当随机报**(每份 exec 报告都带 `coverage_bias_bad_vwap` 原话,且 sanity 报告把这句话放在覆盖率表**之上**,使读者不会先看到百分比;有测试钉住措辞)。N_eff 在 9/11 上缩水是更真实基准**付出的统计功效代价**,是真实借方而非记账层面的假象。 - - **VWAP 与 bar close 在无条件分布层面几乎同一序列**:均值差 **+1.0e-07**(SE 1.3e-06,t = 0.074)、中位差 0、逐日相关中位 **0.998** → 改基准买到的是**执行锚点的真实性,不是另一个信号**,与 I5d 归因(VWAP 什么都没动、复权动了全部)一致。**close-to-close 结果保留作并排对照**,exec_to_exec 为操作基准。 - - **正确性验收(CSI500 全规模,996 票 / 1,210 日,cache-only,十个日频/universe gap-fetch 计数器全 0)**:① 与 close_to_close 的逐日截面相关中位 **0.9890**(p10 0.9821 / p90 0.9933,Spearman 中位 0.9814),下限 0.90 → PASS(**低于下限时 builder 直接 raise 而不是继续跑**);② 量级同阶(exec mean 0.000429 / std 0.027718 vs c2c 0.000451 / 0.028050);③ **5 行从 raw 1min parquet 用普通算术手算复核**(不 import 任何执行层 helper,故不会继承其 bug),最坏绝对差 **0.0 精确**;④ 除权持有期两种口径并列(如 `000564.SZ` 2021-12-29→12-31:未复权 −0.598581 → 复权 **−0.045367**)。gates(PR 正文):`pytest` **1774 passed**(= 当时 `main` 的 1740 + 34,恰为两个新测试文件、0 个既有测试被挤掉)、`ruff` clean、phase0 `0.9600/0.8408`、31 配置、secret scan 0。 - - ⚠️ **它是怎么合并的:派出的 code review 约 1.7 小时未返回,本 PR 靠自查而非评审通过而合并**(PR 正文下有一条 comment 如实说明,以免日后有人以为它被评审签过字)。**合并前独立核过的**:① 既有路径没动——分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 叶、0 处不同**(两个目录分别读取、无共享中间产物),与 diff **+2595/−0 纯新增**一致;② **`h` 走的是因子自己的评估网格而非价格面板的行**(`exec_forward_returns` 先 `isin(dates)` 再 `groupby(symbol).shift(-h)`,正是 `analytics/eval/ir.py::_resolve_forward_returns` 标注为 close 路径隐患的那条性质;非正分母掩成 NaN);③ 因子信息集**无前视**(日 d 的入场锚是 d 当天 14:51 的执行 bar,因子 14:50 已知,因果成立);④ 共享 artifact 的 **cache key 完整**(含 schema 版本 / universe 类型 + 指数代码 / 起止 / 缓存根 / `price_adjust` / 全部执行参数 / **精确 symbol 集合与精确日期网格各自的 sha1**)——陈旧 artifact 无法被喂给另一份配置,那正是"悄悄拿错收益给一族因子打分"的失效模式;⑤ 覆盖率成因**按构造互斥划分**(先归入场锚、否则归出场锚)。**未独立核、因此仍对后续检查开放的**:五条 mutation 声称(只读了、未重跑)、十一个 runner 改动是否机械等同、`EvalContext.execution_capacity` 透传是否在作者自带测试之外仍保持 Tradable 轴 `NOT_ASSESSED`。**若日后评审在这个未核集合里查出问题,要重述的是 exec 基准的数字——不是 close-to-close,后者未被触碰且仍是已验证的参照。** -- ✅ **闸门/成交价描述全仓清扫**(**PR #82 已 merge 到 `main`**,`f432dc5`;#78 的 review follow-up):#78 的守卫**只扫它刚修过的那两个 report 模块**,于是同一缺陷的**六处存活实例原则上就够不到**——`qt/config.py:343` 加 **五个 I5b/c/d/e/f config YAML**。后者是操作者启用 `price_limit_check` 前会读的文件,**读者层级高于 docstring**。 - - **教训(本条是全轮最值得留下的一条)**:**一个只扫你已经修过的文件的守卫,永远只能确认你已经知道的事。** 把扫描扩到整个特性面后**立刻又抓出六处**——不是同一个声明,是同一次 PR #75 行为变更留下的另一批:`执行价 = 窗口内第一根有效 1min **收盘价**`(`qt/intraday_tail_framework.py` module docstring + I5a/I5b/I5f config),而 #75 之后成交价是 bar VWAP。**共 12 处:7 处描述闸门,5 处描述成交价。** - - **两处因导入环无法组合短语,改为指针并写明原因**:`runtime/backtest/event_models.py` 与 `qt/config.py` 都在 `qt.intraday_tail_framework` 的**上游**(是它 import 这两个),反向 import 必成环 → 不再复述,改指向 `limit_basis_phrase`,扫描作为这两处的兜底。`event_models.py` 的 docstring 明写这一点,免得下一个人再去试那个 import。 - - **刻意不扫、逐条核过的**:`RUNBOOK.md` / `CLAUDE.md` 的「涨跌停用未复权 raw close 比 `stk_limit`」是**日频** universe 可交易性过滤(`universe/filters.py` + `data/clean/tradability.py`),另一套更老的机制,本来就该比日收盘——**粗心的 sed 会把真话改成假话**。 - - **守卫的射程如实写进它自己的 docstring**:它抓的是"close"写在"execution minute"附近,**抓不住换词改写**(review 实测七种逃逸)。**组合(author once)才是真答案,扫描只是导入环挡住组合的那两处下面的网。** review 独立复核:对 merge-base 与本 PR tip 各跑一遍 → **修前 12 命中 / 修后 0 命中,行号逐一对上**;另用更宽松模式扫全仓 `.py/.yaml/.md` 共 54 处命中**逐条读过**,无新增真缺陷。 - - **顺手把 #79 的 `qt/exec_basis_*.py` / `qt/exec_forward_returns.py` 提前纳入扫描**(今天是对的,但同属"执行基准"领地)——等它们变陈旧再加,就是把刚吃过的亏再吃一遍。 -- ✅ **因子层深度重构 —— 设计定稿 v3.2 + D0 纸面契约**(**PR #84 已 merge 到 `main`** @ `703bd3e`;重构工作流启动,迁移计划 D0–D7):全面重构因子层(registry/requires/两视图/store/统一 eval runner/服务层),设计经三轮迭代 + 对抗性 agent-team 审查定稿。 - - **五项拍板决策**(2026-07-23 用户逐项确认):① 全线深度(bitwise 验收退役 → 语义对账四腿;旧 demo 锚保到 D1 末 → D2 冻 provisional → D6 转正);② 运营形态 = **日频 14:50 决策 / 14:51 执行**(当前侧重投研;实时 = 决策点批处理 + 同一 cache,不建流式引擎);③ 存量机器全部迁移(真实目标 = 杀第二条因子取数路径,编排/分析逻辑留代码);④ 收官全量重评估 **只跑 exec**(close 口径从评估契约退役,decision-view 因子簿关闭 with_book close(d) 混用活缺陷);⑤ 爆炸半径 = 因子侧全重写,data 层与 runtime 事件引擎/fills 与 `analytics/eval` 统计裁决核**保留**。设计文档 `tmp/design/factor_refactor_design_v3.md`(v3.2,SER8 同步)。 - - **对抗性审查**(8 角色 Opus 4.8 max effort + 20 kill agent,~3.07M tokens):53 发现,20 HIGH/CRIT 经交叉证伪 **1 存活 / 6 削弱 / 13 被杀**;24 条修订 R1–R25 折入 v3.2(综合报告 `tmp/design/review_round3_v32_revision_list.md`,含被杀档案**禁翻烧饼**)。唯一完整存活的 HIGH 是设计文档自身的事实错误:「峰/岭/谷分类 ≥5 处重复」与代码相反——实为 author-once 单点定义(`intraday_volume_prv.py::peak_mask_for_symbol`,6 模块 import 复用),已更正并撤回 #78 援引;primitives 层理由重述为 surface 蔓延 + 延迟承重(实测 11 因子朴素逐读 ≈58s vs 共享读 ≈17.6s)。审查另发现当前 `main` 潜在缺陷:**回测 `_build_cache` 不传 fina 400 天 `recent_tail_overrides`**(仅 data-update 路径有),晚披露财务在纯回测 read-through 下可能漏抓——修复排入 D3 前置。 - - **D0(PR #84,纯附加 +1148/−0,无引擎代码)**:`data/availability_policy.py`(§1.3 policy 表代码常量单一来源:**字段级 market_daily**——decision 视图仅 `open` 当日合法;3 处 DOMAIN ASSUMPTION 显式标注;修订视界从调用方传入的 config 值派生、**禁硬编码 14/400**、fina 缺 override 即 raise;端点封闭性 `require_known_source`;视图×基准配对合法性;`adjustment`/`overnight_boundary` 三值枚举单一来源)+ 30 测试(含对 `data/cache` 端点常量的真 drift guard、frozen 真赋值断言、bool-混入-int 守卫的 mutation 有牙实证)+ `docs/factors/refactor_d0_contract.md`(收官 14 因子 adjustment/overnight_boundary **预指派表**带 file:line 证据——9 RETURNS_INVARIANT / 5 NONE / 0 PRICE_LEVEL,`valley_price_quantile` = CROSSED_DISCLOSED,另发现 `minute_ideal_amplitude` 以 raw close 跨日池化排序 → CROSSED_DISCLOSED 候选、偏差测量义务留 D2;11 类性质**双向编目**,107 个既有测试引用逐一核实存在,NET-NEW 性质钉 D 格:adjustment→D3、overnight_boundary→D2、单点填≡批量填→D4、视图隔离→D1/D3/D4)。 - - **D1(PR #86 + #87,整步已关账)**:① registry + requires(PR #86,+1751/−69)——`factors/registry/`(register/build/requirements + 显式 builtin 列表 + 三时点命名校验)、`PanelField`(endpoint-only,构造期 `require_known_source`)、FactorSpec **契约 v1.0**(三个必填声明维 requires/adjustment/overnight_boundary,值域引用 D0 枚举;既有 metaclass 双检/预注册/property-spec 全不削弱)、18 个 Factor 类全部落声明(14 收官因子照 D0 表逐行、21 行参数化测试锁死;日频因子逐类带证据派生)、`qt/pipeline._build_factors` if/elif 链退役为纯 `registry.build()`。**因子数学零改动三重证**:compute 文件删除行仅 4 条相同 import / 评审逐 hunk 扫 `def compute` 零命中 / phase0 锚逐字不变。评审 APPROVE-WITH-NITS(1 MEDIUM 经裁定为 diff 基线假象——分支切在 #85 之前,三点 diff 证实未碰 AGENTS/CLAUDE;1 LOW 前瞻记录)。两处披露的行为差异:unknown 因子名提前到 config 解析期报错;旧链对每因子 eager 算 `int(params.get("window",20))` 的 accidental ValueError 不再复现(实证无依赖)。gates:1879 passed / ruff / 31 configs / 锚不变 / secret 0。 - - ② **面板冻结**(PR #87,v3.2 §五第 4 腿/R11,D5 逐格对账的唯一基线):`qt/panel_freeze.py`(**零公式重抄**——分钟因子调各 runner 私有 `_load_*_panel`、常数从 runner 模块对象引用、书因子走 `_build_book_factors`;canonical content hash = sha256(排序索引串行化+float64 原始字节),NaN payload 归一、±0.0 保留区分;原子写;`--resume` 从严——读回文件也重过 process+对账+重算哈希)+ 23 测试(NaN payload mutation 证据、写中途失败原子性实测)+ manifest 入 git(producing SHA 钉 `3669c90`——**先跑后 commit** 使 run 时 tracked 树逐位 = main;provenance 规则原文在案)。真实 run:14/14 面板(网格 1,158,912/1,159,263 行,CSI500 996 成分 1210 交易日,2.79 亿行 raw 1min),`stk_mins_live_calls=0`,**写盘前对账 11/11**(mismatch 即 raise 不落盘;书因子无覆盖字段可对账、照实披露不假装,证据换双跑+跨 run 同哈希+全网格行数恒等),确定性双跑 3/3 跨进程一致;两段 run(会话限额 60min 杀首段 → resume)与首段日志丢失(`_make_logger` truncate)均如实披露。评审 **APPROVE 零发现(含 LOW,本轮首次)**:逐参数镜像比对调用点、14/14 行 doc↔json 程序化核对、3 面板盘上重算哈希、独立 secret 扫描 17 文件 0 命中。gates:**1902 passed** / ruff / 锚不变 / 31 configs。 - - 验收:独立对抗评审 **APPROVE-WITH-NITS**(0 CRIT/HIGH/MED,1 LOW 当场修 + mutation 证据 rc=1/rc=0);lead 独立重跑 gates:pytest **1806 passed**(= 1776 + 30)、`ruff` clean、`validate-config` 31/31、phase0 锚 **0.9600/0.8408 不变**、diff secret scan 0。执行流程:实现与验收均由 subagent 承担、lead 把控(含 5h 窗 <10% 不开新 subagent 的节流规则)。 - - **D2(PR #89,整步已关账——首个动数学步骤,实测逐位一致)**:`factors/ops/` 7 算子(momentum/volatility/liquidity/overnight_mom 改写其上,naive-oracle 等值锁定)+ `factors/compute/minute/`(primitives 吸收 `peak_mask_for_symbol` 单点定义 + 11 因子一因子一文件,数学+surface+声明同文件)+ R14 粒度 shim 化(10 纯模块 + intraday_derived AST 锁纯度;aggregate 通用核留真实代码;共享常量安家 intraday_schema——live import 测试证留 aggregate 成真环)。**验收三条独立路径互证**:① 逐格对账 vs D1 冻结基线 **14/14 `max_rel_diff=0.0`、canonical hash 全等(逐位一致)**;② 88 行分层手算锚 0 失配(脚本运行时守卫禁 import 引擎,曾自抓 3 个口径错);③ mutation 证据实跑 rc=1/rc=0 三组(评审活体复跑其二)。I5 四 run 值级锚全命中(I5b/I5f 1.019318;I5d Spearman +0.9;I5e −0.5)。PR-D 测量债清偿(4.58% 窗含真 ex-date,评审从冻结 manifest 独立复算,维持 CROSSED_DISCLOSED)。provisional 锚全精度值入测试(PROVISIONAL,D6 转正)。评审 APPROVE-WITH-NITS(1 LOW:guard_boundary 静默跳过无机器可读披露——当场修,skip 显式落 JSON)。留 D6 两注记:shim 期 aggregate→factors.minute 方向(披露的临时例外,D6d 日落);`asof_daily_features` 的 mmp_ew hook 届时需新家。gates:**1955 passed** / ruff / 31 configs / phase0 锚不变 / secret 0。 - - **D3(PR #91,整步已关账——store 层,纯附加未接线)**:`factors/store/`(hashing/code_hash/fingerprint/keys/values/run_registry/incremental,全 <800 行)+ **R5 前置修复**(`fina_tail_days` 单一真相迁至 `data.cache.fina_tail_days`,回测 `_build_cache` 与 data-update 两 builder 统一传 fina 400 尾 override——回测 read-through 现会补 fina 尾窗,行为变更已披露;`data_update.fina_tail_days` 删除对 repo 外旧 YAML 属 breaking,仓内两 config 已迁)。key = params_hash + code_hash(自身模块 + 枚举共享集{minute primitives/ops/base/spec} + **one-hop 直接项目内 import 折叠**:AST 自动派生、无手工清单、不做传递闭包——评审 MEDIUM 实测 `reversal_20` 可 build 且值=−momentum 而 momentum.py 取值面不入 key = 陈旧复用洞,据此修;改 momentum → candidates 系 key 变、financial/分钟系不变,无全局过度失效)+ view + 数据指纹(schema-version 维 = data/clean PIT 承重模块内容 hash;`price_level` 另掺 per-symbol adj_factor 事件表 hash 含锚值,收官集零成员不豁免 fixture 测试)。存储:单文件 parquet date 排序 / 原子写 tmp+`os.replace` / per-artifact O_CREAT|O_EXCL 锁(两进程 gap-fill 零丢行实测;锁 neuter mutation 证明锁对原子写也承重)/ JSONL append-only 运行注册表(no-secret:sanitize_text 过自由文本,redaction 测试钉住)/ 损坏文件=miss / **不建 coverage ledger**(值确定性可重算)/ 启动一致性断言(registry 条目必须命中代码注册表)。tail-recompute:`w_overlap=max(W_dep, 端点修订视界)`——W_dep=传递 lookback 深度经 **`FactorSpec.lookback_depth`(optional 附加字段,contract v1.1)**声明、store 消费点 HARD-REQUIRE(14 真实因子未声明 → loud 报错,声明属预注册动作留 D4);视界经 `revision_horizon()` 从 live config 派生(调大 refresh_recent_days → 重叠窗变宽 mutation 实证,无硬编码 14/400);重叠 NaN-aware bitwise,失配按 adjustment 分类(returns_invariant→loud 上游回改记录 / price_level→预期);schema 指纹失配 → schema_void_full 全量重算不误报 revision;§六.18 teeth(headline 深度欠采 → 假 mismatch)测试钉住。#adjustment NET-NEW 性质测试钉本格:扰动 qfq 锚 → returns_invariant 真因子(日频 volatility_20 + 分钟 valley_relative_vwap)逐 bit 不变,批量 census 9/5/0 ≡ D0 表。评审 APPROVE-WITH-NITS(0 CRIT/HIGH,1 MEDIUM+3 LOW+1 NIT;MEDIUM/LOW-1/LOW-3 合并前修于 `fd906f4` 并经 delta 复核三项 CLOSED——评审亲手重跑 momentum on-disk mutation 5/5、allowlist 注入、锁 neuter,并猎杀「不可能失败的测试」零发现)。留 D4 注记:materializer 必须传 live `data.cache.fina_tail_days` 勿靠 400 默认;two-hop 组合链是 one-hop 折叠的已知边界(今日无实例,设计明令不做传递遍历器);`availability_policy` 编辑会 over-invalidate 其 importer 的 store(安全方向、D4/D5 churn 期真实重算成本);单点填≡批量填(P8)是 D4 验收项。gates:**2031 passed**(=1955+76)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 - - **D4(PR #93,整步已关账——FactorService + materializer;四轮评审揪出两个 HIGH + 一项设计修正)**:`factors/service.py`(frozen `DecisionPoint` + `cross_section`/`panel` **都 read-through 读 D3 store**,miss 触发唯一引擎,配对合法性复用 D0 `require_legal_pairing` 构造期强制)+ decision-view materializer(R18 **全** policy-lag:日线非-open ≤ d−1、**open[d] 字段级例外从 policy 表派生**、daily_basic/fina ≤ d−1、分钟 ≤14:50;ex-date 只暴露**布尔**的类型边界;`CacheHorizonConfig` 从 live config 构造缺属性即 raise)+ process 编排下沉 `factors/process/orchestrate.py`(`_process_factors` 薄委托逐字行为保持)+ 14 因子 `lookback_depth` 预注册(参数化派生+测试钉死)+ 热路径计时 smoke(真实缓存 cache-only 40 票共享读 ≈2.0x,诚实标注不外推)。**评审故事(P8 单点填≡批量填连揪三层真缺陷)**:①【设计修正】valid-day **pooled** 因子(8/11 分钟因子按滚动机制码检划分,非按观察到分歧的 3 个——#82 教训)trailing N 个**有效日**的日历深度数据依赖、**无界** → **证否 v3.2 §3.3 R4「W_dep=各 lookback 之和」假设**,固定 40 日 trim 使单点填 vs 批量填 finite↔NaN 分歧;②首版不动点判据(值稳定+nbars 稳定)被评审构造 **>1 chunk 停牌 gap 反例**击穿(finite-vs-finite 静默错值)→ 改**结构性判据**(锁定单调性:丢窗口头部 baseline_days 交易日其余分类 FINAL;锁定区 output-date 计数 ≥ lookback_days,gap-immune)+ provider 显式 `earliest_available`(nbars 推断删除,中段 gap 不许当 data start),上版「divergence is gone」overclaim 在 commit message 开篇更正;③判据循环只遍历**有输出的** symbol → 长停牌票**无否决权被静默丢出截面**(撞 I5a 红线)→ 改遍历**请求的** symbol 列表,评审 fixture(600519 丢名复现 + 锁定偏移 416.0 vs 438.0 静默错值)全部落成 committed 测试 + 源码级 mutation 双 witness(rc=1/rc=0,评审亲手用自己未改动的脚本独立复跑确认)。`_MAX_SATURATION_CHUNKS` 耗尽改 loud raise(红线 #9)+ raise 分支有测试。**代价如实入 docstring**:凑不满 lookback 有效日的票拖全 universe 扩到声明 floor;per-symbol 饱和起点是 D5 优化**且非纯优化**(`intraday_amp_cut` 截面 z-score 耦合)。**早期区域量化(D5 面板腿归因名目)**:分钟缓存真实起点 **2015-01-05** → D1 冻结基线(锚 2021-07-01)对 pooled 因子属 **anchor 截断**,饱和加载在 2021-07→10 五票上 ridge 有 157/184 cell 不同(79 NaN→finite,按月递减至零)——D5 对账须具名归因「saturation vs anchor truncation」。JC1 已裁定:P8 逐 bit 对滚动因子降为 ≤1e-12 可归因浮点重排(实测 floor ~3.5e-18,真缺行效应 ≥7 个数量级于容差;NaN mask 必须精确)。留 D5/D6:声明 floor 应从分钟 coverage ledger 派生 per-symbol(现为硬编码常量,声明偏晚会静默截断);`valley_price_quantile` 分钟绑定 DEFERRED(loud 报错,已声明 pooled,D5 绑定继承饱和路径**不许**固定 trim 绕过);零输出否决语义已被测试+mutation 钉住,D5 为性能回退它=放回 HIGH;blanket shift 依赖「因子输入面板只含端点列」的 D6 wiring 契约。gates:**2098 passed**(=2031+67)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 - - **D4b(PR #97,实施期修订 A1 新插入的迁移步——per-symbol 流式物化;改因子取值路径,自带四腿验收)**:materializer 改「逐票读 → 立刻聚合到日频 → 丢弃」,**切口落在截面 combine 之前**。**规模(真实评估面 CSI500 995 票 × 5 年,cache-only `live_calls=0`)**:名义最大单帧 **122.2 GB → 0.066 GB**、峰值 RSS **~115 GB → 0.964 GB**(可用 56 GB,占 1.7%);三种加载几何各跑一因子(pooled×截面 / pooled / bounded)全部 995/995 票、**每次请求 1 票**;峰值稳定 0.73–0.96 GB,**由「一票 bars + 日频面板」决定而非 universe 决定**;扩到 floor 的代价只落在那 84 只 late 票(40 次调用/14.95s),不再拖着 911 只陪跑(dense 票 1.0 次/0.59s)。**四腿**:① D4 既有 31 测试原样通过(P8 四 regime / 锁定偏移 / 零输出否决 / exhaustion loud raise),评审证其**真在测新路径**(单文件即覆盖 `materialize.py` 86%,per-symbol helper 体均被覆盖);② 手算锚 18 行**零项目 import**(运行时守卫断言 `sys.modules` 无 factors/qt/data/runtime/alpha,653 模块 0 命中),`volume_peak_count` 9/9 精确 0.0、`intraday_amp_cut` ≤1.998e-15;③ 逐格对账真实缓存 40 票×6 月 vs 整 universe 单帧(3,711,400 行):10/10 index 相同、**NaN 集合差异全 0**、5 个逐位相同、最差残差 3.275e-14,`intraday_amp_cut` 在 **4,720** 个真实 cell 上对上(评审以 **main 真引擎**为参照复跑得 **max|abs|=0.000e+00**,比文档的 2.5e-14 更干净——因两侧加载几何相同,已写进文档);④ 规模证明如上,且 `--mode reconcile` 使 §6.1 那张最承重的表**可从仓库复跑**(评审亲跑:10 行×4 列与文档逐格一致,且**非空对账**——参照侧完全不经 `materialize_range`,只在被检验的加载几何上不同)。**评审带来的头号反转**:实现方把 per-symbol trim 描述为「更保守的语义细化」,评审证明它在**修 D4 的真缺陷**——同一稀疏 fixture 跨引擎对照,3 个 bounded 因子各动 1 cell,**三个都是 D4b == 几何无关真值、D4 ≠ 真值**(`jump` 0.0901 vs 0.1571;另两个 D4 给 **NaN** 而 D4b 给正确 finite);union trim 给稀疏票喂的自有交易日不足 `lookback_depth` = 红线 #6 的 load-geometry 依赖。且四个旧 runner **逐票读全窗口、根本没有 union trim** ⇒ per-symbol trim 是**朝 D1 冻结基线靠拢**——实现方给 D5 的预警「稀疏票对不上先看这里」**方向反了**(反在安全侧),已订正为指向饱和深度/anchor 截断。超集性质经评审证严(子集第 k 大 ≤ 超集第 k 大),反例构造失败。**MED-1(D4b 新引入的唯一真引擎缺陷)**:请求含重复 symbol 时逐票流式会独立处理每个重复项——48→52 行、4 个重复 (date,symbol),`intraday_amp_cut` 更因重复票**两次进入截面**而 **48/48 共享 cell 全部变值**(D4 免疫是靠 provider 的 `isin` 幂等吸收)→ 改**保序去重**(而非 loud raise:去重**精确复现单帧引擎对同一调用的返回**、保持「只改内存剖面」,raise 会新拒绝前一引擎接受过的输入);评审跨引擎复核修后 0 差异。**另两条「说法与事实相反」**:MED-2 一处 docstring 称旧 mutation 写法「cannot fail」,评审原样跑在 D4b 上是 **FAIL**——它其实是 **cannot pass**(性质保住、只换写法,属 §六.16 而非偷改测试;计数 70→**72**=70 单点填+2 批量填);MED-3 可行性文档声明了一条「本步绝不允许 finite↔NaN 翻转」的政策,而 per-symbol trim **能且实测确实产生**(3 个 bounded 里 2 个 NaN→finite),40 票样本只是没触发 → 改为如实陈述。**lead 合并前补的闭环**:派生 id 列表只守住了「加了因子却没进对账」一个方向;评审实测另一方向——把某因子从**两张 binding 表都删掉**,该文件 53 passed、全量 2169 passed **全绿**,因子从每条 parametrize 里静默消失。故新增闭包测试,真相源改为**`factors/compute/minute` 下定义的全部 `Factor` 子类**,未分类即红(mutation witness:删 `PeakIntervalKurtosisFactor` occurrences 4→0 先断言落地 → 转红;还原后 `binding.py` 逐字节相同、58 passed)。**A1 点名的致命陷阱未踩**:`factors/service.py` diff **0 行**(不许在 service 层按 symbol 分批——`_ensure_coverage` 只按日期判缺口会静默丢名)。gates:**2174 passed / 1 skipped**(= 2117 + 57,**node-ID 集合证 0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 head 未变 / 冻结面板 mtime 未变。留 D5:扩张步长(late 票 40 次重读,纯性能但仍是加载几何改动、欠自己的对账,**刻意没夹带**);声明 floor 仍是 universe 级硬编码常量未按票从 coverage ledger 派生;`valley_price_quantile` 仍 DEFERRED,D5 绑定**继承流式路径不许固定 trim 绕过**;10 个因子只跑了 3 个的全规模证明(几何全覆盖,评审证未测因子的加载参数与已测者相同或被支配);日频路径仍整 universe 单帧(995×1210 行,量级小,未动)。 - - **D5b(PR #99,D5 后四块只交付了 C3——**C4 中止、C5/C6 未做、什么也没删**;中止本身是本步最重要的产出)**:**发现并(两方独立)确认一个真实前视缺陷**:`jump_amount_corr_20` 是十一个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 **0**,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它、全程无补救。**exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP**,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测(两方各用不同票/不同窗口,前置断言两腿齐全):实现方 12 票/2021H2 **1,477/1,477 格全移动**(max|diff| 1.276)、其余 9 个 bound 因子 **0 格**;评审另 12 票/2023H1 **1,373/1,373**(max|diff| 1.054)、**其余全部 11 条 0 格**(并补测了实现方漏掉的 `valley_price_quantile_stats` 与 `mmp_ew`);评审再加一刀**只扰动 `bar_end >= 14:51`**(执行 bar 本身及其后)→ **1,363/1,363 移动**——**信号看到了自己成交的那一分钟及其后 9 分钟**。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`/`return_basis="exec_to_exec"`/`execution_window="[14:51:00,14:56:59]"`,verdict **Watch**、predictive 轴 **PASS**(|NW-t| 15.19)——**报告声明了它没有执行的检查**。**既有测试为何没抓到**:`test_pit_perturbing_future_bars_does_not_change_factor_at_d` 守的是**跨日**边界,其 fixture session 只有 09:31–09:36 六根 bar、**根本不存在 14:50 边界**;日内边界只在 **PR #79** 把入场锚从 `close(d)` 移到 `14:51(d)` 之后才承重,而 #79 评审未返回、这**恰在其「未独立核」清单射程内**。**裁定(设计文档实施期修订 A3)**:用户 2026-07-19 常设授权明写「全日数据因子一律截断到 14:50……授权直接截断,不必逐因子再问」,而该因子**建于此授权之前** ⇒ 加截断是**补用既有授权**非新研究决策;**但评审的程序性修正已采纳**——从重构视角它仍是定义变更、而授权来自研究侧不来自重构 ⇒ **必须单独成 PR、显式标注 correctness fix,不许进 D5 任何 commit**(已另派)。幅度实测 pearson **r=0.9993**、mean|diff| 0.0042——**缺陷是结构性的必须撤回,但修正代价大概率很小;verdict 存活与否必须由真正的重跑给出、不许由相关系数推定**。**交付的 C3(评估契约 v1.0)**:`EvalConfig.view`/`return_basis` 构造期经 D0 `require_legal_pairing` 强制配对;provenance 增 requires/adjustment/overnight_boundary/lookback_depth + `eval_contract_version`;跨口径汇总**缺 view/basis 列拒渲染**(声明了列却留空同样拒);**判定门一个数没动**(评审证 6 个文件 md5 与 main 逐字节相同,8 个门值逐值钉死)。**评审两轮 APPROVE-WITH-NITS,2 MEDIUM 全关**:**MEDIUM-1「守卫守不住自己」**——声称钉住 identity 修复的测试是对**函数源码做子串匹配**,评审把赋值掏空、两个 token 留**注释**里 → 每份 exec artifact 都会继承调用方 close 身份而实际按 exec 收益打分,**52 passed 全仓无一抓住**(#78 教训的翻版,且放行的恰是同一 commit 刚修的缺陷)→ 抽 `exec_identity()` 改**行为断言**,评审原样重跑逃逸 **4 failed**,并验了**假阳性**方向(重构进 helper 不误报,58 passed)。**MEDIUM-2「假声明上升一层」**——exec 路径无条件声明 `view=decision`,但 with_book 的因子簿是 close(d)+当晚 daily_basic(**§1.1 自己列为「已经咬过人的活缺陷」**),且 jump 连 no_book 格都不成立;而该字段定义是「**因子值**取自哪个信息集」,with_book 有**两个**信息集、一个字段表达不了 → **这正是该字段被造出来防的那种缺陷、只是上升一层**。**现在修免费**(还没 artifact 带它),C5 一跑就写进 22 份。修法:**先拆两个事实**(②单独不成立——登记例外并不能让 `view=decision` 在 with_book 上变成真话)→ 新增 `EvalConfig.book_view`(唯一知情方声明)+ `subject_view(factor_id)` **从事实派生**(deny list `NOT_DECISION_CUTOFF_SAFE` 放**因子侧**——「它是关于**值**的事实」,由可见性测试**测量**而非声明,本地副本已删)。**刻意不校验 `book_view`×basis 配对**,评审的裁定值得作通则记下:**失配摧毁意义 → 拦;失配只带来已披露、方向已知的偏 → 记**(close-view 书是 Incremental 轴的控制变量、偏的方向已预注册;拦它不会让信息集不混,只会让 artifact 说不出它混了)。**并落一个有意的响亮阻断**:jump 现在派生出非法配对 → 其 exec 评估 loud raise(截断修正落地即解除,删 deny list 一条)——在它被修正前**不存在**它的诚实 exec artifact,阻断优于假声明(红线 #9)。**实现方又一次自查出同形缺口**(单测说不了调用方怎么接线:with_book 传 `book_view=None` → **28 测试全绿**)并照实上报、补端到端断言读**两份实际写出的 JSON**。**另三个前提问题已裁定并立项**:(a) 3/11 因子的覆盖率披露无法由 store 供给(每次披露必重算,已写成行为并测试锁定);(b) `valley_price_quantile` 会被 materializer 二次滞后成 close_{d-2},评审实测第三条路**逐比特等价**(`max|diff|=0.000e+00`、`Series.equals=True`,破坏对照 7.3e-2 证非空过)⇒ 属实现变更可授权,落地须**两侧都钉**等价性测试 + 左端多一天历史;(c) **store key 无 universe 维(存量 CRITICAL,对 D6/D7)**——评审拆成**两个独立失效模式**:① `_ensure_coverage` 缺口判定**只按日期不按 (date,symbol)** ⇒ 第二批「无缺口」直接返回、那批票**静默从截面消失**,影响**全部 11 因子**(**讽刺**:A1 否决「按票分批」出路的理由一字不差就是这条,**红线写在文档里而服务层身上就带着它**);② `intraday_amp_cut` 的 z-score 在物化期发生 ⇒ 存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、24 行而非 48、存活 24 格全错** max|Δ| 0.159–0.186)。**「给 key 加 universe 维」被否**(过度失效 10 个 universe 无关因子;**PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来**;跨指数存储爆炸),改为 **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(`MinuteStreamBinding` 本就是 per_symbol+combine 两段,切在两段之间,正合红线 #7)⇒ 立为 **D4c,D5 C4 的前置**(设计文档修订 A2)。(d) 运维陷阱:真实评估 universe **996** 而冻结分钟面板 **995**(`300114.SZ` 无分钟 bar;**冻结面板自证**——三个日频书因子面板各 996 含它、十个分钟面板全 995 不含它),而 `saturation_probe` 默认 universe 从 995 面板派生。**编目 §七之二 已在任何 artifact 重生成之前登记漂移**(`eval_config` +3 键 / 顶层 +1 / Markdown +4 行全为新增 ⇒ C5 **逐字节对账已知不成立**、能成立的是值级 + 结构性断言;另登记 exec 侧 no_book 与 with_book 的 `eval_config` **首次合法地不同**、不得当回归;jump 的 loud raise 属有意阻断及解除方式)。gates:**2227 passed + 1 skipped**(2228 收集 = main 2175 +46 +7,**0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa` / 冻结面板 mtime 未变。**方法论新增两条**:① **「失配摧毁意义 → 拦;失配只带偏 → 记」**(见上);② **lead 与评审共用同一 worktree 时,测量与变异会打架**——本轮 lead 跑 gates 时 `git status` 一度显示 1 个改动(评审正在做 mutation),故合并前在评审收工后重跑了一遍干净 gates。 - - **D4c(PR #101,实施期修订 A2 插入的迁移步,晚于 D5b 合并——store 去 universe 依赖,D5 C4 的前置)**:修 D5b 评审立项的**两个独立失效模式**。① **`factors/service.py::_ensure_coverage` 缺口判定原只按日期不按 (date,symbol)**——第二批 symbol「无缺口」直接返回、**静默从截面消失**(影响全部 11 分钟因子)→ 改按 (date,symbol) 判定。② **store key 无 universe 维**:`intraday_amp_cut` 的截面 z-score 发生在物化期、store 之前,存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、返回 24 行而非 48、存活 24 格全错**)→ **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(切在 `MinuteStreamBinding` 的 per_symbol/combine 两段之间,正合红线 #7)。**「给 store key 加 universe 维」明确否决、不重开**(三条理由:10 个 universe 无关因子过度失效;PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来;跨指数存储爆炸)。**调用方 symbol 列表单点归一化** `materialize.requested_universe` + 入口面 probe 守卫(其射程已写进守卫自己的 docstring:四种逃逸路径实测);**per-symbol floor 契约测试钉住**(`test_a_max_form_floor_breaks_the_premise`,docstring 写明引擎逐票硬化时它变红是预期信号、届时删除/反转)。**评审**:对抗性评审 APPROVE(HIGH-1/MEDIUM-1/NIT-1/floor 契约全 CLOSED);末 commit `131467e` 纯 docstring,经二轮评审 + 真实 mutation(引擎逐票硬化 → 该测试精确变红 9.322e-03→0.0,还原干净)。**gates(lead 独立重跑)**:pytest **2277 passed / 1 skipped** / ruff / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa`。**留 D5 C4 注记**(PR #105 部分处置:NaN 足迹误诊已修(filled_after_footprint 类)+ 中间量具名拒绝;完整接线仍留 21:00 故事):`tail_recompute` 对两列 payload loud raise 未接线 + D4c 的 NaN 足迹会被重叠校验误诊为 upstream revision(**高优先**);**派生 per-symbol floor 只能 per-symbol 或 min-over-symbols,绝不能 max 形**——实测 max 形偏差 9.3e-03~1.45e-02,会静默打破「中间量 universe 无关」的立论前提。 - - **jump_amount_corr 14:50 截断 correctness fix(PR #103,设计文档实施期修订 A3——单独成 PR、显式 correctness fix,非因子层重构一部分)**:修 D5b 发现并(两方独立)确认的真实前视缺陷。`jump_amount_corr_20` 是 11 个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 0,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它;exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测:扰动 cutoff 后 bar → 该因子 1,477/1,477 与 1,373/1,373 格全移动、其余 11 条 0 格;只扰动 `bar_end >= 14:51`(执行 bar 本身及其后)→ 1,363/1,363 移动。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`、verdict **Watch**、predictive 轴 PASS——**报告声明了它没有执行的检查**。**定性**:2026-07-19 owner 常设授权「全日数据因子一律截断到 14:50」建于此因子**之后** ⇒ 补截断是**补用既有授权**非新研究决策;但它是已发布因子的定义变更 ⇒ **单独成 PR、不许进重构任何 commit**。**内容**:截断 + 通用日内边界守卫(覆盖 11 分钟因子 + mmp_ew,目录遍历闭包防新增因子漏网)+ 结构化更正承载(JSON 顶层 `corrections` 恰好 1 处、`CORRECTION_FIELD_MAX_CHARS=2000` 超长 raise 不裁剪)+ 评估契约 v1.0→v1.1(`spec.version` 单独可判别 artifact 存档在更正哪一侧)+ 解除 exec 评估 loud 阻断(D5b 的跨 PR 互锁测试曾逐字给出该做的两个动作)。**评审 BLOCK 两条必修已修**:① dashboard 布局 `DEFINITION_MAX_LINES` 量错分支——`meta_y=0.20/0.10` ⇒ daily 容量 5 行 / intraday 4 行,而 dashboard 实际走 intraday 分支 → exec 形态 9/11 叠字;改 `DEFINITION_MAX_LINES_DAILY=5`/`_INTRADAY=4` + 布局守卫参数化扩到 exec 形态(mutation 证据:`_INTRADAY` 改回 5 → 11 测试红);② `test_the_correction_carrier_is_never_truncated_on_disk` 恒真(`MUST_NEVER_TRUNCATE` 集合与 `_leaves()` 叶子路径交集恒空)→ 改前缀匹配 `corrections.` + mutation(截断 `corrections.superseded` → 红且报对字段,旧逻辑在同语料上仍绿)。**重述结果(真跑出来的,非 pearson r=0.9993 推定)**:exec 与 close 两基准 verdict **Watch/Watch 不变**(exec IC −0.030840→−0.030539,N_eff 1162→1178);**不追认旧 artifact**——每个 emitted cell 都被污染过,聚合秩 IC 动得小是**稀释**(污染 bar 占 20 日池化窗 ~4.5% 且秩 IC 逐日封顶)不是无害。**评审自己撤回的两条(记录防失真)**:① `verdict.reasons` 无长度上限(走 `sanitize_text`;盘上最长未截断 note 1263 字符,「距上限 16 字符」是单样本推总体的假警报;真被截的是六个 payload 字段,真正临界的只有 `coverage_bias_bad_vwap` 158/200);② PNG 叠字「既有缺陷」与「本 PR 回归」两说皆对——renderer 一直如此(`figures.py` 此前只有 1 个 commit),jump 描述 323→702 字符曾是本 PR 引入的回归、又被结构化承载拖出(现 352 字符)。**预期下游信号(预登记非回归)**:`hand_anchors_d2.json` 将报 `all_ok_frozen14: False`(70 行 5 处失配全部且仅有 jump:手算已截断 vs 冻结 panels_d2 未截断);其余 9 因子 exec dashboard 叠字是 renderer 既有缺陷,C5 全量重跑时重渲染。**gates(lead 独立重跑)**:pytest **2389 passed / 5 skipped**(4 个 census 语料测试在 worktree 无语料 skip、已对真实语料验 4/4 PASS;1 个 gitignored 基线字节 skip)/ ruff clean / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret scan 0 / `exec_baseline --verify` 77/77 @ `45c14aa`;jump 4 份 artifact 经 git archive 导出重生成(cache-only,`stk_mins_live_calls=0`)——9 份 JSON/MD 逐字节不变、PNG 不变(jump 3 行描述在两个 cap 下都放得下,本就不叠字),exec dashboard 目视无叠字。 - - **D5 C4(PR #105,统一 exec-only FactorEvalRunner——决策③"杀第二条因子取数路径"的实质落地)**:四块。① `qt/factor_eval_providers.py`(CacheMinuteProvider 从 hotpath_smoke 提升为单一来源、DailyEvalPanelProvider 给 close-view 未滞后面板、build_eval_service 复用 pipeline 同调用序)+ `incremental.py` 足迹误诊修复(stored-NaN→fresh-finite 单立 `filled_after_footprint` 类,不再报 upstream REVISION)+ 中间量 payload 具名拒绝 `IntermediatePayloadNotWiredError`(deliberate deferral);② `qt/factor_eval_runner.py` + `qt/factor_eval_disclosures.py`(四份 coverage 披露 8/8 VERBATIM 搬入唯一家、旧 runner re-export、`book_mode` decision/close 双模式——decision 模式关闭 §1.1 close(d) 书活缺陷、add-Section 后 mandatory+verdict 逐位不变、新 stem `factor_eval_*` 与旧 `eval_*` 命名隔离、编目 BUG 3/5/6 闭环);③ `qt/factor_eval_reconcile.py`(panels/reports/anchors 三模式 + 入口 77/77 硬闸门,基线不可达=硬错误、jump 路由 pr_c_cutoff_fix 参照物);④ 判据补全(见下)。 - - **首轮真实对账失败→根因调查→判据补全**(本轮最重要的方法论事件,照实记):bounded 因子预期≈0 差异却出 4554 unclassified + 2 锚 FAILED → 独立调查定位主因是 **warmup 左延**(旧 runner 左端锚 `data.start` 不扩窗、冻结面板前 w−1 日欠 warmup;materializer 左延到真实缓存起点 2015-01-05),加两个浮点尾部(rolling 求和顺序 rel≤2.9e-12;rolling-σ 噪声×整数成交量阈值翻转 ±1 count)。**认知修正:handoff §3①"bounded vs D1 baseline 预期零差异"的预测错了——旧 runner 的"全窗口"左端同样是 anchor 截断;首轮不绿的源头是预测错、不是引擎 bug。** 新具名类(机器可判、类外仍非零退出):`warmup_left_extension`(bounded 前 w−1 日全方向 / pooled 早区窗+按月非递增)、`float_reordering_tail`(rel≤5e-12 且 ≤101 cell)、`threshold_flip_tail`(±1 count、≤25 cell)、MD 按 key 配对、JSON 登记项。编目新增 §七之三。 - - **真实对账 9/9 绿**(cache-only `stk_mins_live_calls=0`):minute_ideal_amp warmup=8,198 / jump warmup=17,289+float_tail=101 / volume_peak warmup=34,441(按月 19,977→14,464 单调)+threshold_flip=20,三因子 unclassified 全 0;jump 非 warmup 区 3 锚变绿(rel ~1e-15,#103 截断修正被新路径携带确认)。 - - 评审 APPROVE-WITH-NITS(0 CRIT/HIGH/MEDIUM;64 个自造 mutation probe 全过、verbatim 8/8 复核、panels 模式独立重跑逐格一致);登记 LOW-1(bounded warmup 类无量级/cell 数上限,~1.6% 网格,**C5 加 cell 数 ceiling**)与 LOW-2(reports 腿 digit OR 规则散文理由可变义,verdict 标签仍严格);NIT-1/2 装饰性。 - - gates(lead 独立重跑):pytest 2492 passed(node-ID 差分 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret scan 2 处 benign(新 config 的 external_secret_file 路径 + key 名,与 main 上 11 份 eval config 同形,无 token 值)、exec_baseline --verify 77/77 @ 45c14aa。 - - 刻意不做:删旧 runner(C6)、11 因子全量四腿(C5)、vpq 绑定 + binding 折 code_hash(PR-C4b,lead 已裁定批准)、mmp_ew(D6c)、run registry(D7)。 - - **D5 C4b(PR #107,vpq 绑定 + code_hash 折叠——11 分钟因子全部绑定完成)**: - - **vpq 绑定**:`valley_price_quantile` 的 residualize 是逐日截面 OLS(值依赖 universe)⇒ 按 D4c 架构存 per-symbol `raw_qbar` 中间量、截面 combine 移到读出装配;combine 需日频 close ⇒ 唯一引擎扩展 `DailyCombineInput`/`combine_daily` **声明式**槽位(红线 #5 禁 isinstance 守住;顺带删掉了旧的 `isinstance(factor, ValleyPriceQuantileFactor)` 分发);`combine_daily_panel` 用 decision-lagged close + 左端 22 个 lagged 交易日 warmup(欠暖钳位有一行披露)。 - - **平移面板第三条路**:因子内部 T-1 会被 decision 视图二次滞后成 close_{d−2} ⇒ 传平移面板 + 关内部 T-1,**逐比特等价双侧钉**(binding 侧 `Series.equals` + 破坏对照 ~8.5e-2;runner 侧 fixture 240/240 max|diff|=0.0 + 左端 22 日硬断言;评审用不同 fixture + 新扰动独立复验)。 - - **binding.py 折进 code_hash**(lead 裁定 Q2):承重语义文件此前在 key 射程外;mutation 实证改 binding → 18/18 factor key 全变、改无关模块 → 0 变;dev-only `artifacts/factor_store/` 一次性失效已披露。 - - runner 启用 vpq + NeutralizationCoverage 机制 B(经新 `service.stored_payload` 回读 raw 中间量,同一 key/fingerprint/coverage 引擎、非第二取数路径)+ **anchors 模式修截面因子全 universe 服务**(5 锚 symbol < min_cross_section=10 必全 NaN——C5 对 intraday_amp_cut 本也会踩)。 - - **panels 类边界三裁定**(首个 FAIL 100% 分解后 lead 裁定):① pooled 月单调豁免锚到**结构性部分月**(冻结面板首个 finite 值所在月;初版位置性实现被评审 probe 绕过 → 修复 + 回退 mutation 证据);② 新具名类 `threshold_flip_contamination`(窗口 [2023-06-01,2023-07-14]、600623.SH ≤2e-04、其余 ≤1e-06、≤20,000 cell——PRV 临界 bar 翻转×逐日 OLS 污染族,输入侧变化已排除:ledger 零写入 + qbar 双几何逐位一致 1.1e-16,评审独立复算 1,241/1,241 格);③ float 尾加 abs 下限 1e-12 + cap 分档(bars-only 101 / 截面 1,000)。 - - **真实对账(cache-only `stk_mins_live_calls=0`,评审独立重跑逐数一致)**:panels rc=0(warmup=24,568 按月 902→19,980→3,667→19 豁免后单调 / float_tail=883 / flip_contamination=19,177 / unclassified=0)、reports 4/4 OK、anchors 5/5;vpq verdict Watch/Watch 与冻结基线同(数值微移全在登记 warmup 聚合效应内)。 - - 评审两轮:APPROVE-WITH-NITS(LOW-1 位置性豁免可绕过 + NIT-1 静默钳位)→ 修复 → **delta 复核双双 CLOSED 可合并**(评审回退修复自证测试有牙)。 - - gates(lead 独立重跑):pytest 2533 passed(node-ID 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret 0、exec_baseline --verify 77/77 @ 45c14aa。 - - **D5a(PR #95,D5 的前三块——冻结/编目/探测;探测结论迫使插入新迁移步 D4b)**:**C1 冻结 exec 对账基线**(`qt/exec_baseline_freeze.py`)——22 份 exec artifact 是后续一切对账的参照,却 **gitignored 只存在于本机**,且设计假定的耐久副本 `scratchpad/baseline_reports/` **早已不存在**;新 runner 沿用同名输出路径,**跑一次就毁掉唯一基线**,随后的「对账」会比对刚被自己覆盖的文件(= 本仓在案的 `compare_postmerge.py` 空对账形态)。实冻 **77 个文件**(不是交办的 44——同族还有 11 份 `_exec_basis_sanity.md` + 22 张 dashboard,同样单份不可再生),**sha256 manifest 入 git**(字节留 gitignored,哈希只能经评审 diff 变动),每次读都验哈希,读取器结构性拒绝 live 路径(直连/symlink 别名/`..` 三种绕过全拒)。**provenance 按证据强度分级**:观察到的是 77 文件共享毫秒级 mtime(批量拷贝签名),对照 44 份 close 基准的 11 个递进 mtime 与 #74 重跑窗口吻合;「字节出自 #79 run」标注为**推断非观察**,字段名故意叫 `frozen_at_git_head` 而非 `producing_sha`。**C2 编目 11 runner 差异**(§六.5「未编目的差异算失败」)——用 **AST 级比对**而非抽读证明完整性(`_build_book_factors`/`_write_report`/`_section_payload` 11 份逐字符相同;其余 helper 剥掉字符串常量后坍缩成 1 组),另记 **7 个既有 bug**(只标不修)与 `FactorSpec` 16→20 键漂移(来自 #86/#91,须预登记否则对账时会被当 D5 回归去追)。**探测(`qt/saturation_probe.py`,可复跑工具非一次性脚本):D4 的 materializer 在真实评估面上跑不了**——995 票中 **84 票**首根 1min bar ≥ emit_start ⇒ pooled 饱和**永远无法提前终止、必打到 floor**(D4 已披露的代价在全规模是**构造性**兑现);磁盘 8.18/16.43 GB,名义内存 52.7/122.2 GB,而 **frame:峰值RSS 实测 2.2–2.45×**(`minute_bars` 逐票读完 `pd.concat`,parts 与结果同时在内存)⇒ 评估窗真实峰值 **≈115 GB vs 可用 56 GB**;**不止 pooled**——bounded 路径同样整 universe 单帧(`materialize.py:262`),光评估窗就超可用内存约一倍。**三条更便宜的出路全部证否**:按票分批会因 `service._ensure_coverage` 的缺口判定**只按日期不按 (date,symbol)** 而**静默丢名**(I5a 红线);按时间分块只救 3 个 bounded 因子;缩短窗口不动 pooled 加载左端(锚在 floor)。**关门的是 `intraday_amp_cut`**(**同时 pooled × 截面**)——按票分批毁其截面、按时间分块管不住其深度,两轴都绕不开。切口位置已实测:纯因子逐票 ≡ 整 universe **逐位相同**(728/1524 个可比 cell `max|diff|=0.0`),`intraday_amp_cut` 逐票 **1860 finite → 0** ⇒ **切口必须落在截面 combine 之前**。**暴露的设计层缺口(照实记)**:§八只给**决策热路径**(20 日尾窗)定过规模预算,**从未给评估路径定规模**,所以「整 universe 单帧」在设计评审里没人拦;而旧 runner docstring 白纸黑字写着 "the multi-year all-symbol minute panel is NEVER materialized"——**D4 的加载几何相对被替换对象是回归**,只是 D4 验收跑在 40 票 smoke 上看不见。lead 提的「复用 `panel_freeze.py` 的加载形态代替 D4b」**已证否**:它没有自己的加载形态,是 import 11 个 runner 调其私有 `_load_*_panel`——「复用」等于保留决策③要杀的第二条取数路径;可复用的是**形状**不是调用路径。⇒ **插入迁移步 D4b(per-symbol 流式物化)**,设计文档记为实施期修订 A1;C3–C6 刻意未开始(修 materializer 是改取值路径,须自带四腿验收,混进 D5 当「让 run 跑起来」正是本项目反复抓到的形态)。**评审两轮**:首轮 BLOCK 1 HIGH——被文档称作「幂等」的再冻结命令**静默销毁 provenance**(未改动树上跑,输出 `copied: 0 / already ok: 77` 报纯成功,git 里却清空 `source_note` 并把 `frozen_at_git_head` 改指向重跑者 = **主动的假声明**,而 **16 个测试全程绿灯**)→ 改为 provenance 继承 + 内容不变则一个字节不写 + **恒跑断言**(`source_note` 非空 / head ^[0-9a-f]{40}$ / sha256 合法 hex(原只验长度)/ `size_bytes`>0),评审逐条 mutation 四条全红;另修 2 MEDIUM(**本 PR 自己 suite 里的空 mutation**——那条 git-tracking 测试全程不碰 git,注释掉 `.gitignore` 仍通过 → 改查 **git 索引** `git ls-files -- `;**C6 爆炸半径漏编目 3 个 runner importer**,含**生产 D1 冻结基线的 `panel_freeze.py` 本身**,其重跑流程是「旧 SHA + 当前分支文件」的混合式、两种 C6 处置下都会失效)。**方法论(三个空 mutation,全部照实记)**:实现方两次(tamper 测试替换了文件里**不存在**的字节串;git 索引 mutation 用 `git add -f` 被 symlink 拒绝执行 → 什么也没改、测试**为错误的理由通过**),评审一次(它建议的 `git ls-files --error-unmatch` 对「未跟踪」与「路径不存在」**返回相同 rc=1**,据此的断言会在**最需要它的机器上空过**)——**三次都不是被测试抓住的,而是被「与预期矛盾的观察」抓住的;评审建议本身也需要 mutation 证据**。**顺带更正一条长期误诊**:「本机 pytest 摘要行被环境吞掉」是假的——`pyproject.toml:53` 已有 `addopts="-q"`,我们再传 `-q` 就是 `-qq`;去掉即出现 `N passed` 摘要行(点计数仍是有用的交叉校验,本轮就抓到 2113 vs 2114 之差,但它从来不是因为摘要不可得才必须存在)。gates:**2117 collected**(2116 passed + 1 skipped——跳的是字节绑定测试,其字节 gitignored;manifest 自洽测试**无 skipif 恒跑**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `--verify` 77/77 且 head 仍 `45c14aa` / 冻结面板 mtime 未变。 -- ✅ 质量门(**当前 `main` @ `f432dc5` 实测**:`python -m pytest -p no:warnings` **1776 passed**、`ruff` clean、`validate-config` **31/31 配置逐个 OK**、`run-phase0`(demo)**ic 0.9600 / annual 0.8408 不变**)。构成对得上:`aa19968` 实测 **1740** → #78 加 2(含钉住破折号闭合的那条)= **1742** → #79 加 34(两个新测试文件,0 个既有测试被挤掉)= **1776**。新增测试分组(`--collect-only` 实数,`aa19968` 口径):**因子评估契约层 contract=337 + standard=113 + figures=7 / 十一因子(factor + eval runner,21 文件)=421 / PR#75 exec-vwap-basis=46 / D3 market quality 15→21(含 #77 `decreasing_adj_factor` 及其相对-vs-绝对尺度 mutation 测试)**。历史分组(旧 793 基线):`pytest` **793 passed**(P0=97 / P1=78 / P2-1=22 / P2-2=22 / P2-3=14 / P2-4=8 / P3-1=10 / P3-2=18 / P3-3=16 / P3-4=15 / P3-5=22 / P3-6=27 / P3-7=25+1 throttle / P3-8=8 / P4-1=28 / P4-2=17 / **intraday I1 schema=14 + feed=9 / I2 cache=10 / I3 aggregate=13 / I4 execution=10 / P4-3 cache=10 + updater=7 / P-I5a event-backtest=19 / P-I5b exec-feasibility=16 / P-I5c mmp-minute=18 / P-I5d mmp-quintile=19 / P-I5e 仅 config(无新测试,`test_cache_config` 的 config glob 自动 +1)/ P-I5f intraday-liquidity=17(+ `test_cache_config` config glob 自动 +1)/ D1 token-dedup index-feed +2 / D2 cache-modularization +5 / D3 data-quality market=15 + intraday=12 + report=14 / D3b data-update-quality=18 / D4 coverage-ledger-scaling=13 / D5 scheduler=9 + concurrency=12 / schema-registry-guard=62 / all-A-incremental=12 / all-A-backfill=22**);当时 `ruff` clean、`validate-config` 20 配置(data_update + phase_i5a…phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 自 P0 起从未变过**)。 -- ⚠️ 剩余(已显式披露):日线 only、demo 路径非真数据、旧三因子无信号(P3-3/P3-4 实证;但其组合在 2024-2026 holdout 上 SSE50/CSI300/CSI500 全正、CSI500 高达 +17.8%——小样本/regime 翻转的持续例证);value/低波信号获得**独立样本符号级确认**(P3-7 SSE50/CSI300 量级衰减;P3-8 CSI500 泛化成立且更强),组合级盈利能力仍未确立(排名跨 cell 翻转);subset 报告文件名已可配置(P3-8 起不再互覆盖)。**分钟因子侧新增披露**:十一个复现因子全部 EXPLORATORY、**封顶 Watch,无一到 Adopt**,1× 费率下净多空为正的只有 2 个(PR-F/PR-L,且 PR-L 扛不住 2×)——**IC 强 ≠ 可交易**;MMP 的唯一正面证据(I5d)在修正引擎上退化、I5e 负结果加强;I5f 的容量结论已按**真实 100 万名义**重写(1000 万口径的"约半数不足"作废);**日频除权审计已完成**(GENUINE、无 vintage 接缝、评估池暴露 1/603,258,不重述任何因子结果),**分钟路径的复权缺陷已在 PR #75 修**;exec-to-exec 改基准已随 **PR #79 合并**,close-to-close 与 exec 两套 artifact 并存;⚠️ **#79 的 code review 未返回、靠自查合并**,其"未独立核"清单(五条 mutation 声称 / 十一个 runner 改动是否机械等同 / `execution_capacity` 透传)仍待后续检查——若查出问题,重述 exec 基准数字而非 close-to-close;schema 守卫**仍未接** live `data-update` 暖跑路径;HTML compendium(`artifacts/reports/factor_compendium.html`)**还读 v0.8 结果,待按最终结果修订**。 -- 路线图下一步:**MMP 暂缓推进——理由在修正引擎上比过去更强,不是不变**(I5e 独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转** 且**在修正引擎上更负**:Spearman **−0.5000 不变**、Q5 高 MMP 仍最差、合成 Q5−Q1 累计 **−26.77% → −31.82%**;而 I5d 的 CSI500 完美单调性在修正引擎上**退化**(Spearman **+1.0000 → +0.9000**、Q5−Q1 **+17.15% → +8.65%**、Q5 让位 Q4),四档归因实测**归到复权修正本身**——原结论实质是"把除权下跌记成亏损"的产物 → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断;**按用户真实规模 100 万 RMB / 5% 参与率重测:below-1.0x 由 20 笔变 0 笔、最紧 1.22×——单分钟容量在该 universe/窗口不构成约束**;此前"10M RMB 下约半数不足"的说法**已作废**,它描述的组合比本项目会跑的大一个数量级;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**盘后全A auto-fetch**(数据正确性审计 PASS——D3 质量层实测 80GB 真实缓存结构干净、ann_date PIT 轴 100%、跨 shard schema 无漂移;PR #59 = PR-1 全A 增量 auto-warm + systemd @21:00 default-off 已落地;**PR #61 = PR-2 全A 历史回填**——分块/断点续跑/分钟全窗口 + per-symbol 失败隔离 + `start>today` 守卫,已落地。**建设侧完成,剩 live rollout = 用户 Stage-1**:先 `data-backfill` 补全A 历史(可续跑)→ 手动跑增量 warm 观察 → enable systemd timer);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / data-update summary 落 cache-stats artifact);**数据层 D6**(D1+D2+D3+D3b+D4+D5 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分 + report-only `data/quality/` 质量层 + default-off `data-update` 质量报告钩子 + coverage ledger `record_many` 批量写 + 进程内查找缓存 + opt-in 有界并发 + 全局限频器 + **schema 注册表/default-off drift 守卫(PR #57)**;剩余数据层 follow-up:schema 守卫接 live `data-update` 暖跑路径(当前仅接 pipeline/回测) + 分钟 schema 守卫;**D6**=`PanelStore` append/partition / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);**因子层收尾**(① `feat/exec-to-exec-rebasis` 两基准对照已跑完并随 **PR #79 合并**——22 格中 20 格不变、2 格朝更保守走;**补一次 #79 的 code review**(原评审未返回,未独立核的三项见上);② HTML compendium 按最终结果修订;③ 十一因子**无一到 Adopt、净多空几乎全负** → 下一步优先做**换手/成本敏感**的组合层验证与更长 holdout,而不是继续堆新因子);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +`artifacts/refactor_baseline/{panels, panels_d2, exec_baseline, pr_c_cutoff_fix}` —— 后续一切对账的唯一参照物,**无法从当前代码再生**,只读。所有真实 run 一律 **cache-only**(日志须 `stk_mins_live_calls=0`)。 + +## 进度档案(完整历史;**按需 grep,不要整读**) + +本文件曾内联全部阶段日志(~78k tokens,每个会话都被完整加载)。历史已逐字搬进 `docs/progress/`,**内容一字未改**。 + +> **新阶段的详细记录写进 `docs/progress/`(PR 号追加进 `pr_ledger.md`),不要写回本文件。** +> 本文件只维护三处随进度变化的内容:**当前状态**表、**当前有效的披露与限制**、**路线图**。 + +| 档案 | 覆盖 | +|---|---| +| [`01_phase0_3_daily_pipeline.md`](docs/progress/01_phase0_3_daily_pipeline.md) | Phase 0 MVP / Phase 1 偏差边界 / Phase 2 执行真实性 + PIT 行业 / Phase 3 多因子·IC 加权·OOS·robustness·候选因子·子集·独立样本·CSI500(PR #1–#21) | +| [`02_phase4_cache_and_intraday_i1_i4.md`](docs/progress/02_phase4_cache_and_intraday_i1_i4.md) | 端点级持久化缓存 P4-1/4-2/4-3 + 分钟 pipeline I1–I4(PR #23–#31) | +| [`03_intraday_i5.md`](docs/progress/03_intraday_i5.md) | 事件驱动回测 / 执行期涨跌停 / MMP 因子与五分位 / CSI300 泛化负结果 / 流动性诊断(PR #33–#55) | +| [`04_data_layer.md`](docs/progress/04_data_layer.md) | 数据层 D1–D5、schema 注册表与 drift 守卫、全A auto-warm 与历史回填(PR #41–#61) | +| [`05_minute_factors_eval_contract.md`](docs/progress/05_minute_factors_eval_contract.md) | `analytics/eval/` 三轴判决契约 v0.1→v0.9 + 十一因子复现总表与结论(PR #63–#74) | +| [`06_execution_basis_and_corrections.md`](docs/progress/06_execution_basis_and_corrections.md) | VWAP 成交 / 分钟收益复权 / 披露修正 / `adj_factor` 检查 / 日频除权审计 / exec-to-exec 改基准(PR #75–#82) | +| [`07_factor_layer_refactor.md`](docs/progress/07_factor_layer_refactor.md) | 因子层重构 D0–D5 C4b 全部细节与评审发现(PR #84–#107) | +| [`08_gates_disclosures_roadmap.md`](docs/progress/08_gates_disclosures_roadmap.md) | 归档时点的质量门快照 / 完整披露清单 / 路线图原文 | + +其它权威文档:`docs/data/data_layer_contracts.md`(cache vs store 契约)、`docs/factors/`(D0 契约 / D1 面板冻结 manifest / D5 差异编目 / 性质测试映射表 / C5 审计报告骨架)、`docs/ops/data_update_schedule.md`。 + +## 当前有效的披露与限制 + +- **研究侧结论一律 EXPLORATORY、非业绩声明。** 日线 only;demo 路径非真数据。 +- 旧三因子(momentum_20 / roe / netprofit_yoy)**无信号**;**value/低波**获得独立样本**符号级**确认(P3-7 SSE50/CSI300 量级衰减、P3-8 CSI500 泛化更强),但**组合级盈利能力仍未确立**(组间排名跨 cell 翻转)。 +- 十一个分钟因子**全部封顶 Watch,无一到 Adopt**;1× 费率下净多空为正的只有 2 个(其一扛不住 2×)。 +- **MMP 暂缓推进,理由在修正引擎上比过去更强**:I5d 的 CSI500 完美单调性退化(Spearman +1.0→+0.9,Q5 让位 Q4,四档归因把效应归到**复权修正本身**——原结论实质是"把除权下跌记成亏损"的产物);I5e 的 CSI300 负结果不变且更负。**在出现 disjoint 窗口 / 第三 universe 的正向证据前,不当稳健信号推进。** +- I5f 容量诊断已按**真实 100 万 RMB 名义**重写(below-1.0x 由 20 笔变 **0** 笔,最紧 1.22×);此前"10M 名义下约半数不足"的说法**已作废**。执行模型仍无 partial-fill / volume-cap。 +- **日频除权审计已完成:GENUINE,无 vintage 接缝**(评估池暴露 1/603,258,不重述任何因子结果);分钟路径的复权缺陷已在 PR #75 修。 +- ⚠️ **PR #79 的 code review 未返回、靠自查合并**,其"未独立核"清单(五条 mutation 声称 / 十一个 runner 改动是否机械等同 / `execution_capacity` 透传)仍待补检——若查出问题,重述的是 **exec 基准**数字,close-to-close 未被触碰。 +- schema drift 守卫**仍未接** live `data-update` 暖跑路径(当前只接 pipeline/回测);分钟 schema 守卫未做。 +- HTML compendium(`artifacts/reports/factor_compendium.html`)**还读 v0.8 结果**,待按最终结果修订。 +- 全A 盘后自动拉取:**建设侧完成**(增量 auto-warm + systemd default-off + 历史回填),**live rollout 是用户 Stage-1**(先 `data-backfill` → 手动观察一次增量 warm → 才 enable timer)。 + +## 路线图 + +1. **因子层重构收尾(当前主线)**:F1 残桶 correctness PR → C5 audit PR(F2/F3/F4 扩类 + F5 修复 + 真实重跑 + 填审计报告)→ C5 进度文档 → C6 删 11 个旧 runner → D6a–d 存量收口 → D7 收官全量重评估(**exec-only**)。 +2. **因子研究**:十一因子无一到 Adopt ⇒ 下一步优先做**换手/成本敏感的组合层验证**与更长 holdout,而不是继续堆新因子;补 PR #79 的 review;修订 HTML compendium。 +3. **可选、须单独显式 goal**:I5g(强制 partial-fill / volume-cap);数据层 D6(`PanelStore` append/partition,仅当因子研究需要可复用派生面板时才启动);schema 守卫接 live `data-update`。 diff --git a/docs/progress/01_phase0_3_daily_pipeline.md b/docs/progress/01_phase0_3_daily_pipeline.md new file mode 100644 index 0000000..c668403 --- /dev/null +++ b/docs/progress/01_phase0_3_daily_pipeline.md @@ -0,0 +1,100 @@ +# Phase 0 – Phase 3:日频截面多因子链路(PR #1 – #21) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +从 7 层骨架 / Phase 0 MVP,到 Phase 1 偏差边界、Phase 2 执行真实性与 PIT 行业、Phase 3 多因子 baseline / walk-forward IC 加权 / OOS / robustness matrix / 候选因子组 / 子集复检 / 独立样本验证 / CSI500 泛化。 + +--- + +- ✅ 7 层骨架 + 架构文档(`main`) +- ✅ **Phase 0 MVP**(PR #1):DemoFeed → PanelStore → StaticUniverse → momentum_20 → zscore → EqualWeightAlpha → TopN 等权 → 月度回测(成本/换手)→ IC/绩效报告,单命令可复现。 +- ✅ **Phase 1 偏差边界**(PR #1,全部真数据实证): + - 前复权(qfq;store 存 raw,内存复权 → batch≡incremental 安全) + - PIT 指数成分(`index_weight` as-of,survivorship-safe;370 天 pre-start 回看 + 90 天分页) + - 可交易过滤(停牌 / ST / 涨跌停;**涨跌停用未复权 raw close** 比 `stk_limit`) + - 财务 `ann_date` 披露日 as-of(绝不按 end_date;500 天 lookback carry forward) + - 行业 + 市值中性化(按 date 截面 OLS 残差;欠定/无自由度截面 → NaN) + - 路径感知降级披露(demo/static vs tushare/index/ann_date,绝不把 demo 当真实验证) +- ✅ 真数据实证(tushare,非 CI):复权除权日 raw−5.74%→qfq+0.99% / CSI300 全年 24 快照 328 名换手 / ann_date Q1 延后至 04-20 / 中性化 corr −0.617→0。详见 `BIAS_AUDIT.md`、`artifacts/reports/phase1_summary.md`。 +- ✅ **Phase 2-1 真实数据可复现基准**(**PR #2 已 merge 到 `main`**):新 run mode `run-phase2-baseline` + `config/phase2_real_baseline.yaml`(上证50 `000016.SH`,2023-07~2024-06)。**复用 P0/P1 全套机器,不扩因子、不调参**。一次真实跑 ~11min(68 成分 / 25 loaded 快照 / in-window distinct 60 / 11 settled 调仓,候选 12 末日跳过),输出 `artifacts/reports/phase2_real_baseline.md`(gitignored):数据窗口 / PIT 成分摘要(loaded vs in-window) / ann_date 覆盖率(100%) / 可交易过滤命中(首命中互斥) / 每期持仓 / 换手成本 / IC(≈0.008) / 绩效(年化−17.6%,**亏损动量基准,非业绩声明**) / 全部 P2 降级。诊断只读、demo 源拒绝、token 不入报告。 +- ✅ **Phase 2-2 执行真实性**(PR #3 已 merge 到 `main`):**拆分 selection(选谁)与 execution feasibility(能否成交)**。 + - 方向感知执行 `runtime/fills.py::simulate_fills`:涨停挡买 / 跌停挡卖 / 停牌·缺收盘双向挡;按 panel flag 实时判定,与选股 toggle 无关。 + - **现金一致 sell-then-buy**:卖在前释放现金、买在后,现金不足按比例部分成交 → **无杠杆**;被挡交易 carry forward;换手/成本只算实际成交;闲置现金按 driver 的 `cash_return` 计息(BT-007)。 + - `universe.min_listing_days` **真实路径已执行**(`stock_basic.list_date` 富化,买入资格过滤,边界 age==min 放行,缺 list_date 保留并披露);demo 无上市日 → 披露 no-op。 + - 回测 `feasibility_log()`:每调仓期 blocked buys/sells/carried/executed turnover/invested;phase2 报告新增 **Execution feasibility** 小节。 + - **保不变量**:demo 无 flag → 全可成交 → P0/P1 数字不变;无未来函数、PIT/ann_date/real-demo 分离不变。 +- ✅ **Phase 2-3 历史 PIT 行业**(**PR #5 已 merge 到 `main`**,默认 SW-L1 可配置):把行业中性化协变量从 `stock_basic.industry` **当前**标签升级为按 trade_date **as-of** 的历史申万行业。 + - `tushare_covariates.pit_sw_intervals(symbols, level)` 读 `index_member_all` 取每股 SW 行业 `in_date`/`out_date` 区间;`data/clean/pit_industry.py::asof_industry` 按 `[in_date,out_date)` 覆盖该日取行业(改分类日新行业生效,PIT-safe;起始日前成分 carry forward 到窗口开始)。 + - **SW 层级可配置** `processing.neutralize.industry_level`(L1/L2/L3,**默认 L1**=31 宽板块,中性化标准 + 小截面自由度更稳)。**关键实证**:旧 tag 年化 −17.6% / SW-L1 −10.2% / SW-L2 −9.3% → **L1≈L2**,大跳的主因是 **tushare→SW 分类切换**(补 PIT 的必然:只有 SW 有 in/out 历史可 PIT 化,旧 tag 无法),**与粒度无关**。(注:曾误以为是粒度、默认 L2,经 L2 实测推翻,改默认 L1。) + - **不静默退回 current**:无 SW 历史的票 → 行业 NaN,被 neutralize 按截面丢弃(neutralize 数学不变,本就丢 NaN 行);每次运行 **实际 level + PIT 覆盖率**进 phase2 报告。 +- ✅ **Phase 2-4 标准分析集成**(**PR #7 已 merge 到 `main`**):alphalens-reloaded + quantstats 接入报告(**report-only cross-check**)。 + - `analytics/alphalens_adapter.py`(IC mean/IR + 分位均值)+ `analytics/quantstats_adapter.py`(CAGR/Sharpe/maxDD/vol)薄 adapter,各带 `backend` 字段。 + - **简版 numpy/pandas 仍权威**(驱动回测 + cross-check);依赖不可用/报错 → 报告显式披露 backend(unavailable/error,只记异常**类型**不记消息),**绝不静默假装用了标准库**。 + - **不改 alpha/portfolio/runtime/fills/universe**:P0/P2 交易数字不变(demo ic 0.96/annual 0.84 不变;实测 alphalens IC=简版 IC 完全吻合),只新增 **Standard analytics** 报告段。 +- ✅ **Phase 3-1 首个真实多因子 baseline**(**PR #9 已 merge 到 `main`**):pipeline 从"只用第一个 enabled factor"升级为**消费全部 enabled factors**;新增 `config/phase3_real_multifactor.yaml`(momentum_20 + roe + netprofit_yoy,SSE50 同窗口,与 phase2 可比)。**不调参、无 learned weights、非收益承诺**。 + - `_build_factors` 全量实例化(配置序;重名报错);财务字段**一次 fetch + 一次 as-of 对齐**(逐字段独立守 `ann_date <= trade_date`,无每因子重复拉取);demo+财务因子仍可读报错。 + - 合成仍 `EqualWeightAlpha`(处理后各列等权平均,不看 forward returns);`drop_missing` 要求该日该票**所有**因子齐备(显式披露)。 + - 报告增强:active factor list / per-factor coverage+IC+分位 / **combo score** 诊断 / 财务 coverage **按字段**披露(TRADED vs diagnostic 角色标注);`output.baseline_report_name` 使 phase3 报告独立于 phase2。 + - **真实结果**(SSE50 2023-07~2024-06,~14min):多因子 annual **−9.05%**(单因子 −10.19%);per-factor IC: momentum_20 0.0083(与 phase2 run 完全一致,跨 run 一致性实证)/ roe 0.0006 / netprofit_yoy 0.0001;combo IC −0.0038;财务两字段 ann_date 覆盖 **100%**;PIT SW-L1 98.53%。财务因子在该小截面短窗口 IC≈0,照实披露——这是 plumbing 验证。 + - **回归不破**:phase2 单因子真实 rerun annual −10.19% / IC 0.0083 不变;demo ic 0.96/annual 0.84 不变。 +- ✅ **Phase 3-2 walk-forward IC 加权 alpha**(**PR #11 已 merge 到 `main`**):新增 `alpha/ic_weight.py::RollingICWeightAlpha`(`alpha.model: ic_weighted`);EqualWeightAlpha 仍默认 + 回归基线。**不调参、非收益声明**。 + - **Lookahead 边界(测试锁定)**:训练严格 walk-forward——(factor[t], fwd_h[t]) 只有**已实现**(交易日序 `t+h <= d`)才进日 d 的权重;**扰动未实现 forward returns 权重不变**(扰动测试)+ `t+h` 切片精确边界测试。forward returns 由 pipeline 在 alpha 边界计算、只传 `alpha.fit`,factors 层照旧绝不接触(不变量 #1)。 + - rolling(默认保守,窗口=60 交易日,min_periods=20)/ expanding 可配;历史不足 → 该日**退回等权**(与 EqualWeightAlpha 逐 bit 一致)并计数披露;权重 L1 归一化、保留符号(负 IC 因子负权重)。 + - 报告新增 **Alpha model** 必含小节:active model / 超参 / 训练覆盖率 + fallback 次数 / 每调仓日生效权重表(fallback 行标注)/ 非调参声明 + 等权基线对比指引。 + - `config/phase3_real_ic_weighted.yaml`:与 phase3_real_multifactor **唯一差异是 alpha.model**(universe/window/因子/中性化全同,直接可比)。 + - **真实结果**(SSE50 2023-07~2024-06,~14min):annual **−3.57%**(等权 −9.05% / 单因子 −10.19%),maxDD −12.93%,训练覆盖 **201/221**(20 个 fallback 全在窗口攒满前,90.95%)。⚠️ 优于等权**不是**业绩声明——单年窗口 + 权重逐期翻号(如 momentum_20 从 −0.58 到 +0.36)正是小样本不稳定的体现,照实披露。 + - **回归不破**:phase3 等权真实 rerun annual −9.05% / IC 0.0083 不变;demo equal_weight ic 0.96/annual 0.84 不变(测试锁定)。 +- ✅ **Phase 3-3 OOS 稳定性验证**(**PR #12 已 merge 到 `main`**,含 review 两 HIGH+一 LOW 修复):**报告型验证层**,不加新 alpha 复杂度、不改 portfolio/execution/factor math。新 run mode `run-phase3-oos`(`qt/oos_stability.py`)+ `config/phase3_real_oos_stability.yaml`(SSE50 扩到 **2 年** 2022-07~2024-06,split 2023-07-01 → train 1y / test 1y,test 年=旧 baseline 窗口可对照)。 + - **一次数据加载、同一 processed 因子面板、两次回测**(equal_weight vs ic_weighted);所有诊断按 split 切段(子段 nav 重新归一,绝不跨段串味)。 + - **边界语义(测试锁定)**:walk-forward(rolling subperiod)——任何日期的权重只用该日已实现观测(`t+h <= d`);**扰动 split 后全部 forward returns,train 期所有日期权重逐 bit 不变**(split 无泄漏测试);不用 freeze-at-split(那是新 alpha 模式,超范围)。**绩效切片按持有窗口**(train 行持有期 end≤split、test 行 start≥split,跨界调仓从两段排除并披露;IC 按实现日 t+h 切)——绝不按 signal date 单切(review HIGH 修复:旧切法把跨界持有期的 test 收益记进 train);runner 强制 `alpha.model: ic_weighted`(否则假对比,可读报错)。 + - 报告 `phase3_oos_stability.md`:split 边界+跨界行披露/分期绩效(annual/vol/sharpe/maxDD/turnover)/逐序列 IC 分期(mean/IR/hit rate/sign consistency)/权重稳定性(每期权重含 train-test 标注、trained 行 sign flips、fallback 次数+原因)/小样本 caveat。 + - **真实结果(关键发现,~16min,77 成分/2 年,持有窗口切片)**:三个原始因子 train→test **IC 全部翻号**(momentum −0.023→+0.006 / roe −0.029→+0.007 / np_yoy −0.011→+0.005,sign consistency 全 NO),hit rate 46~53%≈抛硬币;权重 23 期 sign flips 7/3/4;绩效 eq train −11.92%/test −5.27%,ic train −8.31%/test −2.70%(跨界行 2023-06-30 排除;修切片前 train 被 test 期收益污染到 −6.81%/−1.69%,修正幅度本身就是边界 bug 的实证)。**结论:ic_weighted 两段都略好但 IC≈0 且翻号——P3-2 单年跑赢不可外推,这正是本验证层要拿到的证据;非收益声明。** + - **回归不破**:phase3 equal_weight rerun −9.05%/0.0083、ic_weighted rerun −3.57% 均不变;demo 0.96/0.84 不变;secret scan 报告 0 处 token/config.json。 +- ✅ **Phase 3-4 robustness matrix**(**PR #14 已 merge 到 `main`**,含 review MEDIUM 修复:矩阵 DOWNGRADES 全 universe 披露):把 P3-3 OOS 检验批量跑到 **universe × window 矩阵**上,回答 P3-2/P3-3 结论是否 SSE50 小样本偶然。不加因子/alpha、不改 portfolio/execution/factor math。 + - 新 run mode `run-phase3-robustness`(`qt/robustness.py`)+ `config/phase3_real_robustness_matrix.yaml`(SSE50+CSI300 × 2020-2022/2022-2024 两 fold;`skip_cells` 显式跳过 CSI300×2020-2022——runtime 预算,报告披露,绝不静默缩覆盖)。 + - **每个 cell 逐字复用 P3-3 cell 核心**(`_run_oos_cell` 重构抽出,单 run 行为不变;持有窗口切片/实现日 IC 切片/walk-forward/ic_weighted guard 全继承);cell config 仅替换 universe/窗口/split/output_name(重过全套 pydantic 验证,parquet 不互覆盖);跨 cell 汇总严格按 cell 归属(测试锁定不串数)。 + - **真实矩阵结果**(3 cells,总 2h:960s/934s/5301s,CSI300 399 名): + - **P3-3 不漂移 smoke ✓**:SSE50|2022-2024 cell 逐数复现 P3-3 报告(−11.92%/−5.27%/−8.31%,boundary 2023-06-30)。 + - 原始因子 train→test sign consistency:momentum 1/3 / roe 0/3 / np_yoy 0/3——**IC 翻号跨 cell 普遍,P3-3 结论非偶然**。 + - `combo_ic_weighted` 是唯一跨 cell 稳定序列(test IC 3/3 正 + 3/3 sign consistent)但量级 ≈0.004~0.008 极小。 + - **关键负面发现**:ic_weighted 组合绩效不稳健——train→test 翻车形态出现在 **3 cells 中的 2 个**:SSE50|2020-2022 train **+7.09%→test −7.85%**、CSI300|2022-2024 train **+11.07%→test −17.74%**(sharpe +0.59→−1.01;CSI300 换手 1.2~1.6 成本拖累更重)。**凡 ic_weighted 样本内显著为正,样本外即翻负——典型过拟合签名;P3-2 的"跑赢"不能外推;微弱正 IC ≠ 净值赢。非收益声明。** + - secret scan 报告 0 处;demo 0.96/0.84 不变。 +- ✅ **Phase 3-5 factor candidate pack**(**PR #16 已 merge 到 `main`**,**EXPLORATORY**):保守日频 PIT-safe 候选因子组,经 P3-4 矩阵原样复检"弱信号是否只是因子集太窄"。不动 alpha/portfolio/execution/OOS/robustness、不调参。 + - 新因子(`factors/compute/candidates.py`,数学/前导 NaN/未来扰动不变/跨 symbol 隔离/不可变全测试锁定):`reversal_5/20`(=−momentum 复用同机器)/`volatility_20`(滚动收益 std,满窗)/`liquidity_20`(log 滚动均额,非正→NaN)/`overnight_mom_20`(Σlog(open_t/close_{t-1}) 20 日,open 当日开盘已知,prev close 不跨 symbol)/`value_ep`/`value_bp`(daily_basic pe/pb **一次 fetch** 取倒数,≤0→NaN,当日发布 PIT-safe)/`grossprofit_margin`(进 SUPPORTED_FIELDS,走既有 ann_date as-of)。 + - dispatch(registry)扩展;**窗口名/params 不一致 → 可读报错**(绝不静默错标列);旧配置逐 bit 复现(demo 0.96/0.84 不变)。 + - `config/phase3_real_factor_candidates.yaml`:旧三 + 候选八 = 11 因子,同 P3-4 矩阵形状(CSI300×2020-2022 显式 skip 披露);**旧 vs 新一跑读出**(raw IC 按列独立)。 + - **真实矩阵(11 因子版 3 cells / 2h;旧三因子+前轮候选 per-cell IC 逐数不变=不漂移 smoke ✓;secret scan 0;一次 CSI300 网络故障后整 run 重跑成功)**: + - **"因子集太窄"假设获得支持**:`value_ep`/`value_bp` test IC **3/3 正、0.037~0.056**;`volatility_20` **3/3 负、−0.044~−0.079**(低波方向稳定)——比旧三因子(|IC|≤0.015)高一个量级且方向跨 cell 一致。liquidity 3/3 负但量级小;`overnight_mom_20` test IC 2/3 正(+0.008/+0.016,2020-22 为负)中等偏弱;reversal/grossprofit_margin 翻号无信号。 + - `combo_ic_weighted` test IC **3/3 正 + 3/3 sign consistent**(0.0253/0.0012/0.0395)——walk-forward IC 加权吃到了新信号;11 因子**等权** combo 反被翻号因子稀释(IC 0/3 正,CSI300 test **−25.2%**)——等权对因子集质量敏感。 + - 组合绩效:ic test −2.21%/−5.02%/−2.76%(**3/3 cells 跑赢等权**;CSI300 train +23.11%→test −2.76%,train→test 衰减仍显著)。 + - ⚠️ **EXPLORATORY,非收益声明**:value/低波在 2020-2024 A 股是已知强势 regime;三 cells 窗口重叠非独立样本;未调参、未做成本敏感性。下一步候选:value+lowvol 子集独立复检。 +- ✅ **Phase 3-6 value+lowvol 子集复检 + 成本敏感性**(**PR #18 已 merge 到 `main`**,**EXPLORATORY**):把 P3-5 发现的 value/低波信号做**组间对照**(同一矩阵、同 footing),并补三档交易成本场景。**report-only:不加 alpha、不调参、不动 portfolio/execution/OOS 切片/robustness 聚合;`_run_oos_cell` 一字未动**。 + - 新 run mode `run-phase3-subset`(`qt/subset_validation.py`)+ `config/phase3_real_subset_costs.yaml`:4 组(legacy_trio / full_pack=11 因子 / value_lowvol=value_ep+value_bp+volatility_20 / value_lowvol_liq=+liquidity_20,/goal 的"可选"直接实测)× 3 成本场景(base ×1 / 2x ×2 / high_cost ×4,**必须有 multiplier=1.0 base 锚**,config 校验强制)× P3-4 矩阵 3 cells(CSI300×2020-2022 skip 照旧披露)。 + - **机制(测试锁定)**:每 cell 一次共享加载 + raw factor panel(与 OOS cell 同调用序)→ 每组从 raw panel **独立重处理**(`drop_missing` 按组——切 processed 列会错因为 drop_missing 跨列;全列组与旧处理 bitwise 一致)→ 每组 eq vs walk-forward ic_weighted × 每场景回测。成本场景只乘 `cost.fee_rate`:scores/fills 不见 fee → **trades/turnover/gross 跨场景严格不变,只动成本线**;`_run_backtest_for` 增加 default-preserving `fee_rate` 参数(None==旧行为,bitwise 测试锁定)。 + - **三层不漂移对账(真实 run 全过)**:① raw 因子 IC **66/66 行**(11 因子×train/test×3 cells)与 P3-5 报告逐数一致;② `legacy_trio`@base 逐数复现 P3-3/P3-4(ic test −7.85%/−2.70%/−17.74%,train +7.09%/−8.31%/+11.07%);③ `full_pack`@base 逐数复现 P3-5(ic test −2.21%/−5.02%/−2.76%,combo IC 0.0253/0.0012/0.0395,eq test −4.30%/−6.83%/−25.16%)——按组重处理 ≡ 把该组当配置因子集单跑。 + - **真实结果(3 cells×4 组×3 场景,~2.1h)**: + - **子集假设在净值层不成立**:value_lowvol ic test base **−4.49%/−8.66%/−5.84%** vs full_pack **−2.21%/−5.02%/−2.76%**——full_pack 在 **3/3 cells × 全部场景**净值更优。IC 层子集略稳:value_lowvol combo_ic test IC 3/3 正(0.0245/**0.0107**/0.0387),量级与 full_pack 相当且中间 cell 远离 0(full_pack 0.0012≈0)——**IC 稳定性与净值排序不同向**,照实披露。 + - value_lowvol_liq(+liquidity_20):CSI300 IC 最高(0.0427)、SSE50|2020-2022 单 cell 最佳(−2.48%),但 2022-2024 两 cell 均劣于 value_lowvol——**加 liquidity 非免费午餐**,"可选"问题用数据回答。 + - 等权稀释复确认:full_pack eq CSI300 −25.16% vs value_lowvol eq −14.97%(子集等权耐稀释);ic beats eq(base):trio 2/3、full_pack 3/3、value_lowvol 3/3、liq 2/3。 + - **成本敏感性(新维度)**:base→2x→high_cost **全组全 cell 单调恶化**(无例外);4× fee 退化 1.7~4.4pp 年化——legacy_trio CSI300 最重(−17.74%→−22.13%,高换手代价),value_lowvol 低换手(0.41~0.74/月)退化 ~1.8~2.4pp;算术年化 drag base ~0.5-0.9% / high_cost ~1.4-4.8%;turnover 跨场景不变实测 ✓。 + - ⚠️ **全组 test annual 均为负——非收益声明**;**POST-HOC 选择已披露**(子集是在同一批窗口上看完 P3-5 结果后选的,本 run 只量化相对稳健性+成本敏感性,**不是独立确认**——独立确认需真正新窗口/新 universe)。 + - secret scan 报告+日志 0 处(token 值/"token"/".config.json");demo 0.96/0.84 不变。 +- ✅ **Phase 3-7 独立样本验证**(**PR #19 已 merge 到 `main`**,曾 stacked 在 P3-6 分支上、PR #18 先合后顺序合入。**EXPLORATORY**,含 review 2 HIGH 修复):把 value/低波发现从"同窗口 POST-HOC 对照"推进到真正独立 holdout。不加因子/alpha、不调参;P3-6 group/cost 逻辑与配置不变(旧 27 测试原样通过)。 + - **review 2 HIGH 修复**:P3-7 报告曾沿用 P3-6 标题/"not independent confirmation"开场白/"SAME overlapping windows"收尾 caveat,与自身 verdict 节矛盾 → `render_subset_validation` 标题/框架/caveat 与 `_subset_downgrades` 按是否含 independent cells 分支(P3-6 配置原文不变,回归测试锁定,+3 测试);artifact 按用户指示**不重跑矩阵**,以确定性脚本把 5 处与数据无关的静态文案行替换为新渲染器的精确输出(新字符串从新代码路径提取、非手敲;diff 仅 5 行、全部数字未动、secret scan 0)。 + - **机制(测试锁定)**:独立性是**人的声明**——`subset_validation.independent_cells` 显式列 cell(必须引用矩阵已声明 cell、**禁止与 skip_cells 冲突**——"声明独立验证却不跑"是配置错误;未声明一律默认 screened,保守);`hypotheses`(因子→期望 IC 符号)**run 前固定**于 config(value_ep+/value_bp+/volatility_20−,源自 P3-5 筛选);`min_rebalances`(默认 8)不足 → **INSUFFICIENT-DATA**(样本量必披露,绝不静默通过)。verdict=事实性符号检查(HOLDS=期望符号在 holdout **双子期**都成立;SUPPORTED/PARTIAL/NOT SUPPORTED;NaN/缺列不成立);**分类汇总绝不混**(summarize_by_sample 按 sample class 各自聚合;verdict 节只读独立 cells)。 + - 真实 config:screened anchor SSE50|2022-2024(必须逐数复现 P3-6=run 内不漂移锚)+ 独立 SSE50|2024-2026、CSI300|2024-2026(**2024-07-01→2026-05-31 后于全部筛选**,split 2025-07-01;数据实测到 2026-06);CSI300|2022-2024 skip 披露。 + - **运维实证**:两次真实 run 死于瞬时 ConnectionError(旧默认 3 次重试≈3s 容忍)→ 默认重试预算 **3→6**(≈23s 容忍,成功路径不变,测试锁定)→ 第三次 ~2.2h 跑通。⚠️ P3-7 报告与 P3-6 同名(`phase3_subset_validation.md`),后跑覆盖前跑(regenerable,数字都在进度文档)。 + - **anchor 不漂移 ✓**:raw IC 22/22 ≡ P3-5 矩阵报告;组级 base 年化逐数 ≡ P3-6。 + - **真实结果(3 cells/~2.2h,独立结论只来自 2 个 holdout cells)**: + - **独立 verdict:2/2 cells SUPPORTED**(各 21 settled rebalances vs min 8)——value_ep IC +0.0322/+0.0134(SSE50 holdout train/test)与 +0.0245/+0.0072(CSI300);value_bp +0.0379/+0.0033 与 +0.0310/+0.0041;volatility_20 −0.0320/−0.0120 与 −0.0373/−0.0164。**P3-5 的符号假设在未参与筛选的样本上全部成立**。 + - ⚠️ **量级显著衰减**:筛选期 |IC| 0.037~0.079 → holdout 后段 0.003~0.016(value_bp 后段≈0)——符号存活、强度衰减(部分向零回归),照实披露。 + - combo_ic_weighted 独立 test IC **8/8 正**(4 组×2 cells,0.0060~0.0238)——walk-forward IC 加权在 holdout 上仍有效。 + - **组合净值仍未确立**:独立 base ic test——legacy_trio **+1.15%/+8.13%**(2/2 正,恰是此前实证"无信号"的组!)、full_pack +7.30%/−1.93%、value_lowvol +1.72%/−4.68%、liq +3.53%/−0.39%——组间排名跨 cell 翻转,~21 rebalances 小样本,**IC 符号确认 ≠ 组合盈利**;成本阶梯仍全单调。非收益声明。 + - secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 +- ✅ **Phase 3-8 CSI500 独立泛化检验**(**PR #21 已 merge 到 `main`**,**EXPLORATORY**):P3-7 的符号级结论是否泛化到筛选 universe 之外——新增独立 cell **CSI500(000905.SH)|2024-2026**(universe+时间双独立)。**机器零改动**复用 P3-7 层(同组/同成本场景/同假设,不加因子不调参);唯一代码变更 `output.subset_report_name`(沿 `baseline_report_name` 先例,默认 None 保持旧文件名 bitwise,测试锁定)——P3-8 报告独立成文件,**不再覆盖已验收的 P3-7 artifact**。 + - cell 设计:SSE50|2022-2024(screened 锚,须 ≡P3-6/P3-7)+ SSE50|2024-2026(independent 锚,须 ≡P3-7 verdict 数字)+ CSI500|2024-2026(主问题);CSI500|2022-2024 skip 披露(~645 名×2y 再加 ~3h 不值)。数据可行性先实测:index_weight 月度 500 名快照到 2026-05-29 含 2024-06-28 pre-start 锚。 + - **真实 run(3 cells/~3.55h,CSI500 735 distinct 名主导;一次跑通)双锚对账 ✓**:screened raw IC 22/22 ≡ P3-5 报告;independent SSE50 verdict IC 逐数 ≡ P3-7(+0.0322/+0.0134、+0.0379/+0.0033、−0.0320/−0.0120)——复现性二度确认。 + - **CSI500 verdict:SUPPORTED**(21 settled vs min 8):value_ep **+0.0083/+0.0145**(train/test)、value_bp **+0.0230/+0.0127**、volatility_20 **−0.0350/−0.0272**——三假设双子期全保持。**且衰减更小**:CSI500 test 子期量级(0.0127~0.0272)明显高于 SSE50/CSI300 holdout 的后段(0.003~0.016)——value/低波信号在中盘股上更强,**P3-7 结论泛化成立(GENERALIZES,未减弱)**。 + - combo_ic_weighted CSI500 test IC 全 4 组正(0.0243/0.0294/0.0286/0.0285,为三个 holdout cells 最高);**组合净值 CSI500 base 全组正且 4× 高成本仍全正**(trio +17.80%→+10.57% / full_pack +7.83%→+2.95% / value_lowvol +4.19%→+1.14% / liq +4.02%→+0.84%)——首个全成本阶梯为正的 cell。⚠️ 仍然诚实:trio +17.80% 又是组间排名跨 cell 翻转的例证(中盘动量 regime);单窗口 ~21 调仓小样本;**非收益声明**。 + - **报告标题配置化**(`output.subset_report_title`,沿 `subset_report_name` 先例):P3-8 报告 H1 自报 study 名「Phase 3-8 — CSI500 Independent Generalization Check」而非机器默认 P3-7 标签(review 同类 stale-wording 问题修复,测试断言首行非 P3-7;P3-6/P3-7 配置不设此项、保持 sample-aware 默认,回归锁定);文案/分区检查全过(无 stale P3-6 措辞、verdict 节只含独立 cells);secret scan 报告+日志 0 处;demo 0.96/0.84 不变。 diff --git a/docs/progress/02_phase4_cache_and_intraday_i1_i4.md b/docs/progress/02_phase4_cache_and_intraday_i1_i4.md new file mode 100644 index 0000000..f651778 --- /dev/null +++ b/docs/progress/02_phase4_cache_and_intraday_i1_i4.md @@ -0,0 +1,46 @@ +# Phase 4-1/4-2/4-3 持久化缓存 + 分钟级 pipeline I1–I4(PR #23 – #31) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +端点级 raw 缓存(行情 / universe / tradability / 因子支撑端点)、`data-update` 增量暖跑,以及 1min raw feed → 缓存 → PIT 日频聚合 → 尾盘执行骨架。 + +--- + +- ✅ **Phase 4-1 持久化 Tushare 行情缓存**(**PR #23 已 merge 到 `main`**,daily + adj_factor):feed 之下的 **endpoint 级 raw 缓存**——真实 run 不再每次重抓全量日线+复权因子。**默认 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后行情走 read-through,只有未覆盖的日期区间打 API。 + - **缓存只存 raw**(未复权 OHLCV/amount + 原始 adj_factor),绝不存 qfq、绝不存任何 secret;`front_adjust` 仍在内存跑,公式/时机零改动;`PanelStore` 仍是 per-run artifact,**不是**缓存 SoT。 + - `data/cache/`:`intervals`(闭区间日历算法做 gap 规划——按"日历区间是否抓过"而非"行是否存在"判定,正确区分"未抓"vs"源本无行")/ `parquet_store`(按 `(endpoint,symbol)` 分文件存 `symbol_prefix` 分片,原子 upsert 按 `(date,symbol)` 去重,latest 胜)/ `coverage`(append-only ledger,11 列含 status ok/empty/failed,**只 ok/empty 算覆盖**,failed 留待重试)/ `tushare_cache`(read-through:只抓 gap、upsert、记 coverage 含空返回、再从缓存读全区间;`refresh_recent_days` 重抓近端 tail;`force_refresh` 整端点重拉;per-run 抓取计数 stats 日志)。 + - `TushareFeed.get_bars`:`cache=None` → 旧直抓路径**逐字不变**;`cache` 注入 → read-through 后用**与直抓完全相同的 join+select**,故 `front_adjust` 前面板逐字节一致(qfq 等价单测锁定);per-symbol 限流/重试仍留在 feed 的 `_call` 闭包里。`_build_market_cache` 仅在 `data.cache.enabled` 时接线。 + - **配置**:`data.cache`(enabled=False / root_dir=`artifacts/cache/tushare/v1`(gitignored)/ refresh_recent_days=14 / force_refresh=[]);校验拒绝负刷新窗/空 root/未知键;全部旧配置仍 validate。 + - **真实 smoke(phase2 baseline;非缓存参照 ref / 缓存冷 cold / 缓存暖 warm 三轮)**: + - **三轮 report 指标完全一致** ✓(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%,REF==COLD==WARM)——qfq 等价单测已证 cached==direct 数据层逐字节一致,真实 run 三轮一致是端到端实证。 + - **warm 轮零市场端点调用**:cold 后 coverage ledger = 68 market_daily + 68 adj_factor 行(68 成分全 ok);warm 后**仍 68+68 不变**(零新 coverage 行 = 零 gap-fetch = 零 daily/adj 调用)。 + - wall:ref 998s / cold 1025s / warm **734s**(暖跑省 ~290s = 行情抓取部分;index_weight/财务/covariates 仍 live——P4-1 只缓存行情,诚实标注)。 + - secret scan:缓存 parquet + ledger 0 处 token / `.config.json`;ledger 列只有端点元数据。 + - **缓存命中直接可见(review follow-up)**:`TushareFeed.cache_stats()` 暴露各端点 gap-fetch 计数,`_load_panel` 经 run-scoped logger 打 `data cache: market_daily_gap_fetches=N adj_factor_gap_fetches=M`——冷跑非零、暖跑 0/0(warm rerun 实证 run_phase2_baseline.log 含 `0/0`,secret scan 0)。 +- **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report 全不动;`artifacts/data/{output_name}.parquet` 不当 SoT。范围克制:P4-1 只 market_daily+adj_factor,其余端点(index_weight/daily_basic/fina_indicator/...)P4-2/P4-3 再缓存。 +- ✅ **Phase 4-2 持久化 Tushare universe + tradability 缓存**(**PR #25 已 merge 到 `main`**,index_weight + suspend_d + namechange + stk_limit + stock_basic):把 P4-1 端点级 raw 缓存扩到 universe/可交易性端点——真实 run 不再每次重抓成分股/停牌/ST/涨跌停/上市日。**默认仍 disabled(向后兼容,旧配置行为一字不变)**;opt-in 后这五端点走 read-through。 + - **三种规划形态共用一引擎**:① dense per-symbol 日期区间(`suspend_d`/`stk_limit`,复用 P4-1 gap + recent-tail);② index_code 维日期区间(`index_weight`,coverage key=index_code,gap 内仍 **90 天分页**,raw 快照入库);③ snapshot 维度(`namechange` per-symbol、`stock_basic` 全局 sentinel),用 `refresh_dimension_days`(默认 30)staleness + force_refresh(`CoverageLedger.snapshot_fetched_at` 给新鲜度判定)。 + - **语义全保**:`index_weight` 仍 PIT/as-of(370 天 pre-start lookback 进缓存请求区间,latest-snapshot-on-or-before 仍在 feed/universe);`stk_limit` 仍 **raw price**(限价检查在 front-adjust 前,不碰 qfq);`stock_basic` 只取 list_date 供 `min_listing_days`(缺失仍 kept+披露),current-tag `industry` 绝不入缓存/中性化;`suspend_d`/`namechange`/ST 区间形状不变;**缓存只存 raw 端点事实,不存派生 flag 作 SoT**。 + - **三 feed 接线**:`IndexConstituentsFeed`/`TushareFlagsFeed`/`TushareCovariatesFeed` 各加 `cache=None` 注入;cache present → read-through + **共享 finalizer**(cached==direct,限价 frame `assert_frame_equal`、ST 区间集合相等、suspend set / listing dict 相等单测锁定);`cache=None` → 直抓路径**逐字不变**(旧 feed 测试原样过)。per-symbol 限流/重试仍在各 feed 的 `_call` 闭包里(缓存 transport-agnostic)。 + - **coverage ledger 复用 + 扩展**:11 列不变;**empty 算覆盖、failed 不算**(fetch 抛错则不记 coverage、留待重试,测试锁定);ledger 列只有端点元数据,无 token/secret。 + - **单一共享 cache 贯穿 4 runner**(run_phase0/phase2/oos/subset):`_build_cache(cfg)` 建一个实例线穿 `_build_universe`/`_load_panel`/`_enrich_tradability`/`_maybe_enrich_listing`,跑完所有缓存端点后 `_log_run_cache_stats` 打**一行** 7 端点统计;P4-1 的 market 行前缀保留(旧统计测试过)。 + - **配置**:`data.cache.refresh_dimension_days`(默认 30,>=0 校验);旧配置全 validate(含未知键拒绝)。 + - **真实 smoke(phase2 baseline,fresh temp root,cold→warm;fresh root 不动已 merge 的 v1 缓存)**: + - **cold 行**:`market_daily=68 adj_factor=68 index_weight=9 suspend_d=68 namechange=68 stk_limit=68 stock_basic=1`(全非零);coverage = market/adj/namechange/stk_limit 各 68 ok + index_weight **1 ok**(整 gap 一行,内部 9 窗分页)+ stock_basic 1 ok + **suspend_d 68 empty**(SSE50 大盘股该窗口无停牌 → 空返回算覆盖,warm 不重抓)。 + - **warm 行:7 端点全 0**;coverage ledger **零新行**;report 指标与 cold/P4-1 cached baseline **逐数一致**(IC 0.0083 / annual −10.19% / maxDD −16.52% / vol 16.59% / sharpe −0.5703 / turnover 1.0818 / cost 1.19%)——cached==direct 二度端到端实证。 + - wall:cold **960s** / warm **366s**(暖跑省 ~594s = universe+tradability+market 抓取;`daily_basic`(market_cap)/`index_member_all`(pit_sw) 仍 live——P4-2 不缓存这俩,P4-3 再说,诚实标注)。 + - secret scan:缓存 parquet + ledger + 日志 + 报告 0 处 token 值 / `.config.json`;ledger 无 token 列。 + - **不变量守住**:factor/alpha/portfolio/execution/OOS 切片/report/`front_adjust` 全不动;`artifacts/data/*.parquet` 不当 SoT。范围克制:`daily_basic`/`fina_indicator`/`index_member_all` 仍留 P4-3。 +- ✅ **分钟级 intraday pipeline I1–I4**(**PR #29 已 merge 到 `main`**,4 commit 一 PR;**全程与日频链路解耦——`factors`/`alpha`/`portfolio`、日频 `runtime/backtest`、日频 `TushareFeed`/`TushareCache`、全部 `config/` 零改动**,Phase 0/2/3 数字不变)。本地验收文档在 `tmp/context/intraday_pit_checkpoints/stage_i{1,2,3,4}_acceptance.md`(gitignored)。 + - **核心架构决策**:raw intraday SoT = **stk_mins `1min` only**;5/15/30/60min 是从缓存 1min **派生**的视图,不作独立 raw 上游产品抓取;日频 `D` 仍独立保留(不被分钟重采样替代)。三时间戳贯穿全程严格分离:**signal cutoff**(T 14:50,特征只用 `available_time<=cutoff`)/ **execution timestamp**(T 14:51)/ **holding period**(exec→next-exec,绝不 close-to-close)。 + - **I1(feat data)** 1min raw feed + PIT schema:`data/clean/intraday_schema.py`(独立 `MultiIndex(time,symbol)`,分钟精度不归零;`bar_end=trade_time`/`bar_start=bar_end-freq`/`available_time=bar_end+data_lag`;`RAW_INTRADAY_FREQ`+`ensure_raw_intraday_freq` 钉死 1min)+ `data/feed/tushare_intraday.py`(`TushareIntradayFeed.get_minutes`,`vol→volume`/`ts_code→symbol`/`trade_time→time/bar_end`,非 1min 在建 SDK client 前即拒,token 不入库不打印)。 + - **I2(feat cache)** stk_mins 1min read-through cache:`data/cache/intraday_coverage.py`(**timestamp-interval ledger**,`raw_freq/start_time/end_time`,不复用日频 date 语义;ok/empty 算覆盖 failed 不算)+ `intraday_parquet_store.py`(**月分区** `stk_mins_1min/freq=1min/symbol_prefix/symbol/year/month.parquet`,原子幂等 upsert by `(symbol,freq,bar_end)`,只存 raw)+ `intraday_cache.py`(`TushareIntradayCache.stk_mins_1min`:交易日 gap 规划复用 `intervals.py`,≤23 日窗分页 <8000 行 cap,1min-only guard)。接入 `TushareIntradayFeed` 可选 cache path(直抓路径字节级不变)。**冷写/暖零调用/partial 只补缺口/empty 记录/failed 重试/cached==direct 规范化后逐字节等价** 全测试锁定;**日频 `TushareCache` 零改动**(独立类)。 + - **I3(feat data)** 分钟→日频 PIT 聚合:`data/clean/intraday_aggregate.py::asof_daily_features`(默认 `decision_time=14:50:00`;**先 `available_time<=cutoff` 逐 bar 过滤再按日 groupby**,分钟时间戳绝不先归零进日频)。cutoff 编码列名 `intraday_ret_0930_1450`/`intraday_realized_vol_0930_1450`/`intraday_vwap_0930_1450`/`intraday_last30m_ret_1420_1450`。`resample_intraday_bars` 派生粗 bar `available_time=max(source_1min.available_time)`。泄漏测试(扰动 14:50 后 bar → 特征不变)+ 可见性排除测试锁定。 + - **I4(feat runtime)** 尾盘调仓 execution 骨架:`runtime/intraday_execution.py::simulate_tail_rebalance`(`next_minute_close` 模型:14:51 或窗口内最早 bar 成交;持有期收益 `exec(T)→exec(T_next)` 非收盘;缺 bar/NaN/无窗口 bar → 可解释 blocked,**不静默用日收盘替代**;只读分钟 bar 无 EOD 泄漏)。**独立函数,未接 config/pipeline**;日频 `close_to_next_period` 不变。`tail_vwap`/`closing_call_proxy` config 层拒绝(注明 future,后者需 `stk_auction_*` 权限当前无)。 + - **下一步**:执行真实性先于研究因子 → I5b 已补执行期涨跌停可行性(见下);之后 I5c 再把 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine(报告披露 cutoff/lag/execution_model/window),日频回归不破。 +- ✅ **P4-3 因子支撑端点缓存 + 21:00 data updater**(**PR #31 已 merge 到 `main`**,daily_basic + fina_indicator + index_member_all 进既有日频 `TushareCache`;新增独立 `data-update` CLI 增量暖跑,**不跑 factor/alpha/portfolio/backtest、不写 PanelStore**,日频回测仍自走 read-through 补缺口)。本地验收:`tmp/context/intraday_pit_checkpoints/stage_p4_3_data_updater_acceptance.md` + `tmp/context/session_handoff_20260613/p4_3_codex_acceptance.md`(codex 复核 PASS)。 + - **daily_basic**(dense,pe/pb/total_mv,一次缓存调用喂 market_cap+value_ratios);**index_member_all**(per-symbol SW in/out 维度,staleness 刷新)。 + - **fina_indicator 字段集无关**(codex acceptance blocker 修复):cache **永远存 canonical `FINA_FIELDS` superset**(roe/netprofit_yoy/grossprofit_margin),feed 读时选子集——一个配置 warm 不会阻塞另一配置(光加 fields_hash 会在 upsert 时互相覆盖);drift 测试守 `FINA_FIELDS ⊇ financial.SUPPORTED_FIELDS`;coverage 仍按 report-period end_date + 长 trailing tail 抓晚披露,ann_date 作 raw 不当覆盖轴。 + - **not_ready pending window**:今日(`not_ready_days`)空返回记 `not_ready`(非 coverage) 次跑重试,跨界 gap 拆分;`not_ready_days=0`(默认) 行为逐字不变。per-endpoint trailing tail + summary 计数。 + - cached==direct 实证(market_cap/value_ratios/pit_sw/fina-as-of);`cache=None`→direct 逐字不变;缓存只存 raw(无 qfq/因子/token);日频/分钟各自 ledger/store。**日频回测数学/factor/alpha/portfolio/runtime 零改动,Phase 0/2/3 不变**。 diff --git a/docs/progress/03_intraday_i5.md b/docs/progress/03_intraday_i5.md new file mode 100644 index 0000000..ec17fee --- /dev/null +++ b/docs/progress/03_intraday_i5.md @@ -0,0 +1,41 @@ +# 分钟尾盘链路 I5a – I5f(PR #33 – #55) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +事件驱动回测重构、执行期涨跌停可行性、MMP 分钟因子端到端与五分位分组回测、CSI300 独立泛化(负结果)、执行流动性诊断。 + +--- + +- ✅ **P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model**(**PR #33 已 merge 到 `main`**,架构/框架 PR,非研究结果;含 review 两修复):把回测层重构成**共享事件驱动核心**,日频 close-to-close 与分钟尾盘成为**同一 achieved-book ledger 上的两个 event model**,零重复 fill/cash/settle。本地验收:`tmp/context/session_handoff_20260615/stage_i5a_acceptance.md`。 + - **核心**(`runtime/backtest/`):`events.py`(`HoldingPeriod` 显式时间基 + 共享月度日历)/ `engine.py`(`BacktestEngine` 单循环:universe→scores→构建→可行成交 via `SimExecution`/`simulate_fills`→settle→NAV/feasibility/holdings/**event** 日志)/ `event_models.py`(`DailyCloseEventModel` 逐字节复刻日频 + `IntradayTailEventModel` 14:50 决策/14:51 成交/exec-to-exec,复用 `intraday_execution.build_execution_prices`)。`BacktestDriver` 改为 engine+DailyCloseEventModel 的**薄 wrapper**(−237/+167),`pipeline`/`oos`/`phase2` 三 runner 一行未改。 + - **config**:opt-in `intraday`(enabled/decision_time/data_lag/session_open/execution_model/execution_window/require_cache_coverage/missing_execution)+ `backtest.event_order` Literal;`intraday_tail_rebalance` 必须 `intraday.enabled=true`(root validator),旧配置全 validate,非法 model/window 可读报错。 + - **分钟尾盘语义**:决策只用 `available_time ≤ 14:50` 的 bar;成交取执行窗口 `[14:51,14:56:59]` 内最早 1min 收盘;持有期收益 `exec(T_next)/exec(T)−1`(**绝非 close-to-close**);**缺/NaN 执行 bar = 显式 block,绝不退回日收盘**;turnover/cost/holdings 按 achieved book;闲置现金 `cash_return`。事件时间戳全程可审计。 + - **新 CLI `run-phase-i5a-intraday`** + `config/phase_i5a_intraday_tail_framework.yaml`(SSE50 SH/SZ smoke):日频面板/universe 走既有 P4 缓存;分钟 bar **只读既有 intraday 缓存**(缺即 hard blocker,绝不静默暖跑 → **零 `stk_mins` live call**);PIT-safe 分数 = I3 `intraday_ret_0930_1450` 特征(纯框架验证,非业绩声明)。 + - **真实 smoke(SSE50,2026-03-03→06-12,~180s)**:`periods=3 / covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;event 表显示 14:50 决策 / 14:51 执行 / **actual exec bar 范围** / 真实 exit 执行锚;secret scan 0。 + - **review 两修复**:① `require_cache_coverage=true` 下**任一** symbol 缺即 loud fail(原仅全缺才失败 → 静默丢名引入样本覆盖偏差);② `HoldingPeriod.exit_execution_ts` 显式化,event_log + 报告显示 **planned vs ACTUAL 执行 bar 时间**(14:51 缺、14:52 成交可见),末期不再 NaT。 + - **不变量守住**:daily close-to-close 行为不漂移(engine==driver 黄金测试 + phase0 `ic 0.9600/annual 0.8408` 不变);无重复 ledger;缺 bar 绝不日收盘兜底;turnover/cost 按 achieved。 +- ✅ **P-I5b 分钟尾盘执行期价格涨跌停可行性**(**PR #35 已 merge 到 `main`**,执行真实性硬化 PR,非研究 alpha;含 review LOW 修复):补 P-I5a 显式 deferred 的限制——在「有效 1min 执行 bar」规则之上,用 raw `stk_limit` 对**选定执行分钟的 raw 1min 收盘**做方向感知闸门。本地验收:`tmp/context/session_handoff_20260615/stage_i5b_acceptance.md`。 + - **语义(测试锁定)**:`exec ≥ up_limit − tol → can_buy=False`(涨停**只挡买**)/ `exec ≤ down_limit + tol → can_sell=False`(跌停**只挡卖**)/ 缺·NaN 执行 bar 仍**先于**限价逻辑挡两方向;**RAW-vs-RAW**——raw 1min 收盘(intraday 缓存存未复权)vs raw `stk_limit`,绝不碰 qfq/日收盘/`at_up_limit·at_down_limit` 日收盘派生 flag。`can_buy/can_sell` 喂既有 `simulate_fills` achieved-book ledger(turnover/cost/holdings 按实际成交)。 + - **缺限价行绝不静默当通过**:严格 `require_price_limit_coverage=true` 在**模型构造期 raise**(出结果前列缺失 pair);宽松 `false` 计数披露 unchecked 并退回 bar-exists 规则。诊断(涨停挡买/跌停挡卖/unchecked/coverage)按 `(date,symbol)` 幂等,重复 `feasibility()` 不重计。 + - **接线**:`IntradayCfg.{price_limit_check(默认 false)/require_price_limit_coverage/limit_tolerance(≥0 校验)}` + `OutputCfg.intraday_report_name`(I5b 报告独立成文件,不覆盖已验收 I5a artifact,沿 baseline_report_name 先例);runner 经 `TushareFlagsFeed(cache=cache).limits()` 走既有 P4 read-through 缓存取 raw 限价(**不加新端点**),报告 H1/intro 按 check 自报 I5b(修同类 stale-title)。 + - **不变量守住**:`BacktestEngine`/`BacktestDriver`/`DailyCloseEventModel`/`fills`/`execution` **零改动**(限价 map 仅 check 开时构建,默认 false → `feasibility()` 逐字退回 I5a bar-exists 规则);日频 close-to-close 不漂移。 + - **真实 smoke(SSE50 2026-03-03→06-12,3 次跑 NAV 全 `0.976448` 确定性)**:`covered=58/58 / stk_mins_live_calls=0`;限价覆盖 **174/174**;**1 笔涨停挡买 / 0 笔跌停挡卖** → NAV 由 I5a 的 0.952207 变到 **0.976448**(2026-04-30 一只 14:51 封涨停的票被正确挡买,该期只持 9 只——I5a 只查 bar 存在会「买」进涨停板,**这正是 I5b 补的执行真实性,非业绩声明**);`stk_limit` gap-fetches=58(冷暖都 58:窗口尾端距今 3 天落在 `refresh_recent_days=14` 内,P4 按策略重抓 tail,据实报告,非 minute fetch);secret scan 报告+日志+58 缓存 parquet **0 处**。 +- ✅ **P-I5c MMP 分钟因子端到端 opt-in alpha**(**PR #37 已 merge 到 `main`**,**EXPLORATORY**,非业绩声明):把首个 EXPLORATORY 分钟因子作为真实 opt-in alpha 端到端接 I2→I3→I4→event engine,日频回归不破。 + - **MMP(Minute Microstructure Pressure)**(`data/clean/intraday_aggregate.py`,公式/PIT/前导 NaN/跨 symbol 隔离全测试锁定):逐 1min bar `mid=(high+low)/2`、`S=(close−mid)/mid`、`V=√(volume/median(vol[t-20:t]))`、`B=|close−open|/(high−low+eps)`、`R=(high−low)/(mean(hl[t-20:t])+eps)`、`MMP_t=S·V·B·R`(eps=1e-6);日频分数 = 在场 bar `[session_open, decision_time]` 等权均值,rolling baseline 用前 20 个在场 bar(首 20 NaN)。**先 `available_time≤cutoff` 过滤再聚合**(PIT-safe,绝不先归零)。 + - **可配置 `intraday.score_feature`**(默认沿用,`mmp_ew` opt-in)+ 可配置报告标题(修同类 stale-title);`config/phase_i5c_mmp_minute_factor.yaml`。decision feature→alpha→portfolio→event engine 全程复用既有机器,报告披露 cutoff/lag/execution_model/window。**日频 close-to-close 零漂移**(phase0 `0.9600/0.8408` 不变)。 +- ✅ **P-I5d MMP 五分位分组回测**(**PR #39 已 merge 到 `main`**,**standalone against `main`**,**EXPLORATORY,非业绩声明、非调参**):把 I5c MMP 日频分数 `intraday_mmp20_ew_0930_1450` 做 5 等额分位分组回测——每月调仓日按 PIT-safe 分数横截面排名切 `analytics.quantiles=5` 等数桶(Q1 最低 / Q5 最高),每组作独立 long-only 等权组合走**同一** `BacktestEngine`+`IntradayTailEventModel`+`SimExecution(fee_rate=0.001)`,I5b raw `stk_limit` 执行期涨跌停 ON。引擎/执行可行性/MMP 因子数学全不变,仅新增分组 + per-group 编排。本地验收:`tmp/context/session_handoff_20260615/stage_i5d_acceptance.md`。 + - **新增**(机器零改动复用 I5a/I5b/I5c):`qt/intraday_groups.py`(等额 rank 桶 / `GroupScores` / `EqualWeightAll`)、`qt/intraday_group_backtest.py`(cache-only anchor-date-sliced 分钟加载、一份共享 exec-price 矩阵跨 N 个 fresh per-group 模型、QN−Q1 合成 spread)、`qt/intraday_group_figures.py`/`intraday_group_report.py`(NAV/spread/metric 图 + 报告)、`config/phase_i5d_mmp_quintile_5y.yaml`;唯一 runtime 改动 `IntradayTailEventModel` 可选 `precomputed_prices`(默认 None → I5a/I5b 逐字节不变)。 + - **真实 run(CSI500 `000905.SH`,2021-06-01→2026-05-31,59 月调仓,~95min)**:`covered=892/995`(**103 只分钟未覆盖成分被 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only)/ `fee_rate=0.001`;final NAV Q1→Q5 **0.9822 / 1.0423 / 1.0495 / 1.1275 / 1.1577**(**单调 Spearman 1.0000**,等额 rank 桶对 MMP 肥尾稳健);合成 Q5−Q1 spread +0.29%/期、累计 **+17.15%**;限价覆盖 52,584/52,584(严格 `require_price_limit_coverage=true` 过)。 + - ⚠️ **caveat(保留,不移除)**:单因子/单重叠 5 年窗口/单 universe,无调参/无 robustness/无 learned 权重;`covered=892/995`(103 drop);`stk_mins_live_calls=0`;**Q5−Q1 是合成 long-only 腿差,非单独执行的 dollar-neutral 组合**;执行模型仍缺 partial-fill / liquidity / volume-cap;**Q5>Q1 不作业绩声明**。这是项目首个正向 intraday 信号,下一步需独立泛化(CSI300/中证1000 或 disjoint 窗口,机器冻结)。 + - **PR 处置**:#37(I5c)随 #39 落地后其 head 成 `main` 祖先,GitHub **自动 merged**(非本次手动 merge);#38(i5d stacked on i5c,base 非 main)已被 #39 **完全取代**(零 remaining commit/file diff vs main),保持 **OPEN 待清理**(未获显式授权关闭)。 +- ✅ **P-I5e MMP 五分位 CSI300 独立泛化检验**(**PR #53 已 merge 到 `main`**,**EXPLORATORY,非业绩声明、非调参;负结果**):冻结已验收的 I5d MMP 五分位机器,在**独立 universe** CSI300(`000300.SH`)同窗口(2021-06-01→2026-05-31)复跑,检验 I5d 在 CSI500 上的 Q1→Q5 单调性是否归因于因子而非 universe/regime。**仅加一个 config(`config/phase_i5e_mmp_quintile_csi300_generalization.yaml`),零代码/机器改动**——MMP 数学 / 等额 rank 桶(Q1 最低 Q5 最高)/ I5a/I5b 尾盘 event model(14:50 决策 / 14:51 执行 / exec-to-exec)/ raw `stk_limit` 执行可行性 / cache-only anchor-date 分钟加载全冻结;report 名/标题已 config 驱动故无需改代码。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5d_independent_generalization_20260620/`。 + - **Preflight 选 CSI300(非 fallback CSI1000)**:全窗口 481 distinct 成分,分钟全窗口覆盖 **448/481=93.1%**(≥ CSI500 89.6%)→ 够切 5 等额组,窗口不缩短;CSI300 日频成分 ledger 诚实仅覆盖近 1 年(无谎报、无静默截断),缺的经 P4 日频 read-through 补(21 次 index_weight,允许,同 I5d)。 + - **真实 run(CSI300,59 月调仓,~30min)**:`covered=448/481`(**33 只分钟未覆盖成分 drop 并披露,5 年窗口不缩短**)/ `stk_mins_live_calls=0`(cache-only,缺分钟即 hard miss 绝不暖)/ `fee_rate=0.001`;限价覆盖 26386/26386 全过。 + - **结论:FAILS / 不泛化**。final NAV Q1→Q5 **0.8809 / 0.7377 / 0.9418 / 0.7573 / 0.6665**;年化 **−2.55% / −6.00% / −1.21% / −5.50% / −7.92%**;**单调 Spearman −0.5000**(vs CSI500 +1.0000);合成 Q5−Q1 累计 **−26.77%**(vs CSI500 +17.15%)。**Q5(高 MMP)在 CSI300 上最差**——高 MMP 组不跑赢低 MMP 组,I5d 的 CSI500 单调性在独立 universe 上**反转**,强烈提示其为 universe/regime 特异、非稳健因子属性。这正是独立检验要拿到的证据。 + - ⚠️ **caveat(保留)**:单因子/单重叠 5 年窗口/单 universe;Q5−Q1 是合成 long-only 腿差非 dollar-neutral 执行;丢弃未覆盖分钟名的 coverage bias;执行模型仍缺 partial-fill/liquidity/volume-cap;无调参/无 learned alpha;**非业绩声明**。CSI500 对照仅作 context,不作任何组业绩声明。 + - **回归不破 + 范围克制**:phase0 `0.9600/0.8408` 不变;`pytest` 678 passed(677 基线 + `test_cache_config.py` 的 `config/*.yaml` glob 参数化自动覆盖新 config 的 +1,无新测试文件);**不动 progress docs 以外的代码/config/test**,不碰 schema registry / D6;**PR #38 未动**。 +- ✅ **P-I5f 分钟尾盘执行流动性诊断**(**PR #55 已 merge 到 `main`**,**执行真实性诊断、非因子研究结果;report-only**):给分钟尾盘链路加 opt-in、**report-only** 执行流动性诊断——对每笔 desired trade `|target_w − current_w|`,用**选定执行分钟 1min bar 的成交额 `amount`(RMB)**在参与率上限下估容量,报 capacity ratio(`>=1` 覆盖、`<1` 潜在受限)。新增 default-off `intraday.liquidity_diagnostics`(`enabled` 默认 false → 现存配置字节级不变;启用时 `portfolio_notional`>0、`max_participation_rate`∈(0,1]、仅 `mode=report_only`,各可读报错)。本地 handoff:`tmp/context/session_handoff_20260620/cc_goal_i5f_intraday_liquidity_diagnostics_retry_20260620/`。 + - **report-only 不变量(测试锁定 + 真实 smoke 双证)**:不改 fills / can_buy·can_sell / blocked reason / target 权重 / achieved holdings / turnover / cost / NAV / 因子分数 / MMP 分组 / alpha / 组合构建。容量**只用选定执行分钟 1min `amount`**,绝不用未来 bar、日频 amount/volume、日收盘、或 EOD 代理。`BacktestEngine` 加 additive、default-off `record_rebalance_plan`(默认 False → 循环字节级不变)捕获引擎真实用的 (target,current);诊断在 settle 后从已产出 log 构建,只读不写。既有 missing-bar/missing-price/raw stk_limit 阻挡**保留原 reason,绝不重分类为流动性阻挡**;missing/NaN/≤0 amount 报 missing capacity;零 desired trade 跳过防除零。 + - **真实 smoke(SSE50,2026-03-03→06-12,3 期,机器冻结)**:`covered=58/58 / stk_mins_live_calls=0 / blocked_fills=0`;**`final_nav=0.976448` 与已验收 I5b 逐字节一致** → 启用诊断未动账面(report-only 端到端证明)。诊断:**43** desired trades;**1** 被既有 I5b feasibility 挡(2026-04-30 涨停那笔,原 reason 保留);**0** missing-capacity;**42** 有可用 capacity ratio;**20** below 1.0x;ratio min/p10/median/p90 = **0.122 / 0.222 / 1.104 / 2.730**;最紧 trade `2026-04-30 601658.SH sell` ratio 0.122。 + - ⚠️ **解读(诚实)**:10M RMB 诊断名义 + 5% 参与率下,约一半被检 SSE50 单分钟 desired trade 容量不足——**支持后续做 size-aware 执行建模,但不证明 alpha/业绩/可交易性,也不外推到其他名义**。**非业绩声明、非策略改进**;partial-fill / volume-cap 强制不在本阶段(那是将来 I5g,须单独显式 goal)。I5f 与 MMP 正交,MMP 仍暂缓(I5e 负结果)。 diff --git a/docs/progress/04_data_layer.md b/docs/progress/04_data_layer.md new file mode 100644 index 0000000..a4d31e7 --- /dev/null +++ b/docs/progress/04_data_layer.md @@ -0,0 +1,53 @@ +# 数据层 D1 – D5 + schema 守卫 + 全A 自动拉取(PR #41 – #61) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +契约文档化、`TushareCache` 拆分、report-only 质量层、coverage ledger 批量写、opt-in 并发与全局限频器、schema 注册表与 drift 守卫、全A 增量 auto-warm 与历史回填。 + +--- + +- ✅ **数据层 D1 — cache/store 契约文档化 + token 解析去重**(**PR #41 已 merge 到 `main`**,行为保持型重构,非研究/非数据语义变更):数据层第一步低风险清理,只做两件事——把 `cache` vs `store` 边界写成可提交契约,并把重复的 token 解析收敛到既有 `data/feed/secret.py::read_token`。 + - **契约文档** `docs/data/data_layer_contracts.md`:`data/cache` = 可复用 raw endpoint 缓存 / SoT;`data/store/PanelStore` = per-run canonical 面板 artifact(非 raw lake,`artifacts/data/*.parquet` 不当缓存权威);缓存只存 raw;`front_adjust` / PIT 成分 / PIT SW 行业 / `ann_date` 财务对齐 / raw 涨跌停检查 / factor·alpha·portfolio·runtime 数学全在下游;`data-update` 只暖缓存不跑研究;data-quality + 并发是后续阶段(不声称已实现)。 + - **token 去重**:`tushare_feed.py` 删掉重复的 module-level `_lookup_dotted` + 内联 json/Path 解析(`_read_token` 变 `read_token(...)` 一行 wrapper);`index_feed.py` 不再从 `tushare_feed` 导入私有 `_lookup_dotted`,`_client` 直接走 `read_token`。`_lookup_dotted` 全仓清零;6 个 tushare feed(feed/index/flags/fina/intraday/covariates)统一走 `secret.read_token`。构造签名 / `__repr__` token 安全 / fetch+cache 路径全不变;+2 窄测试锁定 index feed 真实 token 路径(fake config、不碰网络、不泄漏)。 + - **行为保持**:phase0 `ic 0.9600 / annual 0.8408` 不变;无数据抓取/缓存语义/factor/alpha/portfolio/runtime/report 变更。**显式非目标(留给 D2+)**:`TushareCache` 内部拆分 / endpoint schema registry / data-quality validator / 并发 / `CoverageLedger` 存储变更 / `PanelStore` append·partition。 +- ✅ **数据层 D2 — TushareCache endpoint specs/parsers 拆分**(**PR #43 已 merge 到 `main`**,行为保持型内部重构,**公开缓存语义零变更**,非性能/并发/data-quality 层):把 814 行的 `data/cache/tushare_cache.py` 内部拆成小文件,让 endpoint 元数据与 raw 解析器可单独审阅,`TushareCache` 仍是公开门面。 + - **拆分**(解析器逐字搬移,行为靠既有 cache 测试锁定):`tushare_specs.py`(89 行,endpoint ids/`ALL_ENDPOINTS`/`_GLOBAL_KEY`/列集+自然键/`FINA_FIELDS`/`_INDEX_WINDOW_DAYS`/rename maps,纯常量)、`tushare_parsers.py`(216 行,10 个 `_parse_*`)、`tushare_planning.py`(22 行,叶子 helper `_fields_hash`/`_compact`);`tushare_cache.py` **814→596 行**,保留 `TushareCache` 门面 + read-through 引擎(gap 规划 / index_weight 90 天分页 / snapshot staleness / `refresh_recent_days`·`recent_tail_overrides`·`not_ready_days`·`force_refresh` / ok·empty·not_ready·failed coverage / 逐自然键 upsert / `stats`·`update_summary`)+ `FetchOne`/`FetchSnapshot`。 + - **向后兼容导入保持**:门面 re-export endpoint ids + `FINA_FIELDS` + `ALL_ENDPOINTS`,`from data.cache.tushare_cache import DAILY_BASIC/FINA_FIELDS/INDEX_WEIGHT/...`(被 `tushare_fina.py` + cache 测试用)全照常;新增 5 个窄测试锁定 re-export identity + 模块边界。**公开方法/签名/内部规划/coverage 语义全不变**;phase0 `0.9600/0.8408` 不变。**非目标(D3+)**:schema registry / data-quality / 并发 / `CoverageLedger` 存储 / `PanelStore` append·partition / intraday 缓存重构 / 限频重试,均未做。 +- ✅ **数据层 D3 — report-only 数据质量层**(**PR #45 已 merge 到 `main`**,含 review 一修复;纯库 + 测试,**report-only 非 cleaning**):新增独立 `data/quality/` 包,在接入处附近**只报告**可疑上游数据,**绝不**过滤行/修复值/改 qfq/改 cache coverage/动 feed·factor·alpha·portfolio·runtime。 + - **包**(纯函数,输入 DataFrame→findings,输入永不被改):`report.py`(`QualityFinding` + `make_finding`(样本限 5、清洗)+ `findings_to_frame`(稳定确定序)+ `render_report`(确定性 Markdown,clean→"No findings")+ secret redaction);`market.py`(日频:dup(date,symbol)/非正 OHLC/high1` 且多 symbol → **只有日频密集 per-symbol 取数**走有界 `ThreadPoolExecutor`,**store upsert + ledger 写仍在主线程按计划序**(成功 gap 先 durable 再抛首个失败,失败 gap 不记覆盖、可重试;store/ledger 内容与取数完成序无关)。snapshot / `index_weight` 90 天分页 / intraday 在 D5 仍串行(并发开启时仍共享同一限频器)。 + - **updater 接线**:`run_data_update` 仅 `max_workers>1` 才建**一个** `GlobalRateLimiter`(用 `rate_limit_per_min`),经 `_build_feeds` 共享给全部 feed(6 个 feed 加 `scheduler=None` 默认不变参);`UpdateResult`+`format_summary` 暴露 `max_workers`/全局 `rate_limit_per_min`(非密)。 + - **测试**(`tests/test_scheduler.py` + `tests/test_data_update_concurrency.py`,network-free,21):全局时隙在争用下逐次升 sleep(fake clock)/ **8 线程实测全局非按线程**(`next_allowed==N*interval`)/ 重试逐次 acquire / scheduler 模式不做 per-call sleep / 失败 secret-safe(仅异常类型)/ feed 转发其 scheduler / config 校验(`<1` 拒;默认 1)/ **serial==concurrent** 帧·ledger·请求计数 / 失败留 gap 未覆盖可重试而成功 durable / empty·not_ready 不变 / 单 symbol 仍串行。真实 token 扫描 **0 hits**;**未跑 live `data-update`**(network-free 验收)。 +- ✅ **数据层 schema 注册表 + default-off drift 守卫**(**PR #57 已 merge 到 `main`**,行为保持型、默认关闭、opt-in):把缓存已存却无人校验的 `fields_hash` 升级成显式 schema drift 守卫。**核心洞察**:每个 `_parse_*` 硬编码 canonical 列集 + 防御性 `fillna`,故存储侧 `fields_hash` 对 tushare 改列免疫——真风险在 **raw 输入侧**(tushare 删/改 parser 依赖的源列 → 静默 NaN 入缓存零告警),守卫在 **parse 之前**查 raw frame。 + - 新 `data/cache/schema_registry.py`:每端点声明式 `EndpointSchema`(required/optional/known_extra 源列 + canonical 列 + 自然键 + 期望 canonical hash,从 `tushare_specs.py` **派生**=单一真相源)+ 有状态 `SchemaGuard`。**4 检查**:#1 缺 required 源列(HARD)/ #2 真未知额外源列(WARNING,被 `known_extra_columns` 目录收窄,只对真新列响)/ #3 parsed canonical ≠ 注册表(HARD)/ #4 存储 `fields_hash` ≠ ledger 历史(HARD,每端点每 run 去重一次,迁移检测器)。 + - **模式**:`report_only`(启用默认,只记 findings + 一行 secret-free summary 日志)/ `strict`(opt-in,HARD 在 upsert **前** raise → 该 gap 不记覆盖、可重试,沿"failed fetch 不记覆盖"语义)。接进 `qt/pipeline.py::_build_cache`(仅 enabled 时建,默认 None);`data/cache/coverage.py` 加只读 `last_fields_hash(endpoint)` 访问器。 + - **不变量**:`data.cache.schema_guard.enabled=false` 默认 → `_guarded_parse(None)` 精确直通、既有 cache 测试全过、factor/alpha/portfolio/runtime/qfq/coverage 语义不变、phase0 `0.9600/0.8408` 不变;findings/日志/异常只含 endpoint+列名+16 字 SHA1,**无 token / `.config.json` / 数据值**;**仅日频 `TushareCache`**,分钟 schema 守卫 defer。 + - **review 两轮**(cc 接替外部 reviewer 验收):security PASS;code-review 首轮 BLOCK 修两 HIGH——`index_member_all` 误把 SW 级名标 required(strict 下缺级名永久 raise,直连路径 `tushare_covariates.py:155` 已 guard 证明 tushare 真省略)→ required 收敛到 `{in_date}`、级名入 optional(WARNING);check#2 噪声 → 加 `known_extra_columns` 目录只对真未知列告警;另:module `assert`→`raise`(`-O` 安全)+ 反向 droppability lock 测试 + check#3/#4 secret-absence 断言。**独立重跑 758 passed / ruff clean / 19 配置 / phase0 0.9600·0.8408 / `-O` registry-sync 守卫成立 / secret 0 新增**。 + - **已知 follow-up(本 PR 范围外)**:守卫只接 pipeline/回测缓存路径,**未接 live `data-update` 暖跑(`qt/data_updater.py`)**——那是下一步最该开 drift 检测处。 +- ✅ **数据层 盘后全A 自动拉取 —— PR-1 全A 增量 auto-warm + systemd 定时**(**PR #59 已 merge 到 `main`**,行为保持型、默认关闭、装不 enable;**2-PR 之 PR-1**,历史回填是 PR-2):先做数据正确性审计(D3 质量层真跑 80GB 真实缓存:daily 全扫结构 nan/dup/high today` 守卫**:逆序窗口本会被 `subtract_intervals` 当"零缺口=已覆盖"→ 未来 start typo 静默 no-op;故加 loud `ValueError`(仿 `DataCfg._check_date_order`)。config `data_update.backfill: {start, chunk_size, include_minute}`(全默认 → 现存配置照常 validate);`config/data_update_all_a.yaml` 加 `backfill:` 块 + 诚实成本注(冷全A 分钟回填数小时~多日,可续跑)。ops doc 加"历史回填"节(先回填再 enable 夜间 timer)。 + - **不变量**:增量 `run_data_update` 路径**字节级不变**——唯一共享改动是 `_build_scheduler`/`_build_caches` verbatim 抽取(same kwargs/顺序);49 个既有 data_update 测试原样过(main-vs-branch pytest 对照 **771→793 恰好 +22、0 既有测试被动**)。phase0 `0.9600/0.8408` 不变;缓存仍 raw-only;无 token。 + - **review 两轮**(cc 接替外部 reviewer 验收):security CLEAN(失败/进度日志只 counts+异常类型);code-review 无 CRIT/HIGH(共享重构 verbatim 验证),修 2 MEDIUM(`start>today` 静默 no-op 守卫 + 失败×续跑联合测)+ LOW(summary 显并发模式)。独立重跑 **793 passed / ruff / 20 配置 / phase0 0.9600·0.8408**。**建设侧完成;live rollout(先 `data-backfill` 补史 → 手动增量 warm → enable timer)= 用户 Stage-1 driver**。 diff --git a/docs/progress/05_minute_factors_eval_contract.md b/docs/progress/05_minute_factors_eval_contract.md new file mode 100644 index 0000000..3818a15 --- /dev/null +++ b/docs/progress/05_minute_factors_eval_contract.md @@ -0,0 +1,37 @@ +# 因子评估契约层 + 十一个分钟因子复现(PR #63 – #74) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +`analytics/eval/` 三轴判决契约(v0.1→v0.9)、十一因子结果总表与三条循环级结论、以及 v0.8/v0.9 两处判定门缺陷的修复。 + +--- + +- ✅ **因子评估契约层 `analytics/eval/` + 十一个分钟因子复现**(**PR #63–#73 已 merge 到 `main`**,**EXPLORATORY,非业绩声明**):研报 → 复现循环(10/10 收官)与支撑它的判定契约层。全部因子同一数据面:CSI500 `000905.SH` 2021-07-01→2026-06-30 日频、cache-only(`stk_mins_live_calls=0`)、每因子 no_book / with_book 两跑(因子簿 = 已确认的 value_ep + value_bp + volatility_20)。 + - **契约层(PR #63,v0.1→v0.7 迭代后 frozen)**:`FactorSpec`(Factor 必带 classattr、**预注册 `expected_ic_sign`**、PIT 契约、intraday 块校验)+ `FactorEvaluator` ABC(8 个必填 section,缺一即 raise)+ `FactorEvalReport`(确定性 md/json,secret-redacting renderer)+ **三轴判决 Predictive × Incremental × Tradable → Adopt / Watch / Reject / INSUFFICIENT-DATA**。三条判定纪律:**非对称门**(hard Reject 先于样本量门)、**unknown never convicts**(判不了绝不定罪)、**PASS 一律读 N_eff 置信区间下界**(`N_eff = N/(1+2Σρ)`,Geyer 扩展;点估计不得买 PASS)。v0.7:增量轴独立门 `min_incremental_abs_icir=0.15`(正交化收缩残差尺度,沿用原始 0.30 是类别错误)+ 单调性作**方向门**(默认 0.0,非强度门)。exploratory 因子**封顶 Watch**(Tradable 轴全程 NOT_ASSESSED)。一次向量化 IR 喂全部 section(与朴素逐期循环等价性测试锁定)+ 单 PNG dashboard,带**强制 FACTOR DEFINITION 带**——不说明怎么算就不许展示因子。 + - **十一因子(下表 verdict 为当前 v0.9 close-to-close 口径,非各 PR 当时口径;出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` 参照表 + `tmp/Quantitative_Research_Report/factors/ledger.md`)**: + + | PR | 因子 | 家族 | sign | IC | ICIR | 换手 | 净1× | no_book / with_book | + |---|---|---|---|---|---|---|---|---| + | #63 | `jump_amount_corr` | 跳跃×成交额相关 | −1 | −0.0298 | −0.401 | 0.415 | −0.000834 | Watch / Watch | + | #64 | `minute_ideal_amplitude` | 振幅 | −1 | −0.0291 | −0.464 | 0.990 | −0.001981 | Watch / Watch | + | #65 | `amp_marginal_anomaly_vol` | 振幅波动 | **+1** | −0.0412 | −0.425 | 0.285 | −0.000504 | Reject / Reject(documented negative:预注册 +1 未迁移,**实测负 IC ≠ sign=−1**) | + | #66 | `volume_peak_count` | 计数 | +1 | +0.0177 | +0.210 | 0.304 | **+0.000071** | Reject / Reject(符号迁移成功但 ICIR 下界 0.151 < 0.30 预注册门) | + | #67 | `intraday_amp_cut` | 振幅 | −1 | −0.0347 | −0.435 | 0.578 | −0.000260 | **INSUFFICIENT-DATA** / Watch | + | #68 | `peak_interval_kurtosis` | 时序 | +1 | +0.0002 | +0.006 | 0.576 | −0.000463 | Reject / Reject(诚实空结果;研报称 RankIC +7.19%/ICIR 4.63 → 本循环最大复现落差) | + | #69 | `valley_relative_vwap` | 价格比值 | +1 | +0.0327 | **+0.506** | 0.473 | −0.000085 | Watch / Watch | + | #70 | `valley_ridge_vwap_ratio` | 价格比值 | +1 | +0.0339 | +0.425 | **2.101** | −0.001705 | Watch / Watch(对齐单调 CI 下界仅 **+0.0010**,全表最薄) | + | #71 | `ridge_minute_return` | 收益 | −1 | −0.0312 | −0.381 | **2.108** | −0.001983 | **INSUFFICIENT-DATA** / Reject(增量轴与因子簿冗余) | + | #72 | `valley_price_quantile` | 价格位置 | +1 | +0.0265 | +0.414 | 0.679 | **+0.000048** | Watch / Watch(按日单调 +0.1064 全表最高) | + | #73 | `peak_ridge_amount_ratio` | 成交额 | +1 | **+0.0401** | +0.488 | **2.127** | −0.001402 | Watch / Watch(IC 全场最高,零价格含量) | + + - **三条循环级结论**:① **家族图谱**——计数弱正 / 时序归零 / 收益 Reject / **价格比值×2 + 价格位置 + 成交额 = 四个双轴 PASS**;峰岭谷行为学划分本身有效,且 **不止价格信息有效**(PR-M 零价格含量却 IC 最高)。② **IC 强 ≠ 可交易(贯穿证据)**——11 个因子里只有 PR-F(+0.71bp)与 PR-L(+0.48bp)在 1× 费率下净多空为正,且 PR-L 扛不住 2×;毛价差常相近,差距**几乎全部来自换手**(PR-I/L 的 0.47/0.68 vs PR-J/K/M 的 2.1+)。③ **假说证伪记录(不得作为已确立规律引用)**——"秩IC/Pearson 背离比预测单调性"被 PR-M 证伪(3.86×→0.60 与 PR-J 4.22×→0.90 交叉),仅两个极端处有不确定关联。 + - **方法论(本轮建立,已作常设规则)**:① **不可能失败的测试**——三个实例:PR-L 的旗舰反泄漏测试(全截面统一扰动是带截距 OLS 残差不可见的仿射变换)、`compare_postmerge.py`(拿新结果和自己比,见下条 #74 重跑对账)、I5b fixture 给每根 bar 定价使 **VWAP ≡ close**(对"闸门读哪个"结构性失明)。**任何不变性测试都必须有实际跑过的 mutation 证据,且 mutation 要打中所主张的那个具体性质**。② **行为变了而措辞没变**(#76/#78 实证)——正确的修法**不是**"断言不存在第 N+1 处副本"(#76 就是这么做的,用扫描式正则,被七种改写全部绕过),而是 **把主张只写一遍、其余每一处都去组合它**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以**。 +- ✅ **因子评估契约 v0.8 + v0.9**(**PR #74 已 merge 到 `main`**,`1ae555e`):十一个因子在 `analytics/eval/` **严格 no-touch** 下跑完后暴露的两项 frozen 缺陷,本 PR 是**对该 frozen 契约的首次授权修改**。 + - **缺陷一:对齐净价差把成本加了回去(判定门缺陷,非仅展示)**。四处写成 `sign * net`(`net` 已扣成本),`sign=−1` 下展开为 **`−gross + cost`**——因子付的手续费被当利润返还。正确语义 `aligned = sign*gross − cost`(先按假设翻腿、再永远扣成本)。**其中两处在 `verdict.py`(`_base_spread` / `_all_spreads_negative`),直接喂 Tradable 轴 PASS 条件**,历来未触发仅因该轴全程 NOT_ASSESSED。手算实证:`gross 0.000125 / net 1× −0.001983 → cost 0.002108 → aligned −0.002233`(缺陷式给 **+0.001983**)。`sign=+1` 逐比特不变。年化展示值修正:jump_amount_corr **+23.14%→−0.01%**、minute_ideal_amplitude **+64.07%→−0.27%**、intraday_amp_cut **+6.30%→−20.52%**、ridge_minute_return **+64.28%→−43.25%**(**旧值全是虚高**)。 + - **缺陷二:用量级敏感的池化统计量给秩基轴把门**。`monotonicity_spearman` 是"桶序号 vs 跨日等权算术均值"的相关,无界,少数极端收益日集中于某桶即可翻号,而每日封顶的秩 IC 几乎不动。新增 `monotonicity_spearman_by_date`(逐日 Spearman,先封顶 [−1,1] 再平均,与秩 IC 同构)并把门移过去;池化版保留为报告字段 + 回退披露。 + - **v0.9:新统计量被日内噪声衰减,裸 0.0 门落在噪声带内**。实测分位阶梯完美的因子按日只有 **0.045–0.106**,门却仍在裸 0.0 且无离散度估计,两个因子相差 0.021 就分生死。改为读该按日序列的 **N_eff 置信区间**(复用既有 `mean_ci`),三值:对齐 CI 下界 > bar → **HOLDS**;对齐 CI 上界 < 0 → **CONTRADICTED**(FAIL);跨零 → **UNKNOWN**(既不定罪也不放行)。实测 CI 半宽 **0.0291–0.0448** → 裸 0.0 实为一道约 0.035 的隐式门槛。**最要害的路由规则:UNKNOWN 绝不救本就不合格的因子**——单调性已从点信号布尔链中摘出(三值事实塞进 `and` 链必然坍缩),ICIR / NW-t / win / OOS 任一不清关仍走 FAIL,UNKNOWN 只在其余全清关时把预测轴落到 INSUFFICIENT_DATA。 + - **3 处 verdict 变化**(全部归因于门的变化,并如此陈述):`intraday_amp_cut`(no_book) Reject→INSUFFICIENT-DATA(对齐 CI [−0.0353,+0.0343] 跨零)、`intraday_amp_cut`(with_book) **Reject→Watch**(同上 + 增量轴本就 PASS)、`ridge_minute_return`(no_book) Watch→INSUFFICIENT-DATA(对齐 CI [−0.0151,+0.0558] 跨零)。`ridge_minute_return` 跨三版本轨迹 **v0.7 Reject → v0.8 Watch → v0.9 INSUFFICIENT-DATA**:前两次都是在噪声上判决、只是方向不同,第三次才承认判不了。 + - **不变量**:IC / ICIR / NW-t / win / 增量 ICIR / 两个单调点值 **全部逐比特不变**(修复未泄漏到 IC 路径与增量路径)。**路由自检**:`amp_marginal_anomaly_vol`(OOS 符号翻转,第一步即 FAIL)与 `peak_interval_kurtosis`(OOS 不一致 + ICIR 0.006)单调方向均为 UNKNOWN 但仍 Reject,无渗漏。 + - **合并后十一因子干净全量重跑(`main` @ `1ae555e`,11/11 rc=0,~87min)**:`repro_reconcile.py` 11 因子 × 20 字段 = 220 项,**容差 0.0**(精确浮点相等,不用 `isclose`、不留 epsilon —— 1e-6 是"可能动"的下限,用恰好为零来测才能让任何 ≥ 该下限的变化浮出来)→ **0 处差异**;`repro_compare.py` 对 22 个 JSON artifact 做展平叶子深比 → **5,756 叶、0 处不同**,外加 **22/22 Markdown 逐字节相同**。防空过:22 个 JSON 确实被重写(mtime 落在各自 run 窗口 09:24:33→10:43:54)、前后 HEAD 都是 `1ae555e`、11 个 rc=0、全程 `stk_mins_live_calls=0`。 + - ⚠️ **记一次自己的失误:写了一个不可能失败的对账脚本**。第三个脚本 `scratchpad/compare_postmerge.py` 曾被跑并报出结论,**但它是空的,其输出什么也不证明**:它执行 `_baseline_table.py` 取新值,而那个脚本第 123 行**会写** `baseline_rows.json`,随后"旧值"读的正是刚被覆盖的同一个文件 → `old == new` 由构造成立,哪怕 artifact 错得离谱也会打印"11 个全部精确复现"(已在沙箱里用 baseline `ic=1.0` vs fresh `ic=999.0` 复现该假通过)。它还顺带毁掉了 `baseline_rows.json`。**这正是整轮重跑要消灭的失效模式,而我们亲手造了一个**(同 P2-3「曾误以为是粒度」、P3-4「过拟合签名」,照实记录)。上面的结论只站在不碰该文件的两个脚本上(其中一个跑在覆盖之前)。**权威基线是 `scratchpad/baseline_reports/`(22 文件,冻结于 run 前);复核必须比它,不是 `baseline_rows.json`。** diff --git a/docs/progress/06_execution_basis_and_corrections.md b/docs/progress/06_execution_basis_and_corrections.md new file mode 100644 index 0000000..c028302 --- /dev/null +++ b/docs/progress/06_execution_basis_and_corrections.md @@ -0,0 +1,76 @@ +# 执行基准与更正:VWAP 成交 / 复权收益 / 除权审计 / exec-to-exec 改基准(PR #75 – #82) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +尾盘执行价改 bar VWAP、分钟持有期收益复权、报告披露修正、`adj_factor` 下降质量检查、日频除权正确性审计(GENUINE)、I5d/I5e/I5f 在修正引擎上重跑、十一因子改 14:51 VWAP 基准。 + +--- + +- ✅ **尾盘执行价改 VWAP + 分钟持有期收益复权**(**PR #75 已 merge 到 `main`**):两项改动都源于"14:50 就知道的信号为什么按 15:00 形成的价格计分"。 + - **改动 1:成交价 = 执行 bar 的 VWAP**(`amount/volume`,整分钟成交量加权均价)而非收盘 tick。`IntradayCfg.execution_price_basis: "bar_vwap"|"bar_close"`,默认 `bar_vwap`;`bar_close` **逐比特复现旧行为**——用独立 worktree 跑 main 版本、**字节比对价格矩阵与完整 fills 日志**(含 `exec_time` 与 block `reason`)验证,非断言。VWAP 未定义(volume≤0 / amount≤0 / 非有限)→ 走**显式阻挡**路径,**绝不退回 bar 收盘价、更不退回日收盘**。 + - **闸门改比实际成交价**:涨停分钟只有两种形态——**LOCKED 封死**(全部成交在涨停价 → VWAP = 涨停价)与 **OPENED 盘中打开**(有成交低于涨停 → VWAP < 涨停价),恰好对应"能不能买到";按收盘价判两头都错。这是**有意的行为变化,且被计数不被静默**:`opened_limit_up_minutes()` / `opened_limit_down_minutes()` 报出与收盘价闸门的每一处分歧。真实数据取证:`601858.SH 2023-05-15 14:51`,close = up_limit 46.13、VWAP 46.048583(差 0.18%,远高于舍入量级)。 + - **容差保持 `1e-6`**:实测 13,699 根 14:51 bar(149 根收在限价),LOCKED 偏离 ≤0.0021%、OPENED 0.013–0.017%(两总体不重叠)。1e-6 下 100% 的 OPENED 正确放行,代价是 2.1% 的 LOCKED 被误判为打开(≈全样本 0.02%)。**残差偏乐观且有界**,已写入 docstring。0.001 在本样本双向为 0%,但那是在 149 个观测上重标定阈值 = 后验调参,不为 0.02% 的效应破例。 + - **改动 2:分钟持有期收益复权(先前缺陷)**。`holding_returns` 原在 **raw 未复权价**上算 `exec(exit)/exec(entry)−1`,而日频模型用 qfq 面板 → 两条路径不对称,除权除息的机械性下跌被读成亏损。改为 `(raw_exit·af_exit)/(raw_entry·af_entry)−1`(qfq 锚在比值中相消,无需另存复权序列)。缺 `adj_factor` **不静默按 1.0**(那会把刚去掉的除权偏差放回来),走既有丢弃路径 + `missing_adj_factor_pairs()` 计数。复权**不渗进 I5b 限价闸门**(保持 raw-vs-raw),有专测钉住。 + - **实测发生率(月频调仓)**:I5d CSI500 52,923 个 (symbol, 持有期) 对中 **4,450 (8.41%)** 跨 `adj_factor` 变化,修正均值/中位 **+3.92% / +1.07%**,92.9% 为正;I5e CSI300 26,314 对中 **2,566 (9.75%)**,**+3.74% / +1.31%**,94.5% 为正。均值远高于中位 = 右尾**送转股**(af 翻倍 → raw 价腰斩 → 未复权读成 −50% 假亏损)。 +- ✅ **报告披露修正 + I5f 名义按真实规模重测**(**PR #76 已 merge 到 `main`**,`runtime/` 行为未变):**报告曾谎报自己做的检查**——PR #75 把闸门从 bar 收盘价移到实际成交价,却漏改了两个 report writer 里**描述闸门本身**的那句,此后每次跑出的报告都在描述一个它没有执行的检查。在"绝不静默降级"是常设红线的项目里,报告写错自己的方法比不出报告更糟。 + - **三个一直在算却被丢弃的计数器进报告**:`opened_limit_up_minutes` / `opened_limit_down_minutes`(本闸门与收盘价闸门的**全部分歧集合**——什么能不能成交变了,绝不许静默)、`missing_adj_factor_pairs`(因锚点 `adj_factor` 不可用而丢掉的持有期;非零即已测覆盖率短少)。尾盘报告另行声明收益是复权的并给出恒等式("exec-to-exec"一词已不足以描述它们)。 + - 措辞抽成 `limit_basis_lines()` 并由三个测试钉住;**mutation evidence 实跑**:把措辞改回收盘口径 → 2 个测试 FAIL;从分组表里删掉新计数器 → 第 3 个 FAIL。**旧 I5b 测试为何没抓到**:其 fixture 给每根执行 bar `volume=1.0, amount=close` → **VWAP ≡ close**,使所有断言在两个基准上都成立,对"闸门读哪个"结构性失明;已写进 module docstring 防止重建同一盲点。 + - ⚠️ 本 PR 的守卫办法**随后被证明不够**——见下条 **PR #78**。 +- ✅ **`adj_factor` 下降质量检查**(**PR #77 已 merge 到 `main`**,D3 层新检查,WARNING、report-only):`data/quality/market.py::check_decreasing_adj_factor` 接进 `run_adj_factor_checks`。tushare 的累计复权因子随每次公司行为**只增不减**,`front_adjust` 拿它乘 raw 价,一个发布出来的下降会直穿到每一个下游收益;而真实实例是**手工**发现的(见下条审计)。 + - **阈值 1%(相对,非绝对)由全缓存扫描定**(扫全部 5562 只已缓存票的每一个负步进:7898 个事件、涉及 4479 只票): + + | 阈值 | 触发事件数 | 触发的**非已知缺陷**票数 | + |---|---|---| + | 0.01% | 3653 | 2429(纯浮点噪声) | + | 0.10% | 179 | 2 | + | 0.50% | 178 | 1(`603081.SH`) | + | **1.00%** | **177** | **0** | + | 2.00% | 176 | 0 | + + 低于 1% 会点名状态**判不了**的票:`603081.SH` 最坏步 −0.776%,对应 raw close −1.810%,而真实公司行为需要 +0.782%——有缺陷的**形状**,但在该量级上普通日波动淹没隐含值,价格检验没有功效,结论不可解释。高于 1% 则漏掉一个真事件(1% 触发 177、2% 只触发 176,多出的那个属已知缺陷票)。1% 同时让最小的**已确认**缺陷(`000998.SZ` −2.636%)站在门槛的 2.6× 而非 1.3×——report-only 层宁可误报(代价一行 WARNING)也不可漏报(代价是被污染的价格静默通过)。 + - **相对而非绝对**(factor 跨缓存 ~1…~500):mutation 实证 `pct_change()`→`diff()` 让原六个测试**全部通过**(fixture 都落在 factor ~1–11 两种切法重合处),补两个别的尺度才有牙(`0.200→0.195` 是真 −2.5% 断裂而绝对门看成 −0.005 放行;`500.0→499.97` 是 −0.006% 舍入而绝对门 −0.03 误报)。 + - **本条 WARNING 的语义与 `check_extreme_returns` 不同**:本模块所有 HARD 检查都是零参数的代数不可能(重复键 / 非正 OHLC / high50% 的收盘跳变确实可能是拆股或复牌,而累计复权因子**没有任何合法的变小方式**。severity 反映的是阈值标定风险,不是对主张本身的怀疑。 +- ✅ **闸门描述改单一来源**(**PR #78 已 merge 到 `main`**,`ecd8db2`;#76 的结构性 follow-up):#76 加了一道扫描式正则守卫,并在 commit message 里声称"再多一处错误描述就会失败而不是发出去"。**review 用实测证否了这个声称**:写了七种自然改写("its last print"、"the final tick"、"1-minute" 代替 "1min" 等)跑真正的正则,**七种全部逃逸**——这是拿词法守卫去守语义主张的普遍下场。 + - **修法是结构性的,不是再加一条正则**:`limit_basis_phrase()` 成为**唯一被撰写的那句话**,feasibility 段、分组报告、报告的 Limitations 条目三处**组合它**而非各写各的;`_load_price_limits` 的 docstring 改为指向它而不再复述。本 PR 只改"有多少处可以各自出错"。正则守卫**保留**,但 docstring 缩到它真能做的事:抓**字面 revert**(坏 merge / rebase 把旧句子带回来),而这正是真实发生过的那次失效。 + - ⚠️ **"渲染文本逐字节不变"这个声称一开始是错的**(合并前修掉,`5b4d1ec`):一次**渲染探针**(在 main 与分支上各自构造同样那几行、比较字符串)查出**三个宿主里有两个丢了收尾的破折号**,渲染成 `...on the `bar_vwap` basis to the raw `stk_limit` band` 这样**破折号不成对**的病句——即一次以"没有输出变化"名义发出去的真实输出变化。**review 在探针跑之前恰好问过这个问题**("在一处读起来通顺的片段,换个上下文可能不合语法"),答案是它已经发生了。两处修复后重跑探针:所有组合出的行与 main **字符串完全相同**,并加测试钉住"片段被嵌进句中时,起头的破折号必须在其后被闭合"。 + - **代价面照记**:把一个片段组合进多个句子,是用**"三处可以各自把事实说错"换成"一处可以把标点打错"**——仍是划算的交易,**但它是一笔交易,不是白拿**。 + - **可带走的规则(已写进上面的方法论 ②)**:**正则断言不了"没有别的句子这么说",而"根本没有别的句子"可以。** mutation 证据用的正是 review 自己那批能逃逸的措辞——它们**通过**正则守卫、**失败**于结构性测试(Limitations 条目改写成"the execution minute's final tick"、分组报告改写成"the selected bar's last print",两者都被结构测试判死)。 + - gates(`5b4d1ec` 修复后):`pytest` **1742 passed**、`ruff` clean、phase0 `0.9600/0.8408` 不变。 +- ✅ **日频除权正确性审计**(用户点名最高优先级;调查记录 `tmp/design/FINDING_adj_factor_seam_2026-07-21.md`,全程只读 `artifacts/cache/tushare/v1`,无独立 PR):**结论 GENUINE——日频 qfq 路径本来就是对的,没有 vintage 接缝**;十一因子的 close-to-close 前瞻收益一直是除权正确的。 + - **接缝假设被证否**(双向检验:重述接缝既可能是降也可能是升):CSI500 池 996 票、979 票的 `adj_factor` 来自 >1 次 fetch、960 对相邻交易日跨 vintage 边界,其中因子**真的变化的只有 12 对**——全部为正、全部落在同一个边界(2026-05-29→06-01,A股年度除权高峰首个交易日,12/996 = 1.2% 属正常季节性),且大的都伴随 raw 价格的对应跳变(`002837.SZ` Δaf +30.24% / raw close −27.41% / 隐含 −23.22%)。**任何 fetch 边界上都不存在负向因子变化。** + - **`−57.27%` 是北交所占位符,不是分布尾部**:`920627.BJ` 一只票占全部 −2% 以下 176 行中的 171 行,因子在 `2.3404 ↔ 1.0000` 之间反复跳(175 行正好等于 `1.0000` = "无调整"占位符),其数据来自**单次 fetch**(一次 API 响应内不可能有跨 vintage 接缝),且**连一行 `market_daily` 收盘都没有**(永远进不了价格面板)。`1/(1−0.572723)−1 = +134.04%` —— 最初报的 `+134%` 上尾与 `−57.3%` 下尾是**同一只票的一正一负**。 + - **评估池内暴露:603,258 个 (symbol, date) 对里 1 对**(`000998.SZ` 2023-06-01,PIT 确认的 CSI500 成分:af −2.636% 而 raw close **+1.228%**、当日**开盘 −0.41% 没有任何机械缺口** → 判 **AMBIGUOUS**,倾向 tushare 自身发布缺陷而非缓存 artifact);**CSI300 零**。传播后:单日分位桶均值畸变 0.0208pp、摊到 1,211 个评估日 **0.0000172pp**、多空价差 ≤0.0000344pp——比任何能动 verdict 的量级低四个数量级,**不重述任何因子结果**,只作披露。 + - **污染的均值 vs 干净的均值(独立复测)**:全 CSI500 评估窗口日频 1,154,642 对中 6,906(**0.5981%**)跨因子变化,均值修正 **+2.28%**、中位 **+0.52%**、82% 为正,摊到全样本 **+1.37bp/日 ≈ +3.4%/年**(正是 A股股息率量级)。与最初被污染的测量对照:**发生率(0.604% vs 0.5981%)与中位(+0.45% vs +0.52%)一直是对的,错的只有均值(+4.38% vs +2.28%)与尾部(+134%)**——单个极端污染样本的典型签名(中位稳健、均值不稳健)。**经验:当均值与其中位差一个数量级,先去找那一行坏数据,别先给分布编故事。** + - 与 PR #75 的关系:**那是另一个 bug**——*分钟* `exec_to_exec` 路径用未复权价算持有期收益,日频路径从来不受影响。BSE `920xxx.BJ` 的 `adj_factor` 一律按不可信处理(零成本:无研究 universe 含它们,且它们没有日线价格行)。 +- ✅ **I5d / I5e / I5f 在修正引擎上重跑**(出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md`;**EXPLORATORY,非业绩声明**)。⚠️ **读数前提(不许混谈)**:三个 run 的 NAV 全线上移**全部来自 bug 修复**(把除权除息还回去),**不是业绩改善**;VWAP 成交基准本身是**略微不利**的(−0.18pp)。 + - **I5f 四档归因**(SSE50 2026-03-03→06-12,3 期,`covered=58/58`,`stk_mins_live_calls=0`,每档同窗口实跑、确定性): + + | 引擎配置 | final NAV | delta | + |---|---|---| + | 已验收 I5b/I5f:`bar_close` 成交 + **未复权**收益 | `0.976448` | — | + | `bar_close` 成交 + **复权**收益 | `1.021137` | **+4.47pp** | + | `bar_vwap` 成交 + 复权收益(**新默认**) | `1.019318` | **−0.18pp** | + + 复权修正 ≈ 全部位移(该窗口正跨 A股年度除权高峰,暴露处于季节最大值);**更诚实的成交价反而差 0.18pp**(单根收盘 tick 比整分钟成交量加权价"更走运",方向符合预期)。闸门没有放松任何东西:`opened_limit_up_minutes`=**0**、`opened_limit_down_minutes`=**0**(这正是本闸门与收盘价闸门可能分歧的**全集**,本窗口为空),涨停挡买仍 1 笔、跌停挡卖 0 笔;限价覆盖 174/174;`missing_adj_factor_pairs`=**0**(修正测在全样本上)。 + - **I5f 容量按真实规模重测**:`portfolio_notional` 10,000,000 → **1,000,000 RMB**(用户明示资金上限),`max_participation_rate` 0.05 不变。43 笔 desired trade / 1 笔已被既有 feasibility 挡 / 42 笔有可用 ratio:**below 1.0x 由 20 笔变 0 笔**,ratio min/p10/median/p90 由 `0.122/0.222/1.104/2.730` 变 **`1.217/2.219/11.037/27.296`**。**此前"约半数单分钟 desired trade 容量不足"描述的是一个比本项目会跑的任何组合大一个数量级的规模**;真实规模下单分钟容量在该 universe/窗口**不构成约束**(最紧 1.22×)。这**不取消** partial-fill / volume-cap(可能的 I5g),只是重新界定其动机;仍是诊断,**非可交易性/业绩声明**,不外推到其他 universe / 名义 / 窗口。 + - **I5d(CSI500 五分位,2021-06→2026-05,59 期):原结论不成立**。Q1→Q5 终值 `0.9822/1.0423/1.0495/1.1275/1.1577` → **`1.1758/1.2310/1.2313/1.3090/1.2712`**;**单调 Spearman +1.0000 → +0.9000**、合成 Q5−Q1 累计 **+17.15% → +8.65%**(腰斩)、**Q5 不再是最优组**(Q4 1.3090 > Q5 1.2712)——I5d 的全部主张就是那条完美的 Q1→Q5 排序,它断了。分钟覆盖 892/995 → 900/995;`stk_mins_live_calls=0`;限价覆盖 52836/52836;各组 `returns dropped: no adj_factor` 均为 **0**。 + **四档归因(四件事同时变,实跑中间配置而非猜)**:① 老引擎 + **新缓存**(900 票)Spearman 仍 **+1.0000** → **样本变化被排除**;② `bar_close` 成交 + 闸门不变、**只把分红除回去**即已 Spearman **+0.9000**、价差 **+8.83%** → **复权修正是全部效应**;③ 再换 `bar_vwap` Spearman 不变、价差 −0.18pp → **VWAP 基准近乎惰性**。**即 I5d 的完美单调性实质上是"把除权下跌记成亏损"的产物**:高 MMP 的 Q5 篮子除权暴露低于 Q4,错扣分红反而抬高了 Q5 相对 Q4;把账算对,Q4 就超过它。74 个 (date,symbol) 对收在限价却盘中打开、现在获准成交(opened limit-up 22/14/10/10/18、limit-down 3/2/1/2/4),真实且已披露,但按 ②→③ 不是移动结果的原因。 + - **I5e(CSI300 独立泛化):负结果不变且更强**。Q1→Q5 `0.8809/0.7377/0.9418/0.7573/0.6665` → `1.0919/0.8777/1.1575/0.9223/`**`0.7850`**;**Spearman −0.5000 完全不变**,合成 Q5−Q1 累计 **−26.77% → −31.82%**(更负);Q5 仍是最差组(年化 −4.81%、maxDD −47.79%);覆盖 448→450/481;opened limit-up 21 对(远小于 I5d 的 74)。**这正是该问的问题:若光靠复权修正就能翻转 I5e,那个负结果就有一部分是"把分红记成亏损"的假象。它没翻——泛化失败是因子的属性,不是执行假设的产物。** + - **MMP 合并读数**:唯一的正面证据在修正引擎上**退化**,独立 universe 上的负面证据**加强** → **暂缓 MMP 的理由比过去更强,而不是不变**。 +- ✅ **十一因子改用 14:51 VWAP 的 exec-to-exec 基准**(**PR #79 已 merge 到 `main`**,`b04c966`;⚠️ **合并方式见下条**;数字出处 `tmp/design/RESULTS_post_pr75_2026-07-21.md` + PR #79 正文):每个因子**只算一次、在两个基准上各评估一次**;既有 `{stem}_{no,with}_book` artifact 原样不动,新增的是 `{stem}_exec_{no,with}_book`。**why**:分钟因子在 14:50 截止时就已定死,而本框架**能建模的唯一成交**是 14:51 开始的那根 1min bar;按 `close(t+h)/close(t)` 计分等于把一个**本项目模拟不了的收盘集合竞价**算进因子功劳(`closing_call_proxy` 需要 `stk_auction_*`,数据方案不含)。 + - **回归先行**:rebasis 分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 个叶子、0 处不同** → 本次是**加了一个基准,不是移动了一个**(两个目录由两次独立文件读取比对,不经任何共享中间产物——正是上面那次空对账的教训)。 + - **22 格(11 因子 × no_book/with_book)中 20 格不变;移动的 2 格都朝"更没把握"走**: + - `valley_ridge_vwap_ratio`(no_book)Watch → **INSUFFICIENT-DATA**:对齐按日单调 CI `[+0.00100,+0.07110]` → `[−0.00535,+0.06804]`。它的 Watch 本就建立在 +0.0010 这个全表最薄的下界上(**跑之前就已在参照表里标注**),CI 稍宽即跨零 → 方向 UNKNOWN → 预测轴 INSUFFICIENT_DATA。其 with_book 仍是 Watch,但**改由增量轴独扛**(正交 ICIR 下界 +0.232 > 0.15)——同一个标签靠的是不同证据,reasons 里写明。 + - `ridge_minute_return`(with_book)Reject → **INSUFFICIENT-DATA**:close 基准上增量轴 FAIL(正交 ICIR −0.141≈0,与因子簿冗余);exec 基准上点估计 −0.151 方向仍对,但对齐下界 +0.084 够不到 0.15 门 → 既不通过也不否证。**这是把握变弱,不是翻案**:INSUFFICIENT-DATA 的语义是"既没证明也没证否",且它到不了正面主张。 + - **机制只有一个**:点估计几乎全面**变强**(|ICIR| 11 个里 10 个上升;唯一例外 `peak_interval_kurtosis` 0.0058→0.0008,本就是空因子),但 **N_eff 11 个里 9 个下降**(1187.7→1116.0、1009.5→933.3、1122.9→1055.8)——exec-to-exec 的 IC 序列比 close-to-close 带更多自相关,于是每个 CI 都变宽。**更强的点估计落在更宽的区间里**,恰是 v0.9 那道门被造出来裁的情形,而它裁得保守。 + - **新基准的诚实代价(两个分母刻意分开,因为 `1,541` 在两种口径下都会出现)**:**按锚点**(面板网格 1,158,912 个 (date, symbol) 对)——`ok` 1,156,368(99.78%)/ `no_bar` 执行窗口内无 1min bar **1,734(0.150%)**/ `bad_vwap` 有 bar 但 VWAP 未定义 810(0.070%)/ `bad_adj_factor` **0**。**按前瞻收益对**(每因子各自的评估网格)——比 close_to_close 少 3,170–3,274 对(**0.278%–0.283%**),涉及 175–180 只票,`bad_adj_factor` 十一个因子**全为 0**,`stk_mins_live_calls` 全为 0。**VWAP 定价比 bar-close 基准多挡 1,541 对(占收盘基准可测的 1,156,183 的 0.133%),因为那一分钟根本没有成交——这部分排除与流动性相关、有方向、非随机**(被排掉的正是最不流动的分钟):**这是有偏的样本限制,不是舍入损失;百分比小不等于随机,不许当随机报**(每份 exec 报告都带 `coverage_bias_bad_vwap` 原话,且 sanity 报告把这句话放在覆盖率表**之上**,使读者不会先看到百分比;有测试钉住措辞)。N_eff 在 9/11 上缩水是更真实基准**付出的统计功效代价**,是真实借方而非记账层面的假象。 + - **VWAP 与 bar close 在无条件分布层面几乎同一序列**:均值差 **+1.0e-07**(SE 1.3e-06,t = 0.074)、中位差 0、逐日相关中位 **0.998** → 改基准买到的是**执行锚点的真实性,不是另一个信号**,与 I5d 归因(VWAP 什么都没动、复权动了全部)一致。**close-to-close 结果保留作并排对照**,exec_to_exec 为操作基准。 + - **正确性验收(CSI500 全规模,996 票 / 1,210 日,cache-only,十个日频/universe gap-fetch 计数器全 0)**:① 与 close_to_close 的逐日截面相关中位 **0.9890**(p10 0.9821 / p90 0.9933,Spearman 中位 0.9814),下限 0.90 → PASS(**低于下限时 builder 直接 raise 而不是继续跑**);② 量级同阶(exec mean 0.000429 / std 0.027718 vs c2c 0.000451 / 0.028050);③ **5 行从 raw 1min parquet 用普通算术手算复核**(不 import 任何执行层 helper,故不会继承其 bug),最坏绝对差 **0.0 精确**;④ 除权持有期两种口径并列(如 `000564.SZ` 2021-12-29→12-31:未复权 −0.598581 → 复权 **−0.045367**)。gates(PR 正文):`pytest` **1774 passed**(= 当时 `main` 的 1740 + 34,恰为两个新测试文件、0 个既有测试被挤掉)、`ruff` clean、phase0 `0.9600/0.8408`、31 配置、secret scan 0。 + - ⚠️ **它是怎么合并的:派出的 code review 约 1.7 小时未返回,本 PR 靠自查而非评审通过而合并**(PR 正文下有一条 comment 如实说明,以免日后有人以为它被评审签过字)。**合并前独立核过的**:① 既有路径没动——分支的 `close_to_close` artifact vs 已验证的主树版本 **5,756 叶、0 处不同**(两个目录分别读取、无共享中间产物),与 diff **+2595/−0 纯新增**一致;② **`h` 走的是因子自己的评估网格而非价格面板的行**(`exec_forward_returns` 先 `isin(dates)` 再 `groupby(symbol).shift(-h)`,正是 `analytics/eval/ir.py::_resolve_forward_returns` 标注为 close 路径隐患的那条性质;非正分母掩成 NaN);③ 因子信息集**无前视**(日 d 的入场锚是 d 当天 14:51 的执行 bar,因子 14:50 已知,因果成立);④ 共享 artifact 的 **cache key 完整**(含 schema 版本 / universe 类型 + 指数代码 / 起止 / 缓存根 / `price_adjust` / 全部执行参数 / **精确 symbol 集合与精确日期网格各自的 sha1**)——陈旧 artifact 无法被喂给另一份配置,那正是"悄悄拿错收益给一族因子打分"的失效模式;⑤ 覆盖率成因**按构造互斥划分**(先归入场锚、否则归出场锚)。**未独立核、因此仍对后续检查开放的**:五条 mutation 声称(只读了、未重跑)、十一个 runner 改动是否机械等同、`EvalContext.execution_capacity` 透传是否在作者自带测试之外仍保持 Tradable 轴 `NOT_ASSESSED`。**若日后评审在这个未核集合里查出问题,要重述的是 exec 基准的数字——不是 close-to-close,后者未被触碰且仍是已验证的参照。** +- ✅ **闸门/成交价描述全仓清扫**(**PR #82 已 merge 到 `main`**,`f432dc5`;#78 的 review follow-up):#78 的守卫**只扫它刚修过的那两个 report 模块**,于是同一缺陷的**六处存活实例原则上就够不到**——`qt/config.py:343` 加 **五个 I5b/c/d/e/f config YAML**。后者是操作者启用 `price_limit_check` 前会读的文件,**读者层级高于 docstring**。 + - **教训(本条是全轮最值得留下的一条)**:**一个只扫你已经修过的文件的守卫,永远只能确认你已经知道的事。** 把扫描扩到整个特性面后**立刻又抓出六处**——不是同一个声明,是同一次 PR #75 行为变更留下的另一批:`执行价 = 窗口内第一根有效 1min **收盘价**`(`qt/intraday_tail_framework.py` module docstring + I5a/I5b/I5f config),而 #75 之后成交价是 bar VWAP。**共 12 处:7 处描述闸门,5 处描述成交价。** + - **两处因导入环无法组合短语,改为指针并写明原因**:`runtime/backtest/event_models.py` 与 `qt/config.py` 都在 `qt.intraday_tail_framework` 的**上游**(是它 import 这两个),反向 import 必成环 → 不再复述,改指向 `limit_basis_phrase`,扫描作为这两处的兜底。`event_models.py` 的 docstring 明写这一点,免得下一个人再去试那个 import。 + - **刻意不扫、逐条核过的**:`RUNBOOK.md` / `CLAUDE.md` 的「涨跌停用未复权 raw close 比 `stk_limit`」是**日频** universe 可交易性过滤(`universe/filters.py` + `data/clean/tradability.py`),另一套更老的机制,本来就该比日收盘——**粗心的 sed 会把真话改成假话**。 + - **守卫的射程如实写进它自己的 docstring**:它抓的是"close"写在"execution minute"附近,**抓不住换词改写**(review 实测七种逃逸)。**组合(author once)才是真答案,扫描只是导入环挡住组合的那两处下面的网。** review 独立复核:对 merge-base 与本 PR tip 各跑一遍 → **修前 12 命中 / 修后 0 命中,行号逐一对上**;另用更宽松模式扫全仓 `.py/.yaml/.md` 共 54 处命中**逐条读过**,无新增真缺陷。 + - **顺手把 #79 的 `qt/exec_basis_*.py` / `qt/exec_forward_returns.py` 提前纳入扫描**(今天是对的,但同属"执行基准"领地)——等它们变陈旧再加,就是把刚吃过的亏再吃一遍。 diff --git a/docs/progress/07_factor_layer_refactor.md b/docs/progress/07_factor_layer_refactor.md new file mode 100644 index 0000000..9928263 --- /dev/null +++ b/docs/progress/07_factor_layer_refactor.md @@ -0,0 +1,39 @@ +# 因子层深度重构 D0 – D5 C4b(PR #84 – #107) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +设计定稿 v3.2 与 D0 纸面契约、registry/requires、面板冻结、minute primitives、store 层、FactorService + materializer、per-symbol 流式物化、store 去 universe 依赖、统一 exec-only eval runner。**进行中的 D5 C5 见 `tmp/design/HANDOFF_2026-07-30_claude_code.md`。** + +--- + +- ✅ **因子层深度重构 —— 设计定稿 v3.2 + D0 纸面契约**(**PR #84 已 merge 到 `main`** @ `703bd3e`;重构工作流启动,迁移计划 D0–D7):全面重构因子层(registry/requires/两视图/store/统一 eval runner/服务层),设计经三轮迭代 + 对抗性 agent-team 审查定稿。 + - **五项拍板决策**(2026-07-23 用户逐项确认):① 全线深度(bitwise 验收退役 → 语义对账四腿;旧 demo 锚保到 D1 末 → D2 冻 provisional → D6 转正);② 运营形态 = **日频 14:50 决策 / 14:51 执行**(当前侧重投研;实时 = 决策点批处理 + 同一 cache,不建流式引擎);③ 存量机器全部迁移(真实目标 = 杀第二条因子取数路径,编排/分析逻辑留代码);④ 收官全量重评估 **只跑 exec**(close 口径从评估契约退役,decision-view 因子簿关闭 with_book close(d) 混用活缺陷);⑤ 爆炸半径 = 因子侧全重写,data 层与 runtime 事件引擎/fills 与 `analytics/eval` 统计裁决核**保留**。设计文档 `tmp/design/factor_refactor_design_v3.md`(v3.2,SER8 同步)。 + - **对抗性审查**(8 角色 Opus 4.8 max effort + 20 kill agent,~3.07M tokens):53 发现,20 HIGH/CRIT 经交叉证伪 **1 存活 / 6 削弱 / 13 被杀**;24 条修订 R1–R25 折入 v3.2(综合报告 `tmp/design/review_round3_v32_revision_list.md`,含被杀档案**禁翻烧饼**)。唯一完整存活的 HIGH 是设计文档自身的事实错误:「峰/岭/谷分类 ≥5 处重复」与代码相反——实为 author-once 单点定义(`intraday_volume_prv.py::peak_mask_for_symbol`,6 模块 import 复用),已更正并撤回 #78 援引;primitives 层理由重述为 surface 蔓延 + 延迟承重(实测 11 因子朴素逐读 ≈58s vs 共享读 ≈17.6s)。审查另发现当前 `main` 潜在缺陷:**回测 `_build_cache` 不传 fina 400 天 `recent_tail_overrides`**(仅 data-update 路径有),晚披露财务在纯回测 read-through 下可能漏抓——修复排入 D3 前置。 + - **D0(PR #84,纯附加 +1148/−0,无引擎代码)**:`data/availability_policy.py`(§1.3 policy 表代码常量单一来源:**字段级 market_daily**——decision 视图仅 `open` 当日合法;3 处 DOMAIN ASSUMPTION 显式标注;修订视界从调用方传入的 config 值派生、**禁硬编码 14/400**、fina 缺 override 即 raise;端点封闭性 `require_known_source`;视图×基准配对合法性;`adjustment`/`overnight_boundary` 三值枚举单一来源)+ 30 测试(含对 `data/cache` 端点常量的真 drift guard、frozen 真赋值断言、bool-混入-int 守卫的 mutation 有牙实证)+ `docs/factors/refactor_d0_contract.md`(收官 14 因子 adjustment/overnight_boundary **预指派表**带 file:line 证据——9 RETURNS_INVARIANT / 5 NONE / 0 PRICE_LEVEL,`valley_price_quantile` = CROSSED_DISCLOSED,另发现 `minute_ideal_amplitude` 以 raw close 跨日池化排序 → CROSSED_DISCLOSED 候选、偏差测量义务留 D2;11 类性质**双向编目**,107 个既有测试引用逐一核实存在,NET-NEW 性质钉 D 格:adjustment→D3、overnight_boundary→D2、单点填≡批量填→D4、视图隔离→D1/D3/D4)。 + - **D1(PR #86 + #87,整步已关账)**:① registry + requires(PR #86,+1751/−69)——`factors/registry/`(register/build/requirements + 显式 builtin 列表 + 三时点命名校验)、`PanelField`(endpoint-only,构造期 `require_known_source`)、FactorSpec **契约 v1.0**(三个必填声明维 requires/adjustment/overnight_boundary,值域引用 D0 枚举;既有 metaclass 双检/预注册/property-spec 全不削弱)、18 个 Factor 类全部落声明(14 收官因子照 D0 表逐行、21 行参数化测试锁死;日频因子逐类带证据派生)、`qt/pipeline._build_factors` if/elif 链退役为纯 `registry.build()`。**因子数学零改动三重证**:compute 文件删除行仅 4 条相同 import / 评审逐 hunk 扫 `def compute` 零命中 / phase0 锚逐字不变。评审 APPROVE-WITH-NITS(1 MEDIUM 经裁定为 diff 基线假象——分支切在 #85 之前,三点 diff 证实未碰 AGENTS/CLAUDE;1 LOW 前瞻记录)。两处披露的行为差异:unknown 因子名提前到 config 解析期报错;旧链对每因子 eager 算 `int(params.get("window",20))` 的 accidental ValueError 不再复现(实证无依赖)。gates:1879 passed / ruff / 31 configs / 锚不变 / secret 0。 + - ② **面板冻结**(PR #87,v3.2 §五第 4 腿/R11,D5 逐格对账的唯一基线):`qt/panel_freeze.py`(**零公式重抄**——分钟因子调各 runner 私有 `_load_*_panel`、常数从 runner 模块对象引用、书因子走 `_build_book_factors`;canonical content hash = sha256(排序索引串行化+float64 原始字节),NaN payload 归一、±0.0 保留区分;原子写;`--resume` 从严——读回文件也重过 process+对账+重算哈希)+ 23 测试(NaN payload mutation 证据、写中途失败原子性实测)+ manifest 入 git(producing SHA 钉 `3669c90`——**先跑后 commit** 使 run 时 tracked 树逐位 = main;provenance 规则原文在案)。真实 run:14/14 面板(网格 1,158,912/1,159,263 行,CSI500 996 成分 1210 交易日,2.79 亿行 raw 1min),`stk_mins_live_calls=0`,**写盘前对账 11/11**(mismatch 即 raise 不落盘;书因子无覆盖字段可对账、照实披露不假装,证据换双跑+跨 run 同哈希+全网格行数恒等),确定性双跑 3/3 跨进程一致;两段 run(会话限额 60min 杀首段 → resume)与首段日志丢失(`_make_logger` truncate)均如实披露。评审 **APPROVE 零发现(含 LOW,本轮首次)**:逐参数镜像比对调用点、14/14 行 doc↔json 程序化核对、3 面板盘上重算哈希、独立 secret 扫描 17 文件 0 命中。gates:**1902 passed** / ruff / 锚不变 / 31 configs。 + - 验收:独立对抗评审 **APPROVE-WITH-NITS**(0 CRIT/HIGH/MED,1 LOW 当场修 + mutation 证据 rc=1/rc=0);lead 独立重跑 gates:pytest **1806 passed**(= 1776 + 30)、`ruff` clean、`validate-config` 31/31、phase0 锚 **0.9600/0.8408 不变**、diff secret scan 0。执行流程:实现与验收均由 subagent 承担、lead 把控(含 5h 窗 <10% 不开新 subagent 的节流规则)。 + - **D2(PR #89,整步已关账——首个动数学步骤,实测逐位一致)**:`factors/ops/` 7 算子(momentum/volatility/liquidity/overnight_mom 改写其上,naive-oracle 等值锁定)+ `factors/compute/minute/`(primitives 吸收 `peak_mask_for_symbol` 单点定义 + 11 因子一因子一文件,数学+surface+声明同文件)+ R14 粒度 shim 化(10 纯模块 + intraday_derived AST 锁纯度;aggregate 通用核留真实代码;共享常量安家 intraday_schema——live import 测试证留 aggregate 成真环)。**验收三条独立路径互证**:① 逐格对账 vs D1 冻结基线 **14/14 `max_rel_diff=0.0`、canonical hash 全等(逐位一致)**;② 88 行分层手算锚 0 失配(脚本运行时守卫禁 import 引擎,曾自抓 3 个口径错);③ mutation 证据实跑 rc=1/rc=0 三组(评审活体复跑其二)。I5 四 run 值级锚全命中(I5b/I5f 1.019318;I5d Spearman +0.9;I5e −0.5)。PR-D 测量债清偿(4.58% 窗含真 ex-date,评审从冻结 manifest 独立复算,维持 CROSSED_DISCLOSED)。provisional 锚全精度值入测试(PROVISIONAL,D6 转正)。评审 APPROVE-WITH-NITS(1 LOW:guard_boundary 静默跳过无机器可读披露——当场修,skip 显式落 JSON)。留 D6 两注记:shim 期 aggregate→factors.minute 方向(披露的临时例外,D6d 日落);`asof_daily_features` 的 mmp_ew hook 届时需新家。gates:**1955 passed** / ruff / 31 configs / phase0 锚不变 / secret 0。 + - **D3(PR #91,整步已关账——store 层,纯附加未接线)**:`factors/store/`(hashing/code_hash/fingerprint/keys/values/run_registry/incremental,全 <800 行)+ **R5 前置修复**(`fina_tail_days` 单一真相迁至 `data.cache.fina_tail_days`,回测 `_build_cache` 与 data-update 两 builder 统一传 fina 400 尾 override——回测 read-through 现会补 fina 尾窗,行为变更已披露;`data_update.fina_tail_days` 删除对 repo 外旧 YAML 属 breaking,仓内两 config 已迁)。key = params_hash + code_hash(自身模块 + 枚举共享集{minute primitives/ops/base/spec} + **one-hop 直接项目内 import 折叠**:AST 自动派生、无手工清单、不做传递闭包——评审 MEDIUM 实测 `reversal_20` 可 build 且值=−momentum 而 momentum.py 取值面不入 key = 陈旧复用洞,据此修;改 momentum → candidates 系 key 变、financial/分钟系不变,无全局过度失效)+ view + 数据指纹(schema-version 维 = data/clean PIT 承重模块内容 hash;`price_level` 另掺 per-symbol adj_factor 事件表 hash 含锚值,收官集零成员不豁免 fixture 测试)。存储:单文件 parquet date 排序 / 原子写 tmp+`os.replace` / per-artifact O_CREAT|O_EXCL 锁(两进程 gap-fill 零丢行实测;锁 neuter mutation 证明锁对原子写也承重)/ JSONL append-only 运行注册表(no-secret:sanitize_text 过自由文本,redaction 测试钉住)/ 损坏文件=miss / **不建 coverage ledger**(值确定性可重算)/ 启动一致性断言(registry 条目必须命中代码注册表)。tail-recompute:`w_overlap=max(W_dep, 端点修订视界)`——W_dep=传递 lookback 深度经 **`FactorSpec.lookback_depth`(optional 附加字段,contract v1.1)**声明、store 消费点 HARD-REQUIRE(14 真实因子未声明 → loud 报错,声明属预注册动作留 D4);视界经 `revision_horizon()` 从 live config 派生(调大 refresh_recent_days → 重叠窗变宽 mutation 实证,无硬编码 14/400);重叠 NaN-aware bitwise,失配按 adjustment 分类(returns_invariant→loud 上游回改记录 / price_level→预期);schema 指纹失配 → schema_void_full 全量重算不误报 revision;§六.18 teeth(headline 深度欠采 → 假 mismatch)测试钉住。#adjustment NET-NEW 性质测试钉本格:扰动 qfq 锚 → returns_invariant 真因子(日频 volatility_20 + 分钟 valley_relative_vwap)逐 bit 不变,批量 census 9/5/0 ≡ D0 表。评审 APPROVE-WITH-NITS(0 CRIT/HIGH,1 MEDIUM+3 LOW+1 NIT;MEDIUM/LOW-1/LOW-3 合并前修于 `fd906f4` 并经 delta 复核三项 CLOSED——评审亲手重跑 momentum on-disk mutation 5/5、allowlist 注入、锁 neuter,并猎杀「不可能失败的测试」零发现)。留 D4 注记:materializer 必须传 live `data.cache.fina_tail_days` 勿靠 400 默认;two-hop 组合链是 one-hop 折叠的已知边界(今日无实例,设计明令不做传递遍历器);`availability_policy` 编辑会 over-invalidate 其 importer 的 store(安全方向、D4/D5 churn 期真实重算成本);单点填≡批量填(P8)是 D4 验收项。gates:**2031 passed**(=1955+76)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 + - **D4(PR #93,整步已关账——FactorService + materializer;四轮评审揪出两个 HIGH + 一项设计修正)**:`factors/service.py`(frozen `DecisionPoint` + `cross_section`/`panel` **都 read-through 读 D3 store**,miss 触发唯一引擎,配对合法性复用 D0 `require_legal_pairing` 构造期强制)+ decision-view materializer(R18 **全** policy-lag:日线非-open ≤ d−1、**open[d] 字段级例外从 policy 表派生**、daily_basic/fina ≤ d−1、分钟 ≤14:50;ex-date 只暴露**布尔**的类型边界;`CacheHorizonConfig` 从 live config 构造缺属性即 raise)+ process 编排下沉 `factors/process/orchestrate.py`(`_process_factors` 薄委托逐字行为保持)+ 14 因子 `lookback_depth` 预注册(参数化派生+测试钉死)+ 热路径计时 smoke(真实缓存 cache-only 40 票共享读 ≈2.0x,诚实标注不外推)。**评审故事(P8 单点填≡批量填连揪三层真缺陷)**:①【设计修正】valid-day **pooled** 因子(8/11 分钟因子按滚动机制码检划分,非按观察到分歧的 3 个——#82 教训)trailing N 个**有效日**的日历深度数据依赖、**无界** → **证否 v3.2 §3.3 R4「W_dep=各 lookback 之和」假设**,固定 40 日 trim 使单点填 vs 批量填 finite↔NaN 分歧;②首版不动点判据(值稳定+nbars 稳定)被评审构造 **>1 chunk 停牌 gap 反例**击穿(finite-vs-finite 静默错值)→ 改**结构性判据**(锁定单调性:丢窗口头部 baseline_days 交易日其余分类 FINAL;锁定区 output-date 计数 ≥ lookback_days,gap-immune)+ provider 显式 `earliest_available`(nbars 推断删除,中段 gap 不许当 data start),上版「divergence is gone」overclaim 在 commit message 开篇更正;③判据循环只遍历**有输出的** symbol → 长停牌票**无否决权被静默丢出截面**(撞 I5a 红线)→ 改遍历**请求的** symbol 列表,评审 fixture(600519 丢名复现 + 锁定偏移 416.0 vs 438.0 静默错值)全部落成 committed 测试 + 源码级 mutation 双 witness(rc=1/rc=0,评审亲手用自己未改动的脚本独立复跑确认)。`_MAX_SATURATION_CHUNKS` 耗尽改 loud raise(红线 #9)+ raise 分支有测试。**代价如实入 docstring**:凑不满 lookback 有效日的票拖全 universe 扩到声明 floor;per-symbol 饱和起点是 D5 优化**且非纯优化**(`intraday_amp_cut` 截面 z-score 耦合)。**早期区域量化(D5 面板腿归因名目)**:分钟缓存真实起点 **2015-01-05** → D1 冻结基线(锚 2021-07-01)对 pooled 因子属 **anchor 截断**,饱和加载在 2021-07→10 五票上 ridge 有 157/184 cell 不同(79 NaN→finite,按月递减至零)——D5 对账须具名归因「saturation vs anchor truncation」。JC1 已裁定:P8 逐 bit 对滚动因子降为 ≤1e-12 可归因浮点重排(实测 floor ~3.5e-18,真缺行效应 ≥7 个数量级于容差;NaN mask 必须精确)。留 D5/D6:声明 floor 应从分钟 coverage ledger 派生 per-symbol(现为硬编码常量,声明偏晚会静默截断);`valley_price_quantile` 分钟绑定 DEFERRED(loud 报错,已声明 pooled,D5 绑定继承饱和路径**不许**固定 trim 绕过);零输出否决语义已被测试+mutation 钉住,D5 为性能回退它=放回 HIGH;blanket shift 依赖「因子输入面板只含端点列」的 D6 wiring 契约。gates:**2098 passed**(=2031+67)/ ruff / 31 configs / phase0 锚不变 / provisional 锚绿 / secret 0。 + - **D4b(PR #97,实施期修订 A1 新插入的迁移步——per-symbol 流式物化;改因子取值路径,自带四腿验收)**:materializer 改「逐票读 → 立刻聚合到日频 → 丢弃」,**切口落在截面 combine 之前**。**规模(真实评估面 CSI500 995 票 × 5 年,cache-only `live_calls=0`)**:名义最大单帧 **122.2 GB → 0.066 GB**、峰值 RSS **~115 GB → 0.964 GB**(可用 56 GB,占 1.7%);三种加载几何各跑一因子(pooled×截面 / pooled / bounded)全部 995/995 票、**每次请求 1 票**;峰值稳定 0.73–0.96 GB,**由「一票 bars + 日频面板」决定而非 universe 决定**;扩到 floor 的代价只落在那 84 只 late 票(40 次调用/14.95s),不再拖着 911 只陪跑(dense 票 1.0 次/0.59s)。**四腿**:① D4 既有 31 测试原样通过(P8 四 regime / 锁定偏移 / 零输出否决 / exhaustion loud raise),评审证其**真在测新路径**(单文件即覆盖 `materialize.py` 86%,per-symbol helper 体均被覆盖);② 手算锚 18 行**零项目 import**(运行时守卫断言 `sys.modules` 无 factors/qt/data/runtime/alpha,653 模块 0 命中),`volume_peak_count` 9/9 精确 0.0、`intraday_amp_cut` ≤1.998e-15;③ 逐格对账真实缓存 40 票×6 月 vs 整 universe 单帧(3,711,400 行):10/10 index 相同、**NaN 集合差异全 0**、5 个逐位相同、最差残差 3.275e-14,`intraday_amp_cut` 在 **4,720** 个真实 cell 上对上(评审以 **main 真引擎**为参照复跑得 **max|abs|=0.000e+00**,比文档的 2.5e-14 更干净——因两侧加载几何相同,已写进文档);④ 规模证明如上,且 `--mode reconcile` 使 §6.1 那张最承重的表**可从仓库复跑**(评审亲跑:10 行×4 列与文档逐格一致,且**非空对账**——参照侧完全不经 `materialize_range`,只在被检验的加载几何上不同)。**评审带来的头号反转**:实现方把 per-symbol trim 描述为「更保守的语义细化」,评审证明它在**修 D4 的真缺陷**——同一稀疏 fixture 跨引擎对照,3 个 bounded 因子各动 1 cell,**三个都是 D4b == 几何无关真值、D4 ≠ 真值**(`jump` 0.0901 vs 0.1571;另两个 D4 给 **NaN** 而 D4b 给正确 finite);union trim 给稀疏票喂的自有交易日不足 `lookback_depth` = 红线 #6 的 load-geometry 依赖。且四个旧 runner **逐票读全窗口、根本没有 union trim** ⇒ per-symbol trim 是**朝 D1 冻结基线靠拢**——实现方给 D5 的预警「稀疏票对不上先看这里」**方向反了**(反在安全侧),已订正为指向饱和深度/anchor 截断。超集性质经评审证严(子集第 k 大 ≤ 超集第 k 大),反例构造失败。**MED-1(D4b 新引入的唯一真引擎缺陷)**:请求含重复 symbol 时逐票流式会独立处理每个重复项——48→52 行、4 个重复 (date,symbol),`intraday_amp_cut` 更因重复票**两次进入截面**而 **48/48 共享 cell 全部变值**(D4 免疫是靠 provider 的 `isin` 幂等吸收)→ 改**保序去重**(而非 loud raise:去重**精确复现单帧引擎对同一调用的返回**、保持「只改内存剖面」,raise 会新拒绝前一引擎接受过的输入);评审跨引擎复核修后 0 差异。**另两条「说法与事实相反」**:MED-2 一处 docstring 称旧 mutation 写法「cannot fail」,评审原样跑在 D4b 上是 **FAIL**——它其实是 **cannot pass**(性质保住、只换写法,属 §六.16 而非偷改测试;计数 70→**72**=70 单点填+2 批量填);MED-3 可行性文档声明了一条「本步绝不允许 finite↔NaN 翻转」的政策,而 per-symbol trim **能且实测确实产生**(3 个 bounded 里 2 个 NaN→finite),40 票样本只是没触发 → 改为如实陈述。**lead 合并前补的闭环**:派生 id 列表只守住了「加了因子却没进对账」一个方向;评审实测另一方向——把某因子从**两张 binding 表都删掉**,该文件 53 passed、全量 2169 passed **全绿**,因子从每条 parametrize 里静默消失。故新增闭包测试,真相源改为**`factors/compute/minute` 下定义的全部 `Factor` 子类**,未分类即红(mutation witness:删 `PeakIntervalKurtosisFactor` occurrences 4→0 先断言落地 → 转红;还原后 `binding.py` 逐字节相同、58 passed)。**A1 点名的致命陷阱未踩**:`factors/service.py` diff **0 行**(不许在 service 层按 symbol 分批——`_ensure_coverage` 只按日期判缺口会静默丢名)。gates:**2174 passed / 1 skipped**(= 2117 + 57,**node-ID 集合证 0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 head 未变 / 冻结面板 mtime 未变。留 D5:扩张步长(late 票 40 次重读,纯性能但仍是加载几何改动、欠自己的对账,**刻意没夹带**);声明 floor 仍是 universe 级硬编码常量未按票从 coverage ledger 派生;`valley_price_quantile` 仍 DEFERRED,D5 绑定**继承流式路径不许固定 trim 绕过**;10 个因子只跑了 3 个的全规模证明(几何全覆盖,评审证未测因子的加载参数与已测者相同或被支配);日频路径仍整 universe 单帧(995×1210 行,量级小,未动)。 + - **D5b(PR #99,D5 后四块只交付了 C3——**C4 中止、C5/C6 未做、什么也没删**;中止本身是本步最重要的产出)**:**发现并(两方独立)确认一个真实前视缺陷**:`jump_amount_corr_20` 是十一个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 **0**,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它、全程无补救。**exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP**,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测(两方各用不同票/不同窗口,前置断言两腿齐全):实现方 12 票/2021H2 **1,477/1,477 格全移动**(max|diff| 1.276)、其余 9 个 bound 因子 **0 格**;评审另 12 票/2023H1 **1,373/1,373**(max|diff| 1.054)、**其余全部 11 条 0 格**(并补测了实现方漏掉的 `valley_price_quantile_stats` 与 `mmp_ew`);评审再加一刀**只扰动 `bar_end >= 14:51`**(执行 bar 本身及其后)→ **1,363/1,363 移动**——**信号看到了自己成交的那一分钟及其后 9 分钟**。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`/`return_basis="exec_to_exec"`/`execution_window="[14:51:00,14:56:59]"`,verdict **Watch**、predictive 轴 **PASS**(|NW-t| 15.19)——**报告声明了它没有执行的检查**。**既有测试为何没抓到**:`test_pit_perturbing_future_bars_does_not_change_factor_at_d` 守的是**跨日**边界,其 fixture session 只有 09:31–09:36 六根 bar、**根本不存在 14:50 边界**;日内边界只在 **PR #79** 把入场锚从 `close(d)` 移到 `14:51(d)` 之后才承重,而 #79 评审未返回、这**恰在其「未独立核」清单射程内**。**裁定(设计文档实施期修订 A3)**:用户 2026-07-19 常设授权明写「全日数据因子一律截断到 14:50……授权直接截断,不必逐因子再问」,而该因子**建于此授权之前** ⇒ 加截断是**补用既有授权**非新研究决策;**但评审的程序性修正已采纳**——从重构视角它仍是定义变更、而授权来自研究侧不来自重构 ⇒ **必须单独成 PR、显式标注 correctness fix,不许进 D5 任何 commit**(已另派)。幅度实测 pearson **r=0.9993**、mean|diff| 0.0042——**缺陷是结构性的必须撤回,但修正代价大概率很小;verdict 存活与否必须由真正的重跑给出、不许由相关系数推定**。**交付的 C3(评估契约 v1.0)**:`EvalConfig.view`/`return_basis` 构造期经 D0 `require_legal_pairing` 强制配对;provenance 增 requires/adjustment/overnight_boundary/lookback_depth + `eval_contract_version`;跨口径汇总**缺 view/basis 列拒渲染**(声明了列却留空同样拒);**判定门一个数没动**(评审证 6 个文件 md5 与 main 逐字节相同,8 个门值逐值钉死)。**评审两轮 APPROVE-WITH-NITS,2 MEDIUM 全关**:**MEDIUM-1「守卫守不住自己」**——声称钉住 identity 修复的测试是对**函数源码做子串匹配**,评审把赋值掏空、两个 token 留**注释**里 → 每份 exec artifact 都会继承调用方 close 身份而实际按 exec 收益打分,**52 passed 全仓无一抓住**(#78 教训的翻版,且放行的恰是同一 commit 刚修的缺陷)→ 抽 `exec_identity()` 改**行为断言**,评审原样重跑逃逸 **4 failed**,并验了**假阳性**方向(重构进 helper 不误报,58 passed)。**MEDIUM-2「假声明上升一层」**——exec 路径无条件声明 `view=decision`,但 with_book 的因子簿是 close(d)+当晚 daily_basic(**§1.1 自己列为「已经咬过人的活缺陷」**),且 jump 连 no_book 格都不成立;而该字段定义是「**因子值**取自哪个信息集」,with_book 有**两个**信息集、一个字段表达不了 → **这正是该字段被造出来防的那种缺陷、只是上升一层**。**现在修免费**(还没 artifact 带它),C5 一跑就写进 22 份。修法:**先拆两个事实**(②单独不成立——登记例外并不能让 `view=decision` 在 with_book 上变成真话)→ 新增 `EvalConfig.book_view`(唯一知情方声明)+ `subject_view(factor_id)` **从事实派生**(deny list `NOT_DECISION_CUTOFF_SAFE` 放**因子侧**——「它是关于**值**的事实」,由可见性测试**测量**而非声明,本地副本已删)。**刻意不校验 `book_view`×basis 配对**,评审的裁定值得作通则记下:**失配摧毁意义 → 拦;失配只带来已披露、方向已知的偏 → 记**(close-view 书是 Incremental 轴的控制变量、偏的方向已预注册;拦它不会让信息集不混,只会让 artifact 说不出它混了)。**并落一个有意的响亮阻断**:jump 现在派生出非法配对 → 其 exec 评估 loud raise(截断修正落地即解除,删 deny list 一条)——在它被修正前**不存在**它的诚实 exec artifact,阻断优于假声明(红线 #9)。**实现方又一次自查出同形缺口**(单测说不了调用方怎么接线:with_book 传 `book_view=None` → **28 测试全绿**)并照实上报、补端到端断言读**两份实际写出的 JSON**。**另三个前提问题已裁定并立项**:(a) 3/11 因子的覆盖率披露无法由 store 供给(每次披露必重算,已写成行为并测试锁定);(b) `valley_price_quantile` 会被 materializer 二次滞后成 close_{d-2},评审实测第三条路**逐比特等价**(`max|diff|=0.000e+00`、`Series.equals=True`,破坏对照 7.3e-2 证非空过)⇒ 属实现变更可授权,落地须**两侧都钉**等价性测试 + 左端多一天历史;(c) **store key 无 universe 维(存量 CRITICAL,对 D6/D7)**——评审拆成**两个独立失效模式**:① `_ensure_coverage` 缺口判定**只按日期不按 (date,symbol)** ⇒ 第二批「无缺口」直接返回、那批票**静默从截面消失**,影响**全部 11 因子**(**讽刺**:A1 否决「按票分批」出路的理由一字不差就是这条,**红线写在文档里而服务层身上就带着它**);② `intraday_amp_cut` 的 z-score 在物化期发生 ⇒ 存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、24 行而非 48、存活 24 格全错** max|Δ| 0.159–0.186)。**「给 key 加 universe 维」被否**(过度失效 10 个 universe 无关因子;**PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来**;跨指数存储爆炸),改为 **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(`MinuteStreamBinding` 本就是 per_symbol+combine 两段,切在两段之间,正合红线 #7)⇒ 立为 **D4c,D5 C4 的前置**(设计文档修订 A2)。(d) 运维陷阱:真实评估 universe **996** 而冻结分钟面板 **995**(`300114.SZ` 无分钟 bar;**冻结面板自证**——三个日频书因子面板各 996 含它、十个分钟面板全 995 不含它),而 `saturation_probe` 默认 universe 从 995 面板派生。**编目 §七之二 已在任何 artifact 重生成之前登记漂移**(`eval_config` +3 键 / 顶层 +1 / Markdown +4 行全为新增 ⇒ C5 **逐字节对账已知不成立**、能成立的是值级 + 结构性断言;另登记 exec 侧 no_book 与 with_book 的 `eval_config` **首次合法地不同**、不得当回归;jump 的 loud raise 属有意阻断及解除方式)。gates:**2227 passed + 1 skipped**(2228 收集 = main 2175 +46 +7,**0 个既有测试被挤掉**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa` / 冻结面板 mtime 未变。**方法论新增两条**:① **「失配摧毁意义 → 拦;失配只带偏 → 记」**(见上);② **lead 与评审共用同一 worktree 时,测量与变异会打架**——本轮 lead 跑 gates 时 `git status` 一度显示 1 个改动(评审正在做 mutation),故合并前在评审收工后重跑了一遍干净 gates。 + - **D4c(PR #101,实施期修订 A2 插入的迁移步,晚于 D5b 合并——store 去 universe 依赖,D5 C4 的前置)**:修 D5b 评审立项的**两个独立失效模式**。① **`factors/service.py::_ensure_coverage` 缺口判定原只按日期不按 (date,symbol)**——第二批 symbol「无缺口」直接返回、**静默从截面消失**(影响全部 11 分钟因子)→ 改按 (date,symbol) 判定。② **store key 无 universe 维**:`intraday_amp_cut` 的截面 z-score 发生在物化期、store 之前,存储值是 universe 的函数而 key 不这么说(实测:12 票灌 → 24 票读同一 store → **provider 调用 0 次、返回 24 行而非 48、存活 24 格全错**)→ **store 只存 per-symbol 中间量、截面 combine 移到读出装配**(切在 `MinuteStreamBinding` 的 per_symbol/combine 两段之间,正合红线 #7)。**「给 store key 加 universe 维」明确否决、不重开**(三条理由:10 个 universe 无关因子过度失效;PIT universe 在一个日期区间上根本不是单一集合、该维度定义不出来;跨指数存储爆炸)。**调用方 symbol 列表单点归一化** `materialize.requested_universe` + 入口面 probe 守卫(其射程已写进守卫自己的 docstring:四种逃逸路径实测);**per-symbol floor 契约测试钉住**(`test_a_max_form_floor_breaks_the_premise`,docstring 写明引擎逐票硬化时它变红是预期信号、届时删除/反转)。**评审**:对抗性评审 APPROVE(HIGH-1/MEDIUM-1/NIT-1/floor 契约全 CLOSED);末 commit `131467e` 纯 docstring,经二轮评审 + 真实 mutation(引擎逐票硬化 → 该测试精确变红 9.322e-03→0.0,还原干净)。**gates(lead 独立重跑)**:pytest **2277 passed / 1 skipped** / ruff / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret 0 / `exec_baseline --verify` 77/77 @ `45c14aa`。**留 D5 C4 注记**(PR #105 部分处置:NaN 足迹误诊已修(filled_after_footprint 类)+ 中间量具名拒绝;完整接线仍留 21:00 故事):`tail_recompute` 对两列 payload loud raise 未接线 + D4c 的 NaN 足迹会被重叠校验误诊为 upstream revision(**高优先**);**派生 per-symbol floor 只能 per-symbol 或 min-over-symbols,绝不能 max 形**——实测 max 形偏差 9.3e-03~1.45e-02,会静默打破「中间量 universe 无关」的立论前提。 + - **jump_amount_corr 14:50 截断 correctness fix(PR #103,设计文档实施期修订 A3——单独成 PR、显式 correctness fix,非因子层重构一部分)**:修 D5b 发现并(两方独立)确认的真实前视缺陷。`jump_amount_corr_20` 是 11 个分钟因子中**唯一**不做 14:50 截断的(`decision_time`/`prepare_visible_minute_bars` 引用数 0,其余 4–10),旧 runner 用 `end+23:59:59` 取整日 bar 直接喂它;exec_to_exec 下日 d 的入场锚是当天 14:51 bar 的 VWAP,而值用到 d 的 14:50–15:00 bar ⇒ **信息集越过自己的入场锚**。扰动实测:扰动 cutoff 后 bar → 该因子 1,477/1,477 与 1,373/1,373 格全移动、其余 11 条 0 格;只扰动 `bar_end >= 14:51`(执行 bar 本身及其后)→ 1,363/1,363 移动。而其已发布 artifact 明写 `spec.decision_cutoff="14:50:00"`、verdict **Watch**、predictive 轴 PASS——**报告声明了它没有执行的检查**。**定性**:2026-07-19 owner 常设授权「全日数据因子一律截断到 14:50」建于此因子**之后** ⇒ 补截断是**补用既有授权**非新研究决策;但它是已发布因子的定义变更 ⇒ **单独成 PR、不许进重构任何 commit**。**内容**:截断 + 通用日内边界守卫(覆盖 11 分钟因子 + mmp_ew,目录遍历闭包防新增因子漏网)+ 结构化更正承载(JSON 顶层 `corrections` 恰好 1 处、`CORRECTION_FIELD_MAX_CHARS=2000` 超长 raise 不裁剪)+ 评估契约 v1.0→v1.1(`spec.version` 单独可判别 artifact 存档在更正哪一侧)+ 解除 exec 评估 loud 阻断(D5b 的跨 PR 互锁测试曾逐字给出该做的两个动作)。**评审 BLOCK 两条必修已修**:① dashboard 布局 `DEFINITION_MAX_LINES` 量错分支——`meta_y=0.20/0.10` ⇒ daily 容量 5 行 / intraday 4 行,而 dashboard 实际走 intraday 分支 → exec 形态 9/11 叠字;改 `DEFINITION_MAX_LINES_DAILY=5`/`_INTRADAY=4` + 布局守卫参数化扩到 exec 形态(mutation 证据:`_INTRADAY` 改回 5 → 11 测试红);② `test_the_correction_carrier_is_never_truncated_on_disk` 恒真(`MUST_NEVER_TRUNCATE` 集合与 `_leaves()` 叶子路径交集恒空)→ 改前缀匹配 `corrections.` + mutation(截断 `corrections.superseded` → 红且报对字段,旧逻辑在同语料上仍绿)。**重述结果(真跑出来的,非 pearson r=0.9993 推定)**:exec 与 close 两基准 verdict **Watch/Watch 不变**(exec IC −0.030840→−0.030539,N_eff 1162→1178);**不追认旧 artifact**——每个 emitted cell 都被污染过,聚合秩 IC 动得小是**稀释**(污染 bar 占 20 日池化窗 ~4.5% 且秩 IC 逐日封顶)不是无害。**评审自己撤回的两条(记录防失真)**:① `verdict.reasons` 无长度上限(走 `sanitize_text`;盘上最长未截断 note 1263 字符,「距上限 16 字符」是单样本推总体的假警报;真被截的是六个 payload 字段,真正临界的只有 `coverage_bias_bad_vwap` 158/200);② PNG 叠字「既有缺陷」与「本 PR 回归」两说皆对——renderer 一直如此(`figures.py` 此前只有 1 个 commit),jump 描述 323→702 字符曾是本 PR 引入的回归、又被结构化承载拖出(现 352 字符)。**预期下游信号(预登记非回归)**:`hand_anchors_d2.json` 将报 `all_ok_frozen14: False`(70 行 5 处失配全部且仅有 jump:手算已截断 vs 冻结 panels_d2 未截断);其余 9 因子 exec dashboard 叠字是 renderer 既有缺陷,C5 全量重跑时重渲染。**gates(lead 独立重跑)**:pytest **2389 passed / 5 skipped**(4 个 census 语料测试在 worktree 无语料 skip、已对真实语料验 4/4 PASS;1 个 gitignored 基线字节 skip)/ ruff clean / phase0 锚 **0.9600/0.8408** 不变 / **31/31 configs** / secret scan 0 / `exec_baseline --verify` 77/77 @ `45c14aa`;jump 4 份 artifact 经 git archive 导出重生成(cache-only,`stk_mins_live_calls=0`)——9 份 JSON/MD 逐字节不变、PNG 不变(jump 3 行描述在两个 cap 下都放得下,本就不叠字),exec dashboard 目视无叠字。 + - **D5 C4(PR #105,统一 exec-only FactorEvalRunner——决策③"杀第二条因子取数路径"的实质落地)**:四块。① `qt/factor_eval_providers.py`(CacheMinuteProvider 从 hotpath_smoke 提升为单一来源、DailyEvalPanelProvider 给 close-view 未滞后面板、build_eval_service 复用 pipeline 同调用序)+ `incremental.py` 足迹误诊修复(stored-NaN→fresh-finite 单立 `filled_after_footprint` 类,不再报 upstream REVISION)+ 中间量 payload 具名拒绝 `IntermediatePayloadNotWiredError`(deliberate deferral);② `qt/factor_eval_runner.py` + `qt/factor_eval_disclosures.py`(四份 coverage 披露 8/8 VERBATIM 搬入唯一家、旧 runner re-export、`book_mode` decision/close 双模式——decision 模式关闭 §1.1 close(d) 书活缺陷、add-Section 后 mandatory+verdict 逐位不变、新 stem `factor_eval_*` 与旧 `eval_*` 命名隔离、编目 BUG 3/5/6 闭环);③ `qt/factor_eval_reconcile.py`(panels/reports/anchors 三模式 + 入口 77/77 硬闸门,基线不可达=硬错误、jump 路由 pr_c_cutoff_fix 参照物);④ 判据补全(见下)。 + - **首轮真实对账失败→根因调查→判据补全**(本轮最重要的方法论事件,照实记):bounded 因子预期≈0 差异却出 4554 unclassified + 2 锚 FAILED → 独立调查定位主因是 **warmup 左延**(旧 runner 左端锚 `data.start` 不扩窗、冻结面板前 w−1 日欠 warmup;materializer 左延到真实缓存起点 2015-01-05),加两个浮点尾部(rolling 求和顺序 rel≤2.9e-12;rolling-σ 噪声×整数成交量阈值翻转 ±1 count)。**认知修正:handoff §3①"bounded vs D1 baseline 预期零差异"的预测错了——旧 runner 的"全窗口"左端同样是 anchor 截断;首轮不绿的源头是预测错、不是引擎 bug。** 新具名类(机器可判、类外仍非零退出):`warmup_left_extension`(bounded 前 w−1 日全方向 / pooled 早区窗+按月非递增)、`float_reordering_tail`(rel≤5e-12 且 ≤101 cell)、`threshold_flip_tail`(±1 count、≤25 cell)、MD 按 key 配对、JSON 登记项。编目新增 §七之三。 + - **真实对账 9/9 绿**(cache-only `stk_mins_live_calls=0`):minute_ideal_amp warmup=8,198 / jump warmup=17,289+float_tail=101 / volume_peak warmup=34,441(按月 19,977→14,464 单调)+threshold_flip=20,三因子 unclassified 全 0;jump 非 warmup 区 3 锚变绿(rel ~1e-15,#103 截断修正被新路径携带确认)。 + - 评审 APPROVE-WITH-NITS(0 CRIT/HIGH/MEDIUM;64 个自造 mutation probe 全过、verbatim 8/8 复核、panels 模式独立重跑逐格一致);登记 LOW-1(bounded warmup 类无量级/cell 数上限,~1.6% 网格,**C5 加 cell 数 ceiling**)与 LOW-2(reports 腿 digit OR 规则散文理由可变义,verdict 标签仍严格);NIT-1/2 装饰性。 + - gates(lead 独立重跑):pytest 2492 passed(node-ID 差分 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret scan 2 处 benign(新 config 的 external_secret_file 路径 + key 名,与 main 上 11 份 eval config 同形,无 token 值)、exec_baseline --verify 77/77 @ 45c14aa。 + - 刻意不做:删旧 runner(C6)、11 因子全量四腿(C5)、vpq 绑定 + binding 折 code_hash(PR-C4b,lead 已裁定批准)、mmp_ew(D6c)、run registry(D7)。 + - **D5 C4b(PR #107,vpq 绑定 + code_hash 折叠——11 分钟因子全部绑定完成)**: + - **vpq 绑定**:`valley_price_quantile` 的 residualize 是逐日截面 OLS(值依赖 universe)⇒ 按 D4c 架构存 per-symbol `raw_qbar` 中间量、截面 combine 移到读出装配;combine 需日频 close ⇒ 唯一引擎扩展 `DailyCombineInput`/`combine_daily` **声明式**槽位(红线 #5 禁 isinstance 守住;顺带删掉了旧的 `isinstance(factor, ValleyPriceQuantileFactor)` 分发);`combine_daily_panel` 用 decision-lagged close + 左端 22 个 lagged 交易日 warmup(欠暖钳位有一行披露)。 + - **平移面板第三条路**:因子内部 T-1 会被 decision 视图二次滞后成 close_{d−2} ⇒ 传平移面板 + 关内部 T-1,**逐比特等价双侧钉**(binding 侧 `Series.equals` + 破坏对照 ~8.5e-2;runner 侧 fixture 240/240 max|diff|=0.0 + 左端 22 日硬断言;评审用不同 fixture + 新扰动独立复验)。 + - **binding.py 折进 code_hash**(lead 裁定 Q2):承重语义文件此前在 key 射程外;mutation 实证改 binding → 18/18 factor key 全变、改无关模块 → 0 变;dev-only `artifacts/factor_store/` 一次性失效已披露。 + - runner 启用 vpq + NeutralizationCoverage 机制 B(经新 `service.stored_payload` 回读 raw 中间量,同一 key/fingerprint/coverage 引擎、非第二取数路径)+ **anchors 模式修截面因子全 universe 服务**(5 锚 symbol < min_cross_section=10 必全 NaN——C5 对 intraday_amp_cut 本也会踩)。 + - **panels 类边界三裁定**(首个 FAIL 100% 分解后 lead 裁定):① pooled 月单调豁免锚到**结构性部分月**(冻结面板首个 finite 值所在月;初版位置性实现被评审 probe 绕过 → 修复 + 回退 mutation 证据);② 新具名类 `threshold_flip_contamination`(窗口 [2023-06-01,2023-07-14]、600623.SH ≤2e-04、其余 ≤1e-06、≤20,000 cell——PRV 临界 bar 翻转×逐日 OLS 污染族,输入侧变化已排除:ledger 零写入 + qbar 双几何逐位一致 1.1e-16,评审独立复算 1,241/1,241 格);③ float 尾加 abs 下限 1e-12 + cap 分档(bars-only 101 / 截面 1,000)。 + - **真实对账(cache-only `stk_mins_live_calls=0`,评审独立重跑逐数一致)**:panels rc=0(warmup=24,568 按月 902→19,980→3,667→19 豁免后单调 / float_tail=883 / flip_contamination=19,177 / unclassified=0)、reports 4/4 OK、anchors 5/5;vpq verdict Watch/Watch 与冻结基线同(数值微移全在登记 warmup 聚合效应内)。 + - 评审两轮:APPROVE-WITH-NITS(LOW-1 位置性豁免可绕过 + NIT-1 静默钳位)→ 修复 → **delta 复核双双 CLOSED 可合并**(评审回退修复自证测试有牙)。 + - gates(lead 独立重跑):pytest 2533 passed(node-ID 0 挤掉)、ruff clean、phase0 锚 0.9600/0.8408、32/32 configs、secret 0、exec_baseline --verify 77/77 @ 45c14aa。 + - **D5a(PR #95,D5 的前三块——冻结/编目/探测;探测结论迫使插入新迁移步 D4b)**:**C1 冻结 exec 对账基线**(`qt/exec_baseline_freeze.py`)——22 份 exec artifact 是后续一切对账的参照,却 **gitignored 只存在于本机**,且设计假定的耐久副本 `scratchpad/baseline_reports/` **早已不存在**;新 runner 沿用同名输出路径,**跑一次就毁掉唯一基线**,随后的「对账」会比对刚被自己覆盖的文件(= 本仓在案的 `compare_postmerge.py` 空对账形态)。实冻 **77 个文件**(不是交办的 44——同族还有 11 份 `_exec_basis_sanity.md` + 22 张 dashboard,同样单份不可再生),**sha256 manifest 入 git**(字节留 gitignored,哈希只能经评审 diff 变动),每次读都验哈希,读取器结构性拒绝 live 路径(直连/symlink 别名/`..` 三种绕过全拒)。**provenance 按证据强度分级**:观察到的是 77 文件共享毫秒级 mtime(批量拷贝签名),对照 44 份 close 基准的 11 个递进 mtime 与 #74 重跑窗口吻合;「字节出自 #79 run」标注为**推断非观察**,字段名故意叫 `frozen_at_git_head` 而非 `producing_sha`。**C2 编目 11 runner 差异**(§六.5「未编目的差异算失败」)——用 **AST 级比对**而非抽读证明完整性(`_build_book_factors`/`_write_report`/`_section_payload` 11 份逐字符相同;其余 helper 剥掉字符串常量后坍缩成 1 组),另记 **7 个既有 bug**(只标不修)与 `FactorSpec` 16→20 键漂移(来自 #86/#91,须预登记否则对账时会被当 D5 回归去追)。**探测(`qt/saturation_probe.py`,可复跑工具非一次性脚本):D4 的 materializer 在真实评估面上跑不了**——995 票中 **84 票**首根 1min bar ≥ emit_start ⇒ pooled 饱和**永远无法提前终止、必打到 floor**(D4 已披露的代价在全规模是**构造性**兑现);磁盘 8.18/16.43 GB,名义内存 52.7/122.2 GB,而 **frame:峰值RSS 实测 2.2–2.45×**(`minute_bars` 逐票读完 `pd.concat`,parts 与结果同时在内存)⇒ 评估窗真实峰值 **≈115 GB vs 可用 56 GB**;**不止 pooled**——bounded 路径同样整 universe 单帧(`materialize.py:262`),光评估窗就超可用内存约一倍。**三条更便宜的出路全部证否**:按票分批会因 `service._ensure_coverage` 的缺口判定**只按日期不按 (date,symbol)** 而**静默丢名**(I5a 红线);按时间分块只救 3 个 bounded 因子;缩短窗口不动 pooled 加载左端(锚在 floor)。**关门的是 `intraday_amp_cut`**(**同时 pooled × 截面**)——按票分批毁其截面、按时间分块管不住其深度,两轴都绕不开。切口位置已实测:纯因子逐票 ≡ 整 universe **逐位相同**(728/1524 个可比 cell `max|diff|=0.0`),`intraday_amp_cut` 逐票 **1860 finite → 0** ⇒ **切口必须落在截面 combine 之前**。**暴露的设计层缺口(照实记)**:§八只给**决策热路径**(20 日尾窗)定过规模预算,**从未给评估路径定规模**,所以「整 universe 单帧」在设计评审里没人拦;而旧 runner docstring 白纸黑字写着 "the multi-year all-symbol minute panel is NEVER materialized"——**D4 的加载几何相对被替换对象是回归**,只是 D4 验收跑在 40 票 smoke 上看不见。lead 提的「复用 `panel_freeze.py` 的加载形态代替 D4b」**已证否**:它没有自己的加载形态,是 import 11 个 runner 调其私有 `_load_*_panel`——「复用」等于保留决策③要杀的第二条取数路径;可复用的是**形状**不是调用路径。⇒ **插入迁移步 D4b(per-symbol 流式物化)**,设计文档记为实施期修订 A1;C3–C6 刻意未开始(修 materializer 是改取值路径,须自带四腿验收,混进 D5 当「让 run 跑起来」正是本项目反复抓到的形态)。**评审两轮**:首轮 BLOCK 1 HIGH——被文档称作「幂等」的再冻结命令**静默销毁 provenance**(未改动树上跑,输出 `copied: 0 / already ok: 77` 报纯成功,git 里却清空 `source_note` 并把 `frozen_at_git_head` 改指向重跑者 = **主动的假声明**,而 **16 个测试全程绿灯**)→ 改为 provenance 继承 + 内容不变则一个字节不写 + **恒跑断言**(`source_note` 非空 / head ^[0-9a-f]{40}$ / sha256 合法 hex(原只验长度)/ `size_bytes`>0),评审逐条 mutation 四条全红;另修 2 MEDIUM(**本 PR 自己 suite 里的空 mutation**——那条 git-tracking 测试全程不碰 git,注释掉 `.gitignore` 仍通过 → 改查 **git 索引** `git ls-files -- `;**C6 爆炸半径漏编目 3 个 runner importer**,含**生产 D1 冻结基线的 `panel_freeze.py` 本身**,其重跑流程是「旧 SHA + 当前分支文件」的混合式、两种 C6 处置下都会失效)。**方法论(三个空 mutation,全部照实记)**:实现方两次(tamper 测试替换了文件里**不存在**的字节串;git 索引 mutation 用 `git add -f` 被 symlink 拒绝执行 → 什么也没改、测试**为错误的理由通过**),评审一次(它建议的 `git ls-files --error-unmatch` 对「未跟踪」与「路径不存在」**返回相同 rc=1**,据此的断言会在**最需要它的机器上空过**)——**三次都不是被测试抓住的,而是被「与预期矛盾的观察」抓住的;评审建议本身也需要 mutation 证据**。**顺带更正一条长期误诊**:「本机 pytest 摘要行被环境吞掉」是假的——`pyproject.toml:53` 已有 `addopts="-q"`,我们再传 `-q` 就是 `-qq`;去掉即出现 `N passed` 摘要行(点计数仍是有用的交叉校验,本轮就抓到 2113 vs 2114 之差,但它从来不是因为摘要不可得才必须存在)。gates:**2117 collected**(2116 passed + 1 skipped——跳的是字节绑定测试,其字节 gitignored;manifest 自洽测试**无 skipif 恒跑**)/ ruff / 31 configs / phase0 锚不变 / secret 0 / `--verify` 77/77 且 head 仍 `45c14aa` / 冻结面板 mtime 未变。 diff --git a/docs/progress/08_gates_disclosures_roadmap.md b/docs/progress/08_gates_disclosures_roadmap.md new file mode 100644 index 0000000..db1c79e --- /dev/null +++ b/docs/progress/08_gates_disclosures_roadmap.md @@ -0,0 +1,12 @@ +# 历史质量门快照 / 完整披露清单 / 路线图原文 + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +这三条随每次进度更新而重写;此处保留归档时点(`main` @ `c9cc12e`)的完整原文,**当前有效的精简版在仓库根 `CLAUDE.md`**。 + +--- + +- ✅ 质量门(**当前 `main` @ `f432dc5` 实测**:`python -m pytest -p no:warnings` **1776 passed**、`ruff` clean、`validate-config` **31/31 配置逐个 OK**、`run-phase0`(demo)**ic 0.9600 / annual 0.8408 不变**)。构成对得上:`aa19968` 实测 **1740** → #78 加 2(含钉住破折号闭合的那条)= **1742** → #79 加 34(两个新测试文件,0 个既有测试被挤掉)= **1776**。新增测试分组(`--collect-only` 实数,`aa19968` 口径):**因子评估契约层 contract=337 + standard=113 + figures=7 / 十一因子(factor + eval runner,21 文件)=421 / PR#75 exec-vwap-basis=46 / D3 market quality 15→21(含 #77 `decreasing_adj_factor` 及其相对-vs-绝对尺度 mutation 测试)**。历史分组(旧 793 基线):`pytest` **793 passed**(P0=97 / P1=78 / P2-1=22 / P2-2=22 / P2-3=14 / P2-4=8 / P3-1=10 / P3-2=18 / P3-3=16 / P3-4=15 / P3-5=22 / P3-6=27 / P3-7=25+1 throttle / P3-8=8 / P4-1=28 / P4-2=17 / **intraday I1 schema=14 + feed=9 / I2 cache=10 / I3 aggregate=13 / I4 execution=10 / P4-3 cache=10 + updater=7 / P-I5a event-backtest=19 / P-I5b exec-feasibility=16 / P-I5c mmp-minute=18 / P-I5d mmp-quintile=19 / P-I5e 仅 config(无新测试,`test_cache_config` 的 config glob 自动 +1)/ P-I5f intraday-liquidity=17(+ `test_cache_config` config glob 自动 +1)/ D1 token-dedup index-feed +2 / D2 cache-modularization +5 / D3 data-quality market=15 + intraday=12 + report=14 / D3b data-update-quality=18 / D4 coverage-ledger-scaling=13 / D5 scheduler=9 + concurrency=12 / schema-registry-guard=62 / all-A-incremental=12 / all-A-backfill=22**);当时 `ruff` clean、`validate-config` 20 配置(data_update + phase_i5a…phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 自 P0 起从未变过**)。 +- ⚠️ 剩余(已显式披露):日线 only、demo 路径非真数据、旧三因子无信号(P3-3/P3-4 实证;但其组合在 2024-2026 holdout 上 SSE50/CSI300/CSI500 全正、CSI500 高达 +17.8%——小样本/regime 翻转的持续例证);value/低波信号获得**独立样本符号级确认**(P3-7 SSE50/CSI300 量级衰减;P3-8 CSI500 泛化成立且更强),组合级盈利能力仍未确立(排名跨 cell 翻转);subset 报告文件名已可配置(P3-8 起不再互覆盖)。**分钟因子侧新增披露**:十一个复现因子全部 EXPLORATORY、**封顶 Watch,无一到 Adopt**,1× 费率下净多空为正的只有 2 个(PR-F/PR-L,且 PR-L 扛不住 2×)——**IC 强 ≠ 可交易**;MMP 的唯一正面证据(I5d)在修正引擎上退化、I5e 负结果加强;I5f 的容量结论已按**真实 100 万名义**重写(1000 万口径的"约半数不足"作废);**日频除权审计已完成**(GENUINE、无 vintage 接缝、评估池暴露 1/603,258,不重述任何因子结果),**分钟路径的复权缺陷已在 PR #75 修**;exec-to-exec 改基准已随 **PR #79 合并**,close-to-close 与 exec 两套 artifact 并存;⚠️ **#79 的 code review 未返回、靠自查合并**,其"未独立核"清单(五条 mutation 声称 / 十一个 runner 改动是否机械等同 / `execution_capacity` 透传)仍待后续检查——若查出问题,重述 exec 基准数字而非 close-to-close;schema 守卫**仍未接** live `data-update` 暖跑路径;HTML compendium(`artifacts/reports/factor_compendium.html`)**还读 v0.8 结果,待按最终结果修订**。 +- 路线图下一步:**MMP 暂缓推进——理由在修正引擎上比过去更强,不是不变**(I5e 独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转** 且**在修正引擎上更负**:Spearman **−0.5000 不变**、Q5 高 MMP 仍最差、合成 Q5−Q1 累计 **−26.77% → −31.82%**;而 I5d 的 CSI500 完美单调性在修正引擎上**退化**(Spearman **+1.0000 → +0.9000**、Q5−Q1 **+17.15% → +8.65%**、Q5 让位 Q4),四档归因实测**归到复权修正本身**——原结论实质是"把除权下跌记成亏损"的产物 → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断;**按用户真实规模 100 万 RMB / 5% 参与率重测:below-1.0x 由 20 笔变 0 笔、最紧 1.22×——单分钟容量在该 universe/窗口不构成约束**;此前"10M RMB 下约半数不足"的说法**已作废**,它描述的组合比本项目会跑的大一个数量级;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**盘后全A auto-fetch**(数据正确性审计 PASS——D3 质量层实测 80GB 真实缓存结构干净、ann_date PIT 轴 100%、跨 shard schema 无漂移;PR #59 = PR-1 全A 增量 auto-warm + systemd @21:00 default-off 已落地;**PR #61 = PR-2 全A 历史回填**——分块/断点续跑/分钟全窗口 + per-symbol 失败隔离 + `start>today` 守卫,已落地。**建设侧完成,剩 live rollout = 用户 Stage-1**:先 `data-backfill` 补全A 历史(可续跑)→ 手动跑增量 warm 观察 → enable systemd timer);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / data-update summary 落 cache-stats artifact);**数据层 D6**(D1+D2+D3+D3b+D4+D5 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分 + report-only `data/quality/` 质量层 + default-off `data-update` 质量报告钩子 + coverage ledger `record_many` 批量写 + 进程内查找缓存 + opt-in 有界并发 + 全局限频器 + **schema 注册表/default-off drift 守卫(PR #57)**;剩余数据层 follow-up:schema 守卫接 live `data-update` 暖跑路径(当前仅接 pipeline/回测) + 分钟 schema 守卫;**D6**=`PanelStore` append/partition / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);**因子层收尾**(① `feat/exec-to-exec-rebasis` 两基准对照已跑完并随 **PR #79 合并**——22 格中 20 格不变、2 格朝更保守走;**补一次 #79 的 code review**(原评审未返回,未独立核的三项见上);② HTML compendium 按最终结果修订;③ 十一因子**无一到 Adopt、净多空几乎全负** → 下一步优先做**换手/成本敏感**的组合层验证与更长 holdout,而不是继续堆新因子);研究侧:更长 holdout 滚动复检 / 成本模型细化。 diff --git a/docs/progress/README.md b/docs/progress/README.md new file mode 100644 index 0000000..888f4a2 --- /dev/null +++ b/docs/progress/README.md @@ -0,0 +1,36 @@ +# 进度档案索引 + +仓库根 `CLAUDE.md`(= `AGENTS.md`)曾内联全部阶段日志(~78k tokens,每个会话都被完整加载)。 +2026-07-30 把历史搬到本目录,**逐字搬移、一字未改**;`CLAUDE.md` 只留每个会话都需要的东西 +(定位 / 架构 / 不变量 / 环境 / secret 纪律 / 开发约定 / 当前状态 / 方法论 / 本索引)。 + +## 怎么用 + +- **不要整读。** 这些文件按阶段切分,用 `grep` 定位:`grep -rn "关键词" docs/progress/`。 +- 找某个 PR 干了什么:先查 [`pr_ledger.md`](pr_ledger.md)(PR 号 → 一句话内容),再到对应档案里 grep。 +- 每条记录都保留了**真实 run 的数字、被推翻的假设、评审揪出的缺陷、以及自己犯过的错**—— + 这些负面记录是刻意留下的,**不要因为"看起来不好看"就删**。 +- 惯例:**文档是地图,`git log` / `gh pr list` 是地形**;冲突以后者为准。 + +## 档案 + +| 文件 | 覆盖范围 | PR | +|---|---|---| +| [`pr_ledger.md`](pr_ledger.md) | 全部 PR 编号 → 内容映射(含 **#38 保持 OPEN** 的说明) | #1–#107 | +| [`01_phase0_3_daily_pipeline.md`](01_phase0_3_daily_pipeline.md) | 7 层骨架 / Phase 0 MVP / Phase 1 偏差边界(复权·PIT 成分·可交易·`ann_date`·中性化)/ Phase 2 真实基准与执行真实性与 PIT 行业与标准分析 / Phase 3 多因子 baseline·walk-forward IC 加权·OOS·robustness matrix·候选因子组·子集复检与成本敏感性·独立样本验证·CSI500 泛化 | #1–#21 | +| [`02_phase4_cache_and_intraday_i1_i4.md`](02_phase4_cache_and_intraday_i1_i4.md) | 端点级持久化 raw 缓存(行情 / universe+tradability / 因子支撑端点)+ `data-update` 增量暖跑;分钟 pipeline I1–I4(1min feed·月分区缓存·PIT 日频聚合·尾盘执行骨架) | #23–#31 | +| [`03_intraday_i5.md`](03_intraday_i5.md) | I5a 事件驱动回测重构 / I5b 执行期涨跌停 / I5c MMP 分钟因子 / I5d 五分位 / I5e CSI300 泛化(负结果)/ I5f 流动性诊断 | #33–#55 | +| [`04_data_layer.md`](04_data_layer.md) | D1 契约文档 + token 去重 / D2 cache 拆分 / D3+D3b report-only 质量层 / D4 ledger 批量写 / D5 并发+全局限频器 / schema 注册表与 drift 守卫 / 全A 增量 auto-warm 与历史回填 | #41–#61 | +| [`05_minute_factors_eval_contract.md`](05_minute_factors_eval_contract.md) | `analytics/eval/` 三轴判决契约(v0.1→v0.9,含两处判定门缺陷修复)+ 十一个分钟因子复现总表与三条循环级结论 | #63–#74 | +| [`06_execution_basis_and_corrections.md`](06_execution_basis_and_corrections.md) | 尾盘成交价改 bar VWAP / 分钟持有期收益复权 / 报告披露修正 / `adj_factor` 下降检查 / 日频除权正确性审计(GENUINE)/ I5d·I5e·I5f 在修正引擎上重跑 / 十一因子改 14:51 VWAP exec-to-exec 基准 / 闸门描述单一来源 | #75–#82 | +| [`07_factor_layer_refactor.md`](07_factor_layer_refactor.md) | 因子层深度重构 D0 纸面契约 → D1 registry+面板冻结 → D2 primitives → D3 store → D4/D4b/D4c → D5a/D5b/C4/C4b,含每一步的评审发现与实施期修订 A1/A2/A3 | #84–#107 | +| [`08_gates_disclosures_roadmap.md`](08_gates_disclosures_roadmap.md) | 归档时点(`main` @ `c9cc12e`)的质量门快照、完整披露清单、路线图原文 | — | + +## 未在 git 里的权威文档(只在本机) + +`.gitignore` 排除了 `tmp/`(仅保留 `tmp/framework/architecture.html`),所以下面这些**不在版本控制里**: + +- `tmp/design/HANDOFF_2026-07-30_claude_code.md` —— 当前唯一有效的执行交接(重构 D5 C5) +- `tmp/context/cc_handoff_20260730_d5_c5/HANDOFF.md` —— F1–F5 的逐格实测证据 +- `tmp/design/factor_refactor_design_v3.md` —— 重构设计权威(红线 #1–#11 / 四腿对账 / D0–D7) +- `tmp/design/RESULTS_post_pr75_2026-07-21.md`、`tmp/design/FINDING_adj_factor_seam_2026-07-21.md` —— 修正引擎重跑与除权审计的原始记录 diff --git a/docs/progress/pr_ledger.md b/docs/progress/pr_ledger.md new file mode 100644 index 0000000..e155bde --- /dev/null +++ b/docs/progress/pr_ledger.md @@ -0,0 +1,11 @@ +# PR 台账(PR #1 – #107) + +> 本文件是 `CLAUDE.md` 进度日志的**归档**,内容逐字搬移,未做任何改写。 +> 索引见 [`docs/progress/README.md`](README.md);当前状态与操作约定仍在仓库根 `CLAUDE.md`。 + +下面一段是 `CLAUDE.md` 开发约定里 **Git** 那一条的原文,逐字搬移。 +**OPEN 未合并:#38**(见 P-I5d 处置,未获授权关闭,永远保持 OPEN)。 + +--- + +- **Git**:feature 分支 + PR。**PR #1(P0+P1)、#2(P2-1)、#3(P2-2)、#4(进度文档)、#5(P2-3)、#6(进度文档)、#7(P2-4)、#8(进度文档)、#9(P3-1)、#10(进度文档)、#11(P3-2)、#12(P3-3)、#13(进度文档)、#14(P3-4)、#15(进度文档)、#16(P3-5)、#17(进度文档)、#18(P3-6)、#19(P3-7)、#20(进度文档)、#21(P3-8)、#22(进度文档)、#23(P4-1)、#24(进度文档)、#25(P4-2)、#28(tushare 权限/限频探测 + capability registry)、#29(I1–I4 分钟级 intraday pipeline,4 commit 一 PR)、#30(进度文档)、#31(P4-3 因子支撑端点缓存 + 21:00 data updater,2 commit 一 PR)、#33(P-I5a 事件驱动回测架构重构 + opt-in 分钟尾盘 event model,4 commit 一 PR)、#35(P-I5b 分钟尾盘执行期 raw stk_limit 涨跌停可行性,4 commit 一 PR)、#37(P-I5c MMP 分钟因子端到端 opt-in alpha)、#39(P-I5d MMP 五分位分组回测 standalone,含 I5c plumbing)、#41(数据层 D1 契约文档 + token 解析去重)、#43(数据层 D2 TushareCache specs/parsers 拆分)、#45(数据层 D3 report-only 数据质量层)、#47(数据层 D3b default-off `data-update` 质量报告钩子)、#49(数据层 D4 coverage ledger 批量写 + 进程内查找缓存)、#51(数据层 D5 opt-in 有界并发 + 全局限频器)、#53(P-I5e MMP 五分位 CSI300 独立泛化检验,负结果)、#55(P-I5f 分钟尾盘执行流动性/容量诊断,report-only)、#57(数据层 schema 注册表 + default-off drift 守卫)、#59(数据层 全A 增量 auto-warm + systemd 定时,default-off)、#61(数据层 全A 历史回填 `data-backfill`,分块/续跑/失败隔离)、#62(进度文档)、#63(因子评估契约层 `analytics/eval/` + StandardFactorEvaluator + dashboard + PR-C 因子,4 commit 一 PR)、#64–#73(PR-D…PR-M 十个分钟因子复现,**一因子一分支一 PR**)、#74(因子评估契约 v0.8 + v0.9 判定门修复)、#75(尾盘执行价 VWAP + 分钟持有期收益复权)、#76(报告披露修正 + I5f 名义按真实规模重测)、#77(`adj_factor` 下降质量检查)、#78(#76 的 follow-up:扫描式正则守卫被七种改写实测证否 → 闸门描述改单一来源)、#79(十一因子改 14:51 VWAP exec-to-exec 基准,**评审未返回、靠自查合并**)、#82(闸门/成交价描述全仓清扫,12 处;#78 守卫射程不足的修正)、#80(进度文档)、#84(因子层重构 D0 纸面契约:availability policy 常量 + 预指派表 + 性质编目)、#85(进度文档)、#86(因子层重构 D1:registry + PanelField requires + FactorSpec 契约 v1.0)、#87(因子层重构 D1 面板冻结:14 因子 raw 基线 + manifest)、#88(进度文档)、#89(因子层重构 D2:minute primitives + ops + 11 因子迁移,与冻结基线逐位一致)、#90(进度文档)、#91(因子层重构 D3:store 层——稀疏值存储 + key/指纹 + tail-recompute + R5 fina 视界统一)、#92(进度文档)、#93(因子层重构 D4:FactorService + decision-view materializer + pooled 饱和填充)、#94(进度文档)、#95(因子层重构 D5a:exec 基线冻结 + 11 runner 差异编目 + 可行性探测)、#96(进度文档)、#97(因子层重构 D4b:per-symbol 流式物化)、#98(进度文档)、#99(因子层重构 D5b:评估契约 v1.0 + 诊断通道 + 14:50 可见性守卫;**C4 中止上报**)、#101(因子层重构 D4c:store 去 universe 依赖——(date,symbol) 缺口判定 + store 只存 per-symbol 中间量、截面 combine 移到读出装配 + 调用方 symbol 列表单点归一化)、#103(jump_amount_corr_20 补 14:50 截断 correctness fix——唯一未做截断的分钟因子,exec_to_exec 下信息集越过自己入场锚的真实前视;含 dashboard 布局 5/4 分支修复 + census 守卫前缀匹配修复;单独成 PR 非重构一部分)、#105(因子层重构 D5 C4:统一 exec-only FactorEvalRunner——因子取数全走 factors.service、异构披露唯一家、C5 对账 harness 三模式 + 77/77 硬闸门、warmup 左延具名类登记)、#107(因子层重构 D5 C4b:valley_price_quantile 绑定——combine_daily 声明式引擎扩展 + 平移面板第三条路逐比特等价双侧钉 + binding.py 折进 code_hash + 截面因子 panels 类边界三裁定)均已 merge 到 `main`**;**OPEN 未合并:#38**(见 P-I5d 处置,未获授权关闭)。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。