diff --git a/AGENTS.md b/AGENTS.md index 3359348..cc9d7b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,7 +57,7 @@ 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)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 +- **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 守卫)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -251,6 +251,12 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - **缓存并发**(`TushareCache(max_workers=)`):`==1` 串行字节级不变;`>1` 且多 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 验收)。 -- ✅ 质量门:`pytest` **696 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**);`ruff` clean;`validate-config`(全部 19 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d + phase_i5e + phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 +- ✅ **数据层 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 检测处。 +- ✅ 质量门:`pytest` **758 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**);`ruff` clean;`validate-config`(全部 19 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d + phase_i5e + phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 - ⚠️ 剩余(已显式披露):日线 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 起不再互覆盖)。 -- 路线图下一步:**MMP 暂缓推进(I5e 负结果)**(I5e 已在独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转**(Spearman −0.5,Q5 高 MMP 最差,合成 Q5−Q1 累计 −26.77%);I5d 的 CSI500 单调性疑 universe/regime 特异、**非稳健** → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断,实测 10M RMB/5% 参与率下约半数 SSE50 单分钟 desired trade 容量不足;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / 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 有界并发 + 全局限频器;剩余数据层 follow-up:**endpoint schema registry** 仍延后;**D6**=`PanelStore` append/partition / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +- 路线图下一步:**MMP 暂缓推进(I5e 负结果)**(I5e 已在独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转**(Spearman −0.5,Q5 高 MMP 最差,合成 Q5−Q1 累计 −26.77%);I5d 的 CSI500 单调性疑 universe/regime 特异、**非稳健** → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断,实测 10M RMB/5% 参与率下约半数 SSE50 单分钟 desired trade 容量不足;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / 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 / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 diff --git a/CLAUDE.md b/CLAUDE.md index 3359348..cc9d7b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ 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)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 +- **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 守卫)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -251,6 +251,12 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - **缓存并发**(`TushareCache(max_workers=)`):`==1` 串行字节级不变;`>1` 且多 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 验收)。 -- ✅ 质量门:`pytest` **696 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**);`ruff` clean;`validate-config`(全部 19 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d + phase_i5e + phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 +- ✅ **数据层 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 检测处。 +- ✅ 质量门:`pytest` **758 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**);`ruff` clean;`validate-config`(全部 19 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d + phase_i5e + phase_i5f)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 - ⚠️ 剩余(已显式披露):日线 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 起不再互覆盖)。 -- 路线图下一步:**MMP 暂缓推进(I5e 负结果)**(I5e 已在独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转**(Spearman −0.5,Q5 高 MMP 最差,合成 Q5−Q1 累计 −26.77%);I5d 的 CSI500 单调性疑 universe/regime 特异、**非稳健** → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断,实测 10M RMB/5% 参与率下约半数 SSE50 单分钟 desired trade 容量不足;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / 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 有界并发 + 全局限频器;剩余数据层 follow-up:**endpoint schema registry** 仍延后;**D6**=`PanelStore` append/partition / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +- 路线图下一步:**MMP 暂缓推进(I5e 负结果)**(I5e 已在独立 universe CSI300 同窗口机器冻结复跑 → 单调性 **FAILS/反转**(Spearman −0.5,Q5 高 MMP 最差,合成 Q5−Q1 累计 −26.77%);I5d 的 CSI500 单调性疑 universe/regime 特异、**非稳健** → **在出现 disjoint 窗口 / 第三 universe 的正向证据前,不把 MMP 当稳健或可交易信号推进**;新 intraday 因子候选只作显式 EXPLORATORY 工作);**I5f 已落地 → I5g(可选,须单独显式 goal)**(I5f 已加 report-only 执行流动性/容量诊断,实测 10M RMB/5% 参与率下约半数 SSE50 单分钟 desired trade 容量不足;**强制 partial-fill / volume-cap(真正约束成交)= 可能的 I5g,仅在单独显式 goal 后启动**,本阶段未做,不声称已实现);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / 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 / 可选物化派生面板存储,**仅当因子研究需要可复用派生面板时才启动**——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 diff --git a/docs/data/data_layer_contracts.md b/docs/data/data_layer_contracts.md index 8c135ee..9deed7a 100644 --- a/docs/data/data_layer_contracts.md +++ b/docs/data/data_layer_contracts.md @@ -3,7 +3,7 @@ > 目的:把 **`cache`(缓存)** 与 **`store`(面板存储)** 的边界写成可提交的契约, > 让后续改动有明确不变量可守。本文件只描述 **当前已实现** 的行为,不预告未实现的阶段。 -已实现:**D1**(边界文档化 + 低风险 token 解析去重)、**D2**(`TushareCache` endpoint specs/parsers 拆分,公开缓存行为不变)、**D3**(report-only `data/quality/` 数据质量层)、**D3b**(默认关闭的 `data-update` 质量报告钩子,report-only)、**D4**(coverage ledger `record_many` 批量写 + 进程内查找缓存,公开路径/列/语义不变)、**D5**(opt-in 有界并发的 updater/缓存暖跑取数 + 单一全局限频器,默认串行)。**D6 尚未实现**,本文件不声称其存在。 +已实现:**D1**(边界文档化 + 低风险 token 解析去重)、**D2**(`TushareCache` endpoint specs/parsers 拆分,公开缓存行为不变)、**D3**(report-only `data/quality/` 数据质量层)、**D3b**(默认关闭的 `data-update` 质量报告钩子,report-only)、**D4**(coverage ledger `record_many` 批量写 + 进程内查找缓存,公开路径/列/语义不变)、**D5**(opt-in 有界并发的 updater/缓存暖跑取数 + 单一全局限频器,默认串行)、**schema 注册表 + drift 守卫**(默认关闭、opt-in 的 endpoint schema 校验,report-only / strict;PR #57)。**D6 尚未实现**,本文件不声称其存在。 --- @@ -87,7 +87,9 @@ durable 再抛首个失败,失败 gap 不记覆盖、可重试);**snapshot、`in (并发开启时它们仍共享同一限频器)。`cache` 仍是 raw endpoint SoT、`PanelStore` 仍是 per-run 面板 artifact,缓存/ store 边界不变;factor/alpha/portfolio/runtime/backtest 数学不变。 +**schema 注册表 + drift 守卫**(默认关闭、opt-in,行为保持)新增 `data/cache/schema_registry.py`:每端点声明式 `EndpointSchema`(required / optional / known_extra 源列,从 `tushare_specs.py` 派生=单一真相源)+ 有状态 `SchemaGuard`。**核心**:parser 硬编码 canonical 列集 + 防御性 `fillna`,故存储侧 `fields_hash` 对 tushare 改列免疫——真风险在 raw 输入侧,守卫遂在 **parse 之前**校验 raw frame:#1 缺 required 源列(HARD)/ #2 真未知额外列(WARNING,被 `known_extra_columns` 收窄)/ #3 canonical ≠ 注册表(HARD)/ #4 `fields_hash` ≠ ledger 历史(HARD,迁移检测器)。`report_only` 只报告;`strict` 在 upsert 前 raise(该 gap 不记覆盖、可重试,沿"failed fetch 不记覆盖")。接进 `qt/pipeline.py::_build_cache`(仅 enabled 时建);`data/cache/coverage.py` 加只读 `last_fields_hash`。**默认 `data.cache.schema_guard.enabled=false` ⇒ `_guarded_parse` 直通、字节级不变**;findings/日志/异常只含 endpoint+列名,无 token/secret;**仅日频 `TushareCache`**(分钟守卫 defer),**未接 live `data-update`**(follow-up)。 + 以下明确 **仍未实现**(D6+,本文件 **不声称已实现**): -- endpoint schema registry(改运行时 dispatch 语义); +- 改运行时 dispatch 语义的 endpoint schema 注册表(PR #57 已加 default-off drift **校验**守卫;用注册表改运行时 dispatch 仍未做); - `PanelStore` 的 append/partition / 可选物化派生面板存储(**D6**,仅当因子研究需要可复用派生面板时才启动)。