docs: remove design and contract documents from the repository

Drops all markdown docs under docs/ (backtesting contract, model card,
feature/fix/refactor records). The documents remain available in git
history at 87f2f35 if they need to be recovered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Starriv
2026-07-11 22:45:06 +08:00
co-authored by Claude Fable 5
parent 87f2f35e21
commit e7a26da78e
10 changed files with 0 additions and 940 deletions
-158
View File
@@ -1,158 +0,0 @@
# AI 意见 Overlay 设计方案
状态:已实施,2026-07-10 review 修正完成
日期:2026-07-08(2026-07-10 更新)
任务系统:`tasks/projects/ai-opinion-overlay.md`
## 1. 背景与目标
worth-buy-stocks 当前是"脚本 + 量价算法"的确定性评分体系:`indicators.py` 是唯一评分权威,外部信息(新闻、账户、Finnhub、snapshot)全部以 overlay JSON + 契约校验 + 独立降级的方式接入。
目标:引入一个 AI 模型对同一份证据做独立分析,在最终回复的结论段新增一行"AI 意见",让用户对比"量价纪律结论 vs AI 判断",**同时不破坏现有模式**——不动评分逻辑、不动硬性纪律实质、不给分析链路增加硬依赖。
## 2. 设计原则
1. **零评分权力**:AI 意见不改评分、不改结论、不触发降级,只作对照展示。权力级别低于新闻 overlay(可封顶)和账户 overlay(可调仓位建议)。硬性纪律第 2 条"脚本是唯一评分来源"不变。
2. **防锚定**:AI 模型只拿到证据层,不拿到脚本的 verdict/composite,独立形成判断;"同意/分歧"由脚本比较得出,不由模型自述。
3. **优雅降级**:无 API key 不触网,`auto` 模式 API 失败/超时/限流不阻断评分,输出行写"AI 意见:无法确认"。显式 `on` 模式要求每个目标标的成功,任一条目不可用时命令明确失败。
4. **契约化**:ai_opinion 是第五个 overlay,有 JSON 契约、`validate_agent_contract.py` 校验、离线复盘 flag、pytest 覆盖。
## 3. 架构
```
indicators.py 流程(现有):
bars/account/finnhub/snapshot 并行采集 → 指标计算 → 评分 → 组装输出
新增(评分完成后、组装输出前):
评分结果 ──提取证据(隐去 verdict/composite)──▶ ai_opinion.py
│ Claude API (structured outputs)
▼
supplemental.ai_opinion ◀──脚本比较 stance vs verdict 得出 agreement──┘
```
注意:AI 意见依赖指标计算结果,不能与 bars 采集并行;它是评分后的一个后置步骤。
## 4. `scripts/ai_opinion.py` 规格
### 4.1 输入(证据载荷)
从单 symbol 的评分结果中提取,**必须隐去** `score.verdict`、`score.composite`、`raw_composite`、`suggested_position_pct`、`trade_plan`(含结论倾向的字段一律不进):
- 日线/周线趋势状态、MA 排列、关键趋势位
- 30 日结构(位置、回踩价、突破价)
- 相对 SPY/QQQ 强度(3m/6m)
- MACD/RSI/KDJ、量价确认状态
- market regime
- `news_context` 的红旗与 catalyst(如有)
- data_flags(历史不足、低流动性等)
### 4.2 API 调用
- SDK:官方 `anthropic` Python SDK(需加入依赖说明,不用 requests 裸调)
- 模型:`claude-opus-4-8`
- 结构化输出:`output_config: {format: {type: "json_schema", schema: ...}}`,保证返回合法 JSON,无需解析容错
- thinking:`{"type": "adaptive"}`;effort:`medium`
- 超时:显式设置(建议 30-60s),失败即降级,不重试超过 SDK 默认
- key 查找:`ANTHROPIC_API_KEY`,复用现有 `.env` 查找链(`WORTH_BUY_STOCKS_ENV_FILE` → skill 根目录 `.env` → `~/.config/worth-buy-stocks/.env` → 环境变量),实现在 `local_env.py` 基础上扩展
- 提示词:系统提示声明角色(独立技术分析视角)、纪律框架(趋势跟随/相对强度取向)、只基于给定证据不引入外部知识、不输出投资建议措辞
### 4.3 模型返回 schema
```json
{
"stance": "buy | watch | avoid",
"confidence": "low | medium | high",
"view": "一句话核心理由(中文,≤60字)",
"key_risks": ["...", "..."]
}
```
### 4.4 脚本输出(ai_opinion 契约)
顶层为 `symbols` 映射包装(与 finnhub_context/snapshot_context 同构),以支持多 symbol 离线注入;每个 symbol 的条目即写入该 symbol `supplemental.ai_opinion` 的内容:
```json
{
"contract_version": "worth-buy-stocks.agent.v1",
"kind": "ai_opinion",
"status": "ok | unavailable",
"reason": "非 ok 时必填",
"symbols": {
"AAPL": {
"status": "ok | unavailable",
"reason": "非 ok 时必填",
"stance": "buy | watch | avoid",
"confidence": "low | medium | high",
"view": "一句话核心理由(中文,≤60字)",
"key_risks": ["..."],
"agreement": "agree | diverge | not_applicable",
"model": "claude-opus-4-8",
"generated_at": "ISO 8601"
}
}
}
```
`status=ok` 的 symbol 条目必填 `stance/confidence/view/agreement`;`status=unavailable` 的条目只需 `reason`。顶层 `status=unavailable` 表示整体不可用(如无 key)。
> 2026-07-08 调整:原设计为单 symbol 扁平结构,实施 task-001 时改为 symbols 包装,原因:多 symbol 离线复盘(`--ai-opinion-context-file`)需要按 symbol 键控,且与现有 overlay 契约保持同构。
### 4.5 agreement 映射(脚本计算,不由模型自述)
| 脚本 verdict | AI stance | agreement |
|---|---|---|
| 是 | buy | agree |
| 观察 | watch | agree |
| 否 / 持仓需减风险 | avoid | agree |
| 其余组合 | — | diverge |
| 无法评分 | 任意 | not_applicable(此时不调用 API,直接 unavailable) |
## 5. `indicators.py` 接入
- 新 flag:`--ai-opinion auto|on|off`,默认 `auto`(有 key 才调用,无 key 不触网,行为对齐 `--finnhub-context`)
- 离线复盘:`--ai-opinion-context-file path`,加载时经契约校验,失败则丢弃并 stderr 警告(对齐现有 overlay)
- 结果落点:`supplemental.ai_opinion`,**不进 `score` 对象**
- 多 symbol:每个评分 symbol 各一次调用(SPY/QQQ 基准不调用);注意成本与延迟提示
## 6. `scanner.py` 接入
- 只对**最终 top-N 候选**跑 AI 意见(位置在新闻复核降级之后),不对全股池跑
- flag:`--ai-opinion auto|on|off`,默认 `auto`
- 输出中每个候选带 AI 意见行;降级不影响扫描主流程
## 7. 契约与校验
- `validate_agent_contract.py` 新增 kind `ai_opinion`
- `references/agent-contracts.md` 增补 `ai_opinion` 契约形状与示例
- `indicators.py` 加载 `--ai-opinion-context-file` 时自动复检
## 8. 输出格式变更(SKILL.md)
1. **数据角色**段落加一句:"AI 意见默认作为对照展示 overlay,不参与评分、不触发降级、不可用于升级结论。"
2. **结论段**固定字段,在"一句话"之前新增:
- `AI 意见: 同意,<view> / 分歧:AI 倾向<stance 中文>,<view> / 无法确认`
3. **输出前自检清单**同步该行。
4. `references/setup.md` 增加 `ANTHROPIC_API_KEY` 可选配置说明(未配置时功能自动关闭)。
5. 轻量模式:结论段本就完整保留,AI 意见行随之保留,无需特殊处理。
## 9. 测试计划
- `tests/test_ai_opinion.py`:mock API —— 正常返回、超时/异常降级、无 key 跳过、agreement 映射全组合、证据载荷不含 verdict/composite(防锚定回归测试)
- `tests/test_agent_contracts.py`:新增 `ai_opinion` kind 的合法/非法样例
- `tests/test_indicators.py`:`--ai-opinion off` 默认路径不变;`--ai-opinion-context-file` 离线注入;契约校验失败丢弃
- `tests/test_scanner.py`:top-N 候选才调用;降级不阻断
- 端到端:真实 key 单股跑通;全量 pytest 通过
2026-07-10 review 增补回归:
- 顶层 batch 成功但单票 unavailable 时,`on` 模式必须失败。
- 离线 artifact 附加时忽略其 `agreement`,按当前脚本 verdict 重算。
- `docs/` 属于版本化权威文档,不得被 `.gitignore` 整体排除。
## 10. 明确不做(第一版)
- 不给 AI 意见任何评分权(封顶/降级/升级都不给)
- 不做多模型投票、不做 AI 意见缓存
- 不在 scanner 初筛阶段调用 AI
升级路径:若后续用 `backtest_*` 体系回测证明 AI 意见有信息量,再考虑给予类似新闻面的 `min(cap)` 封顶权——届时才需要动纪律条款。
-166
View File
@@ -1,166 +0,0 @@
# 单标的历史回测契约
> 契约版本:`1.4`(`1.0`–`1.3` artifact 保持可加载、可渲染,但不得跨版本合并统计)
> 评分模型版本:由 `scoring.MODEL_VERSION` 唯一提供,artifact 生成时原样记录。
本文冻结单标的历史回放、事件研究、交易仿真和可视化报告的语义。实现不得根据某次回测结果临时改变窗口、样本门槛、成交时点或验证结论。
## Contract 1.4 stateful 实验场景
`task-047` 证明当前 managed 存在“保护退出快、最终买入结论恢复慢”的状态错配。候选 1.4 只新增独立实验场景,不改变 1.3 managed:
- 首次进入仍要求生产最终结论“是”,下一 open 成交。
- long 沿用建议仓位、初始止损、1R 保本和延迟移动保护;高位弱反转仍下一 open 退出,MA60 下行破位在实验场景只记录风险观察。
- 任一保护退出后进入 63-session 恢复观察。最终“是”、高置信超跌反转、量能确认通过的回调反转,或“趋势破坏→趋势延续且反转确认不少于 2 项”可生成下一 open 重新入场。
- 重新入场继承前次目标仓位;持有中后续最终“是”只允许下一 open 向上恢复到更高建议仓位,不按日下调。
- artifact 必须记录状态转换、前次退出、恢复等待、重新入场和仓位恢复事件;场景名固定为 `disciplined_verdict_stateful`,验证前标记 experimental。
- 候选规则在 QQQ 新结果产生前冻结。保留决定只使用 `SPY,AAPL,MSFT,NVDA,AMZN,META,GOOGL,TSLA,JPM,XOM,UNH,CAT,WMT,COST,JNJ,KO` 的 2025-04-04~2026-07-09 cache-only holdout;开发证据截止 2025-01-02,中间 purge 63 sessions。
- 通过标准同时约束收益改善占比、收益/Sharpe 中位差、正基准捕获率、最大回撤和换手;任一核心条件失败则拒绝升级,禁止在同一 holdout 调参。
1.4 已实现上述独立实验场景;旧 managed 语义和交易结果保持不变。实验场景能否升级为正式持仓策略只由 task-050 的预注册 holdout 决定。
## 研究目标与边界
回测回答两个不同问题:
1. 当前价量评分在同一标的的历史时间序列上,是否领先未来 21/63 个交易日的绝对及相对收益。
2. 信号在下一交易日成交、承担成本并禁止重叠持仓后,固定研究窗口与计划仓位持仓管理分别如何表现。
首版只回放价量主链路。新闻、Finnhub、账户敞口、snapshot 和 AI opinion 没有可靠的历史 point-in-time 数据,全部关闭。回测不下单,不模拟分钟级止损/止盈,不做自动调参或组合资金分配。
## 数据契约
- 标的集合固定为研究标的、`SPY`、`QQQ`;评分始终调用生产 `pipeline.build_result()`。
- feed 默认 `iex`,adjustment 默认 `split`。收益不含现金股息,不等同于 total return。
- 只允许完成日线。SQLite 模式只读 `daily_bars`,不得读取 `live_daily_bars` 或 snapshot。
- 默认数据源为 `cache-only`:不触网、不创建/迁移/写入数据库。缺库、schema 不兼容或 coverage 不足直接失败。
- 请求结束日若为周末、休市日、未完成的当前 session 或未来日期,只能依据 SQLite 已缓存的 `market_sessions` 截到最近完成 session,并在 provenance notes/coverage 记录 effective end;已完成 session 的真实缺口仍必须失败。
- `auto` 必须由操作者显式选择,复用现有 read-through;`input` 读取 multi-bars JSON,并保持硬离线。
- 评估开始日前至少加载 260 个 SPY 交易 session 作为 warmup(12-1 动量因子需 253 根收盘价;不足会让窗口开头的评分静默缺失最大权重因子)。实现额外加载 30 根 SPY session 缓冲,使零星停牌的个股仍能满足每标的 warmup 门槛。首个评分日必须同时具备研究标的、SPY、QQQ 的可评分数据。
- bar 按 symbol/date 去重并升序排列;OHLC 必须为正且 `low <= open/close <= high`,volume 不得为负。
- SPY/QQQ 必须覆盖相同的已完成交易日;`sync_ranges` 只证明请求范围完成,不能替代逐 session 日线完整性校验。研究标的缺价仍按下条处理。
- 研究日历使用 SPY session。评分日个股缺价的 session 写入 exclusions;停牌/退市尾部不得用未来恢复后的价格回填。
## 点时点评分与防未来函数
交易日 `t` 的信号只使用时间戳日期 `<= t` 的完成日线。评分完成后记录:
- `raw_composite`、封顶后的 `composite`;
- `base_verdict`、最终 `verdict`;
- 因子分解、风险闸门、确认层和入场时机;
- 计划价摘要与数据警告。
若修改或追加 `t` 之后的数据,`t` 及以前的评分、信号和待执行订单必须完全不变。
正式有效性验证固定逐 session 评分,即 `signal_step=1`。大于 1 的下采样只可用于性能或流程测试;artifact 仍可生成,但顶层 validation 必须为 `invalid_run`,不得用其自动结论支持或否定算法。
## 事件研究
- horizon 固定为 21、63 个 SPY 交易日。
- 收益口径为信号日 `t` 收盘到 `t+horizon` 收盘。
- target 日个股缺价时不剔除事件(停牌/退市与坏结果相关,剔除会向上偏置样本):改用信号日之后、target 日或之前的最后一个收盘价做保守 mark,事件标记 `degraded`(`stale_target_price`:之后仍有数据的临时停牌;`terminal_price_before_horizon`:数据在 horizon 前终止;`bankrupt_terminal`:破产名单按 -100% 结算)。信号日之后完全无价才写入 exclusions(`no_price_after_signal`)。degraded 事件同时进入描述与推断统计。
- 同时计算标的绝对收益、相对 SPY/QQQ 超额收益,以及窗口内 MFE/MAE。
- 全部逐日重叠事件只用于描述统计。
- 推断样本从评估区间第一个有效事件锚定,之后每隔 `horizon` 个 session 取一个非重叠事件。
- 推断统计包括时间序列 Spearman IC、组间均值差和固定 seed `42` 的循环块 bootstrap 95% CI(circular block bootstrap,块长 ≈ n^(1/3)、下限 2;非重叠采样消除机械窗口重叠后,信号仍在 regime 内成簇,块重采样保留该序列相关而非假设 iid)。
- 分组固定为 `<60`、`60-75`、`>=75`,并平行报告最终 verdict、confirmation、risk gate 和 entry timing。
## 交易仿真
运行六个独立场景,两个入场策略 × 三种出场:
- 入场 `rank_only`:核心评分数据完整且 `raw_composite >= 75` 时产生入场信号。
- 入场 `disciplined_verdict`:最终 verdict 为 `是` 时产生入场信号。
- 出场 `_21` / `_63`(固定研究窗口):持有期从 entry session 开始计完整 21/63 个可交易 session,之后在下一可交易 session open 结束观察;不执行 trade plan。该终点不是系统识别的高位退出,HTML 必须标为“窗口终点”。
- 出场 `_managed`(持仓管理):执行建议仓位、初始止损、1R 保本、延迟移动止损,以及高位反转/趋势破位收盘信号的下一开盘退出;无固定持有期,无静态止盈。
共同规则:
- 信号在 `t` 收盘后形成,最早在 `t+1` 的个股 open 成交;禁止同日成交。
- 每个场景最多一个 long position;持仓期间重复信号记录为 skipped,不叠加仓位。
- 找不到 next open 的 pending entry/exit 明确标记,不虚构成交价。
- 默认单边成本 10bp;入场与退出各扣一次。
- 单纯“观察/过热”不构成退出;只有冻结的过热弱收盘组合或已取得趋势资格后的 MA60 下行破位才生成下一开盘退出。
- benchmark 为相同可比区间的研究标的 buy-and-hold 与 SPY buy-and-hold。普通终止行情在研究标的最后可得 session 截止,SPY 同步截断;已知破产标的在最后日线后的首个 SPY session 按零终值结算,之后净值保持为零至评估结束。
`_managed` 出场的日线近似口径(无分钟级路径,宁可保守):
- 入场只投入信号日 `suggested_position_pct` 对应权益,余额保留现金;旧 fixture/缺仓位字段回退 100%,只用于兼容测试与旧 artifact 渲染。
- 初始风险 `R = entry_open − stop_loss_price`。最高完成收盘达到 `entry + 1R` 后,下一 session 起保护位至少提高到入场价上方 0.25%,并从该时点激活 `peak_close × (1 − trailing_stop_pct/100)`;当日收盘不得抬高当日保护位。
- 某 session open ≤ 保护位 → 按 open 出场(跳空);否则 low ≤ 保护位 → 按保护位出场(盘中触发)。低点与高点同日的日内先后顺序不可知,一律按触发止损处理。
- 每个完成日线的生产评分可以给出 `overextended_reversal` 或 `trend_break` 持仓退出信号;信号形成当日不成交,最早在下一交易日 open 退出。超跌反转仓在首次重新取得 MA60 资格前忽略 `trend_break`,仍由结构止损管理。
- 入场日开盘价 ≤ 初始止损 → 放弃该次入场(skipped,`open_at_or_below_stop`);入场日盘中仍可触发初始止损。
- trade plan 缺失可用止损价时该信号 skipped(`plan_exit_unavailable`);`trailing_stop_pct` 缺失时回退 8%。
- 固定持有期与 `_managed` 两族场景的指标不得合并统计。
## 自动验证结论
主观察窗口固定为 63 日,主 benchmark 为 SPY。每个主比较组至少需要 8 个非重叠样本。
- `supports`:“是”组 63 日平均 SPY 超额收益的 95% CI 下界大于 0,且“是”组相对非“是”组均值差的 95% CI 下界大于 0。
- `contradicts`:样本门槛满足,但上述两项方向相反且 CI 上界均小于 0。
- `inconclusive`:样本不足、CI 跨 0,或两项证据不一致。
- `invalid_run`:数据完整性、防未来函数或 artifact 契约失败。该状态不得显示为有效回测。
`inconclusive` 是正常成功结果,不能通过改阈值或窗口将其包装为支持。
## Canonical artifact v1
JSON 顶层固定包含:
| 字段 | 语义 |
|---|---|
| `artifact_version` | 回测 artifact schema,独立于评分版本 |
| `model_version` | `scoring.MODEL_VERSION` |
| `semantic_hash` | 排除生成时间和本机路径后的内容 hash |
| `generated_at` | UTC 生成时间,不参与 semantic hash |
| `config` | symbol、请求区间、horizons、成本、数据模式、feed/adjustment、warmup 与 signal step |
| `provenance` | coverage、数据内容 hash、实际范围、根数与数据源 |
| `bars` | 可视化所需的研究标的完成日线 |
| `timeline` | 每个有效评分时点的生产评分摘要 |
| `event_study` | 事件、exclusions、描述/推断统计和自动结论条件 |
| `simulations` | 六个既有场景与一个 stateful 实验场景的 trades、状态转换、skipped、equity、drawdown 和 metrics |
| `benchmarks` | 标的与 SPY buy-and-hold 曲线及 metrics |
| `warnings` | 复权、overlay、样本与执行语义警告 |
| `validation` | `supports/inconclusive/contradicts/invalid_run` 与逐条件证据 |
HTML 只消费此 artifact,不访问 SQLite、不调用评分、不重算统计。任何页面新增数据必须先进入 artifact schema。
## 版本与变更
- 修改评分权重、阈值或 verdict 语义:提升 `MODEL_VERSION` 并更新 model card。
- 修改数据、事件研究、成交、成本或自动结论语义:提升 artifact/contract version,并保留旧 artifact 解释能力。
- 不同 model version 或 artifact version 的结果不得合并统计。
### 1.4(2026-07-11)
- 新增与旧 managed 并存的 `disciplined_verdict_stateful` 实验场景,记录恢复观察、重新入场、风险观察和仓位恢复状态转换。
- MA60 下行破位在实验场景只记录风险观察;高位弱反转和既有保护价继续执行。
- 保护退出后提供 63-session 恢复窗口,重新入场与仓位恢复全部下一 open 执行并计成本。
- 多标的 holdout 决定在 QQQ 新场景运行前预注册;验证失败不得在同一 holdout 内调参。
### 1.3(2026-07-11)
- managed 从标准化全仓改为执行建议仓位并保留现金;新增 1R 保本、延迟移动保护和保护位审计字段。
- timeline 增加持仓退出信号;高位反转与已取得趋势资格后的 MA60 下行破位在下一 open 执行。
- 固定 21/63 日场景冻结为“研究窗口”,HTML 默认展示 managed,并将固定场景 marker 改为“研究起点/窗口终点”。
- cache-only/input/auto 数据边界新增 SPY/QQQ 逐 session 对齐校验;coverage 完整但基准日线缺行时直接失败,避免研究日历和 21/63 日窗口静默错位。
- managed 移动止损峰值严格从入场后首个存活收盘开始,不再把入场开盘价当作最高收盘价。
- 仿真与 buy-and-hold 基准统一比较起止区间;已知破产标的在终止日线后的首个 SPY session 按零终值结算,固定持有期和 managed 场景均记录 `bankrupt_terminal` 出场。
- 破产事件的 MAE 纳入零终值,固定为 -100%。
- `1.0`–`1.2` artifact 保持可加载、可渲染。
### 1.2(2026-07-11)
- 交易仿真新增 `rank_only_managed` / `disciplined_verdict_managed` 两个场景:按信号自身 trade plan 的初始止损 + 移动止损出场(日线近似、无止盈、无固定持有期)。固定持有期四场景语义不变。
- timeline 的 trade plan 摘要新增 `trailing_stop_pct`。
- 事件研究、自动验证结论与冻结条件完全不变;managed 场景只提供描述性执行证据,不参与 `supports/contradicts` 判定。
- `1.0`/`1.1` artifact 保持可加载、可渲染。
### 1.1(2026-07-10)
- 推断 CI 由 iid percentile bootstrap 改为循环块 bootstrap(保留 regime 内信号聚集的序列相关;seed 与门槛不变)。
- target 日缺价事件改用保守 stale mark 并标记 `degraded`,不再剔除(消除停牌/退市相关的事件级幸存者偏差)。
- warmup 由 230 提升到 260(12-1 动量因子需 253 根收盘价),并额外加载 30 根 SPY session 缓冲以容忍个股零星停牌。
- 数据加载增加只读复权漂移探针,命中写入 provenance `quality_flags` 与 artifact warnings;read-through 缓存检测到重叠修订时改为刷新该 symbol 的全部缓存历史。
- `1.0` artifact 保持可加载、可渲染。
@@ -1,74 +0,0 @@
# SQLite 行情缓存与 session-aware 读穿
> **日期**: 2026-07-10
## 背景
单股、图表、scanner、回测和 forward log 原先会跨进程重复请求相同历史日线;盘中固定时钟剔除当日 K 线也无法正确覆盖节假日、提前收盘和 finalization 延迟。
## 实现方案
- `fetching.py` 保持兼容门面,并将 Alpaca bars/snapshot transport 与缓存策略分开。
- `market_data_cache.py` 使用 XNYS calendar 区分盘前、盘中、收盘宽限期和休市;final 日线 read-through,盘中 snapshot 更新 latest/live。
- 新完成 session 回拉最近 5 个交易日;重叠 OHLCV 变化时刷新该 symbol 的完整请求范围。
- DB/calendar/写入失败时保守回退远端;远端也失败时不写 coverage,不把陈旧数据伪装为已同步。
- `--input` 成为硬离线边界,不读写 DB,也不触发账户、Finnhub、snapshot、AI 或通知网络调用。
## API
- 行情入口新增 `cache_mode=auto|refresh|off`、`cache_db`;原位置参数调用保持兼容。
- `indicators.py`、`scanner.py`、`chart.py`、三个 backtest CLI 和 `forward_log.py` 新增 `--cache` / `--cache-db`。
- `cache_admin.py` 提供 `status`、`integrity-check`、`warm`、`refresh`、全市场 `warm-market` 与受保护的 `purge`。
## 数据库变更
默认路径为 `data/market-data.sqlite3`,可由 `WORTH_BUY_STOCKS_DB` 或 `--cache-db` 覆盖。行情缓存基础交付为 schema v2;资产目录功能将当前 schema 提升到 v3,包含:
- `daily_bars`:只保存可评分的完成日线;
- `live_daily_bars`:只保存当前 regular session 的未完成日 K;
- `latest_snapshots`:每个 symbol/feed 只保留最新一份(v2 起为 rowid 表,适配 KB 级 JSON payload);
- `sync_ranges`:表达已查询覆盖,包括合法空区间(v2 移除了与主键重复的索引);
- `market_sessions`:缓存 XNYS regular calendar;
- `feed_fallbacks`:记录 sip→iex 等 feed 受限回退,后续调用直接读可达 feed 的缓存,`--cache refresh` 会重新探测并清除。
- `assets`:保存完整 Alpaca active US equity 目录、交易属性、当前状态、IEX 对账资格与排除原因;
- `asset_catalog_state`:记录目录 UTC 同步时间和 source/current/tradable/eligible/unsupported 计数。
v1/v2 数据库在首次打开时自动迁移到 v3,保留 snapshot、bars 与 coverage。SQLite 使用 WAL、busy timeout、短事务和每操作独立连接(进程内按路径复用 store 实例)。范围 purge 会同步切开 coverage;`purge --all` 之后执行 VACUUM 回收文件空间;status/integrity 对缺失 DB 保持只读、不创建文件(status 使用 `quick_check`,完整扫描走 `integrity-check` 子命令)。
## 会话策略要点
- 收盘 + 15 分钟宽限后,若远端尚未发布最新完成日的日线,该日不标记覆盖,后续调用自动重查直到发布,避免当日 bar 静默缺失。
- 盘中捕获的 snapshot 在该 session 收盘定稿后视为陈旧:优先用 `daily_bars` 的最终日线合成(零网络),否则仅对受影响 symbol 触发一次远端刷新;远端不可用时才降级返回陈旧快照并置 `stale=true`。
- 日线远端故障时,warm 缓存返回已有数据并在 note 中标注降级;仅在缓存完全无数据时向上抛错。
## 修改文件
| 文件 | 变更 |
|---|---|
| `scripts/market_data_store.py` | schema、迁移、CRUD、coverage、并发与完整性 |
| `scripts/market_data_cache.py` | session 分类、bars/snapshot read-through、修订与降级策略 |
| `scripts/fetching.py` | 兼容门面、纯远端 transport、cache mode |
| `scripts/cache_admin.py` | 缓存治理 CLI 与全市场 warm-market 运维入口 |
| `scripts/market_warmup.py` | universe、coverage plan、有界 batch、resume 与维护窗口 |
| `scripts/market_data_rate_limit.py` | page 级均匀限速、429 全局冷却与同库单实例 |
| `scripts/asset_catalog.py` | Alpaca 资产目录拉取、UTC 日级 freshness 与原子替换 |
| `scripts/market_reconcile.py` | 全资产目标选择与三年 coverage 查漏补缺 |
| `scripts/indicators.py`、`scripts/scanner.py`、`scripts/chart.py` | 运行时接入与硬离线边界 |
| `scripts/backtest_*.py`、`scripts/forward_log.py` | 研究入口接入与无缓存基线 |
| `tests/test_*cache*.py`、`tests/test_market_data_policy.py` | schema/session/入口/admin/golden/故障与并发覆盖 |
## 验证
- SQLite 基础交付时 `unittest`/临时 pytest 各 392 passed;后续 warm-market 增量验收见 `002-market-data-warmup.md`。
- 真实 AAPL 5 次基准:cold 中位 4.1091s,warm 中位 0.0082s,提升 99.8%;warm 的 bars/snapshot/calendar 调用均为 0。
- 真实 4 票 scanner 冒烟:cold 4.513s,warm 0.032s;warm 三类行情调用均为 0,DB 118,784 bytes。
- remote/cache 的完整 `symbols.*.score` 与 scanner candidates 深度相等;live snapshot 写入后评分 `last_date` 仍为上一完成 session。
- skill `quick_validate`、SQLite `integrity_check`、`py_compile` 与 `git diff --check` 通过。
## 注意事项
- 完整 warm 覆盖才承诺盘前/休市零网络;冷启动和 partial miss 会只补缺失 symbol/range。
- SIP 回退 IEX 后只写 IEX 命名空间。
- 缓存错误随当前调用返回,不持久化到 SQLite,`cache_admin status` 会明确报告这一点。
*创建于: 2026-07-10*
-63
View File
@@ -1,63 +0,0 @@
# Alpaca Basic 全市场历史行情安全预热
> **日期**: 2026-07-10
## 背景
SQLite read-through 已能复用单次分析取得的 final 日线,但原有 `cache_admin warm` 需要显式 symbols,也没有全市场发现、分页级共享限速、维护窗口或单实例保护。直接以多线程拉取数千 ticker 会把 worker/batch 并发误当作真实请求预算,并在中断后重复获取已完成范围。
## 实现方案
- 每次运行发现一次 NYSE/NASDAQ active scanner universe;`--symbols-file` 用于受控 canary,并标记 `scope_limited`。
- 以 `sync_ranges` 批量计算最近三年真实缺口;完整覆盖不读三年 bars、不进入远端队列,partial 只按缺口组批。
- 默认 4 workers、50 symbols/batch;每个 Alpaca bars page(含失败与 retry)共享 120 RPM 均匀 limiter,首分钟无 burst。
- 429 使用有界指数退避与 jitter,并触发 job-wide cooldown;同一 DB 用 POSIX advisory lock 限制单实例。
- 网络请求在 SQLite 事务外;成功 batch 原子提交 bars 与 coverage,失败、取消或写入错误保持 missing。
- `--max-runtime`/SIGINT 停止派发并安全收敛;重跑根据 coverage 续传,不保存 job 历史表。
## API
```bash
python3 scripts/cache_admin.py warm-market --dry-run
python3 scripts/cache_admin.py warm-market \
--years 3 --workers 4 --batch-size 50 \
--max-rpm 120 --max-runtime 3600 \
--feed iex --adjustment split
```
默认数据库为 skill 根目录 `data/market-data.sqlite3`。stdout 只输出最终 JSON,进度/心跳写 stderr。退出码:完成或 dry-run `0`、fatal `1`、参数 `2`、可恢复 partial `3`、同库锁 `4`、SIGINT `130`。
## 数据库变更
本预热功能本身无 schema 变更,交付时继续使用 schema v2 的 `daily_bars`、`sync_ranges` 与 `market_sessions`;后续资产目录将当前数据库提升到 v3,见 `003-asset-catalog-reconciliation.md`。锁文件位于 `<database>.warm-market.lock`,不保存凭证或远端响应。
## 修改文件
| 文件 | 变更 |
|---|---|
| `scripts/market_data_rate_limit.py` | page 级均匀限速、429 retry/cooldown、同库单实例 |
| `scripts/market_warmup.py` | universe、三年 coverage plan、有界 batch、resume、取消与摘要 |
| `scripts/fetching.py` | 在真实 `multi-bars` page 前增加可选 limiter/retry hook |
| `scripts/cache_admin.py` | `warm-market` CLI、dry-run、心跳、退出码与 coverage status |
| `tests/test_market_data_rate_limit.py` | 5,000-symbol/多页/8-worker、429、timeout、非 JSON 与锁测试 |
| `tests/test_market_warmup.py` | universe、partial、resume、故障、max-runtime、DB 写失败测试 |
| `tests/test_cache_admin.py` | 参数、stdout/stderr、SIGINT、退出码与 status 测试 |
| `SKILL.md`、`README.md`、`scripts/README.md`、`references/setup.md` | 运维入口、安全边界与恢复说明 |
## 验证
- 离线 fake-time:1,000 pages 和 5,000-symbol/3-page/8-worker 场景的任意 60 秒窗口均不超过 120;相邻令牌约 0.5 秒,429 后全局冷却且 retry 重新取令牌。
- 故障矩阵:429 耗尽、timeout、非 JSON、SQLite locked/read-only、单 batch 失败、SIGINT、max-runtime 与双实例锁均保持 coverage 诚实;第二次 plan 只含缺口。
- 真实 canary:AAPL/MSFT/SPY/QQQ、3 年、2 workers、2-symbol batch、30 RPM;2 pages、0 retries、0 429、峰值 2 RPM、平均 18.812 RPM、6.379 秒,写入 3,012 根 final 日线,SQLite integrity `ok`;二次运行 0 pages。
- 真实全市场 dry-run:4,793 symbols、已覆盖 3、待处理 4,790、96 batches、估算 3,755,360 行、bars pages 0;未启动完整长任务。
- 全量回归:423 unittest passed;一次性 pytest 环境 423 passed;`py_compile` 与后续 skill/whitespace 校验通过。
## 注意事项
- 120 RPM 只约束本 warm-market 进程;Alpaca Basic 200 RPM 的余量不能防止同账号其他客户端叠加超额。
- 首期只拉 `iex + split` final 日线,不拉 snapshot、分钟线、账户、新闻或 AI。
- 完整全市场预热由用户在空闲窗口主动运行,不进入 CI、cron 或自动收尾。
- asset universe 长期持久化和 coverage 查漏补缺已在后续 `003-asset-catalog-reconciliation.md` 交付;直接 HTTP rate-limit headers 与系统空闲探测仍不在范围。
*创建于: 2026-07-10*
@@ -1,72 +0,0 @@
# Alpaca 资产目录与行情完整性对账
> **日期**: 2026-07-10
## 背景
`warm-market` 已支持 scanner 股池的安全三年预热,但 scanner universe 不是 Alpaca 全部可交易资产,也不能回答“今天 Alpaca 支持哪些 symbol”“哪些当前资产尚无三年 coverage”。需要将资产发现持久化,并把资产目录与行情 coverage 分开管理。
## 实现方案
- 每个 UTC 日期最多远端刷新一次完整 Alpaca `active + us_equity` 目录;`--force` 可显式刷新。
- 资产按 Alpaca 稳定 ID upsert;新资产记录 `first_seen_at`,持续出现更新 `last_seen_at`,本次响应消失的旧资产只设为 `is_current=0`,不删除历史。
- 目录保留 tradable、fractionable、marginable、shortable、easy-to-borrow、exchange、name 等属性,并单独记录是否适合 Basic IEX 行情对账及排除原因。
- 对账目标为当前、active、US equity、tradable,且交易所属于 AMEX/ARCA/BATS/NASDAQ/NYSE 的安全 symbol;OTC 和不可交易资产保留在目录,但不伪装成行情完整。
- `reconcile-market` 复用 `warm-market` 的三年 coverage planner、batch worker、page 级均匀限速、429 全局冷却、原子写入、维护窗口、SIGINT 收敛和同库单实例锁。
- coverage 表达“远端已成功查询该范围”,不要求每个交易日都有 bar;合法无成交/上市前空区间可被诚实记录。实际存在 final bars 的 symbol 数在摘要中另行报告。
## API
```bash
# 同日默认读目录缓存;需要时用 --force 重新拉取
python3 scripts/cache_admin.py sync-assets
# 只规划完整资产目录的三年缺口,不请求 bars
python3 scripts/cache_admin.py reconcile-market --dry-run
# 在维护窗口显式执行,可中断并直接复跑
python3 scripts/cache_admin.py reconcile-market \
--years 3 --workers 4 --batch-size 50 \
--max-rpm 120 --max-runtime 3600 \
--feed iex --adjustment split
```
`--symbols-file` 仅用于低速 canary;文件内 symbol 必须属于当前 eligible 资产。完整长任务不会由 skill、CI 或收尾流程自动启动。
## 数据库变更
schema v3 新增:
- `assets`:按 `asset_id` 保存目录属性、`is_current`、`data_eligible`、排除原因与 first/last seen;
- `asset_catalog_state`:单行保存同步时间和 source/current/tradable/eligible/unsupported 统计。
目录刷新先在内存完成校验,再在一个短事务中标记旧记录、upsert 新记录并更新 state。空响应、非法响应、SQLite 锁或触发器错误都会整体回滚,旧目录仍可用。v1/v2 首次打开自动迁移到 v3 并保留原有行情数据。
## 修改文件
| 文件 | 变更 |
|---|---|
| `scripts/market_data_store.py` | schema v3、资产资格判断、目录原子替换与查询 |
| `scripts/asset_catalog.py` | Alpaca asset list transport、日级 freshness 与强制刷新 |
| `scripts/market_reconcile.py` | 目录目标选择、coverage plan 与 warm executor 复用 |
| `scripts/cache_admin.py` | `sync-assets`、`reconcile-market`、资产 status 与退出码 |
| `tests/test_asset_catalog.py` | 资格、增删、原子失败、UTC freshness 与远端故障 |
| `tests/test_market_reconcile.py` | dry-run、补缺、续传、force、14k 容量与 canary |
| `tests/test_cache_admin.py`、`tests/test_market_data_cache.py` | CLI/status 与 v1/v2→v3 迁移 |
## 验证
- 离线容量测试:14,098 个资产、13,268 个 tradable,得到 12,949 个 IEX eligible 目标和 259 个 50-symbol batches;资产列表仅获取一次。
- 真实资产同步快照:14,027 个 current、13,231 个 tradable、12,971 个 eligible、1,056 个 unsupported;其中 796 个不可交易、260 个 OTC 排除。
- 真实全目录 dry-run:12,971 个待处理、260 batches、估算 10,169,264 行、bars pages 为 0。
- 真实 canary:AAPL/MSFT/SPY/QQQ、30 RPM、2 workers;2 pages、0 retry、0 次 429、5.729 秒,写入 3,012 根 final 日线;二次运行 0 pages,SQLite integrity `ok`。
- 全量回归:460 unittest passed;临时 pytest 环境 460 passed + 13 subtests。
## 注意事项
- 未执行全市场长任务;真实 canary 后仍有 12,967 个 eligible symbol 待补,不能报告为全市场完整。
- 资产数量是同步时点快照,会随 Alpaca 上下架和状态变化;目录不是历史每一天的 point-in-time 成分表。
- 本地 RPM 预算只约束当前进程;同一 Alpaca 账号的其他客户端仍需避免同时高频请求。
- Basic IEX 不支持的 OTC 被明确标记为 unsupported;目录完整不等于所有资产都有可获取的 IEX 行情。
*创建于: 2026-07-10*
-69
View File
@@ -1,69 +0,0 @@
# Make 命令统一入口
> **日期**: 2026-07-10
## 背景
分析、扫描、缓存治理、资产同步、全市场预热、回测和质量检查已有稳定 Python CLI,但日常命令分散在多份文档,长参数重复且 purge 容易误用。需要一个可发现的工程入口,同时避免在 Makefile 中复制业务逻辑。
## 实现方案
- 根目录新增 Makefile,默认目标为只读 `help`,按基础、运行、缓存、预热、危险操作、研究和质量分类展示 target。
- target 使用静默 recipe,只转发现有 Python CLI,保持 stdout JSON/图表协议、stderr 日志、退出码、coverage、限速和锁语义不变。
- 公共配置统一为 `PYTHON`、`CACHE_DB`、`FEED`、`SYMBOLS`、`YEARS`、`WORKERS`、`BATCH_SIZE`、`MAX_RPM`、`MAX_RUNTIME` 等变量。
- `GLOBAL_ARGS` 放在带 subcommand CLI 的 subcommand 之前;`ARGS` 追加在具体 subcommand 之后,兼容 argparse 参数位置。
- plan 与长任务分开:`warm-market-plan`/`reconcile-plan` 只 dry-run,实际 `warm-market`/`reconcile-market` 必须显式调用。
- `cache-purge` 使用空默认 `PURGE_SYMBOLS`,日期必须成对;`cache-purge-all` 额外要求 `CONFIRM=purge-all`,均在 Python 启动前拦截错误用法。
## API
```bash
make help
make analyze SYMBOLS=AAPL,SPY,QQQ
make scan TOP=20
make status
make sync-assets
make warm-market-plan
make reconcile-plan
make test
make check
```
高级示例:
```bash
make analyze SYMBOLS=AAPL,SPY,QQQ ARGS="--cache refresh"
make status GLOBAL_ARGS="--log-format console --log-level DEBUG"
make cache-purge PURGE_SYMBOLS=AAPL START=2026-07-01 END=2026-07-10
```
## 数据库变更
无。Makefile 默认继续使用 skill 根目录 `data/market-data.sqlite3`,也可通过 `CACHE_DB` 覆盖。
## 修改文件
| 文件 | 变更 |
|---|---|
| `Makefile` | 统一 target、公共变量、参数透传、长任务分流和 purge guard |
| `tests/test_makefile.py` | help、dry-run、纯 JSON status、缺参数与确认 guard |
| `README.md`、`references/setup.md` | 用户安装、分析、缓存和预热入口改为 Make 优先 |
| `SKILL.md`、`scripts/README.md` | agent 运维边界、变量契约和维护规则 |
## 验证
- `make` 与 `make -C <skill> help` 退出 0,不触网、不创建数据库,也不输出 entering/leaving directory 噪声。
- `make doctor` 使用临时缺失 DB 通过,Python 3.12、structlog、Alpaca CLI 和只读 status 均正常。
- reconcile dry-run、日志全局参数、forward-log 默认篮子和 purge guard 的代表性命令通过。
- 466 unittest passed;466 pytest + 13 subtests passed。
- `make check` 完成 compileall、pytest、skill quick validation 和 `git diff --check`。
## 注意事项
- Makefile 是薄转发层;新增评分、行情、数据库或限速逻辑必须继续放在 Python 模块。
- `ARGS`/`GLOBAL_ARGS` 按 shell 参数原样展开,只应传可信的本地命令参数。
- `make warm-market` 和 `make reconcile-market` 是显式联网长任务,不属于默认目标、安装、测试或自动收尾。
*创建于: 2026-07-10*
@@ -1,104 +0,0 @@
# 单标的可视化回测
> 本文记录 v1.1 / contract 1.0 的首版交付证据。当前契约已由后续反转与退出修复升级到 1.3;固定路径是研究窗口,managed 执行建议仓位与持仓保护。现行定义与 QQQ v1.2 证据以 `docs/backtesting-contract.md` 和 `docs/model-card.md` 为准。
## 交付结果
本功能把历史研究拆成独立 `scripts/backtesting/` 包,并以 `scripts/backtest.py` 作为唯一 CLI。同一条生产评分链路现在可以在每个历史信号时点重放,形成 canonical JSON,再由只读渲染器生成自包含 HTML。
首版回答两个不同问题:
- 事件研究:某个历史分数/结论出现后,未来 21/63 个交易日的标的收益与相对 SPY/QQQ 超额收益如何。
- 交易仿真:如果只按 rank-only 或 disciplined-verdict 入场,在下一交易日开盘成交、固定持有 21/63 日并扣单边 10bp,账本、权益和回撤如何。
二者都不能单独证明宽市场 alpha;自动结论只按冻结的 63 日非重叠样本、组样本下限和 bootstrap 置信区间给出 `supports`、`inconclusive`、`contradicts` 或数据无效时的 `invalid_run`。
## 架构
```mermaid
flowchart LR
A["SQLite final bars<br/>或显式 input fixture"] --> B["backtesting.data<br/>coverage · provenance · data hash"]
B --> C["backtesting.replay<br/>point-in-time build_result"]
C --> D["event_study<br/>21/63 日证据"]
C --> E["simulation<br/>next-open 固定持有期"]
D --> F["canonical artifact<br/>semantic hash"]
E --> F
F --> G["CLI JSON summary"]
F --> H["self-contained HTML<br/>只读渲染"]
```
主要边界:
- `contracts.py` 冻结研究版本、窗口、成本、预热和验证下限。
- `data.py` 提供 `cache-only`、`auto`、`input`;默认只读 SQLite schema v3 的 final rows,完全排除 live/Snapshot。
- `replay.py` 只切片,不复制权重、闸门或 verdict;唯一评分实现仍是 `pipeline.build_result()` → `scoring.score()`。
- `event_study.py` 的全部重叠样本只作描述,非重叠样本才进入 IC、CI 和自动判断。
- `simulation.py` 维护下一开盘、固定持有期、成本、跳过信号、交易 ledger、权益、回撤和 buy-and-hold 基准。
- `artifact.py` 负责 schema、无 NaN 校验、原子写入与稳定语义 hash;生成时间和数据库绝对路径不参与语义 hash。
- `visualization.py` 只消费 artifact,内嵌价格/评分、交易标记、权益/回撤、证据、交易表、排除原因和 provenance,不加载外部资产。
- `panel.py`、`cross_sectional.py` 与 `universe.py` 承担宽票池取数和三类研究;统一 CLI 通过 `factor-ic`、`score`、`robustness` 子命令调用。
完整研究和执行定义见 [`docs/backtesting-contract.md`](../backtesting-contract.md)。
## 使用
缓存覆盖充分时:
```bash
make backtest-visual SYMBOL=AAPL START=2021-07-01 END=2026-07-09
```
显式离线 fixture:
```bash
python3 scripts/backtest.py single \
--symbol AAPL --start 2021-01-04 --end 2025-12-31 \
--data-source input --input bars.json
```
从 canonical JSON 重建页面:
```bash
python3 scripts/backtest.py render \
--artifact artifacts/backtests/<artifact>.json \
--html artifacts/backtests/rerendered.html
```
成功的 `single` 默认把 `<symbol>-<actual-start>-<actual-end>-<hash>.json/.html` 写入 gitignored 的 `artifacts/backtests/`。CLI stdout 是单一 JSON 结果,结构化运行日志只写 stderr。
## 真实 AAPL 验收
2026-07-10 对本地 schema v3 缓存显式补齐 AAPL/SPY/QQQ 后,按以下冻结配置运行两次 cache-only:
```bash
python3 scripts/backtest.py --log-format json single \
--symbol AAPL --start 2021-07-01 --end 2026-07-09 \
--data-source cache-only --signal-step 1 \
--output-dir artifacts/backtests
```
- 实际区间:2021-07-01 至 2026-07-09;每个 symbol 共读取 1,490 根 final bar(含预热),coverage 无缺口。
- 版本:model `v1.1`、contract/artifact `1.0`。
- data hash:`8bc3a7e9f6cbd682e717a4623ae7cac7280fb824da297f05f1ee94d3092cbade`。
- 两次 semantic hash 均为 `909b73190a64837b0e37c3700d41c2463b1e8cc44252dfeb47b080fb69c17268`;第二次没有 transport 事件。
- 21 日:描述 N=1,239,非重叠推断 N=59,时间序列 IC=−0.1942。
- 63 日:描述 N=1,197,非重叠推断 N=19,时间序列 IC=−0.3115。
- 63 日 rank-only:总收益 −15.1426%,最大回撤 −30.7895%,8 笔交易。
- 63 日 disciplined-verdict:总收益 +0.1813%,最大回撤 −22.5687%,5 笔交易。
- 同期 AAPL / SPY buy-and-hold:+130.3534% / +74.5882%。
自动结论为 `inconclusive`。63 日“是”组非重叠样本只有 1 个,未达到冻结下限 8,无法形成可靠的组均值及差值置信区间。负 IC 和策略落后基准作为负面证据完整保留,但不能在样本不足时改写成 `contradicts`。
## 验证
- 1,300 个评估 bars、3 symbols 的 deterministic E2E 覆盖 data → replay → study → simulation → JSON → HTML,耗时约 2.63 秒(性能专用 `signal_step=13`,artifact 按契约标记 `invalid_run`,45 秒上限)。
- 真实 AAPL 约五年逐日完整回放与 HTML 生成耗时约 30.08 秒,低于 45 秒验收上限。
- 单元与集成测试覆盖未来数据变形不变性、生产评分 parity、cache-only 零 transport/零写入/final-only、coverage/schema 失败、下一开盘与成本、ledger/权益/回撤恒等、artifact hash、HTML 转义与旧 CLI 兼容。
- 内嵌 artifact 可解析、页面 JavaScript 可编译,九个关键 DOM 节点均存在。Codex in-app Browser 的本地 `file://` URL 策略阻止了自动视觉点击验收;未绕过该策略,HTML 仍可由用户直接打开检查。
## 明确不做
- 不历史回填新闻、账户、Finnhub、Snapshot 或 AI opinion。
- 不按日线内高低价模拟 trade plan 止损止盈,不解决同日触发顺序歧义。
- 不做组合资金分配、参数寻优、滚动再训练、分钟级撮合或实盘下单。
- 不把单个 ticker 的结果外推成宽市场有效性结论。
@@ -1,48 +0,0 @@
# Fix: 入场时机与 AI overlay 深度 review 缺陷
> **日期**: 2026-07-10
## 问题描述
- 回调拐点没有约束高点、低点和反转的时间顺序,量能缺失仍可能放行“是”。
- AI 强制模式只看 batch 状态,单票失败会被吞掉;离线 agreement 可以与当前评分不一致。
- forward log 没有第四层字段,v1.0/v1.1 语义无法隔离,股票与 SPY 起始日可能错位。
- scanner 测试夹具被新过热规则击穿,入场类型过滤发生在 Finnhub 调用之后。
- `docs/` 被整体忽略,model card 与设计文档无法版本化。
## 根因分析
- 回撤深度直接用无时间顺序的窗口极值计算,历史低点又与当前 MA20 比较。
- 确认层把缺失值视为通过;AI `on` 只校验顶层 artifact。
- 样本外日志只保存最终 verdict/composite,没有信号日、基础结论或时机分类。
- 文档、测试和任务验收没有随第四层语义同步。
## 修复方案
- 回调改为严格“结构高点早于近期低点”,近五日触碰使用同时点 MA20;五种时机分类均有直接测试。
- technical、volume、trend quality 必须可计算且达标;拐点仅替代 technical/trend quality,不能替代量能。
- AI `on` 对任一目标条目失败报错;离线 agreement 在附加时按当前 verdict 重算。
- 评分输出第四层介入前结论;model card 升为 v1.1。新 forward log 从信号日后下一交易日统一使用股票和 SPY 收盘,并按时机分类统计。
- scanner 在新闻复核前应用入场类型过滤;重建不过热的健康趋势夹具。
- 恢复 `docs/` 版本跟踪并同步 README、契约与设计文档。
## 修改文件
| 文件 | 变更 |
|---|---|
| `scripts/metrics.py` | 有序回调、同时点 MA20、完整时机元数据 |
| `scripts/scoring/engine.py`、`scripts/scoring/gates.py` | 缺失确认不放行、保留第四层介入前结论 |
| `scripts/ai_opinion.py`、`scripts/indicators.py`、`scripts/scanner.py` | 单票失败检测、agreement 重算、过滤前移 |
| `scripts/forward_log.py`、`docs/model-card.md` | v1.1 日志与统一起始日对账 |
| `tests/` | 入场五分类、升级/降级、AI 强制失败、日志日期与 scanner 顺序回归 |
| `.gitignore`、`README.md`、`scripts/README.md`、`references/agent-contracts.md` | 权威文档恢复跟踪并与行为同步 |
## 验证方式
- `python3 -m unittest discover -s tests -q`:295 tests passed。
- `pytest -q`:295 passed。
- `python3 -m compileall -q scripts tests`、`git diff --check`:通过。
- 评分反例:量能缺失维持观察;过热前后 composite 相同且“是”降为“观察”。
- AI 契约:顶层成功但单票 unavailable 时 `on` 报错;离线假 agreement 被重算。
*创建于: 2026-07-10*
-136
View File
@@ -1,136 +0,0 @@
# Model Card — worth-buy-stocks 评分引擎
| | |
|---|---|
| 版本 | **v1.2** |
| 冻结日期 | 2026-07-11 |
| 代码锚点 | `scripts/scoring/`(constants/factors/gates/engine/trade_plan) |
| 变更规则 | 任何下表参数变更 → bump 版本、在本文件追加变更记录(动机+证据),`forward_log.py` 记录会带上版本号,跨版本样本不混算 |
## 用途与定位
把日线价量数据映射为 0-100 排名分与「是/观察/否」结论的**交易纪律工具**。不是收益预言机:统计显著性不足(见下),从未在完整熊市周期做样本外验证。输出用于纪律对照与复盘,不构成投资建议,系统不下单。
## 冻结参数
### ALPHA 加权(排名分,缺失按可用权重重归一)
| 因子 | 权重 | 定义 |
|---|---|---|
| momentum | 55 | risk_adj_6m(6 月年化收益/年化波动,logistic x0=0.5, k=3.0)×0.6 + m12_1(12-1 月动量,logistic x0=5.0, k=0.08)×0.4 |
| rel_strength | 35 | 3m/6m 两视界各取可用非自身基准的较弱者,均值过 logistic(x0=0, k=0.12);普通股票用 SPY+QQQ,SPY/QQQ 排除自身 |
| efficiency | 10 | Kaufman 效率比(30 日),裸值截断到 [0,1] |
### 风险否决层(只封顶不加分)
| 条件 | 封顶 |
|---|---|
| 价格 < 200 日线且 200 日线下行 | 55 |
| 价格 < 200 日线 | 70 |
| 周线均线空头排列 | 50 |
| 价格 < MA60 | 65 |
| 大盘 risk-off(SPY < 自身 MA200) | 65 |
### LLM 事件风控层(非对称,只降级)
致命红旗 → 封顶 50;软红旗/数据存疑 → 封顶 74;利好只回显不加分。
财报临近(Finnhub 日历,确定性):≤7 天 → medium(封顶 74,「是」降级);8–14 天 → low 仅提示。无 Finnhub key 时本层整体不生效。
### 确认层(不进加权,仅挡「是」)
technical ≥ 0.50、volume ≥ 0.40、trend_quality ≥ 0.40 全部满足才放行「是」;否则降为「观察(技术未确认)」。
### 入场时机层(不改 composite,只改新开仓结论)
- 过热:距 MA20 ≥3.0 ATR,或 RSI14≥80,或距 MA20≥2.2 ATR 且 RSI14≥75 → 原三层「是」降为「观察」。
- 回调拐点:近 30 日结构高点必须早于近 10 日低点,回撤 ≥4% 或近 5 日按同时点 MA20 确认触碰;当前完成日线为反转日且 ≥2 个触发信号。
- 拐点替代:仅在原三层因 technical/trend_quality 未确认而为「观察」时生效;volume 必须可计算且 ≥0.40,缺失不视为通过。
- 高置信超跌反转:趋势破坏后结构回撤 ≥12%,反转日涨幅 ≥3%、收盘强度 ≥75%、量比 ≥1.5、五类确认至少 4 类,且收盘不高于 MA20 0.75 ATR;200 日线仍上行、周线未空头排列、核心数据完整且无事件红旗时,允许独立受限试仓。
- 超跌反转不抬高 composite;最终「是」只表示独立时机覆盖,目标仓位封顶 15%,结构低点下方 1% 为初始止损。
- 分类阈值均为手调,需由 v1.2 forward log 按 `entry_timing` 分桶积累样本外证据。
### 结论阈值与仓位
composite ≥75 且无否决且确认通过 → 是;≥75 → 观察;≥60 → 观察;<60 → 否。仓位 = min(20%/年化波动, 1) × composite/100。低流动性(20 日均成交额 < $5M)只 flag 不封顶。
## 校准证据摘要(全部为样本内,2020-07→2026-06;口径:破产票终值 -100%、五分位扣单边 10bp)
**最重要的结论(2026-07-09 扩篮子对照):排名分的 edge 是篮子条件的。**
同一套代码、唯一变量是篮子,63 日非重叠:
| 篮子 | 全样本 IC | risk-on IC | 行业中性 IC | 五分位毛价差 |
|---|---|---|---|---|
| 旧:45 手挑 + 8 退市 | +0.076(t≈1.57) | **+0.103(t≈2.01, CI[+0.007,+0.200])** | +0.085(t≈2.36, CI[+0.016,+0.155]) | +1.85% |
| 新:132 规则化 + 8 退市 | +0.013(t≈0.35, CI 跨 0) | +0.030(t≈0.70, CI 跨 0) | +0.015(CI 跨 0) | **−0.64%** |
诚实解读:横截面 edge 只在手挑大盘票池内可测得(该篮子按"今天著名"入选,带后见之明),无法推广到宽篮子。**composite 应作纪律工具与风险过滤器使用,不是经过验证的选股 alpha。**
- Regime 条件性(旧篮子内):risk-on 有效、risk-off 反转——否决层存在的依据;宽篮子上两个 regime 都无 edge。
- 确认层 A/B(旧篮子):21 日放行组 +1.01% 优于封顶组;63 日洗平(-0.05%)。保留,证据不足待前瞻验证。
- 已知不单调:60-75 桶(63 日 +6.19%)高于 ≥75 桶(+4.68%),高分≠高确定性。
- 21 日五分位净价差为负:本评分只服务 ≥3 个月持有周期。
## 单标的时间序列验收(不属于模型校准)
### AAPL v1.1 基线
2026-07-10 用冻结后的 v1.1 对 AAPL 做了一次预注册口径的真实 cache-only 验收。区间为 2021-07-01 至 2026-07-09,输入是 `iex + split` 的 AAPL/SPY/QQQ final 日线,评估区间前保留 230 个 session 预热;信号在收盘后形成,仿真最早于下一交易日开盘成交,固定持有 21/63 个交易日,单边成本 10bp。新闻、账户、Snapshot、Finnhub、AI opinion 与 trade plan 止损止盈均未历史回填。
可复现标识:model `v1.1`、contract/artifact `1.0`、data hash `8bc3a7e9f6cbd682e717a4623ae7cac7280fb824da297f05f1ee94d3092cbade`、semantic hash `909b73190a64837b0e37c3700d41c2463b1e8cc44252dfeb47b080fb69c17268`。
| 事件研究窗口 | 描述样本 N | 非重叠推断 N | composite 对 SPY 超额收益的时间序列 IC |
|---|---:|---:|---:|
| 21 日 | 1,239 | 59 | −0.1942 |
| 63 日 | 1,197 | 19 | −0.3115 |
| 63 日路径 | 总收益 | CAGR | 最大回撤 | Sharpe | 交易数 |
|---|---:|---:|---:|---:|---:|
| rank-only 固定持有 | −15.1426% | −3.2172% | −30.7895% | −0.1281 | 8 |
| disciplined-verdict 固定持有 | +0.1813% | +0.0361% | −22.5687% | +0.0618 | 5 |
| AAPL buy-and-hold | +130.3534% | +18.0790% | −33.3205% | +0.7421 | — |
| SPY buy-and-hold | +74.5882% | +11.7374% | −25.3752% | +0.7430 | — |
自动结论为 **`inconclusive`**。主窗口 63 日的最终“是”组只有 1 个非重叠样本,低于预注册下限 8;非“是”组为 18 个,无法计算可靠的“是”组均值及组间差值置信区间。负 IC 与策略显著落后 buy-and-hold 都是必须保留的负面描述证据,但在当前样本约束下既不能确认评分有效,也不满足 `contradicts` 的双置信区间条件。该结果只描述 AAPL 这段历史,不能外推为宽市场结论,也不覆盖样本外 forward log。
### QQQ v1.2 缺陷修复验收
2026-07-11 在冻结 v1.2 反转与退出规则后,对 QQQ 2023-07-03 至 2026-07-09 做 cache-only 复跑。输入为 `iex + split` 的 QQQ/SPY final 日线,预热 260 个 session,单边成本 10bp。可复现标识:contract/artifact `1.3`、data hash `17c0f9e446e7f6aa2f9d71b7f49264d66622526b4fa736937ff713826412467d`、semantic hash `36a9ee0e12c77ccdfcaa9135dc478068366191e2ed4fbb87d43b247cd8ae8b09`。同一命令复跑得到相同语义 hash。
| QQQ 路径 | 总收益 | CAGR | 最大回撤 | Sharpe | 已平仓 / 未平仓 |
|---|---:|---:|---:|---:|---:|
| disciplined 21 日研究窗口 | +29.8777% | +9.0511% | −9.2382% | +0.8393 | 12 / 1 |
| disciplined 63 日研究窗口 | +50.0055% | +14.3851% | −13.5359% | +0.9619 | 7 / 1 |
| disciplined managed | +7.0928% | +2.2972% | −8.1633% | +0.3351 | 9 / 1 |
| QQQ buy-and-hold | +95.2880% | +24.8367% | −22.8377% | +1.2091 | — |
| SPY buy-and-hold | +69.3100% | +19.0680% | −18.9806% | +1.2522 | — |
机械缺陷已修复:QQQ 不再与自身比较,相对强度子分实际覆盖 38~89;最终“是”从旧 artifact 的 7 个 session 增至 97 个。2025-04-09 的 +11.62% 强反转保留原 composite `33.7`,分类为 `recovery_reversal` 并限制 15% 仓位;2025-04-10 以 453.67 入场,2025-05-07 在趋势退出信号后的下一开盘 482.095 离场,单笔净收益 +6.0531%。2026-05-15 的趋势仓则由 `overextended_reversal` 在下一开盘退出,净收益 +3.1298%。
这仍**不能证明评分有效**。63 日描述样本中,“是”组平均收益 −0.9585%,非“是”组 +1.7584%;非重叠样本只有 3 / 9,未达到“是”组至少 8 个的门槛。“是”减非“是”的 95% CI 为 `[−6.8898%, −0.8260%]`,方向明确不利;“是”组 SPY 超额的 95% CI 为 `[−3.4917%, +0.7061%]`,仍跨 0,因此冻结规则下自动状态是 **`inconclusive`** 而非 `contradicts`。managed 从旧 v1.1 artifact 的 −7.4479% 改善到 +7.0928%,但仍大幅落后 QQQ 买入持有;这次交付只证明自基准、反转识别、仓位和退出执行缺陷被修复,不证明已经获得可推广 alpha。
#### QQQ managed 落后基准归因
同日对 canonical artifact 做了只读、确定性归因,不修改规则。managed 只有 32.63% 的时间持仓,全周期平均实际资本投入约 24.01%;把每次建议仓位反事实提高到 100%、保持全部入退场不变,收益也只有 +14.4020%,所以仓位低是次因,不是主因。退出到下一次入场之间累计 509 个交易 session,空档中位数 44、最长 130;这些互不重叠的空档里 QQQ 执行价路径复合上涨 +68.22%。7 个具备完整后续窗口的退出,退出后 63 日平均上涨 +8.08%,其中 6 个为正。
评分与持仓状态机存在结构错配:退出使用止损、1R 保本、移动保护和 MA60 下行破位等较快条件;重新入场仍要求以 3/6/12 个月动量和相对强度为主的慢评分重新达到最终“是”。97 个“是”分布在 29 个连续簇中,87 个因已有仓位而跳过,只形成 10 次实际入场。全部“是”日的 63 日描述均值为 −0.9585%,但每个“是”簇首日的 63 日均值为 +3.03%、胜率 78.3%,说明信号并非完全无方向,而是持续状态被当成重复点事件,且退出后缺少独立、较快的再入场状态。
QQQ 样本还显示慢趋势资格与指数反弹方向相反:63 日高分桶均值 −0.7620%,低于 60 分桶 +4.0372%;风险闸门触发日 +4.5808%,`trend_broken` +5.3592%。按连续段首个信号去重后,11 个 `trend_break` 退出点之后 63 日全部上涨,均值 +13.79%;`overextended_reversal` 只有 2 个样本,21 日均值 −1.37%,方向更符合保护利润但证据不足。诊断反事实中,关闭单一退出组件不能修复结果:取消持仓信号退出为 +8.6780%,取消保本为 +2.9174%,取消移动保护为 +7.3903%;只有退化为接近长期持有的“仅初始止损”才升至 +63.4978%(全仓为 +82.7570%)。这些反事实不可相加,也不是新策略建议;它们说明首要问题是“快出慢进”的状态架构和将横截面选股分数直接用作 QQQ 二元择时,而不是某一个止损百分比。
#### Stateful contract 1.4 holdout 决定
在运行任何 QQQ 新 stateful 结果前,预注册 16 标的、2025-04-04~2026-07-09 cache-only holdout,并冻结收益、Sharpe、基准捕获率、回撤和换手联合门槛。16/16 标的完成后决定为 **reject**:收益改善率 43.75%(要求至少 60%)、收益差中位数 −0.3767%、Sharpe 差中位数 −0.1017、正 buy-and-hold 标的捕获率差中位数 −0.5331 个百分点;最大回撤差中位数 −4.9427 个百分点,也超过允许恶化 3 个百分点的边界。换手比中位数 1.8167 是唯一通过的执行约束。
该实验改善 AAPL、WMT、CAT、JPM 等部分标的,但在 MSFT、NVDA、TSLA、GOOGL 等标的降低收益并扩大回撤,证明统一放宽重新入场并不具备跨标的一致性。contract 1.4 stateful 保留为 rejected experiment,不进入正式评分/持仓纪律;最终报告 semantic hash 为 `ced3ecef9a62adfa6b873f302fae75cd75b9443df99efd020708b9920876e71f`。同一 holdout 不再用于选择下一组参数。
冻结 reject 决定后才运行 QQQ contract 1.4 复验。正式 managed 基线逐字段保持不变:总收益 +7.0928%、最大回撤 −8.1633%、Sharpe 0.3351、时间暴露 32.63%。已拒绝的 stateful 实验为 +63.8703%、最大回撤 −12.2365%、Sharpe 1.2299、时间暴露 86.79%,而 QQQ 买入持有仍为 +95.2880%。该局部改善支持“QQQ 的快出慢进是主要缺陷”这一诊断,却不能推翻先验冻结的跨标的 reject;更合理的后续假设是区分指数 ETF 与个股生命周期,而不是把统一状态机升级为生产策略。QQQ artifact semantic hash 为 `88399589e252a3c3fbfc47aa1f0d756a889b3e959e4be09f09649e44efcabd34`。
## 已知局限
1. **排名 IC 在宽篮子上不可测得(见上表)**;否决层/确认层/事件层的风险过滤价值不依赖排名 IC,但"高分票更可能跑赢"这一主张目前没有可推广的证据。
2. 全部校准是样本内的;本 model card + `scripts/forward_log.py` 是样本外验证的起点。
3. 样本只含 2022 一个熊市,regime 结论本质 n=1。
4. 隐藏自由度:logistic 参数、确认阈值、封顶值、75/60 分界均无独立校准依据。
5. 全市场 scanner 的排名尤其只应作纪律过滤解读,不是 alpha 排序。
6. 入场时机阈值尚无独立样本外校准;v1.0 的 46 条历史记录不含时机字段,不得与 v1.1 时机 A/B 混算。
7. AAPL v1.1 与 QQQ v1.2 单标的时间序列验收均为 `inconclusive`;QQQ 的 63 日“是”组相对非“是”组置信区间甚至显著为负。单一标的、split-adjusted 价格和稀少非重叠信号不能替代跨标的、含股息、样本外验证。
## 变更记录
- v1.2(2026-07-11):修复 SPY/QQQ 相对自身比较导致的系统性压分;新增与趋势排名分分离的高置信超跌反转试仓路径,冻结深度、量能、强收盘、多重确认、长期趋势和 15% 仓位约束。该路径来自 QQQ 缺陷审计,参数在重跑新 artifact 前冻结,历史 v1.1 结果不合并。
- v1.1(2026-07-10):新增并冻结第四层入场时机语义;修正回调高低点时序、同时点 MA20 触碰与量能缺失放行。forward log 新增时机分类/介入前结论,股票与 SPY 统一从信号日后下一交易日收盘开始对账。v1.0 历史记录保留 legacy 口径并按版本隔离。
- v1.0(2026-07-09):首次冻结。含同日两项口径修正(破产终值、交易成本)、确认层 A/B 决策(保留,证据不足)、财报临近确定性软封顶(≤7 天 medium)。财报闸门与冻结同日落地、尚无跨版本样本,并入 v1.0;注意 `forward_log.py record` 走纯价量管道(无 Finnhub overlay),其 verdict 不受财报闸门影响。
- v1.0 证据更新(2026-07-09 同日,参数未变不 bump):扩篮子对照(132 规则化 vs 45 手挑)确认排名 edge 是篮子条件的,校准证据摘要与已知局限已重写;评分参数刻意不动(在噪声样本里重新拟合只会更糟),以 forward log 样本外证据为后续决策准绳。
@@ -1,50 +0,0 @@
# Refactor: 全 skill 统一结构化日志
> **日期**: 2026-07-10
## 动机
脚本曾混用 `sys.stderr.write`、root `logging.*`、import-time `basicConfig` 和 warm-market 自写 JSONL。不同入口的格式、上下文与脱敏能力不一致,也容易污染 stdout 的 JSON artifact、终端 K 线和回测报告。
本次引入 `structlog==26.1.0`,把所有运行诊断统一到 stderr,同时保持既有产品输出与退出码协议。
## 变更映射
| Before | After |
|---|---|
| 各 CLI 自行写 stderr 或配置 root logger | `scripts/skill_logging.py` 统一配置 `worth_buy_stocks.*` 命名空间 |
| warm-market 自行序列化 progress JSON | progress/heartbeat 作为普通结构化事件进入同一 processor chain |
| 只有自由文本,难以稳定解析 | UTC timestamp、level、snake_case event、logger 加按需上下文字段 |
| 无统一敏感信息处理 | 字段、URL、header、环境赋值和 traceback 在 renderer 前递归脱敏 |
| 日志格式固定或分叉 | `auto` 在 TTY 输出 console、非 TTY 输出 JSONL,可由 CLI/env 覆盖 |
| 新代码可能重新引入 raw stderr | AST guard 阻止绕过 facade、root logger 和独立 JSON log writer |
stdout 契约没有改变:JSON CLI 仍输出单一 JSON 文档,chart/backtest/forward-log 仍输出原终端或研究报告。所有日志只写 stderr。
## 修改文件
| 文件 | 变更 |
|---|---|
| `requirements.txt` | 精确锁定 `structlog==26.1.0` |
| `scripts/skill_logging.py` | 统一参数、handler、processor、console/JSONL、contextvars、stdlib bridge 与脱敏 |
| `scripts/indicators.py`、`scripts/scanner.py`、`scripts/chart.py` | 迁移告警、AI 提示、artifact 与顶层异常 |
| `scripts/cache_admin.py` | 迁移 warm/reconcile/sync progress、heartbeat 与失败事件 |
| `scripts/backtest.py`、`scripts/backtesting/*.py`、`scripts/forward_log.py` | 使用统一日志门面,保留 stdout 研究报告 |
| `scripts/notify_telegram.py`、`scripts/validate_agent_contract.py`、`scripts/ai_opinion.py` | 接入统一 CLI 配置与结构化错误事件 |
| `tests/test_skill_logging.py` | facade、并发、幂等、stdlib bridge、JSON/console 与脱敏测试 |
| `tests/test_logging_guard.py` | 全仓日志静态守卫 |
| `tests/test_logging_entrypoints.py` | 离线 stdout/stderr 端到端协议测试 |
| `README.md`、`references/setup.md`、`scripts/README.md`、`SKILL.md` | 安装、参数、通道、schema 与维护约束 |
## 验证
- Python 3.10.0 成功导入 `structlog 26.1.0`;系统 Python 3.9 明确不在支持范围。
- 11 个公共 CLI 的 `--help` 均包含 `--log-level` 和 `--log-format`。
- raw stderr、root `logging.*`、`logging.basicConfig` 和独立 progress serializer 静态扫描为零。
- facade 10 项、静态 guard 3 项、离线入口 5 项专项测试通过。
- cache-admin status/integrity 实测 `ok`;3-symbol warm-market dry-run 为 covered 2、pending 1、pages 0,stdout/stderr 分离测试通过。
- `python3 -m unittest discover -s tests -v`:456 passed。
- `python3 -m pytest -q`:460 passed。
- `python3 -m compileall -q scripts tests/test_skill_logging.py` 与 `git diff --check` 通过。
*创建于: 2026-07-10*