Files
ggx/README.md
T
simon cf6d4d2c56 功能:每日动态股票池回测(--mode daily)+ 每日增量同步 + PIT 批量取数层
说明:本提交是工作区中此前的未提交工作(在 14ec0c6 之后产生),**非本次会话所写**,
按用户要求整理并推送。已做安全检查(无明文凭据、无大文件、.env/logs/output 仍被忽略),
并完成可执行范围内的测试验证(见「测试」一节)。

## 新增能力

1) `hdiv backtest --mode daily --start <日期>`
   - src/hdiv/backtest/daily.py:两趟式(先逐日选股,再复用既有引擎模拟)
   - 每个交易日按当日可见数据重建股票池(PIT),每个交易日判断买卖点
   - `pool_exit_action`:hold(只减不加、不因掉出池子而清仓)/ sell(掉出即清仓)
   - `profile_on_trade`:买卖决策发生时计算并留痕个股画像,**不区分是否在当日池内**
     (卖出/减仓同样留痕,否则「为什么卖」缺证据)
   - 与 walkforward 的分工:daily 是一条连续路径的推演,不是过拟合检验;
     因此不使用训练段、不冻结分布,阈值口径一律 rolling
   - 拒绝 `--universe-run`(daily 的定义就是逐日重筛,冻结池与之矛盾)

2) PIT 批量取数层 src/hdiv/universe/pit.py
   - PitRepo 继承 Repo,**只重写取数**(按区块批量预载 + 逐日内存切片),
     派生逻辑(最新一期财报合并、单位归一化、支付率口径等)一行不重写
     —— 以保证与逐日单点查询**结果等价**
   - 候选集预剪枝:用「不可能通过」的边界条件提前排除,文档论证为精确等价而非近似
   - src/hdiv/universe/daily.py:每日动态筛选器(仍然调用既有 selector 与四个 Filter)

3) 每日增量同步 `hdiv sync daily`
   - src/hdiv/data/sync/daily.py:只抓「库里还没有的那几天」,
     按「当日股票数 ≥ 当年规模阈值」判定缺口,不重拉历史、不覆盖既有行;
     支持 `--dry-run` 先看待抓清单
   - deploy/daily-sync.sh、deploy/install-sync-schedule.sh、
     deploy/com.hddiv.sync.plist.example(launchd 每天 17:00)
   - 新表 hd_daily_universe(逐日入选成员留痕)+ sql/hd_daily_universe.sql + schema.py
     (该表已存在于库中,`ddl plan` 返回 0 个待执行动作)

4) Web 与文档
   - 前端支持 daily 模式记录下钻(web/app.js、web/app.css、web/index.html、
     web/favicon.svg)
   - README / docs/user-guide.md / docs/implementation-status.md 同步更新:
     三种回测模式的取舍、daily 的成本说明(6.7 年约 1.5 小时)与调优手段

## 测试

tests/ 共 500 项(新增 tests/test_daily.py 43 项、tests/test_sync_daily.py 36 项)。

已验证通过:
- 排除上述两个新文件的 **421 项:全部通过(pytest 退出码 0)**
- 两个新文件的**非 DB 单元测试 60 项:全部通过**

未能在合理时间内跑完:
- 两个新文件中 **19 项 DB 标记的重型测试**。实测瓶颈是一条**无界全表扫描**:
  `SELECT ... FROM hd_cashflow WHERE ann_date <= :asof ORDER BY symbol, end_date, ann_date`
  (31 万行,无 symbol/报告期下限)。全量套件跑到 161 项时已耗时 20 分钟、
  0 失败,按该速率预计需 3 小时以上,因此改为分档验证。
- 旁证:库中存在 3 次成功的 daily 端到端运行(2026-10-05 10:05 / 10:32 / 11:03,
  区间 2024-03-01~03-15),说明该路径可正常完成。

## 已知待改进

- 上述 `hd_cashflow`(及同类「按 ann_date 上界取全历史」)的查询缺
  symbol / 报告期下限,是 daily 模式的主要性能瓶颈,建议下一轮优化。
2026-10-05 11:57:13 +08:00

184 lines
7.7 KiB
Markdown
Raw 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
# 日常增量:只抓「库里还没有的那几天」,不重拉历史
.venv/bin/python -m hdiv sync daily --dry-run # 先看待抓清单
.venv/bin/python -m hdiv sync daily # 真抓
# 注册为每天 17:00 的 launchd 定时任务
./deploy/install-sync-schedule.sh install
# 审计 → 打开 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/ 439 项自动化测试(含每日动态股票池的取数等价性、预剪枝等价性、PIT 纪律与端到端)
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)。
---
## 三种回测模式(不要混用)
| 模式 | 命令 | 回答的问题 |
|---|---|---|
| 单条路径 | `hdiv backtest [--universe-run <id>]` | 某个固定池/周期重筛下的全期表现 |
| 样本外 | `hdiv backtest --mode walkforward` | 参数在**未知未来**能否复现(过拟合检验) |
| **每日动态池** | `hdiv backtest --mode daily --start 2020-01-05` | 从某天起**每个交易日重新选股**连续推演会怎样 |
`--mode daily` 的特点是**股票池每天都在变**:每个交易日按当时可见数据重建池子
(PIT),每个交易日判断买卖点;持仓掉出当日池子默认**只减不加**(不清仓),
买卖决策都会留下个股画像证据,每日入选成员落库到 `hd_daily_universe`。
> **成本要说清楚**:逐日全市场筛选是重活 —— 6.7 年(约 1600 个交易日)约需
> **1.5 小时**;1 年约 20 分钟,3 个月约 5 分钟。命令启动时会打印预计时长。
> 要缩短时间,可缩短区间,或把 `config/backtest.yml` 的
> `daily.universe_refresh_days` 调大(例如 5 = 每周选股、每日判断买卖 ——
> 这是真实的语义取舍)。详见[使用手册 §5.7b](docs/user-guide.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 接口契约。