diff --git a/AGENTS.md b/AGENTS.md index 3c32304..618e32d 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)均已 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 拆分)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -179,6 +179,47 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - 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。 -- ✅ 质量门:`pytest` **428 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);`ruff` clean;`validate-config`(全部 12 配置)+ `run-phase0`(demo)均 OK。 +- ✅ **分钟级 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 待清理**(未获显式授权关闭)。 +- ✅ **数据层 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 缓存重构 / 限频重试,均未做。 +- ✅ 质量门:`pytest` **584 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**);`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 起不再互覆盖)。 -- 路线图下一步:**P4-3 因子支撑端点缓存**(fina_indicator/daily_basic/index_member_all)——暖跑剩余 live 抓取即这三端点;研究侧:更长 holdout 滚动复检 / 中证1000 / 成本模型细化,或分钟级(architecture.html §11)。 +- 路线图下一步:**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);**数据层 D3+**(D1+D2 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分;下一步 endpoint schema registry / data-quality validator / 并发——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。 diff --git a/CLAUDE.md b/CLAUDE.md index c2b8220..618e32d 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 解析去重)均已 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 拆分)均已 merge 到 `main`**。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。 - **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。 - **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。 - 文件小而专(<800 行),immutable 优先。 @@ -216,7 +216,10 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri - ✅ **数据层 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` 模块化)是下一步工程阶段,本 PR 未实现。** -- ✅ 质量门:`pytest` **579 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**);`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 不变**)。 + - **行为保持**: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 缓存重构 / 限频重试,均未做。 +- ✅ 质量门:`pytest` **584 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**);`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);**数据层 D2**(D1 已 merge:cache/store 契约文档化 + token 解析收敛;下一步 `TushareCache` 内部模块化,之后再 schema registry / data-quality validator / 并发——均尚未实现);研究侧:更长 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);**数据层 D3+**(D1+D2 已 merge:契约文档化 + token 解析收敛 + `TushareCache` specs/parsers 拆分;下一步 endpoint schema registry / data-quality validator / 并发——均尚未实现);研究侧:更长 holdout 滚动复检 / 成本模型细化。