Files
simon fce725e13c 初始提交:高股息策略研究与回测系统
从 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。
2026-10-03 13:54:56 +08:00

201 lines
6.9 KiB
HTML
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.
{% 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 %}