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

160 lines
8.3 KiB
CSS
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.
/* ============================================================
高股息回测系统 · 统一前端样式
配色与静态报告保持一致(Blue data + Amber highlight)
A 股约定:红涨绿跌
============================================================ */
:root{
--primary:#1E40AF; --primary-fg:#fff; --secondary:#3B82F6; --accent:#D97706;
--bg:#F8FAFC; --card:#fff; --muted:#E9EEF6; --border:#DBEAFE;
--text:#0F172A; --text-muted:#64748B; --danger:#DC2626;
--gain:#DC2626; --loss:#059669;
--radius:8px; --pill:999px;
--mono:"Fira Code",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--sans:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB",
"Microsoft YaHei","Segoe UI",Roboto,sans-serif;
}
*,*::before,*::after{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:14px/1.6 var(--sans);
font-variant-numeric:tabular-nums}
a{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
/* ---------- 顶栏 ---------- */
.topbar{display:flex;align-items:center;gap:24px;padding:0 24px;height:56px;
background:var(--primary);color:var(--primary-fg);position:sticky;top:0;z-index:50}
.brand{color:var(--primary-fg);font-weight:700;font-size:16px;letter-spacing:-.01em}
.brand:hover{text-decoration:none;opacity:.9}
.nav{display:flex;gap:4px;flex:1}
.nav a{color:rgba(255,255,255,.82);padding:6px 12px;border-radius:var(--radius);
font-size:13px}
.nav a:hover{background:rgba(255,255,255,.12);text-decoration:none;color:#fff}
.nav a.active{background:rgba(255,255,255,.2);color:#fff;font-weight:600}
.topbar-right{display:flex;align-items:center;gap:10px}
.topbar .muted{color:rgba(255,255,255,.7)}
.wrap{max-width:1440px;margin:0 auto;padding:24px}
.foot{max-width:1440px;margin:0 auto;padding:20px 24px 40px}
/* ---------- 徽章 ---------- */
.badge{display:inline-block;padding:1px 9px;border-radius:var(--pill);font-size:11px;
font-weight:600;background:var(--muted);color:var(--primary);white-space:nowrap}
.badge.ok{background:#D1FAE5;color:#065F46}
.badge.warn{background:#FEF3C7;color:#92400E}
.badge.fail{background:#FEE2E2;color:#991B1B}
.badge.info{background:var(--muted);color:var(--primary)}
.badge.gain{background:#FEE2E2;color:#991B1B}
.badge.loss{background:#D1FAE5;color:#065F46}
.badge.dim{background:#F1F5F9;color:#94A3B8}
/* ---------- 卡片 / 栅格 ---------- */
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);
padding:20px;margin-bottom:20px}
.card>h2{margin:0 0 14px;font-size:15px;font-weight:600;color:var(--primary);
padding-bottom:8px;border-bottom:2px solid var(--muted);
display:flex;align-items:center;gap:8px}
.card>h3{margin:16px 0 8px;font-size:13px;color:var(--primary)}
.kpi-grid{display:grid;gap:14px;margin-bottom:20px;
grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.kpi{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);
padding:14px}
.kpi .label{font-size:12px;color:var(--text-muted);letter-spacing:.03em}
.kpi .value{font-family:var(--mono);font-size:22px;font-weight:600;margin-top:4px}
.kpi .note{font-size:11px;color:var(--text-muted);margin-top:4px}
.gain{color:var(--gain)} .loss{color:var(--loss)}
.grid2{display:grid;gap:20px;grid-template-columns:repeat(auto-fit,minmax(420px,1fr))}
/* ---------- 列表(卡片式记录) ---------- */
.rec-list{display:flex;flex-direction:column;gap:10px}
.rec{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);
padding:14px 16px;display:flex;gap:16px;align-items:flex-start;transition:.12s}
.rec:hover{border-color:var(--secondary);box-shadow:0 2px 10px rgba(30,64,175,.07)}
.rec.dim{opacity:.55;background:#FCFDFF}
.rec-main{flex:1;min-width:0}
.rec-title{font-weight:600;color:var(--primary);font-size:15px;
display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.rec-title a{color:inherit}
.rec-meta{font-size:12px;color:var(--text-muted);margin-top:5px;
display:flex;gap:14px;flex-wrap:wrap}
.rec-actions{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;
max-width:330px}
.rec-metrics{display:flex;gap:18px;flex-wrap:wrap;margin-top:10px}
.rec-metric{font-size:12px;color:var(--text-muted)}
.rec-metric b{display:block;font-family:var(--mono);font-size:15px;color:var(--text)}
/* ---------- 按钮 ---------- */
button,.btn{font:inherit;font-size:12px;padding:5px 11px;border-radius:6px;cursor:pointer;
border:1px solid var(--border);background:var(--card);color:var(--primary);
transition:.12s;white-space:nowrap}
button:hover,.btn:hover{border-color:var(--secondary);background:#EFF6FF}
button.primary{background:var(--primary);color:#fff;border-color:var(--primary)}
button.primary:hover{background:#1D4ED8}
button.danger{color:var(--danger);border-color:#FECACA}
button.danger:hover{background:#FEF2F2;border-color:var(--danger)}
button.ghost{color:var(--text-muted)}
button:disabled{opacity:.5;cursor:not-allowed}
/* ---------- 表格 ---------- */
.tw{overflow:auto;border:1px solid var(--border);border-radius:var(--radius)}
table.data{width:100%;border-collapse:collapse;font-size:13px;background:var(--card)}
table.data th,table.data td{padding:7px 11px;border-bottom:1px solid var(--muted);
text-align:right;white-space:nowrap}
table.data th{background:var(--muted);color:var(--primary);font-weight:600;
position:sticky;top:0;z-index:1}
table.data th:first-child,table.data td:first-child{text-align:left}
table.data th.l,table.data td.l{text-align:left}
table.data td.num{font-family:var(--mono)}
table.data tbody tr:hover{background:#F1F5F9}
table.data tbody tr:last-child td{border-bottom:none}
.pager{display:flex;gap:10px;align-items:center;justify-content:flex-end;
margin-top:12px;font-size:12px;color:var(--text-muted)}
/* ---------- 表单 ---------- */
input[type=text],input[type=search],select,textarea{
font:inherit;font-size:13px;padding:6px 10px;border:1px solid var(--border);
border-radius:6px;background:#fff;color:var(--text);width:100%}
input:focus,select:focus,textarea:focus{outline:2px solid var(--secondary);
outline-offset:-1px;border-color:var(--secondary)}
.field{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}
.field label{font-size:12px;color:var(--text-muted)}
.toolbar{display:flex;gap:10px;align-items:center;margin-bottom:16px;flex-wrap:wrap}
.toolbar .grow{flex:1;min-width:200px}
.check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-muted)}
/* ---------- 提示 ---------- */
.callout{border-left:4px solid var(--secondary);background:#EFF6FF;padding:10px 14px;
border-radius:0 var(--radius) var(--radius) 0;margin:0 0 16px;font-size:13px}
.callout.ok{border-color:var(--loss);background:#ECFDF5}
.callout.warn{border-color:var(--accent);background:#FFFBEB}
.callout.fail{border-color:var(--danger);background:#FEF2F2}
.muted{color:var(--text-muted)} .small{font-size:12px}
.mono{font-family:var(--mono)}
.chart{width:100%;height:420px}
.chart.tall{height:640px} .chart.short{height:260px}
.loading{padding:60px;text-align:center;color:var(--text-muted)}
.empty{padding:44px;text-align:center;color:var(--text-muted)}
/* ---------- Toast ---------- */
.toast{position:fixed;left:50%;bottom:32px;transform:translateX(-50%) translateY(20px);
background:#0F172A;color:#fff;padding:10px 18px;border-radius:var(--radius);
font-size:13px;opacity:0;pointer-events:none;transition:.2s;z-index:99;max-width:80vw}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.toast.err{background:var(--danger)}
/* ---------- 模态 ---------- */
.modal-bg{position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;
align-items:center;justify-content:center;z-index:100;padding:20px}
.modal{background:#fff;border-radius:var(--radius);padding:22px;width:min(560px,100%);
max-height:85vh;overflow:auto}
.modal h3{margin:0 0 16px;font-size:15px;color:var(--primary)}
.modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:18px}
/* ---------- 面包屑 ---------- */
.crumb{font-size:12px;color:var(--text-muted);margin-bottom:14px}
.crumb a{color:var(--text-muted)}
.crumb span{margin:0 6px}
h1.page{font-size:20px;margin:0 0 6px;letter-spacing:-.01em}
.page-sub{color:var(--text-muted);font-size:13px;margin-bottom:18px}
/* ---------- 图表面板标签 ---------- */
.panel-grid{display:grid;gap:20px;grid-template-columns:repeat(auto-fit,minmax(380px,1fr))}