从 Point-in-Time 股票筛选到统一 Web 前端的完整链路: 筛选 → 画像 → 策略 → 回测 → Walk-forward → 绩效分析 → 报告/前端。 架构 - 数据层与策略层分离;策略代码不写 SQL,只经 data/repo.py 取数 - 所有业务阈值集中在 config/*.yml,代码零硬编码(字段写错直接报错) - 报告只做「run_id → SQL → 渲染」,不做任何计算,数字可追溯 - 前后端分离:output/ 静态站点 + hdiv web 提供的 REST API 数据安全 - 只增不删:SQL 钩子拦截 DELETE/DROP/TRUNCATE,并有源码扫描测试守护 - qlib 原有表只读,本项目数据写入 hd_ 前缀表 - 回补使用 INSERT IGNORE,保证既有行零改动 - .env 存密钥且已 gitignore;output/、logs/、.venv/ 不入库 交付物 - 30 张 hd_* 表、7 个 YAML 配置、283 项自动化测试 - 统一 Web 前端(hash 路由 SPA)+ nginx 部署配置与 launchd 托管脚本 如实声明的限制 - 策略缺少稳定的样本外超额收益(Walk-forward 7 窗口均值 -0.95%, 基准 +2.29%);其价值体现在回撤控制,而非超额收益 - 涨跌停/停牌约束仅覆盖 2019 年起;index_weight 尚未填充 - AI Agent 层(plan.md 第四版 P8)未实现 详见 docs/user-guide.md 与 docs/implementation-status.md。
1510 lines
56 KiB
Markdown
1510 lines
56 KiB
Markdown
# 高股息回测系统 · 使用手册
|
||
|
||
> 版本 1.0 · 对应代码 `hdiv 0.1.0`
|
||
> 相关文档:[实施状态](implementation-status.md) · [开发计划](development-plan.md) · [需求原文](plan.md)
|
||
|
||
---
|
||
|
||
# 目录
|
||
|
||
1. [系统是什么](#1-系统是什么)
|
||
2. [快速开始](#2-快速开始)
|
||
3. [目录与架构](#3-目录与架构)
|
||
4. [配置文件详解](#4-配置文件详解)
|
||
5. [命令行手册](#5-命令行手册)
|
||
6. [典型工作流](#6-典型工作流)
|
||
7. [报告解读](#7-报告解读)
|
||
8. [数据库](#8-数据库)
|
||
9. [关键口径](#9-关键口径)
|
||
10. [Web 前端与部署](#10-web-前端与部署)
|
||
11. [故障排查](#11-故障排查)
|
||
12. [已知限制](#12-已知限制)
|
||
|
||
---
|
||
|
||
# 1. 系统是什么
|
||
|
||
一套 **A 股高股息策略的研究与回测系统**。它把下面这条链路串成一条可复现的流水线:
|
||
|
||
```text
|
||
Point-in-Time 股票筛选 → 个股特性画像 → 策略定义 → 历史回测
|
||
→ Walk-forward 样本外验证 → 绩效与敏感性分析 → HTML 报告
|
||
```
|
||
|
||
**设计目标不是「预测明天涨跌」**,而是回答:
|
||
|
||
- 哪些股票长期稳定分红、财务质量好、规模大、上市够久?
|
||
- 这只股票当前的股息率,在它自己的历史里处于什么位置?
|
||
- 「股息率进入历史高位时买入、回落到低位时卖出」这条规则,历史上真的有效吗?
|
||
- 换一组参数,结论还成立吗?样本外呢?
|
||
|
||
## 1.1 它做什么
|
||
|
||
| 能力 | 说明 |
|
||
|---|---|
|
||
| PIT 股票池 | 任意历史时点按当时**可见**的数据筛选,无未来函数 |
|
||
| 消除生存者偏差 | 历史股票池包含此后退市的股票 |
|
||
| 个股画像 | 股息率/PE/PB 历史分布、P10–P90 分位、分红质量、安全边际分项得分 |
|
||
| 参数化策略 | 买卖阈值、分批建仓、仓位与风控全部写在 YAML |
|
||
| 回测引擎 | 不复权价 + 独立分红现金流、涨跌停/停牌约束、A 股完整成本 |
|
||
| Walk-forward | 训练段定阈值 → 测试段冻结,逐年样本外检验 |
|
||
| 参数敏感性 | 量化「策略对参数是否敏感」,识别过拟合 |
|
||
| 固定格式报告 | 离线 HTML,数据全部来自数据库,带 SQL 溯源 |
|
||
|
||
## 1.2 它不做什么
|
||
|
||
- **不预测涨跌**,不做择时、不做日内、不做高频
|
||
- **不自动交易**,不接券商接口
|
||
- **不做 AI 策略生成**(`plan.md` 第四版 P8,尚未实现)
|
||
- 不做多因子机器学习选股(框架支持扩展,但当前只实现股息率单因子)
|
||
|
||
## 1.3 数据来源
|
||
|
||
- **数据库**:本机 MariaDB `127.0.0.1:3306/qlib`,与 `~/project/qlib` 项目**共用**
|
||
- **行情与财务**:Tushare Pro
|
||
- 本项目只在该库中新增 `hd_` 前缀的表;**qlib 的原有代码零改动**
|
||
|
||
---
|
||
|
||
# 2. 快速开始
|
||
|
||
## 2.1 环境要求
|
||
|
||
| 项 | 要求 |
|
||
|---|---|
|
||
| Python | 3.12 |
|
||
| 数据库 | 本机 MariaDB / MySQL,可访问 `127.0.0.1:3306` |
|
||
| Tushare | 有效的 Pro token(写权限用于同步数据) |
|
||
| Node.js | 可选,仅用于报告 JS 语法自检 |
|
||
|
||
## 2.2 一次性配置
|
||
|
||
```bash
|
||
cd ~/project/高股息回测
|
||
cp .env.example .env # 若 .env 已存在则跳过
|
||
```
|
||
|
||
编辑 `.env` 填入凭据(**该文件已在 .gitignore 中,切勿提交**):
|
||
|
||
```ini
|
||
MYSQL_HOST=127.0.0.1
|
||
MYSQL_PORT=3306
|
||
MYSQL_DB=qlib
|
||
MYSQL_USER=qlib
|
||
MYSQL_PASSWORD=<你的数据库密码>
|
||
TUSHARE_TOKEN=<你的 tushare token>
|
||
```
|
||
|
||
> `.env` 只放密钥。所有**业务阈值**都在 `config/*.yml` 里,两者不混。
|
||
|
||
## 2.3 三步跑通
|
||
|
||
```bash
|
||
cd ~/project/高股息回测
|
||
export PYTHONPATH=src
|
||
|
||
# ① 建表(幂等,反复执行安全)
|
||
.venv/bin/python -m hdiv ddl apply
|
||
|
||
# ② 同步数据(首次约需 3~4 小时,可后台跑,详见 §6.2)
|
||
.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 audit
|
||
|
||
# 打开 output/index.html
|
||
```
|
||
|
||
## 2.4 完整跑一遍策略研究
|
||
|
||
```bash
|
||
export PYTHONPATH=src
|
||
|
||
# 股票池(时点:2024-06-28)
|
||
.venv/bin/python -m hdiv universe --asof 2024-06-28
|
||
# → 记下输出的 run_id
|
||
|
||
# 个股画像(对股票池内全部股票)
|
||
.venv/bin/python -m hdiv profile --universe-run <上一步的 run_id>
|
||
|
||
# 登记策略
|
||
.venv/bin/python -m hdiv strategy register
|
||
|
||
# 回测(默认区间取 config/backtest.yml 的 period)
|
||
.venv/bin/python -m hdiv backtest
|
||
|
||
# Walk-forward 样本外验证(约 25 分钟)
|
||
.venv/bin/python -m hdiv backtest --mode walkforward
|
||
|
||
# 参数敏感性
|
||
.venv/bin/python -m hdiv sensitivity
|
||
```
|
||
|
||
---
|
||
|
||
# 3. 目录与架构
|
||
|
||
## 3.1 目录结构
|
||
|
||
```text
|
||
高股息回测/
|
||
├── config/ ★ 你要改的东西都在这里
|
||
│ ├── datasource.yml 数据库 / Tushare / 只读白名单
|
||
│ ├── universe.yml ★ 股票筛选条件
|
||
│ ├── profile.yml ★ 个股特性配置
|
||
│ ├── cost.yml 佣金 / 印花税 / 滑点 / 红利税
|
||
│ ├── backtest.yml 区间 / 调度 / Walk-forward / 基准
|
||
│ ├── report.yml 报告与图表
|
||
│ └── strategy/
|
||
│ └── high_dividend_v1.yml ★ 策略定义
|
||
├── src/hdiv/ 代码
|
||
│ ├── core/ 配置加载与校验、路径
|
||
│ ├── data/ 数据库访问、同步器、审计、单位归一化
|
||
│ ├── universe/ 股票池筛选(4 类滤网)
|
||
│ ├── factor/ 股息率因子
|
||
│ ├── profile/ 个股画像
|
||
│ ├── strategy/ 策略注册与版本
|
||
│ ├── backtest/ 回测引擎、Walk-forward
|
||
│ ├── analysis/ 绩效、风险、敏感性
|
||
│ ├── report/ 报告装配与渲染
|
||
│ └── cli.py 命令行入口
|
||
├── templates/ HTML 模板(Jinja2)
|
||
├── assets/echarts.min.js 图表库(离线,1MB)
|
||
├── output/ ★ 报告输出(部署这个目录)
|
||
├── sql/ 建表 SQL 副本(供人工审查)
|
||
├── tests/ 224 项自动化测试
|
||
├── .env 密钥(不提交)
|
||
└── docs/ 文档
|
||
```
|
||
|
||
## 3.2 数据流
|
||
|
||
```text
|
||
Tushare ──► 同步器 ──► MariaDB(qlib)
|
||
├─ qlib 原有表(只读:stock / stock_daily / daily_basic …)
|
||
└─ hd_* 新表(读写)
|
||
│
|
||
config/*.yml ──► 配置校验 ────┤
|
||
▼
|
||
筛选 → 画像 → 策略 → 回测 → 分析
|
||
│
|
||
▼
|
||
报告渲染(只读 DB,不做计算)
|
||
▼
|
||
output/*.html
|
||
```
|
||
|
||
## 3.3 三条硬边界
|
||
|
||
| 边界 | 含义 |
|
||
|---|---|
|
||
| 数据层 ≠ 策略层 | 策略代码不写 SQL,只能调 `data/repo.py` |
|
||
| 策略 ≠ 回测引擎 | 策略只产信号,引擎只执行信号 |
|
||
| 图表 ≠ 业务 | 报告只做「run_id → SQL → 渲染」,不做任何计算 |
|
||
|
||
---
|
||
|
||
# 4. 配置文件详解
|
||
|
||
> **通用规则**
|
||
> - 字段名写错会**直接报错**,不会静默取默认值
|
||
> - 修改任一配置,`config_hash` 变化,历史 run 仍可完整复现
|
||
> - 配置里**不含任何密钥**,密钥统一走 `.env`
|
||
|
||
---
|
||
|
||
## 4.1 `config/universe.yml` — 股票筛选条件 ★
|
||
|
||
### 行业豁免
|
||
|
||
```yaml
|
||
industry_exemptions:
|
||
leverage: [银行, 保险, 证券, 信托] # 豁免负债率上限
|
||
free_cash_flow: [银行, 保险, 证券, 信托] # 豁免 FCF 相关要求
|
||
```
|
||
|
||
> **为什么需要**:银行负债率天然 90%+、也没有「自由现金流」概念。
|
||
> 不做豁免会把整个金融板块误杀(实测:招商银行 90.2% 会被负债率上限直接淘汰)。
|
||
|
||
### market — 市场属性
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `exchanges` | `[SZSE, SSE]` | 交易所白名单;加 `BSE` 纳入北交所 |
|
||
| `markets` | `[主板, 创业板, 科创板]` | 板块白名单 |
|
||
| `min_listing_years` | `10` | 上市年限下限(年) |
|
||
| `min_market_cap` | `5e10` | 总市值下限(**元**,5e10 = 500 亿) |
|
||
| `min_float_market_cap` | `null` | 流通市值下限 |
|
||
| `min_avg_amount_20d` | `2e7` | 近 20 日日均成交额下限(元),流动性过滤 |
|
||
| `require_trading_on_asof` | `true` | 要求当日正常交易(非停牌) |
|
||
|
||
### risk — 风险过滤
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `exclude_st` | `true` | 排除 ST/\*ST(按**名称历史**还原当时状态) |
|
||
| `exclude_delisting` | `true` | 排除退市整理/已退市 |
|
||
| `exclude_suspended` | `true` | 排除当日停牌 |
|
||
| `exclude_negative_equity` | `true` | 排除净资产为负 |
|
||
| `max_debt_to_assets` | `0.80` | 资产负债率上限(金融豁免) |
|
||
| `max_pledge_ratio` | `null` | 质押比例上限(**无数据源,暂不可用**) |
|
||
| `exclude_major_litigation` | `false` | 重大诉讼(**无数据源**) |
|
||
| `exclude_goodwill_anomaly` | `false` | 商誉/净资产 > 50% 时排除 |
|
||
|
||
### dividend — 分红过滤
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `yield_source` | `computed` | 股息率口径:`computed`=自算 PIT-TTM / `dv_ttm`=用 Tushare |
|
||
| `min_dividend_yield` | `0.03` | 股息率下限(3%) |
|
||
| `min_continuous_years` | `5` | 连续分红年数下限 |
|
||
| `window_years` | `6` | 考察窗口(年) |
|
||
| `min_dividend_years_in_window` | `5` | 窗口内最少分红年数 |
|
||
| `max_payout_ratio` | `1.00` | 支付率上限(>100% 说明分红超过当年利润) |
|
||
| `min_payout_ratio` | `null` | 支付率下限 |
|
||
| `min_dps_cagr_5y` | `null` | 每股分红 5 年复合增速下限 |
|
||
| `require_positive_fcf` | `true` | 要求自由现金流为正 |
|
||
| `min_fcf_dividend_cover` | `1.0` | FCF / 现金分红 下限(<1 说明分红靠外部融资) |
|
||
| `on_missing_data` | `pass` | 数据缺失时:`pass`=放行(宽松)/ `reject`=淘汰(严格) |
|
||
|
||
> **`on_missing_data` 怎么选**
|
||
> - `pass`:避免因数据未同步而误杀,但会让「无法验证现金流」的股票进入组合,
|
||
> 与「安全边际」的初衷相悖
|
||
> - `reject`:忠于策略逻辑,但结果会依赖数据完整度
|
||
> - 实测:当前全部在市股票均有现金流数据,两种设置结果相同
|
||
|
||
### quality — 财务质量过滤
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `min_roe_5y_avg` | `0.08` | **年报口径** 5 年平均 ROE 下限 |
|
||
| `min_roic_5y_avg` | `null` | 5 年平均 ROIC 下限 |
|
||
| `min_gross_margin` / `min_net_margin` | `null` | 毛利率 / 净利率下限 |
|
||
| `min_ocf_to_profit` | `0.60` | 经营现金流/净利润 下限(盈利质量,金融豁免) |
|
||
| `max_debt_to_assets` | `null` | 覆盖 `risk` 段设置;`null`=不覆盖 |
|
||
| `pit_rule` | `announce_date_le_asof` | PIT 纪律(不可改) |
|
||
|
||
> **为什么用年报而不是最新季报**:Tushare 的 ROE 是**年初至今累计值**,
|
||
> 一季报 ROE 只有全年的约 1/4。若拿季报 ROE 比「8% 的年均 ROE」,
|
||
> 会把几乎所有好公司误杀(实测招商银行 2024Q1 ROE 仅 3.47%)。
|
||
|
||
### output
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `min_members` | `10` | 少于该数触发 WARN |
|
||
| `max_members` | `200` | 股票池上限(按股息率降序截断) |
|
||
| `warn_on_empty` | `true` | 空池告警 |
|
||
|
||
---
|
||
|
||
## 4.2 `config/profile.yml` — 个股特性 ★
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `windows_years` | `[5, 8, 10]` | 统计窗口(年),另含「全历史」 |
|
||
| `min_obs_days` | `500` | 单窗口最少观测数,不足标记 `INSUFFICIENT` |
|
||
| `series_max_points` | `1500` | 落库序列点数上限(等间隔降采样,仅影响画图) |
|
||
| `series_metrics` | `[dv_yield, pe_ttm, pb, close, drawdown]` | 哪些指标需要落时间序列 |
|
||
| `ttm_dividend.window_days` | `365` | TTM 每股分红回看天数 |
|
||
| `ttm_dividend.grace_days` | `45` | 宽限期(见下方说明) |
|
||
| `percentiles` | `[10,25,50,75,90]` | 需计算的分位数 |
|
||
| `dividend_yield_volatility.*` | 250/60/20/10 | 日/月/季/年频波动窗口 |
|
||
| `safety_margin.mode` | `separate` | `separate`=分项展示 / `composite`=加权综合 |
|
||
| `safety_margin.weights` | 见文件 | 综合分权重(`composite` 模式下必须和为 1.0) |
|
||
| `safety_margin.score_anchors` | 见文件 | 各分项的满分/零分锚点 |
|
||
|
||
> **`grace_days` 为什么必需**:A 股年度分红的除权间隔中位数约 **366 天**
|
||
> (实测招商银行 5 次间隔 > 365 天,最长 393 天)。严格 365 天窗口会制造
|
||
> 1~3 天的「空窗期」,把股息率算成 0 —— 这是统计假象,会污染历史分位。
|
||
> 仅当严格窗口结果为零时,才回退到 `365 + grace_days`。
|
||
|
||
---
|
||
|
||
## 4.3 `config/strategy/high_dividend_v1.yml` — 策略定义 ★
|
||
|
||
```yaml
|
||
strategy:
|
||
id: HD_MR_V1
|
||
version: "1.0"
|
||
status: DRAFT # DRAFT/RESEARCH/BACKTEST/VALIDATED/PAPER/LIVE/ARCHIVED
|
||
```
|
||
|
||
### 阈值与阶梯
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `entry.yield_percentile` | `75` | 买入阈值:股息率 ≥ 历史 P75 |
|
||
| `entry.scale_in[]` | 75→25%, 80→50%, 85→75%, 90→100% | 分批建仓阶梯 |
|
||
| `exit.yield_percentile` | `25` | 卖出阈值:股息率 ≤ 历史 P25 |
|
||
| `exit.scale_out[]` | 50→50%, 25→0% | 分批减仓阶梯 |
|
||
| `position.max_position` | `0.10` | 单股仓位上限 |
|
||
| `position.sector_max_position` | `0.25` | 单行业仓位上限 |
|
||
| `position.max_holdings` | `20` | 最多持仓只数 |
|
||
|
||
### 目标仓位阶梯(重要)
|
||
|
||
`scale_in` 与 `scale_out` 会**合成唯一的目标仓位函数**:
|
||
|
||
| 股息率历史分位 | 目标仓位(占 `max_position`) |
|
||
|---|---|
|
||
| ≥ 90 | 100% |
|
||
| ≥ 85 | 75% |
|
||
| ≥ 80 | 50% |
|
||
| ≥ 75 | 25% |
|
||
| **(50, 75)** | **死区 —— 保持现有仓位,不交易** |
|
||
| ≤ 50 | 50% |
|
||
| ≤ 25 | 0%(清仓) |
|
||
|
||
> **死区的作用**:抑制因分位抖动造成的频繁交易。
|
||
> 早期实现让两套阶梯各自判定,导致「持仓时因高分位被减仓」的逻辑冲突,
|
||
> 表现为年换手 8.9、平均持仓仅 30 天;加入死区后为年换手 0.91、持仓 345 天。
|
||
>
|
||
> **校验规则**:`scale_in` 首档必须等于 `entry.yield_percentile`;
|
||
> `scale_out` 末档必须等于 `exit.yield_percentile` 且权重为 0。
|
||
|
||
### 生命周期状态
|
||
|
||
```
|
||
DRAFT → RESEARCH → BACKTEST → VALIDATED → PAPER → LIVE → ARCHIVED
|
||
```
|
||
|
||
只允许向前迁移(`ARCHIVED` 除外)。状态变更用 `hdiv strategy` 相关命令或直接改 YAML 后重新 `register`。
|
||
|
||
---
|
||
|
||
## 4.4 `config/cost.yml` — 交易成本
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `commission.rate` | `0.00025` | 佣金 万 2.5(双边) |
|
||
| `commission.min` | `5.0` | 单笔最低佣金(元) |
|
||
| `stamp_duty.rate` | `0.0005` | 印花税 万 5(**仅卖出**) |
|
||
| `transfer_fee.rate` | `0.00001` | 过户费 万 0.1(双边) |
|
||
| `slippage.mode` | `bps` | `bps` / `fixed`(元/股)/ `tick`(0.01 元倍数) |
|
||
| `slippage.value` | `10` | 10 bps;买入上滑、卖出下滑 |
|
||
| `dividend_tax.rates` | `{1m:0.20, 1y:0.10, gt1y:0.00}` | 按持股期限差异化红利税 |
|
||
|
||
> **`side` 字段**:`both` / `buy` / `sell`,大小写不敏感。
|
||
|
||
---
|
||
|
||
## 4.5 `config/backtest.yml` — 回测配置
|
||
|
||
### 区间与资金
|
||
|
||
```yaml
|
||
capital: { initial: 1000000 } # 元
|
||
period: { start: 2015-01-01, end: latest }
|
||
```
|
||
|
||
### 调度
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `schedule.signal_frequency_months` | `1` | 多久评估一次买卖信号 |
|
||
| `schedule.universe_refresh_months` | `12` | 多久重建一次股票池 |
|
||
|
||
> 股票池成员(大市值/长上市/连续分红)非常稳定,每年重建一次即可。
|
||
> 调小更严格但显著更慢。
|
||
|
||
### 分位参照口径
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `percentile_reference.mode` | `rolling` | `rolling`=滚动窗口(PIT 自适应)/ `frozen`=冻结窗口 |
|
||
| `percentile_reference.lookback_years` | `5` | rolling 模式回看年数 |
|
||
|
||
> **Walk-forward 测试段强制使用 `frozen`**,由代码传入冻结区间,不受此配置影响。
|
||
|
||
### Walk-forward
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `scheme` | `rolling` | `rolling`=固定长度训练窗 / `expanding`=递增 |
|
||
| `train_years` / `test_years` | `5` / `1` | 训练/测试年数 |
|
||
| `step_months` | `12` | 窗口步进 |
|
||
| `freeze_params_in_test` | `true` | **必须为 true**(配置层强制拒绝关闭) |
|
||
|
||
### 其他
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `benchmark[]` | 沪深300 / 中证红利 / 上证指数 | 基准列表 |
|
||
| `risk_free_rate` | `0.02` | 无风险利率(用于 Sharpe/Sortino) |
|
||
| `fill.price` | `next_open` | 信号次日开盘成交 |
|
||
| `fill.limit_up_down_rule` | `skip` | 涨跌停时跳过 |
|
||
| `fill.suspended_rule` | `defer` | 停牌时顺延 |
|
||
| `dividend.cash_mode` | `reinvest` | 分红处理:`reinvest`/`hold`/`cash_out` |
|
||
|
||
---
|
||
|
||
## 4.6 `config/report.yml` — 报告
|
||
|
||
| 字段 | 默认 | 含义 |
|
||
|---|---:|---|
|
||
| `theme` | `light` | `light` / `dark` / `auto` |
|
||
| `asset_mode` | `shared` | `shared`=引用 `assets/echarts.min.js` / `inline`=内嵌 |
|
||
| `include_sql_provenance` | `true` | 页脚展示数据来源 SQL |
|
||
| `charts.*` | 全部 `true` | 各图表开关 |
|
||
| `naming.*` | 见文件 | 输出文件命名模板 |
|
||
| `layout.max_width` | `1440` | 页面最大宽度(px) |
|
||
|
||
> **`asset_mode` 怎么选**:见 [§7.8 报告部署](#78-报告部署)。
|
||
|
||
---
|
||
|
||
## 4.7 `config/datasource.yml` — 数据源
|
||
|
||
```yaml
|
||
database:
|
||
host / port / db / user / charset # 连接信息(明文可提交)
|
||
password_env: MYSQL_PASSWORD # 密码只从 .env 读
|
||
read_only_tables: [...] # 只读白名单(拦截写操作)
|
||
allow_write_tables: [index_weight] # 例外的可写 qlib 表
|
||
backfill_tables: [stock_daily, adjust_factor, daily_basic]
|
||
allow_backfill: false # 回补总开关(或设 HDIV_ALLOW_BACKFILL=1)
|
||
own_prefix: hd_ # 本项目自有表前缀
|
||
forbidden_tables: [alembic_version] # 禁触表
|
||
forbid_delete: true # 禁止 DELETE/DROP/TRUNCATE
|
||
tushare:
|
||
rate_limit_default: 380 # 每分钟默认限频
|
||
rate_limits: { dividend: 180, ... } # 按接口覆盖(Tushare 限频是按接口算的)
|
||
rate_limit_cooldown_sec: 62 # 命中限频后的冷却秒数
|
||
```
|
||
|
||
> **一般情况下你不需要改这个文件**,除非换数据库或调整同步速度。
|
||
|
||
---
|
||
|
||
# 5. 命令行手册
|
||
|
||
统一入口:`python -m hdiv <命令> [选项]`(需 `export PYTHONPATH=src`)。
|
||
|
||
## 5.1 `ddl` — 建表与结构迁移
|
||
|
||
```bash
|
||
hdiv ddl plan # 只显示将要执行什么,不改库
|
||
hdiv ddl apply # 执行(幂等,可反复运行)
|
||
hdiv ddl verify # 校验库中表结构是否符合代码定义
|
||
```
|
||
|
||
- 只做 `CREATE TABLE IF NOT EXISTS` 与 `ALTER TABLE ... ADD COLUMN`
|
||
- **从不** `DROP` / `TRUNCATE` / `DELETE`
|
||
- 迁移带前置条件(如「仅当表为空」),非空表会跳过并提示
|
||
|
||
## 5.2 `sync` — 数据同步
|
||
|
||
```bash
|
||
hdiv sync dividend [--symbols ...] [--only-missing] [--limit N]
|
||
hdiv sync financial [--interleaved] [--only-missing] [--apis ...] [--limit N]
|
||
hdiv sync index [--no-weight] [--start YYYYMMDD]
|
||
hdiv sync price --which daily|adj_factor|daily_basic --start --end [--no-resume]
|
||
hdiv sync trading [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--no-resume]
|
||
hdiv sync backfill [--start 2015-01-01] [--end 2018-12-31] [--basic-end 2019-12-31] [--no-resume]
|
||
```
|
||
|
||
| 目标 | 说明 | 首次耗时 |
|
||
|---|---|---:|
|
||
| `dividend` | 分红送转全明细(逐只股票) | ~35 分钟 |
|
||
| `financial` | 四张财务报表;**加 `--interleaved` 按股票交错拉取**(推荐) | ~3 小时 |
|
||
| `index` | 基准指数行情 + 成分股权重 | ~2 分钟 |
|
||
| `price` | 日线/复权因子/每日指标(逐交易日) | 视区间 |
|
||
| `trading` | 停牌与涨跌停 | ~20 分钟 |
|
||
| `backfill` | 2015–2018 历史回补(写入 qlib 原有表,`INSERT IGNORE` 不覆盖既有行) | ~15 分钟 |
|
||
|
||
> **`--only-missing`**:跳过已同步的标的,支持断点续传
|
||
> **`--interleaved`**:按股票一次性拉齐四张报表,使策略相关的大市值股票优先就绪
|
||
> **回补需要显式授权**:`HDIV_ALLOW_BACKFILL=1` 或改 `datasource.yml`
|
||
|
||
## 5.3 `audit` — 数据审计
|
||
|
||
```bash
|
||
hdiv audit [--no-persist] [--html]
|
||
```
|
||
|
||
执行 18 项检查(缺口 G1–G6、PIT 纪律、唯一性、单位自检、代码有效性),
|
||
结果写入 `hd_data_audit` 并生成 `output/data_audit_<date>.html`。
|
||
退出码:总体 FAIL 时为 1。
|
||
|
||
## 5.4 `universe` — 股票池筛选
|
||
|
||
```bash
|
||
hdiv universe [-c config/universe.yml] [--asof 2024-06-28 | latest] [--no-persist] [--html]
|
||
```
|
||
|
||
> `--no-persist` 不写数据库(因此前端看不到);`--html` 额外导出静态报告。
|
||
|
||
输出:
|
||
|
||
```
|
||
[market ] 候选 5363 → 淘汰 5191 → 存活 172
|
||
[risk ] 候选 172 → 淘汰 7 → 存活 165
|
||
[dividend] 候选 165 → 淘汰 133 → 存活 32
|
||
[quality ] 候选 32 → 淘汰 4 → 存活 28
|
||
股票池「high_dividend_universe」asof=2024-06-28:候选 5363 → 入选 28
|
||
股票池 <run_id> asof=2024-06-28 候选 5363 入选 28
|
||
```
|
||
|
||
**记下 `run_id`**,下一步要用。
|
||
|
||
## 5.5 `profile` — 个股画像
|
||
|
||
```bash
|
||
hdiv profile --universe-run <run_id> # 对股票池全部股票
|
||
hdiv profile --symbols 600519.SH 000333.SZ --asof 2024-06-28 # 指定股票
|
||
hdiv profile --universe-run <run_id> --html --html-limit 20 # 导出前 20 只的静态报告
|
||
```
|
||
|
||
## 5.6 `strategy` — 策略管理
|
||
|
||
```bash
|
||
hdiv strategy validate [-f config/strategy/high_dividend_v1.yml]
|
||
hdiv strategy register [-f ...] # 写入 hd_strategy / hd_strategy_param
|
||
hdiv strategy list # 列出已登记的所有版本
|
||
hdiv strategy diff -f A.yml --other B.yml # 比较两版差异
|
||
```
|
||
|
||
## 5.7 `backtest` — 回测与 Walk-forward
|
||
|
||
```bash
|
||
hdiv backtest [--start 2015-01-01] [--end 2026-09-30] [--no-persist] [--html]
|
||
hdiv backtest --mode walkforward # 7 个滚动窗口,约 25 分钟
|
||
hdiv backtest --universe-run <run_id> # 用指定股票池(冻结)并建立关联
|
||
```
|
||
|
||
输出示例:
|
||
|
||
```
|
||
期初 1,000,000 → 期末 2,148,010 | 总收益 114.80% | CAGR 6.73% | 最大回撤 -21.05% | Sharpe 0.31 | 成交 180 笔
|
||
累计现金分红 558,797(已扣红利税 13,676) | 对账残差 -0.0000 ✓
|
||
```
|
||
|
||
> **「对账残差」是资金恒等式的校验值**(期初 = 期末现金 + 买入 − 卖出 + 费用 − 分红)。
|
||
> 应为 0;若不为 0 说明成本或分红入账有遗漏,**此时不要采信绩效指标**。
|
||
|
||
## 5.8 `sensitivity` — 参数敏感性
|
||
|
||
```bash
|
||
hdiv sensitivity # 默认扫描 entry.yield_percentile=70,75,80,85,90
|
||
hdiv sensitivity --sweep "entry.yield_percentile=70,75,80;exit.yield_percentile=20,25,30"
|
||
```
|
||
|
||
扫描路径支持点号导航,多参数用 `;` 分隔(笛卡尔积,上限 400 组合)。
|
||
|
||
> 扫描 `entry.yield_percentile` 时会**整体平移** `scale_in` 阶梯(放不下时按比例压缩),
|
||
> 保证阶梯形状不变 —— 否则扫出的差异来自形状畸变而非阈值本身。
|
||
|
||
## 5.9 `report` — 报告索引与校验
|
||
|
||
```bash
|
||
hdiv report index # 重建 output/index.html
|
||
hdiv report validate # 校验全部报告的离线合规性与 JS 语法
|
||
```
|
||
|
||
---
|
||
|
||
# 6. 典型工作流
|
||
|
||
## 6.1 首次建库(一次性)
|
||
|
||
```bash
|
||
export PYTHONPATH=src
|
||
.venv/bin/python -m hdiv ddl apply
|
||
.venv/bin/python -m hdiv ddl verify # 应输出「OK:30 张 hd_* 表结构全部符合 schema 定义」
|
||
```
|
||
|
||
## 6.2 数据同步(首次约 3~4 小时)
|
||
|
||
建议**后台运行**,日志重定向到 `logs/`:
|
||
|
||
```bash
|
||
export PYTHONPATH=src
|
||
|
||
# ① 分红(约 35 分钟)
|
||
nohup .venv/bin/python -m hdiv sync dividend --only-missing > logs/s_dividend.log 2>&1 &
|
||
|
||
# ② 财报:按股票交错(约 3 小时;与 ① 可并行,限频是按接口独立的)
|
||
nohup .venv/bin/python -m hdiv sync financial --interleaved --only-missing > logs/s_financial.log 2>&1 &
|
||
|
||
# ③ 指数(约 2 分钟)
|
||
.venv/bin/python -m hdiv sync index
|
||
|
||
# ④ 停牌与涨跌停(约 20 分钟)
|
||
nohup .venv/bin/python -m hdiv sync trading --start 2019-01-01 > logs/s_trading.log 2>&1 &
|
||
|
||
# ⑤ 2015–2018 历史行情回补(约 15 分钟)
|
||
HDIV_ALLOW_BACKFILL=1 nohup .venv/bin/python -m hdiv sync backfill > logs/s_backfill.log 2>&1 &
|
||
|
||
# 随时查看进度
|
||
tail -f logs/s_financial.log
|
||
```
|
||
|
||
**同步完成后自检**:
|
||
|
||
```bash
|
||
.venv/bin/python -m hdiv audit
|
||
# 期望:总体 WARN 或 OK,FAIL=0
|
||
```
|
||
|
||
## 6.3 日常数据更新
|
||
|
||
```bash
|
||
export PYTHONPATH=src
|
||
.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 price --which daily_basic --start 2026-09-01 --end 2026-10-02
|
||
.venv/bin/python -m hdiv audit
|
||
```
|
||
|
||
## 6.4 研究一个新策略
|
||
|
||
```bash
|
||
# ① 复制策略模板
|
||
cp config/strategy/high_dividend_v1.yml config/strategy/my_strategy.yml
|
||
# 改 strategy.id / name / version,调整 entry/exit/position
|
||
|
||
# ② 校验 + 登记
|
||
.venv/bin/python -m hdiv strategy validate -f config/strategy/my_strategy.yml
|
||
.venv/bin/python -m hdiv strategy register -f config/strategy/my_strategy.yml
|
||
|
||
# ③ 回测
|
||
.venv/bin/python -m hdiv backtest -s config/strategy/my_strategy.yml
|
||
```
|
||
|
||
**注意**:换策略 ID 会得到新的策略版本,历史 run 互不干扰。
|
||
|
||
## 6.5 调参流程(推荐顺序)
|
||
|
||
```bash
|
||
# ① 先看单参数敏感性 —— 判断结论稳不稳
|
||
.venv/bin/python -m hdiv sensitivity --sweep "entry.yield_percentile=70,75,80,85,90"
|
||
|
||
# ② 再做 Walk-forward —— 看样本外是否成立
|
||
.venv/bin/python -m hdiv backtest --mode walkforward
|
||
|
||
# ③ 只有 ① 平滑 且 ② 样本外为正,才考虑把状态推到 VALIDATED
|
||
```
|
||
|
||
> ⚠ **务必先做 ① 和 ②**。单条路径的全期回测会系统性高估策略(见 §7.5)。
|
||
|
||
---
|
||
|
||
# 7. 报告解读
|
||
|
||
所有报告从 `output/index.html` 进入。索引按类型分组,每组首份标注「最新」。
|
||
|
||
## 7.1 数据审计报告
|
||
|
||
**看什么**:`FAIL` 数量必须为 0,否则后续结论不可信。
|
||
|
||
| 检查码 | 关注点 |
|
||
|---|---|
|
||
| G1 分红 | 覆盖股票数、可用分红记录数 |
|
||
| G2/G2b/G3 行情 | 起始日期是否够早(回测窗口依赖) |
|
||
| G4 财务 | 4 张表覆盖与公告日齐全率 |
|
||
| G5 基准 | `backtest.yml` 声明的基准是否齐全 |
|
||
| G6 停牌/涨跌停 | 成交约束是否有数据支撑 |
|
||
| **UNIT 单位自检** | **恒等式 + 绝对量级双判据**(见 §9.1) |
|
||
| PIT | 是否存在未来函数迹象 |
|
||
| DUP / ORPHAN | 唯一性、代码有效性 |
|
||
|
||
## 7.2 股票池报告
|
||
|
||
**看什么**:
|
||
|
||
1. **漏斗图** —— 哪一层淘汰最多(通常是 `market` 的市值门槛)
|
||
2. **入选股票表** —— 按股息率降序,确认没有明显不合理的标的
|
||
3. **行业分布** —— 判断是否过度集中(银行/煤炭/电力常见)
|
||
4. **被淘汰股票与原因表** —— 可回答「为什么某某没选上」
|
||
|
||
> 若入选数为 0 或很少,先确认分红/财报数据是否同步完成,再看是否条件过严。
|
||
|
||
## 7.3 个股画像报告
|
||
|
||
**看什么**:
|
||
|
||
1. **K线 · 股息率 · PE · PB · 回撤 四联图** —— 共享 X 轴;股息率面板上有
|
||
P75(买入)与 P25(卖出)虚线
|
||
2. **当前历史分位** —— 顶部 KPI。≥ 75% 触发买入信号,≤ 25% 触发卖出
|
||
3. **历史分布统计表** —— 每个指标的 Min/P10/P25/P50/P75/P90/Max
|
||
4. **分红质量** —— 连续分红年数、支付率、FCF 覆盖、DPS 增速
|
||
5. **安全边际雷达图** —— 五个分项得分 + 综合分
|
||
|
||
> 「数据不足」会显式标注,**不会用 0 或推测值填充**。
|
||
|
||
## 7.4 回测报告
|
||
|
||
**看什么**:
|
||
|
||
1. **资金对账横幅** —— 必须是「通过」
|
||
2. **净值曲线 vs 基准** —— 相对表现比绝对收益更重要
|
||
3. **回撤曲线** —— 高股息策略的价值主要体现在这里
|
||
4. **基准比较表** —— 与中证红利对比最有意义(同类策略)
|
||
5. **成交流水** —— 每笔都带 `reason`(触发时的股息率、历史分位、所用规则)
|
||
6. **未成交信号** —— 区分「想买」与「买到了」,判断约束是否实质影响绩效
|
||
7. **未建模部分** —— 引擎如实声明哪些约束没生效
|
||
|
||
> 若顶部出现 **「这是一份历史运行报告,已被更新的运行取代」** 的红色横幅,
|
||
> 说明该 run 早于某次修复,**请以标注「最新」的报告为准**。
|
||
|
||
## 7.5 Walk-forward 报告 ★ 最重要
|
||
|
||
**这是判断策略是否真的有效的核心依据。**
|
||
|
||
**看什么**:
|
||
|
||
1. **样本外胜率** —— 7 个窗口里几个为正
|
||
2. **样本外收益均值 vs 基准收益均值** → **超额收益**
|
||
3. **稳定性指标**(样本外均值 / 标准差)—— < 1 说明窗口间差异大于均值本身
|
||
4. **逐窗口明细表** —— 每个测试年的表现
|
||
5. **冻结阈值图** —— 各窗口校准出的绝对股息率阈值是否稳定
|
||
|
||
**判读标准**:
|
||
|
||
| 现象 | 含义 |
|
||
|---|---|
|
||
| 样本外超额 > 0 且稳定性 > 1 | 策略可能真的有效 |
|
||
| 样本外超额 ≈ 0 | 与基准相当,无超额收益能力 |
|
||
| 样本外超额 < 0 | **策略未通过样本外检验** |
|
||
| 全期回测远好于样本外均值 | **单路径回测高估了策略** |
|
||
|
||
> **本系统的实测结论**:全期回测 +114.80%,而 7 窗口样本外收益均值 **−0.95%**
|
||
> (基准 +2.29%,超额 **−3.24pp**)。即**当前策略没有稳定的样本外超额收益**。
|
||
> 它的价值在于**回撤控制**(样本外最差 −24.62%,基准同期 −46%~−52%),
|
||
> 更像降低波动的配置工具,而非超额收益来源。
|
||
>
|
||
> 这个反差不是 bug,正是 Walk-forward 存在的意义 —— 详见
|
||
> [implementation-status.md §4.6](implementation-status.md)。
|
||
|
||
## 7.6 参数敏感性报告
|
||
|
||
**看什么**:
|
||
|
||
1. **平滑度**(0~1,越接近 1 越平滑)—— ≥ 0.6 且无尖峰视为稳健
|
||
2. **尖峰检测** —— 某点的 CAGR 显著高于左右邻居
|
||
3. **CAGR 随参数的变化曲线** —— 平缓下降可接受,锯齿状或孤立高点不可信
|
||
|
||
**判读标准**(`plan.md §27`):
|
||
|
||
| 参数扫描结果 | 结论 |
|
||
|---|---|
|
||
| P75→13%, P80→13.5%, P85→13.2% | 对参数**不敏感**,相对可信 |
|
||
| P79→18%, P80→20%, P81→8% | 存在**尖峰**,很可能过拟合 |
|
||
|
||
> 敏感性结论依赖**扫描区间长度与股票池规模**。若区间仅数年、池内仅数十只,
|
||
> 即使平滑度低也可能只是样本噪声。应与 Walk-forward 结果共同判断。
|
||
|
||
## 7.7 静态报告与统一前端的关系
|
||
|
||
**统一前端(SPA)是主界面**,日常浏览、命名、归档、下钻都在那里。
|
||
|
||
`output/reports/` 下的静态 HTML 报告已降级为**导出件**:
|
||
|
||
| | 统一前端(SPA) | 静态报告(`reports/`) |
|
||
|---|---|---|
|
||
| 默认生成 | 始终可用 | **否**,需 `--html` |
|
||
| 数据来源 | 实时读数据库 | 生成时刻的快照 |
|
||
| 依赖 | 需后端进程 | **零依赖**,单文件可离线打开 |
|
||
| 适合场景 | 日常浏览、管理记录 | 打印、分享、长期存档 |
|
||
|
||
**为什么不默认生成**:改造前静态 HTML 是唯一界面,默认生成合理;现在 SPA 已提供
|
||
同样内容,默认再产出一份纯属冗余,且旧命名(按 asof)会让同一天多次运行互相覆盖。
|
||
|
||
**导出命令**:
|
||
|
||
```bash
|
||
hdiv universe --asof 2025-01-21 --html # 导出股票池报告
|
||
hdiv profile --universe-run <run_id> --html # 导出画像报告
|
||
hdiv backtest --html # 导出回测报告
|
||
hdiv audit --html
|
||
hdiv sensitivity --html
|
||
```
|
||
|
||
**文件名带执行 id**,所以同一天跑多次不会覆盖:
|
||
|
||
```text
|
||
output/reports/universe_2025-01-21_af8a2c80736a992237c6f949aff0ce74.html
|
||
└────────── run_id ──────────┘
|
||
```
|
||
|
||
> `--no-html` 仍然存在但**已是空操作**(默认就不生成),保留是为了让历史命令与脚本
|
||
> 不报错。
|
||
|
||
## 7.8 报告部署
|
||
|
||
报告通过**相对路径**引用图表库:
|
||
|
||
```html
|
||
<script src="assets/echarts.min.js"></script>
|
||
```
|
||
|
||
因此发布时**必须整体部署 `output/` 目录**,包括 `output/assets/`。
|
||
例如映射到 `/ggx/`,则两个 URL 都必须可达:
|
||
|
||
```text
|
||
http://<host>:8080/ggx/index.html ← 报告
|
||
http://<host>:8080/ggx/assets/echarts.min.js ← 图表库(约 1MB)
|
||
```
|
||
|
||
**若第二个 404**:页面能打开但所有图表空白。两种解决办法:
|
||
|
||
| 办法 | 操作 | 代价 |
|
||
|---|---|---|
|
||
| 补部署 assets | 把 `output/assets/` 一起同步 | 无 |
|
||
| 自包含模式 | `config/report.yml` 设 `asset_mode: inline`,重新生成 | 体积 10MB → 79MB |
|
||
|
||
**部署前自检**:
|
||
|
||
```bash
|
||
.venv/bin/python -m hdiv report validate
|
||
# 会真实执行 node --check 校验每份报告的内联 JS
|
||
```
|
||
|
||
---
|
||
|
||
# 8. 数据库
|
||
|
||
## 8.1 表清单(30 张,全部 `hd_` 前缀)
|
||
|
||
### 数据同步层
|
||
|
||
| 表 | 用途 |
|
||
|---|---|
|
||
| `hd_dividend` | 分红送转全明细(PIT 基座) |
|
||
| `hd_fina_indicator` | 扩展财务指标(ROE/ROIC/负债率…) |
|
||
| `hd_cashflow` / `hd_balancesheet` / `hd_income` | 三张财务报表 |
|
||
| `hd_index_daily` | 基准指数行情 |
|
||
| `hd_suspend` / `hd_limit` | 停牌 / 涨跌停 |
|
||
| `hd_sync_log` | 同步台账(含回补前后行数基线) |
|
||
| `hd_data_audit` | 审计结果 |
|
||
|
||
### 研究层
|
||
|
||
| 表 | 用途 |
|
||
|---|---|
|
||
| `hd_universe_run` / `hd_universe_member` | 股票池运行头 / 成员与逐滤网留痕 |
|
||
| `hd_factor_snapshot` | 决策时点因子值 |
|
||
| `hd_profile_run` / `hd_profile_stat` / `hd_profile_series` / `hd_profile_score` | 画像头 / 分布统计 / 时间序列 / 安全边际得分 |
|
||
| `hd_strategy` / `hd_strategy_param` | 策略版本 / 参数扁平表 |
|
||
|
||
### 回测层
|
||
|
||
| 表 | 用途 |
|
||
|---|---|
|
||
| `hd_backtest_run` | 运行头(可复现四元组 + 资金明细) |
|
||
| `hd_backtest_equity` / `hd_backtest_position` | 逐日净值 / 逐日持仓 |
|
||
| `hd_backtest_trade` / `hd_backtest_signal` | 成交(含 reason) / 信号(含未成交原因) |
|
||
| `hd_backtest_metric` | 绩效指标长表(`scope` 区分样本内/外/年度) |
|
||
| `hd_walkforward_run` / `hd_walkforward_window` | Walk-forward 头 / 各窗口与冻结参数 |
|
||
| `hd_sensitivity_run` / `hd_sensitivity_point` | 敏感性头 / 各参数组合绩效 |
|
||
| `hd_report` | 报告元数据(含 SQL 溯源) |
|
||
|
||
## 8.2 常用查询
|
||
|
||
```sql
|
||
-- 最新股票池成员
|
||
SELECT m.symbol, m.name, m.industry
|
||
FROM hd_universe_member m
|
||
WHERE m.run_id = (SELECT run_id FROM hd_universe_run ORDER BY created_at DESC LIMIT 1)
|
||
AND m.passed = 1;
|
||
|
||
-- 某只股票的股息率历史分位
|
||
SELECT metric_code, window_years, n_obs, p10, p25, p50, p75, p90,
|
||
current_value, current_percentile
|
||
FROM hd_profile_stat
|
||
WHERE run_id = (SELECT run_id FROM hd_profile_run ORDER BY created_at DESC LIMIT 1)
|
||
AND symbol = '600036.SH' AND metric_code = 'dv_yield';
|
||
|
||
-- 某次回测的绩效指标
|
||
SELECT category, metric_code, metric_value
|
||
FROM hd_backtest_metric
|
||
WHERE run_id = '<run_id>' AND scope = 'all'
|
||
ORDER BY category, metric_code;
|
||
|
||
-- 某次回测的成交明细(含买入理由)
|
||
SELECT execution_date, symbol, side, price, quantity, total_cost, reason_json
|
||
FROM hd_backtest_trade WHERE run_id = '<run_id>'
|
||
ORDER BY execution_date;
|
||
|
||
-- 为什么某只股票没被选上
|
||
SELECT fail_stage, fail_reason, values_json
|
||
FROM hd_universe_member
|
||
WHERE run_id = '<run_id>' AND symbol = '000002.SZ';
|
||
|
||
-- 各窗口的样本外表现
|
||
SELECT w.window_index, w.test_start, w.test_end, w.frozen_params_json,
|
||
m.metric_value AS test_return
|
||
FROM hd_walkforward_window w
|
||
JOIN hd_backtest_metric m ON m.run_id = w.test_run_id
|
||
WHERE w.wf_id = '<wf_id>' AND m.metric_code = 'total_return' AND m.scope = 'all';
|
||
|
||
-- 资金对账核对
|
||
SELECT initial_capital, total_dividend_net, total_dividend_tax, total_fees, cash_final
|
||
FROM hd_backtest_run WHERE run_id = '<run_id>';
|
||
```
|
||
|
||
## 8.3 数据安全约束
|
||
|
||
| 约束 | 实现 |
|
||
|---|---|
|
||
| 禁止删除 | SQL 钩子拦截 `DELETE`/`DROP`/`TRUNCATE`;源码扫描测试 |
|
||
| 只写自有表 | 只允许写 `hd_` 前缀表(例外见 `allow_write_tables`) |
|
||
| qlib 表只读 | `read_only_tables` 白名单,写操作直接抛异常 |
|
||
| 回补可审计 | `INSERT IGNORE` 保证既有行零改动;`hd_sync_log` 记录前后行数 |
|
||
| 不碰 Alembic | `alembic_version` 在禁触列表 |
|
||
|
||
---
|
||
|
||
# 9. 关键口径
|
||
|
||
> 这一节解释「数字是怎么算出来的」。**口径错了不会报错,只会给出错误结论**,
|
||
> 因此系统开发过程中每个口径都有回归测试守护。
|
||
|
||
## 9.1 单位(最容易出错的地方)
|
||
|
||
Tushare 各接口单位不统一,且从列名看不出来。系统在 `data/units.py` **统一归一化**:
|
||
|
||
| 字段 | Tushare 单位 | 系统内统一为 |
|
||
|---|---|---|
|
||
| `total_mv` / `circ_mv` | 万元 | **元**(×1e4) |
|
||
| `total_share` / `float_share` | 万股 | **股**(×1e4) |
|
||
| `dv_ratio` / `dv_ttm` / `turnover_rate` | 百分数(5.08) | **小数**(0.0508) |
|
||
| `roe` / `roic` / `debt_to_assets` | 百分数 | **小数** |
|
||
| 财务报表金额 | 元 | 元(不变) |
|
||
| `dividend.base_share` | 万股 | **股** |
|
||
|
||
**自检手段**(审计中的 `UNIT` 检查):
|
||
|
||
1. **恒等式** `总市值 ≈ 收盘价 × 总股本` —— 能发现「只换算了一个字段」
|
||
2. **绝对量级** 总市值中位数须落在 A 股合理区间 —— 这是唯一能识别整体单位错误的手段
|
||
|
||
> 为什么必须两项都做:`元/股 × 万股 = 万元`,所以恒等式在**原始单位下也成立**,
|
||
> 单靠它无法发现「万元当元用」。实测该错误曾导致市值过滤选中 **0 只**股票。
|
||
|
||
## 9.2 Point-in-Time(无未来函数)
|
||
|
||
| 数据 | 可见性规则 |
|
||
|---|---|
|
||
| 财务 | `announce_date <= 评估日`,且只取**最新已公告**的那一期 |
|
||
| 分红 | `imp_ann_date <= 评估日` **且** `ex_date <= 评估日` |
|
||
| 行情/指标 | `trade_date <= 评估日` |
|
||
| ST 状态 | 按 `stock_name_history` 的名称生效区间还原,**不看今天的名字** |
|
||
| 股票池 | 包含**此后才退市**的股票(消除生存者偏差) |
|
||
|
||
另外:
|
||
- **成交在信号次日开盘**,信号日只产生信号
|
||
- 滚动分位窗口的**右端必须是评估日本身**
|
||
- Walk-forward 测试段使用**训练段冻结**的分布
|
||
|
||
## 9.3 分红口径
|
||
|
||
| 项 | 口径 |
|
||
|---|---|
|
||
| 认定一次分红 | `div_proc = '实施'` **且** 有 `ex_date` 或 `pay_date` |
|
||
| 分红金额 | **税前**现金分红 `cash_div_tax` |
|
||
| 分红年度归属 | 按**报告期年份** `end_date.year`(即「哪个财年的利润分了红」) |
|
||
| 连续分红年数 | 从「最近一个**年报已公告**的财年」向前逐年数 |
|
||
| **一年宽限期** | 最近可见分红财年不早于目标年 −1 时,从该年起算 |
|
||
| 支付率 | **同财年**:该财年总现金分红 ÷ 该财年归母净利润 |
|
||
| 总现金分红 | Σ(每股税前分红 × `base_share`) |
|
||
| FCF 覆盖 | **同财年**自由现金流 ÷ 该财年总现金分红 |
|
||
|
||
> **为什么需要一年宽限期**:最近一个财年的年报虽已公告,但其分红方案往往
|
||
> 要等次年 6–7 月才除权。实测中国神华 FY2023 的分红在 2024-07 才除权,
|
||
> 若在 asof=2024-06-28 严格比对,会被误判为「连续分红 0 年」。
|
||
>
|
||
> **为什么要同财年**:曾用「FY2023 分红 ÷ 2024Q1 净利润」算出美的集团
|
||
> 230.9% 的支付率(真实 61.6%)。
|
||
|
||
## 9.4 股息率口径
|
||
|
||
```text
|
||
股息率(t) = TTM 每股分红(t) / 不复权收盘价(t)
|
||
```
|
||
|
||
- **TTM 每股分红** = 过去 365 天内**已除权**的税前现金分红之和
|
||
- **宽限期 45 天**:严格窗口结果为零时回退到 410 天窗口
|
||
- **必须用不复权价**:分子是每股现金、分母是每股价格,量纲才一致
|
||
- **历史分位** = 当前股息率在自身历史分布中的位置(`≤ 当前值的观测占比`)
|
||
|
||
## 9.5 回测成交与成本
|
||
|
||
| 项 | 口径 |
|
||
|---|---|
|
||
| 成交价 | 信号**次日开盘价** ± 滑点 |
|
||
| 滑点 | 买入上滑、卖出下滑(方向反了会凭空产生收益) |
|
||
| 佣金 | `max(成交额 × 费率, 最低佣金)` |
|
||
| 印花税 | **仅卖出**收取 |
|
||
| 过户费 | 双边 |
|
||
| 红利税 | 按持股期限:≤1月 20%、≤1年 10%、>1年 免征 |
|
||
| 整手 | 买入按 100 股取整 |
|
||
| 涨跌停 | 开盘即封板则该信号不成交,记录 `skip_reason` |
|
||
| 停牌 | 信号顺延到下一可成交日 |
|
||
|
||
**分红处理**:持仓市值用**不复权价**,现金分红在除权日**单独入账**。
|
||
这样从根上避免了「复权收益 + 分红」的重复计算。
|
||
|
||
**资金对账**(每次回测都会校验):
|
||
|
||
```text
|
||
期末现金 == 期初资金 − 买入额 − 费用 + 卖出额 + 净分红
|
||
```
|
||
|
||
残差应为 0(浮点误差量级)。不为 0 说明有遗漏。
|
||
|
||
## 9.6 绩效指标
|
||
|
||
| 指标 | 口径 |
|
||
|---|---|
|
||
| 年化因子 | 252 个交易日 |
|
||
| Sharpe | (CAGR − 无风险利率) / 年化波动率 |
|
||
| Sortino | 分母改用下行波动率 |
|
||
| Calmar | CAGR / \|最大回撤\| |
|
||
| 最大回撤 | 相对历史高点的最大跌幅(**负值**) |
|
||
| 胜率 | 按**已实现盈亏**统计(仅平仓交易) |
|
||
| 换手率 | 年成交额 / 平均组合市值 |
|
||
|
||
> **最小样本量保护**:日收益观测少于 20 个时,波动率/Sharpe/Sortino 返回
|
||
> 「不可得」而非计算 —— 2~3 个观测算出的年化波动率会产生 300+ 的荒谬 Sharpe。
|
||
> 系统**宁可显示「—」,也不用噪声冒充指标**。
|
||
|
||
---
|
||
|
||
# 10. Web 前端与部署
|
||
|
||
系统采用**前后端分离**:
|
||
|
||
```text
|
||
浏览器
|
||
│
|
||
├── 静态资源 ──► nginx 直接返回 output/ 目录
|
||
│
|
||
└── /api/* ──► nginx 反向代理 ──► hdiv web 进程(Python)──► MariaDB
|
||
```
|
||
|
||
- **前端**:`output/` 目录下的统一单页应用,只负责展示与交互,**不做任何指标计算**
|
||
- **后端**:`hdiv web` 提供的 REST API,负责所有查询与变更
|
||
- 前端使用 **hash 路由**(`#/universes/xxx`),因此 nginx 不需要配置 rewrite 规则
|
||
|
||
## 10.1 目录结构(归一化后)
|
||
|
||
```text
|
||
output/
|
||
├── index.html 统一前端入口(部署后访问这个)
|
||
├── app/
|
||
│ ├── app.css 样式
|
||
│ └── app.js 应用逻辑
|
||
├── assets/
|
||
│ └── echarts.min.js 图表库(离线,1MB)
|
||
├── reports/ 静态报告(导出件,需 --html;可离线打开)
|
||
└── archive/
|
||
└── 2026-10-03_legacy/ 历史平铺报告归档
|
||
```
|
||
|
||
**归一化命令**:
|
||
|
||
```bash
|
||
hdiv site normalize # 归档历史 + 静态报告入 reports/ + 同步前端
|
||
hdiv site status # 查看当前目录状态
|
||
hdiv site archive # 只归档根目录的平铺 HTML
|
||
hdiv site build # 只同步前端
|
||
```
|
||
|
||
`normalize` 可重复执行:已归档的内容不会被再次搬动,同名归档目录会自动加序号。
|
||
|
||
## 10.2 本地预览
|
||
|
||
```bash
|
||
# 方式一:脚本(推荐)
|
||
./deploy/serve.sh start-dev # API + 静态,浏览器打开 http://127.0.0.1:8099/
|
||
./deploy/serve.sh status
|
||
./deploy/serve.sh stop
|
||
|
||
# 方式二:直接命令
|
||
export PYTHONPATH=src
|
||
.venv/bin/python -m hdiv web --host 127.0.0.1 --port 8099
|
||
```
|
||
|
||
默认监听**仅本机**(`127.0.0.1`),避免误暴露到局域网。
|
||
|
||
## 10.3 生产部署(nginx)
|
||
|
||
### 第 1 步:同步前端
|
||
|
||
```bash
|
||
rsync -av --delete output/ user@192.168.1.166:/srv/hddiv/site/
|
||
```
|
||
|
||
### 第 2 步:启动后端(并托管它)
|
||
|
||
**macOS(推荐)**:注册为 launchd 用户级服务,登录自启、崩溃自愈:
|
||
|
||
```bash
|
||
./deploy/install-service.sh install # 安装并启动(幂等)
|
||
./deploy/install-service.sh status # 查看状态 + 连通性
|
||
./deploy/install-service.sh uninstall # 移除
|
||
```
|
||
|
||
**Linux**:用 systemd 或 `./deploy/serve.sh start`(见下)。
|
||
|
||
**手动启动**(重启后需再次执行):
|
||
|
||
```bash
|
||
./deploy/serve.sh start # 默认 api-only,监听 127.0.0.1:8099
|
||
./deploy/serve.sh start-dev # API + 静态,本机预览用
|
||
./deploy/serve.sh status
|
||
./deploy/serve.sh stop
|
||
```
|
||
|
||
或直接:
|
||
|
||
```bash
|
||
PYTHONPATH=src nohup .venv/bin/python -m hdiv web \
|
||
--host 127.0.0.1 --port 8099 --api-only > logs/web.log 2>&1 &
|
||
```
|
||
|
||
> **为什么必须托管**:nginx 由 `brew services` 托管、开机自启。
|
||
> 如果后端只是个 `nohup` 进程,机器重启后它就不在了 ——
|
||
> 页面能打开,但右上角会显示「API 不可用」。两者必须同等对待。
|
||
>
|
||
> 子系统与后端自检的区别:`deploy/install-service.sh status` 查后端,
|
||
> nginx 侧用 `bash scripts/mac_nginx_ggx.sh status`(在 system 项目里)。
|
||
|
||
### 第 3 步:配置 nginx
|
||
|
||
参见 **[deploy/nginx.conf.example](../deploy/nginx.conf.example)**,含两种布局:
|
||
|
||
| 布局 | 访问地址 | 说明 |
|
||
|---|---|---|
|
||
| 挂在子路径 | `http://host:8080/ggx/` | 你当前使用的方式 |
|
||
| 挂在站点根 | `http://host:8080/` | 更简洁 |
|
||
|
||
**最容易漏的一步**:`/ggx/api/` 的反代块必须先于静态规则,且 location 与
|
||
`proxy_pass` 的末尾斜杠要成对。少了它,`/ggx/api/health` 会被当作静态文件去
|
||
`output/api/health` 找,必然 404 —— 页面能打开但显示「API 不可用」。
|
||
|
||
子路径布局的关键两段:
|
||
|
||
```nginx
|
||
# API 必须放在静态规则之前
|
||
location /ggx/api/ {
|
||
proxy_pass http://127.0.0.1:8099/api/;
|
||
proxy_set_header Host $host;
|
||
add_header Cache-Control "no-store" always;
|
||
}
|
||
|
||
location /ggx/ {
|
||
alias /srv/hddiv/site/;
|
||
try_files $uri $uri/ /ggx/index.html;
|
||
}
|
||
```
|
||
|
||
```bash
|
||
nginx -t && nginx -s reload
|
||
```
|
||
|
||
### 第 4 步:验证
|
||
|
||
```bash
|
||
# 后端健康
|
||
curl http://127.0.0.1:8099/api/health
|
||
|
||
# 经 nginx 的接口(应与上面返回一致)
|
||
curl http://192.168.1.166:8080/ggx/api/health
|
||
|
||
# 前端入口
|
||
curl -I http://192.168.1.166:8080/ggx/index.html
|
||
```
|
||
|
||
浏览器打开 `http://192.168.1.166:8080/ggx/`,右上角应显示 **「API 正常」**。
|
||
若显示「API 不可用」,说明 nginx 的 `/ggx/api/` 反代未生效。
|
||
|
||
## 10.4 前端功能
|
||
|
||
| 页面 | 路径 | 功能 |
|
||
|---|---|---|
|
||
| 概览 | `#/` | 统计卡片、最近筛选与回测 |
|
||
| 股票池筛选 | `#/universes` | 记录列表:**命名 / 归档 / 删除 / 打开清单** |
|
||
| 股票清单 | `#/universes/<run_id>` | 入选与淘汰股票、逐股关键指标、**点击个股打开画像** |
|
||
| 个股画像 | `#/stocks/<symbol>` | K线+股息率+PE 四联图、历史分布、安全边际雷达 |
|
||
| 回测记录 | `#/backtests` | 每条记录显示**回测条件与总体结果** |
|
||
| 回测详情 | `#/backtests/<run_id>` | 净值曲线、绩效指标、**逐笔成交与理由** |
|
||
| 归档 | `#/archive` | 已归档与已删除的记录,可恢复 |
|
||
|
||
### 记录管理
|
||
|
||
- **命名**:点「命名」按钮,可设置名称与备注。名称会显示在列表与详情页标题上
|
||
- **归档**:归档后默认列表不再显示,可在「归档」页找到并取消归档
|
||
- **删除**:**软删除** —— 记录被隐藏,但数据完整保留在数据库中,可随时恢复。
|
||
本项目对研究数据坚持「只增不删」,因此不提供物理删除
|
||
|
||
### 股票池 ↔ 回测 的关联
|
||
|
||
有两条途径建立关联:
|
||
|
||
1. **自动**:用指定股票池跑回测
|
||
|
||
```bash
|
||
python -m hdiv backtest --universe-run <run_id>
|
||
```
|
||
|
||
2. **手动**:在回测列表点「命名」,或直接改数据库 `hd_backtest_run.universe_run_id`
|
||
|
||
关联后:
|
||
- 股票池详情页的「关联的回测」区块会列出这些回测
|
||
- 回测详情页顶部会显示「来源股票池」链接
|
||
|
||
## 10.5 API 一览
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|---|---|---|
|
||
| GET | `/api/health` | 健康检查 |
|
||
| GET | `/api/summary` | 概览统计 |
|
||
| GET | `/api/universes` | 筛选记录列表(`?include_archived=&include_deleted=&q=`) |
|
||
| GET | `/api/universes/{run_id}` | 单条记录详情 |
|
||
| PATCH | `/api/universes/{run_id}` | 命名 / 备注 / 归档 / 软删除 |
|
||
| GET | `/api/universes/{run_id}/members` | 股票清单(`?passed=1\|0\|all&page=&size=&q=`) |
|
||
| GET | `/api/universes/{run_id}/members/{symbol}` | 单只股票的完整指标快照 |
|
||
| GET | `/api/universes/{run_id}/backtests` | 关联的回测 |
|
||
| GET | `/api/stocks/{symbol}` | 个股画像(含序列、分位、得分) |
|
||
| GET | `/api/backtests` | 回测记录列表(含条件说明) |
|
||
| GET | `/api/backtests/{run_id}` | 回测详情(含资金对账) |
|
||
| PATCH | `/api/backtests/{run_id}` | 命名 / 归档 / 软删除 / 关联股票池 |
|
||
| GET | `/api/backtests/{run_id}/metrics` | 绩效指标 |
|
||
| GET | `/api/backtests/{run_id}/equity` | 净值曲线 |
|
||
| GET | `/api/backtests/{run_id}/trades` | 逐笔成交与理由 |
|
||
| GET | `/api/backtests/{run_id}/positions` | 持仓明细 |
|
||
| GET | `/api/backtests/{run_id}/signals` | 未成交信号与原因 |
|
||
|
||
## 10.6 CLI 命令
|
||
|
||
```bash
|
||
hdiv web [--host 127.0.0.1] [--port 8099] [--api-only] [--out-dir output]
|
||
hdiv site {normalize|archive|build|status}
|
||
```
|
||
|
||
---
|
||
|
||
# 11. 故障排查
|
||
|
||
## 11.1 图表不显示(页面能开、容器空白)
|
||
|
||
**原因**:内联 JS 被 HTML 转义(`"` → `"`)导致语法错误,或图表库未部署。
|
||
|
||
**排查**:
|
||
|
||
```bash
|
||
.venv/bin/python -m hdiv report validate
|
||
```
|
||
|
||
它会做三件事:检测 `<script>` 内的 HTML 实体、用 `node --check` 校验 JS 语法、
|
||
检查每个图表容器是否被脚本引用。
|
||
|
||
**解决**:
|
||
- 若是语法错误 → 重新生成报告(代码已修复)
|
||
- 若是 404 → 部署 `output/assets/` 目录,或改用 `asset_mode: inline`
|
||
|
||
## 11.2 页面显示「API 不可用」
|
||
|
||
右上角的状态徽章来自 `GET api/health`。显示「API 不可用」说明这个请求失败了。
|
||
|
||
**按顺序排查(三层,从内到外)**:
|
||
|
||
```bash
|
||
# ① 后端进程本身是否活着
|
||
curl http://127.0.0.1:8099/api/health
|
||
# 失败 → 后端没跑。启动:./deploy/install-service.sh install
|
||
|
||
# ② nginx 是否把 /api 反代出去了
|
||
curl http://127.0.0.1:8080/ggx/api/health
|
||
# ①通 ②不通 → nginx 配置缺 /ggx/api/ 反代块(最常见)
|
||
|
||
# ③ 从浏览器所在机器验证
|
||
curl http://192.168.1.166:8080/ggx/api/health
|
||
# ②通 ③不通 → 防火墙 / 监听地址问题
|
||
```
|
||
|
||
**看 nginx 错误日志最直接**:
|
||
|
||
```bash
|
||
tail -5 /usr/local/var/log/nginx/ggx.error.log
|
||
```
|
||
|
||
若看到类似这一行,就确认是缺反代块:
|
||
|
||
```text
|
||
open() "/Users/summer/project/高股息回测/output/api/health" failed (2: No such file or directory)
|
||
```
|
||
|
||
它说明 nginx 把 `/ggx/api/health` 当成**静态文件**去 `output/` 里找了。
|
||
需要在 vhost 里补上(注意 location 与 proxy_pass 的末尾斜杠成对):
|
||
|
||
```nginx
|
||
location /ggx/api/ {
|
||
proxy_pass http://127.0.0.1:8099/api/;
|
||
proxy_set_header Host $host;
|
||
add_header Cache-Control "no-store" always;
|
||
}
|
||
```
|
||
|
||
本项目的 nginx 配置由 `scripts/mac_nginx_ggx.sh` 生成(在 system 项目里),
|
||
改脚本后执行 `bash scripts/mac_nginx_ggx.sh apply` 重生成并热加载,
|
||
不要直接改生成的 `ggx.conf`。
|
||
|
||
**体检命令**(一次性检查 nginx、后端、反代三项):
|
||
|
||
```bash
|
||
bash scripts/mac_nginx_ggx.sh status # system 项目
|
||
./deploy/install-service.sh status # 本项目(后端侧)
|
||
```
|
||
|
||
## 11.3 股票池为空或很少
|
||
|
||
**按顺序排查**:
|
||
|
||
1. **数据是否同步完成?** 看审计报告 G1/G4 是否 OK
|
||
```bash
|
||
.venv/bin/python -m hdiv audit
|
||
```
|
||
2. **看漏斗哪一层淘汰最多**
|
||
```sql
|
||
SELECT fail_stage, COUNT(*) FROM hd_universe_member
|
||
WHERE run_id='<run_id>' GROUP BY fail_stage;
|
||
```
|
||
3. **看具体原因分布**
|
||
```sql
|
||
SELECT fail_reason, COUNT(*) FROM hd_universe_member
|
||
WHERE run_id='<run_id>' AND fail_stage='dividend'
|
||
GROUP BY fail_reason ORDER BY 2 DESC LIMIT 10;
|
||
```
|
||
4. 常见原因:市值门槛过高、`min_dividend_yield` 过高、财报未同步导致 ROE 不可得
|
||
|
||
## 11.4 回测报「资金对账未通过」
|
||
|
||
**含义**:成本或分红入账有遗漏,绩效指标不可信。
|
||
|
||
**排查**:对比 `hd_backtest_run` 的 `cash_final` / `total_dividend_net` / `total_fees`
|
||
与 `hd_backtest_trade` 的汇总值。若分红为 0 但持仓期间有除权,检查 `hd_dividend` 是否有该股票数据。
|
||
|
||
## 11.5 回测收益为 0 / 完全空仓
|
||
|
||
**最可能原因:预热期**。滚动分位窗口需要历史分布;数据起点之前的时段无法产生信号。
|
||
若回测区间前 3~5 年完全空仓、之后才开始建仓,这是**预期行为**(不做未来函数的代价)。
|
||
|
||
**确认方法**:
|
||
|
||
```sql
|
||
SELECT YEAR(trade_date) AS 年, MAX(holding_count) AS 最大持仓,
|
||
ROUND(AVG(cash)/AVG(total_value)*100,1) AS 平均现金占比
|
||
FROM hd_backtest_equity WHERE run_id='<run_id>'
|
||
GROUP BY YEAR(trade_date) ORDER BY 年;
|
||
```
|
||
|
||
## 11.6 同步报「频率超限」
|
||
|
||
Tushare 限频是**按接口**计算的(如 `dividend` 上限 200 次/分钟)。
|
||
系统命中限频会**冷却 62 秒**后重试(滑动窗口)。
|
||
|
||
**调整**:改 `config/datasource.yml` 的 `tushare.rate_limits`,调低对应接口的值。
|
||
|
||
## 11.7 配置报错
|
||
|
||
系统对配置做**严格校验**,字段名写错会直接报错并指出位置:
|
||
|
||
```
|
||
配置校验失败:config/universe.yml
|
||
1 validation error for UniverseConfig
|
||
market.min_market_capp
|
||
Extra inputs are not permitted
|
||
```
|
||
|
||
按提示修正字段名即可。**这是刻意设计** —— 静默取默认值会导致「改了配置但没生效」。
|
||
|
||
## 11.8 回测很慢
|
||
|
||
| 手段 | 效果 |
|
||
|---|---|
|
||
| 调大 `backtest.yml: schedule.universe_refresh_months` | 减少股票池重建次数 |
|
||
| 调大 `schedule.signal_frequency_months` | 减少信号评估次数 |
|
||
| 缩小回测区间 | 线性加速 |
|
||
| 调小 `universe.yml: output.max_members` | 减少画像与信号计算量 |
|
||
|
||
---
|
||
|
||
# 12. 已知限制
|
||
|
||
> 以下均为**如实声明**,不是待办清单的措辞修饰。
|
||
|
||
| # | 限制 | 影响 |
|
||
|---|---|---|
|
||
| 1 | **策略缺少稳定的样本外超额收益** | 见 §7.5。这是最重要的一条 |
|
||
| 2 | 涨跌停/停牌约束覆盖 **2019 年起** | 2015–2018 为近似建模,引擎在 `unimplemented_json` 中声明 |
|
||
| 3 | `index_weight` 成分股权重为空 | 不影响基准收益计算(用指数点位),仅影响成分股分析 |
|
||
| 4 | 未实现部分成交 | 按信号全额成交,受资金与权重上限约束 |
|
||
| 5 | 大股东质押、重大诉讼过滤**无数据源** | 配置项存在但恒不生效 |
|
||
| 6 | AI Agent 层(P8)未实现 | 属 `plan.md` 第四版扩展 |
|
||
| 7 | 回测 2015–2018 为预热期 | 100% 现金,无信号(见 §10.4) |
|
||
| 8 | 参数敏感性结论依赖扫描区间与池规模 | 样本不足时噪声可能被误读为过拟合 |
|
||
|
||
## 12.1 使用前请务必知道
|
||
|
||
1. **系统不会替你判断策略好坏**。它提供的是证据与规范化口径,
|
||
最终判断(尤其「样本外超额为负」这一条)需要你自己权衡。
|
||
2. **单条路径的全期回测数字会显著高估策略**。请始终以 Walk-forward 的
|
||
样本外结果为主要依据。
|
||
3. **数据口径有回归测试守护,但数据本身来自 Tushare**,
|
||
个别记录可能有问题(实测有 2 条公告日早于报告期的错误记录,
|
||
PIT 查询层已过滤并在审计中如实报告)。
|
||
4. **所有输出都可追溯**:报告页脚有数据来源 SQL,数据库里有完整
|
||
`run_id` / `config_hash` / `data_version`,同输入必同输出。
|
||
|
||
---
|
||
|
||
# 附:一分钟速查
|
||
|
||
```bash
|
||
export PYTHONPATH=src
|
||
|
||
# 建表 / 查表
|
||
.venv/bin/python -m hdiv ddl apply
|
||
.venv/bin/python -m hdiv ddl verify
|
||
|
||
# 数据
|
||
.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 audit
|
||
|
||
# 研究
|
||
.venv/bin/python -m hdiv universe --asof 2024-06-28
|
||
.venv/bin/python -m hdiv profile --universe-run <run_id>
|
||
.venv/bin/python -m hdiv strategy register
|
||
.venv/bin/python -m hdiv backtest
|
||
.venv/bin/python -m hdiv backtest --mode walkforward
|
||
.venv/bin/python -m hdiv sensitivity
|
||
|
||
# 报告
|
||
.venv/bin/python -m hdiv report index
|
||
.venv/bin/python -m hdiv report validate
|
||
```
|
||
|
||
**要改的东西**:
|
||
|
||
| 想改什么 | 改哪个文件 |
|
||
|---|---|
|
||
| 筛选条件(市值/分红/ROE…) | `config/universe.yml` |
|
||
| 个股画像口径(窗口/分位/权重) | `config/profile.yml` |
|
||
| 买卖阈值与仓位 | `config/strategy/high_dividend_v1.yml` |
|
||
| 手续费/滑点/红利税 | `config/cost.yml` |
|
||
| 回测区间/Walk-forward/基准 | `config/backtest.yml` |
|
||
| 报告外观与部署方式 | `config/report.yml` |
|
||
| 后端监听地址/端口 | `hdiv web` 命令行参数(或 `deploy/serve.sh` / launchd plist) |
|