初始提交:高股息策略研究与回测系统

从 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。
This commit is contained in:
2026-10-03 13:54:56 +08:00
commit fce725e13c
127 changed files with 28001 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
{% extends "base.html" %}
{% block content %}
<div class="callout {{ 'ok' if overall == 'OK' else ('warn' if overall == 'WARN' else 'fail') }}">
<strong>总体结论:{{ overall }}</strong> —
共 {{ counts.OK + counts.WARN + counts.FAIL }} 项检查,
通过 <strong>{{ counts.OK }}</strong>,
警告 <strong>{{ counts.WARN }}</strong>,
失败 <strong>{{ counts.FAIL }}</strong>。
{% if fail_codes %}未通过项:<span class="mono">{{ fail_codes }}</span>{% endif %}
</div>
<div class="kpi-grid">
<div class="kpi">
<div class="label">检查项</div>
<div class="value">{{ counts.OK + counts.WARN + counts.FAIL }}</div>
<div class="note">覆盖缺口 G1–G6 与一致性</div>
</div>
<div class="kpi">
<div class="label">通过率</div>
<div class="value">{{ pass_rate }}</div>
<div class="note">{{ counts.OK }} / {{ counts.OK + counts.WARN + counts.FAIL }}</div>
</div>
<div class="kpi">
<div class="label">阻塞项 (FAIL)</div>
<div class="value {{ 'loss' if counts.FAIL == 0 else 'gain' }}">{{ counts.FAIL }}</div>
<div class="note">为 0 才可进入策略回测</div>
</div>
<div class="kpi">
<div class="label">审计编号</div>
<div class="value small mono" style="font-size:14px">{{ audit_id }}</div>
<div class="note">可在 hd_data_audit 表回溯</div>
</div>
</div>
<div class="card">
<h2>缺口总览</h2>
<div class="table-scroll">
<table class="data">
<thead>
<tr>
<th>检查码</th><th>检查项</th><th>结论</th><th>数据表</th>
<th style="text-align:left">说明</th>
</tr>
</thead>
<tbody>
{% for r in gap_results %}
<tr>
<td class="mono">{{ r.code }}</td>
<td style="text-align:left">{{ r.name }}</td>
<td><span class="badge {{ r.severity }}">{{ r.severity }}</span></td>
<td class="mono small">{{ r.table or '—' }}</td>
<td style="text-align:left" class="small">{{ r.message }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if coverage_rows %}
<div class="card">
<h2>数据覆盖率</h2>
<div class="table-scroll">
<table class="data">
<thead>
<tr>
<th>表</th><th>行数</th><th>起始</th><th>截止</th>
<th>交易日最少股票数</th><th style="text-align:left">目标</th><th>状态</th>
</tr>
</thead>
<tbody>
{% for c in coverage_rows %}
<tr>
<td class="mono">{{ c.table }}</td>
<td class="num">{{ c.rows }}</td>
<td class="num">{{ c.min or '—' }}</td>
<td class="num">{{ c.max or '—' }}</td>
<td class="num">{{ c.min_per_day if c.min_per_day is not none else '—' }}</td>
<td style="text-align:left" class="small muted">{{ c.target }}</td>
<td><span class="badge {{ c.severity }}">{{ c.severity }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="card">
<h2>分红数据明细(G1 · 高股息策略的数据基座)</h2>
{% if dividend_summary %}
<div class="kpi-grid">
<div class="kpi"><div class="label">覆盖股票</div><div class="value">{{ dividend_summary.symbols }}</div><div class="note">只</div></div>
<div class="kpi"><div class="label">可用分红记录</div><div class="value">{{ dividend_summary.valid }}</div><div class="note">div_proc=实施 且有除权/派息日</div></div>
<div class="kpi"><div class="label">报告期最早</div><div class="value" style="font-size:16px">{{ dividend_summary.min }}</div><div class="note">end_date</div></div>
<div class="kpi"><div class="label">报告期最晚</div><div class="value" style="font-size:16px">{{ dividend_summary.max }}</div><div class="note">end_date</div></div>
</div>
{% endif %}
{% if dividend_by_proc %}
<h3>按 div_proc 分布</h3>
<div class="table-scroll">
<table class="data">
<thead><tr><th>div_proc</th><th>记录数</th><th>占比</th></tr></thead>
<tbody>
{% for r in dividend_by_proc %}
<tr>
<td class="mono">{{ r.div_proc }}</td>
<td class="num">{{ r.n }}</td>
<td class="num">{{ r.share }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="callout">
口径说明(决策 D6):连续性判定只认 <code>div_proc = '实施'</code> 且存在
<code>ex_date</code> 或 <code>pay_date</code> 的记录,且以<strong>税前</strong>现金分红
<code>cash_div_tax &gt; 0</code> 计一次分红。其余状态(预案/股东大会通过/股东提议)
保留在库中供研究,但不参与筛选判定。
</div>
{% endif %}
{% if dividend_by_year %}
<h3>按除权年度分布(近 15 年)</h3>
<div id="chart-div-year" class="chart short"></div>
{% endif %}
</div>
<div class="card">
<h2>全部检查结果</h2>
<div class="table-scroll">
<table class="data">
<thead>
<tr><th>检查码</th><th>检查项</th><th>结论</th><th>数据表</th><th style="text-align:left">说明</th></tr>
</thead>
<tbody>
{% for r in all_results %}
<tr>
<td class="mono">{{ r.code }}</td>
<td style="text-align:left">{{ r.name }}</td>
<td><span class="badge {{ r.severity }}">{{ r.severity }}</span></td>
<td class="mono small">{{ r.table or '—' }}</td>
<td style="text-align:left" class="small">{{ r.message }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<details>
<summary>查看各项检查的原始指标(metric_json)</summary>
<div class="table-scroll">
<table class="data">
<thead><tr><th>检查码</th><th style="text-align:left">指标</th></tr></thead>
<tbody>
{% for r in all_results %}
{% if r.metrics %}
<tr>
<td class="mono">{{ r.code }}</td>
<td style="text-align:left"><pre class="mono small" style="margin:0;white-space:pre-wrap">{{ r.metrics_pretty }}</pre></td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</details>
</div>
{% endblock %}
{% block scripts %}
{% if dividend_by_year %}
<script>
(function () {
var el = document.getElementById('chart-div-year');
if (!el || typeof echarts === 'undefined') return;
var chart = echarts.init(el);
var data = {{ dividend_by_year | json }};
chart.setOption({
color: {{ chart_colors | json }},
grid: { left: 60, right: 24, top: 32, bottom: 40 },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: data.map(function (d) { return d.year; }),
axisLabel: { color: '#64748B' } },
yAxis: { type: 'value', name: '分红实施次数', axisLabel: { color: '#64748B' },
splitLine: { lineStyle: { color: '#E9EEF6' } } },
series: [{
type: 'bar', data: data.map(function (d) { return d.n; }),
itemStyle: { color: '#1E40AF' }, barMaxWidth: 28
}]
});
window.addEventListener('resize', function () { chart.resize(); });
})();
</script>
{% endif %}
{% endblock %}