Files
simon 14ec0c6c86 修复:量价单位 / 未来函数守卫 / 实时画像闸门;行情回补到 2005;手册补全流程
本轮会话的三项正确性改造(均为「不报错、只让结果静默错」的类型):

1) 修复 stock_daily 量价单位前后不一致
   - 现象:2015-2019 存 Tushare 原始单位(手/千元),2020 起存(股/元),2019 同日混合;
     而流动性阈值按「元」配置 → 早年门槛实际是「日均成交额 ≥ 200 亿元」,
     把 2015-2019 的股票池整体清空(实测 2016/2017/2018 各选出 0 只)。
   - 修复:写入端 sync/price.py 统一换算;读取端 units.normalize_ohlcv_units
     按行判定并幂等换算(price_history / avg_amount 都走它);
     审计新增 UNIT-OHLCV 防回归。
   - 效果:2016/2017/2018 的股票池变为 7/11/13 只。

2) 未来函数守卫(单次回测)
   - 股票池自带 asof:若晚于回测起点即**拒绝执行**(原先静默冻结套用),
     与 walk-forward 已有的拒绝理由一致;确需复现加 --allow-lookahead-universe,
     偏差写入 unimplemented_json。

3) 新增实时(PIT)个股画像闸门
   - profile/pit.py:每个决策日按当时可见数据重算过去 5 年画像,
     惰性(仅买入条件已触发的标的)、面板按 asof 缓存、
     规则不含财务指标时不查财报表;被剔除时产出 REJECT + 逐规则留痕。
   - 指标定义复用 ProfileBuilder._profile_one(与批量画像逐值等价的回归测试)。
   - profile/coverage.py:窗口覆盖率(按交易日历的真实开市天数),
     策略新增 entry.profile_gate.min_window_coverage(默认 0,不改变既有行为)。
   - core/metrics.py:闸门可用指标的唯一定义(配置期即校验,避免写错指标名静默失效)。

4) 行情回补到 2005(使 5/8/10 年窗口真正完整)
   - stock_daily / adjust_factor / daily_basic 补到 2005-01-04;
     hd_suspend / hd_limit 补到 2010-01-04。
   - 5 年窗口覆盖率:2018-05-18 由 67.0% → 99.1%,2016-12-30 由 39.8% → 99.0%;
     残差经逐日与 hd_suspend 交叉核实为真实停牌(16/16 命中)。
   - 审计 G2/G3 与断点续传原先用固定阈值(2000 / 1500 只),
     会把 2005-2009 的正常数据误判为异常 —— 改为按「当年应有上市股票数」成比例判定。
   - 节流修正:daily/adj_factor/daily_basic 限频 480 → 170(实测该 token 约 196/min 即被拒)。

5) 自我声明如实化
   - 原先「约束未生效」由「过滤后集合为空」判定,会把「这批股票恰好没停牌」
     误报成「hd_suspend 无数据」;改为按表级判定。
   - 补齐此前静默的「配置承诺但未实现」项:suspended_rule/limit_up_down_rule 的 defer、
     cash_mode=reinvest/reinvest_rule、handle_rights_issue、signal_to_execution、
     max_volume_pct、liquidity_limit_pct_adv —— 全部写入 unimplemented_json。

6) 手册:新增 §0「全流程操作(选股 → 画像 → 回测)」置于最前
   - 逐步说明「命令做了什么、数据从哪来、落了哪些库、有哪些坑」;
     含实时画像闸门 9 问 9 答、未来函数守卫表、成交与成本口径、验证 SQL。
   - 修正旧 §2.4 漏传 --universe-run(选了池子却没用于回测);
     修正两处声称「停牌顺延」「分红再投资」已实现的相反表述。

测试:403 项全部通过(含新增 test_units.py、test_profile_pit.py、
未实现声明诚实性测试、行序无关性回归测试)。

注意:本提交中 docs/*、README.md、src/hdiv/web/service.py 除本轮修改外,
也含此前遗留的未提交改动(无法按文件切分)。
2026-10-04 12:47:17 +08:00

156 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 高股息回测系统
A 股 **高股息 + 安全边际 + 估值均值回归** 策略的研究与回测系统。
从 Point-in-Time 股票筛选 → 个股特性画像 → 策略定义 → 历史回测 →
Walk-forward 样本外验证 → 绩效与敏感性分析 → **统一 Web 前端**,全链路打通。
采用**前后端分离**:前端为 `output/` 下的单页应用(hash 路由,nginx 直接托管),
后端为 `hdiv web` 提供的 REST API(nginx 反代 `/api`)。
---
## 快速开始
```bash
cd ~/project/高股息回测
cp .env.example .env # 填入数据库密码与 Tushare token
export PYTHONPATH=src
# 建表
.venv/bin/python -m hdiv ddl apply
# 同步数据(首次约 3~4 小时,可后台跑)
.venv/bin/python -m hdiv sync dividend --only-missing
.venv/bin/python -m hdiv sync financial --interleaved --only-missing
.venv/bin/python -m hdiv sync index
.venv/bin/python -m hdiv sync trading --start 2019-01-01
# 审计 → 打开 output/index.html
.venv/bin/python -m hdiv audit
```
完整用法见 **[使用手册](docs/user-guide.md)**。
---
## 文档
| 文档 | 内容 |
|---|---|
| **[使用手册](docs/user-guide.md)** | 安装、配置、命令、报告解读、故障排查 |
| [实施状态](docs/implementation-status.md) | 交付清单、验收映射、已修复问题、**实测结论与限制** |
| [开发计划](docs/development-plan.md) | 架构设计、数据库设计、分阶段计划 |
| [需求原文](docs/plan.md) | 项目的原始方案与验收标准 |
---
## 目录
```text
config/ ★ 所有可调参数(YAML,代码零硬编码)
src/hdiv/ 代码:数据层、筛选、画像、策略、回测、分析、报告
templates/ HTML 模板(Jinja2,离线)
assets/ 图表库(ECharts,本地化,不依赖 CDN)
output/ ★ 报告输出(部署这个目录)
sql/ 建表 SQL 副本(供人工审查)
tests/ 262 项自动化测试
docs/ 文档
```
---
## 三条设计原则
| 原则 | 落实方式 |
|---|---|
| **配置零硬编码** | 任何业务阈值只出现在 `config/*.yml`;字段名写错直接报错 |
| **只增不删** | SQL 钩子拦截 `DELETE`/`DROP`/`TRUNCATE`;源码扫描测试守护 |
| **无未来函数** | 所有取数必经 `data/repo.py`,PIT 纪律有负例测试 |
---
## 重要提示
**本系统的实测结论是:策略没有稳定的样本外超额收益。**
全期回测(2015–2026)显示 +92.73% / CAGR 5.75%,
但 7 窗口 Walk-forward 的样本外收益均值仅 **+1.16%**(基准 +2.29%,超额 **−1.12pp**)。
它真正的价值在于**回撤控制**(样本外最差 −22.97%,基准同期 −46%~−52%)——
更像降低波动的配置工具,而非超额收益来源。
> **四条必须说的结论**(详见[实施状态 §4.6c/§4.6d/§9](docs/implementation-status.md)):
>
> 1. **修数据让结果变差、但变真实了。** 行情原先只到 2015-01-05,
> 使「过去 5 年」窗口在 2019 年前被静默截短(覆盖率仅 20%~67%)。
> 2026-10-04 已把行情回补到 **2005-01-04**、涨跌停/停牌到 **2010**,
> 5 年窗口覆盖率提升到 **98.7%~100%**。代价是全期收益从 +117.36%
> 降到 **+92.73%** —— 因为 **2015 年(牛市顶 + 股灾)从「被数据缺口
> 挡住」变成被真实交易(−3.18%)**。靠数据缺口躲过股灾不是策略能力。
> 2. **实时画像闸门在两个口径下结论相反**:
> 单条路径 **−8.47pp**(有害),样本外 **+1.16pp**(有益)。
>
> | 口径 | 闸门开 | 闸门关 |
> |---|---:|---:|
> | 全期单路径总收益 | +92.73% | **+101.20%** |
> | Walk-forward 样本外均值 | **+1.16%** | −0.00% |
> | 样本外最差回撤 | **−22.97%** | −24.22% |
>
> **按本项目一贯立场以样本外为准**:闸门是改善。是否启用由你决定
> (`entry.profile_gate.enabled`);全期剔除 1027 次买入信号。
> 3. **同一项数据修正,在两个口径下的「效果」相差 32.9pp**
> (回补对样本外贡献 **0** —— 7 个窗口逐窗口未变;对单路径贡献 −32.9pp)。
> 这是「不要采信单条路径」最有力的例证。
> 4. **`stock_daily` 的量价单位曾前后不一致**(2015-2019 存「手/千元」,
> 2020 起存「股/元」),使流动性门槛在早年低估 1000 倍、**把 2015-2019
> 的股票池整体清空**。已修复(读取层幂等归一化 + 审计 `UNIT-OHLCV` 防回归)。
**请始终以 Walk-forward 的样本外结果为主要依据,不要采信单条路径的全期数字。**
详见[实施状态 §4.6](docs/implementation-status.md)。
---
## Web 部署(前后端分离)
```bash
# 1) 归一化站点目录(归档历史 + 同步前端)
.venv/bin/python -m hdiv site normalize
# 2) 本地预览
./deploy/serve.sh start-dev # http://127.0.0.1:8099/
# 3) 生产:同步前端 + 启动后端(仅 API)
rsync -av --delete output/ user@host:/srv/hddiv/site/
./deploy/serve.sh start # 监听 127.0.0.1:8099 --api-only
```
nginx 需做两件事:托管 `output/`、把 `/api` 反代到后端。
配置模板见 **[deploy/nginx.conf.example](deploy/nginx.conf.example)**(含子路径与站点根两种布局)。
验证:
```bash
curl http://<host>:8080/ggx/api/health # 应返回 {"ok": true, ...}
```
浏览器打开首页后,右上角应显示 **「API 正常」**;若显示「API 不可用」,
说明 nginx 的 `/api` 反代未生效。
> 静态报告(`output/reports/`)通过相对路径引用 `assets/echarts.min.js`,
> 因此部署时**必须整体同步 `output/` 目录**,不能只拷 HTML。
>
> 静态报告是**导出件**,默认不生成(SPA 已提供同样内容)。需要离线单文件快照时
> 加 `--html` 导出,文件名带 run_id,同一天多次运行不会互相覆盖。
---
## 测试
```bash
.venv/bin/python -m pytest tests/ -q
```
覆盖:配置校验(含 18 类非法配置必须被拒)、SQL 安全约束、表结构幂等性、
单位换算与量级检测、PIT 纪律、分红口径、回测资金对账、参数阶梯、CLI 接口契约。