diff --git a/AGENTS.md b/AGENTS.md index 9957e42..cfed104 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 批量写 + 进程内查找缓存)均已 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 有界并发 + 全局限频器)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -236,6 +236,11 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - **缓存接线**:`TushareCache._record_gap_coverage` 的 not-ready 拆分用**一次** `record_many` 写其 1–2 行(经 `_record_rows`),`not_ready` 计数/状态不变;其余 call site 仍 `record()`(单行批量)——成功 gap 照常记录,后续失败 fetch 绝不反记成功。分钟缓存(每 gap 一行)未改;无明显安全批量。 - **不变量守住**:公开构造/方法(`read`/`covered_intervals`/`snapshot_fetched_at`/`record`/intraday `covered_day_intervals`)、parquet 路径(`manifest/coverage.parquet`/`coverage_intraday.parquet`)、`LEDGER_COLUMNS`/`INTRADAY_LEDGER_COLUMNS` 名序、coverage 语义(仅 ok/empty 算覆盖;failed/not_ready 不算;snapshot 取最新成功 fetched_at;not_ready_days 不变)全不变;无 config 旋钮 / 无 cache-root 布局变更;phase0 `0.9600/0.8408` 不变。 - **测试**(`tests/test_coverage_ledger_scaling.py`,network-free,13):`record_many` ≡ 重复 `record`(名序/值)/ `record_many([])` no-op / `covered_intervals` 仅 ok·empty / `snapshot_fetched_at` 取最新成功 / 分钟镜像 / **重复 lookup 不重读 parquet**(spy load 路径,非计时)/ **外部写失效缓存** / `read()` 返 copy / not-ready 拆分一次批量 / 平凡 gap 单行 / ledger 列无 secret 字段。 -- ✅ 质量门:`pytest` **656 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 / 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**);`ruff` clean;`validate-config`(全部 17 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 +- ✅ **数据层 D5 — opt-in 有界并发 + 单一全局限频器**(**PR #51 已 merge 到 `main`**,Codex 验收;给 `data-update`/缓存暖跑取数阶段加可选并发,默认全串行):`data_update.concurrency.max_workers` 默认 **1**(串行,所有现存配置照常 validate、行为字节级不变;`<1` 可读报错)。**公开缓存方法/返回帧/ledger 路径列/coverage 语义/重试语义全不变,phase0 `0.9600/0.8408` 不变**。 + - **全局限频器**(`data/feed/scheduler.py::GlobalRateLimiter`):线程安全 ticket 限频器,锁内预定下一时隙、锁外 sleep,N 个 worker 汇入**一个**每分钟预算(配额绝不按线程倍增);`monotonic`/`sleep` 可注入(fake-clock 测试),只见整数预算,无 token/kwargs/异常 payload。`request_with_retry(..., scheduler=)` 每次 attempt(含重试)前 acquire 全局时隙,且不再做 per-call rate sleep;未给则历史 per-call 节流不变。 + - **缓存并发**(`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` **677 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 / 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`(全部 17 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d)+ `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 起不再互覆盖)。 -- 路线图下一步:**I5d 独立泛化**(MMP 五分位单调性是项目首个正向 intraday 信号,但只一个重叠 5 年窗口/一个 universe → 在第二个 universe(CSI300/中证1000)和/或 disjoint 窗口上机器冻结复跑分组回测,使单调性可归因于因子而非该 regime,沿 P3-7/P3-8 独立确认先例);**I5b/执行 follow-up**(执行期 feasibility 可再扩 partial-fill / liquidity / volume cap,size-aware 读数前补);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / data-update summary 落 cache-stats artifact);**数据层 D5**(D1+D2+D3+D3b+D4 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分 + report-only `data/quality/` 质量层 + default-off `data-update` 质量报告钩子 + coverage ledger `record_many` 批量写 + 进程内查找缓存;下一步 **D5**=并发 / 线程池 / 全局限频器,以及 endpoint schema registry——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +- 路线图下一步:**I5d 独立泛化**(MMP 五分位单调性是项目首个正向 intraday 信号,但只一个重叠 5 年窗口/一个 universe → 在第二个 universe(CSI300/中证1000)和/或 disjoint 窗口上机器冻结复跑分组回测,使单调性可归因于因子而非该 regime,沿 P3-7/P3-8 独立确认先例);**I5b/执行 follow-up**(执行期 feasibility 可再扩 partial-fill / liquidity / volume cap,size-aware 读数前补);**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 滚动复检 / 成本模型细化。 diff --git a/CLAUDE.md b/CLAUDE.md index 9957e42..cfed104 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 批量写 + 进程内查找缓存)均已 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 有界并发 + 全局限频器)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -236,6 +236,11 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - **缓存接线**:`TushareCache._record_gap_coverage` 的 not-ready 拆分用**一次** `record_many` 写其 1–2 行(经 `_record_rows`),`not_ready` 计数/状态不变;其余 call site 仍 `record()`(单行批量)——成功 gap 照常记录,后续失败 fetch 绝不反记成功。分钟缓存(每 gap 一行)未改;无明显安全批量。 - **不变量守住**:公开构造/方法(`read`/`covered_intervals`/`snapshot_fetched_at`/`record`/intraday `covered_day_intervals`)、parquet 路径(`manifest/coverage.parquet`/`coverage_intraday.parquet`)、`LEDGER_COLUMNS`/`INTRADAY_LEDGER_COLUMNS` 名序、coverage 语义(仅 ok/empty 算覆盖;failed/not_ready 不算;snapshot 取最新成功 fetched_at;not_ready_days 不变)全不变;无 config 旋钮 / 无 cache-root 布局变更;phase0 `0.9600/0.8408` 不变。 - **测试**(`tests/test_coverage_ledger_scaling.py`,network-free,13):`record_many` ≡ 重复 `record`(名序/值)/ `record_many([])` no-op / `covered_intervals` 仅 ok·empty / `snapshot_fetched_at` 取最新成功 / 分钟镜像 / **重复 lookup 不重读 parquet**(spy load 路径,非计时)/ **外部写失效缓存** / `read()` 返 copy / not-ready 拆分一次批量 / 平凡 gap 单行 / ledger 列无 secret 字段。 -- ✅ 质量门:`pytest` **656 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 / 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**);`ruff` clean;`validate-config`(全部 17 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d)+ `run-phase0`(demo)均 OK(**ic 0.9600 / annual 0.8408 不变**)。 +- ✅ **数据层 D5 — opt-in 有界并发 + 单一全局限频器**(**PR #51 已 merge 到 `main`**,Codex 验收;给 `data-update`/缓存暖跑取数阶段加可选并发,默认全串行):`data_update.concurrency.max_workers` 默认 **1**(串行,所有现存配置照常 validate、行为字节级不变;`<1` 可读报错)。**公开缓存方法/返回帧/ledger 路径列/coverage 语义/重试语义全不变,phase0 `0.9600/0.8408` 不变**。 + - **全局限频器**(`data/feed/scheduler.py::GlobalRateLimiter`):线程安全 ticket 限频器,锁内预定下一时隙、锁外 sleep,N 个 worker 汇入**一个**每分钟预算(配额绝不按线程倍增);`monotonic`/`sleep` 可注入(fake-clock 测试),只见整数预算,无 token/kwargs/异常 payload。`request_with_retry(..., scheduler=)` 每次 attempt(含重试)前 acquire 全局时隙,且不再做 per-call rate sleep;未给则历史 per-call 节流不变。 + - **缓存并发**(`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` **677 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 / 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`(全部 17 配置含 data_update + phase_i5a + phase_i5b + phase_i5c + phase_i5d)+ `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 起不再互覆盖)。 -- 路线图下一步:**I5d 独立泛化**(MMP 五分位单调性是项目首个正向 intraday 信号,但只一个重叠 5 年窗口/一个 universe → 在第二个 universe(CSI300/中证1000)和/或 disjoint 窗口上机器冻结复跑分组回测,使单调性可归因于因子而非该 regime,沿 P3-7/P3-8 独立确认先例);**I5b/执行 follow-up**(执行期 feasibility 可再扩 partial-fill / liquidity / volume cap,size-aware 读数前补);**P4-3 follow-up**(fina 按 ann_date 建披露日历 ledger 以去掉启发式 tail / updater 加 stk_mins 历史回填 / data-update summary 落 cache-stats artifact);**数据层 D5**(D1+D2+D3+D3b+D4 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分 + report-only `data/quality/` 质量层 + default-off `data-update` 质量报告钩子 + coverage ledger `record_many` 批量写 + 进程内查找缓存;下一步 **D5**=并发 / 线程池 / 全局限频器,以及 endpoint schema registry——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 +- 路线图下一步:**I5d 独立泛化**(MMP 五分位单调性是项目首个正向 intraday 信号,但只一个重叠 5 年窗口/一个 universe → 在第二个 universe(CSI300/中证1000)和/或 disjoint 窗口上机器冻结复跑分组回测,使单调性可归因于因子而非该 regime,沿 P3-7/P3-8 独立确认先例);**I5b/执行 follow-up**(执行期 feasibility 可再扩 partial-fill / liquidity / volume cap,size-aware 读数前补);**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 滚动复检 / 成本模型细化。 diff --git a/docs/data/data_layer_contracts.md b/docs/data/data_layer_contracts.md index 3f25f74..8c135ee 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–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/缓存暖跑取数 + 单一全局限频器,默认串行)。**D6 尚未实现**,本文件不声称其存在。 --- @@ -42,7 +42,7 @@ - 它 **不跑 factor / alpha / portfolio / backtest,不写 `PanelStore`**。 - 真实回测仍各自走 read-through,按需补自己的缺口;`data-update` 只是把常用 endpoint 提前填好。 -## 4. D1 / D2 / D3 / D3b / D4 已做什么;D5+ 范围(未实现) +## 4. D1 / D2 / D3 / D3b / D4 / D5 已做什么;D6+ 范围(未实现) **D1**(行为零改动)做两件低风险事:**把上述边界写成本契约文档**,以及 **把 `TushareFeed` / `IndexConstituentsFeed` 里重复的 token 解析收敛到共享的 `data/feed/secret.py::read_token`** @@ -76,8 +76,18 @@ not-ready 拆分用**一次** `record_many` 写其 1–2 行覆盖。**公开方 / `coverage_intraday.parquet`)、`LEDGER_COLUMNS`/`INTRADAY_LEDGER_COLUMNS` 名序、coverage 语义(仅 ok/empty 算 覆盖;failed/not_ready 不算;snapshot 取最新成功 fetched_at)全不变**。 -以下明确 **仍未实现**(D5+,本文件 **不声称已实现**): +**D5**(opt-in 有界并发 + 单一全局限频器)给 `data-update` / 缓存暖跑的取数阶段加可选并发。新增 +`data/feed/scheduler.py::GlobalRateLimiter`(线程安全 ticket 限频器,锁内预定下一时隙、锁外 sleep,N 个 worker 汇入 +**一个**每分钟预算 —— 配额绝不按线程倍增;`monotonic`/`sleep` 可注入,只见整数预算,无 token/secret); +`request_with_retry(..., scheduler=...)` 每次 attempt(含重试)前 acquire 全局时隙,且不再做 per-call rate sleep。 +**默认 `data_update.concurrency.max_workers=1` 保持全串行**(所有现存配置照常 validate、行为字节级不变)。 +`max_workers>1` 时:**只有日频密集 per-symbol 取数**(`market_daily`/`adj_factor`/`suspend_d`/`stk_limit`/ +`daily_basic`/`fina_indicator`)走有界线程池,**store upsert + coverage ledger 写仍在主线程按计划序**(成功 gap 先 +durable 再抛首个失败,失败 gap 不记覆盖、可重试);**snapshot、`index_weight` 90 天分页、intraday 缓存在 D5 仍串行** +(并发开启时它们仍共享同一限频器)。`cache` 仍是 raw endpoint SoT、`PanelStore` 仍是 per-run 面板 artifact,缓存/ +store 边界不变;factor/alpha/portfolio/runtime/backtest 数学不变。 + +以下明确 **仍未实现**(D6+,本文件 **不声称已实现**): -- 并发 / 线程池 / 异步抓取 / 全局限频器(concurrency,**D5**); - endpoint schema registry(改运行时 dispatch 语义); -- `PanelStore` 的 append/partition 特性。 +- `PanelStore` 的 append/partition / 可选物化派生面板存储(**D6**,仅当因子研究需要可复用派生面板时才启动)。