From 40c0ea63b012f11444e035030c69dab1dbc7be04 Mon Sep 17 00:00:00 2001 From: shaofl <2899218482@qq.com> Date: Thu, 30 Jul 2026 05:56:05 -0700 Subject: [PATCH] docs: split the progress log out of CLAUDE.md into docs/progress/ CLAUDE.md (= AGENTS.md) is auto-loaded in full at the start of every session. It had grown to 398 lines / 170 KB (~78k tokens, 9% of a 1M context) because every completed phase appended its full record to it. Almost all of that is history: valuable, but needed on demand, not on every turn. Move lines 66-397 verbatim into docs/progress/, split by phase, and keep in CLAUDE.md only what every session needs: positioning, the 7-layer architecture, the numbered design invariants (referenced by name from source docstrings), the correctness red lines, environments, the secret discipline, dev conventions, the current status snapshot, the standing authorizations, the working protocol, the methodology rules, an index of the archive, and the disclosures/roadmap that are currently in force. Nothing is rewritten or summarised away. A reconstruction script verified that concatenating the archive bodies reproduces the original 332 lines exactly, line for line, and the PR ledger line likewise. Lines 1-59 and 61-64 of CLAUDE.md are byte-identical to before; only the Git bullet was replaced by a shorter one pointing at docs/progress/pr_ledger.md. CLAUDE.md: 398 -> 151 lines, 170316 -> 14469 bytes. To keep it from growing back, the archive section states the rule explicitly: new phase records go to docs/progress/, and CLAUDE.md maintains only three moving parts (status table, disclosures, roadmap). Gates: pytest 2537 passed, ruff clean, CLAUDE.md == AGENTS.md. --- AGENTS.md | 410 ++++-------------- CLAUDE.md | 410 ++++-------------- docs/progress/01_phase0_3_daily_pipeline.md | 100 +++++ .../02_phase4_cache_and_intraday_i1_i4.md | 46 ++ docs/progress/03_intraday_i5.md | 41 ++ docs/progress/04_data_layer.md | 53 +++ .../05_minute_factors_eval_contract.md | 37 ++ .../06_execution_basis_and_corrections.md | 76 ++++ docs/progress/07_factor_layer_refactor.md | 39 ++ docs/progress/08_gates_disclosures_roadmap.md | 12 + docs/progress/README.md | 36 ++ docs/progress/pr_ledger.md | 11 + 12 files changed, 615 insertions(+), 656 deletions(-) create mode 100644 docs/progress/01_phase0_3_daily_pipeline.md create mode 100644 docs/progress/02_phase4_cache_and_intraday_i1_i4.md create mode 100644 docs/progress/03_intraday_i5.md create mode 100644 docs/progress/04_data_layer.md create mode 100644 docs/progress/05_minute_factors_eval_contract.md create mode 100644 docs/progress/06_execution_basis_and_corrections.md create mode 100644 docs/progress/07_factor_layer_refactor.md create mode 100644 docs/progress/08_gates_disclosures_roadmap.md create mode 100644 docs/progress/README.md create mode 100644 docs/progress/pr_ledger.md 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)。