Files
sanguo_vnpy_v2/docs/proposal_strategy_library_hybrid.html
T

329 lines
17 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>策略库 · A+B 混合原型</title>
<style>
:root {
--ground: #070b10;
--card: #0d141c;
--panel: #101923;
--panel-2: #0a1119;
--border: #1d2a36;
--border-2: #16202b;
--brand: #00e5ff;
--brand-soft: rgba(0, 229, 255, 0.10);
--amber: #ffb000;
--amber-soft: rgba(255, 176, 0, 0.10);
--ok: #2ee68a;
--ok-soft: rgba(46, 230, 138, 0.10);
--err: #ff5470;
--text: #cfdae2;
--text-2: #9fb2bf;
--text-3: #64798a;
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
--sans: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--ground); color: var(--text);
font-family: var(--sans); font-size: 14px; line-height: 1.55;
padding: 22px 22px 60px; min-height: 100vh;
}
.page { max-width: 1180px; margin: 0 auto; display: flex; flex-direction: column; gap: 14px; }
/* ===== 头部 ===== */
.head { display: flex; align-items: flex-start; justify-content: space-between; }
.title { font-size: 20px; font-weight: 600; color: #e8f1f7; }
.subtitle { font-size: 12.5px; color: var(--text-3); margin-top: 3px; }
.btn {
font-family: var(--mono); font-size: 11px; padding: 6px 14px; border-radius: 6px;
border: 1px solid var(--border); color: var(--text-2); background: transparent; cursor: pointer;
transition: all .12s;
}
.btn:hover { border-color: rgba(0,229,255,.5); color: var(--brand); }
.btn.primary { color: #06252b; background: var(--brand); border-color: var(--brand); font-weight: 700; }
.btn.primary:hover { filter: brightness(1.1); color: #06252b; }
.btn.sm { padding: 3px 10px; font-size: 10.5px; }
.btn.danger:hover { border-color: rgba(255,84,112,.5); color: var(--err); }
/* ===== B 借来的统计条 ===== */
.stats { display: flex; gap: 10px; }
.stat {
flex: 1; border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2);
padding: 10px 16px; display: flex; align-items: baseline; gap: 10px; cursor: default;
}
.stat .n { font-family: var(--mono); font-size: 21px; font-weight: 700; color: #e8f1f7; }
.stat .l { font-size: 11.5px; color: var(--text-3); }
.stat.clickable { cursor: pointer; transition: border-color .12s; }
.stat.clickable:hover { border-color: rgba(0,229,255,.45); }
.stat.on { border-color: rgba(0,229,255,.55); background: var(--brand-soft); }
.stat.on .n { color: var(--brand); }
.stat .n.brand { color: var(--brand); }
.stat .n.warn { color: var(--amber); }
/* ===== 主体:A 主从 ===== */
.body { display: grid; grid-template-columns: 224px 1fr; gap: 0; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--card); min-height: 520px; }
.tree {
border-right: 1px solid var(--border); background: var(--panel-2);
padding: 10px 0; display: flex; flex-direction: column;
}
.search {
margin: 0 10px 8px; padding: 6px 10px; border: 1px solid var(--border-2); border-radius: 6px;
background: var(--card); color: var(--text-3); font-family: var(--mono); font-size: 11px; outline: none; width: calc(100% - 20px);
}
.search:focus { border-color: rgba(0,229,255,.4); }
.grp {
padding: 10px 13px 4px; font-size: 10.5px; font-weight: 700; color: var(--text-3);
letter-spacing: 1.2px; display: flex; align-items: center; gap: 7px; text-transform: uppercase;
font-family: var(--mono);
}
.grp .mark { width: 3px; height: 10px; background: var(--brand); box-shadow: 0 0 7px rgba(0,229,255,.6); }
.item {
display: flex; align-items: center; justify-content: space-between; gap: 6px;
padding: 7px 13px; font-family: var(--mono); font-size: 11.5px; color: var(--text-2);
border-left: 2px solid transparent; cursor: pointer; transition: background .1s;
}
.item:hover { background: rgba(255,255,255,.035); }
.item.on { background: var(--brand-soft); border-left-color: var(--brand); color: var(--brand); }
.cnt { font-size: 9.5px; color: var(--text-3); background: rgba(255,255,255,.05); border-radius: 999px; padding: 0 6px; }
.item.on .cnt { color: var(--brand); }
.item .running-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 6px rgba(46,230,138,.9); }
.main { display: flex; flex-direction: column; }
.codehead {
display: flex; align-items: center; gap: 12px; padding: 14px 18px;
border-bottom: 1px solid var(--border); background: var(--panel);
}
.fname { font-family: var(--mono); font-size: 14.5px; font-weight: 700; color: #e8f1f7; }
.cls { font-family: var(--mono); font-size: 11px; color: var(--text-3); }
.chip {
display: inline-flex; align-items: center; font-family: var(--mono); font-size: 10px;
padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-3); white-space: nowrap;
}
.chip.pf { color: var(--brand); border-color: rgba(0,229,255,.35); background: var(--brand-soft); }
.chip.cta { color: var(--amber); border-color: rgba(255,176,0,.35); background: var(--amber-soft); }
.chip.drift { color: var(--amber); border-color: rgba(255,176,0,.5); background: var(--amber-soft); }
.spacer { margin-left: auto; }
/* 实例行 */
.insts { padding: 6px 10px; flex: 1; }
.row {
display: grid; grid-template-columns: 1.3fr 1.9fr 0.8fr 1.15fr 1.55fr auto;
gap: 12px; align-items: center; padding: 13px 10px;
border-bottom: 1px solid var(--border-2); border-radius: 6px; transition: background .1s;
}
.row:hover { background: rgba(255,255,255,.025); }
.iname { font-size: 13px; font-weight: 600; color: var(--text); cursor: pointer; }
.iname:hover { color: var(--brand); }
.iname .sub { display: block; font-family: var(--mono); font-size: 9.5px; font-weight: 400; color: var(--text-3); margin-top: 2px; }
.params { font-family: var(--mono); font-size: 10.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sym { font-family: var(--mono); font-size: 11.5px; color: var(--amber); }
.ret { font-family: var(--mono); font-weight: 700; font-size: 13.5px; }
.ret .sub { display: block; font-size: 9.5px; font-weight: 400; color: var(--text-3); }
.up { color: var(--err); } .down { color: var(--ok); }
.runs { display: flex; gap: 5px; flex-wrap: wrap; }
.pill {
display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: 10.5px;
padding: 3px 9px; border-radius: 5px; border: 1px solid var(--border); color: var(--text-3);
cursor: pointer; background: transparent; transition: border-color .12s;
}
.pill:hover { border-color: rgba(0,229,255,.5); color: var(--brand); }
.pill .lamp { width: 5px; height: 5px; border-radius: 50%; background: #2a3946; flex-shrink: 0; }
.pill.run .lamp { background: var(--ok); box-shadow: 0 0 6px rgba(46,230,138,.8); }
.pill.done .lamp { background: var(--ok); }
.pill.stopped .lamp { background: #4a5f6e; }
.pill.run { color: var(--text-2); border-color: rgba(46,230,138,.3); }
.ops { display: flex; gap: 5px; }
.empty { padding: 34px 16px; color: var(--text-3); font-size: 12.5px; text-align: center; }
/* 在跑运行列表模式(点统计条「在跑运行」切换) */
.runsview { padding: 6px 10px; }
.runrow {
display: grid; grid-template-columns: 0.9fr 1.1fr 1.5fr 1fr 1.3fr; gap: 12px; align-items: center;
padding: 12px 10px; border-bottom: 1px solid var(--border-2); border-radius: 6px;
}
.runrow:hover { background: rgba(255,255,255,.025); }
.tag-kind {
font-family: var(--mono); font-size: 10px; padding: 2px 8px; border-radius: 4px;
border: 1px solid var(--border); color: var(--text-2);
}
.tag-kind.live { color: var(--err); border-color: rgba(255,84,112,.4); background: rgba(255,84,112,.07); }
.tag-kind.shadow { color: var(--brand); border-color: rgba(0,229,255,.4); background: var(--brand-soft); }
.tag-kind.paper { color: var(--ok); border-color: rgba(46,230,138,.4); background: var(--ok-soft); }
.hint {
margin-top: 10px; font-size: 11.5px; color: var(--text-3); text-align: center;
font-family: var(--mono);
}
</style>
</head>
<body>
<div class="page">
<div class="head">
<div>
<div class="title">策略库</div>
<div class="subtitle">代码 → 实例档案 → 回测 / 模拟 / 实盘 · 全生命周期</div>
</div>
<div style="display:flex;gap:8px">
<span class="btn">导入策略</span>
<span class="btn primary">+ 新建策略</span>
</div>
</div>
<!-- B 借来的统计条:全局一瞥 + 在跑过滤入口 -->
<div class="stats">
<div class="stat"><span class="n brand" id="st-files">14</span><span class="l">策略代码</span></div>
<div class="stat"><span class="n" id="st-insts">8</span><span class="l">实例档案</span></div>
<div class="stat clickable on" id="st-running" title="点击:只看在跑的运行(跨全部档案)">
<span class="n" id="st-run-n">4</span><span class="l">在跑运行 · 点击巡检</span>
</div>
<div class="stat"><span class="n warn">2</span><span class="l">参数漂移</span></div>
</div>
<!-- A 的主从主体 -->
<div class="body">
<aside class="tree">
<input class="search" placeholder="⌕ 搜索策略 / 实例…">
<div class="grp"><span class="mark"></span>组合 · 5</div>
<div class="item on" data-code="all_weather">all_weather <span style="display:flex;gap:5px;align-items:center"><span class="running-dot"></span><span class="cnt">3</span></span></div>
<div class="item" data-code="momentum_timing">momentum_timing <span class="cnt">1</span></div>
<div class="item" data-code="value_selection">value_selection <span class="cnt">1</span></div>
<div class="item" data-code="small_cap">small_cap <span class="cnt">0</span></div>
<div class="item" data-code="channel_test">channel_test <span style="display:flex;gap:5px;align-items:center"><span class="running-dot"></span><span class="cnt">1</span></span></div>
<div class="grp"><span class="mark"></span>个股 · 9</div>
<div class="item" data-code="double_ma">double_ma <span class="cnt">2</span></div>
<div class="item" data-code="cci">cci_strategy <span class="cnt">0</span></div>
<div class="item" style="opacity:.45">… 其余 7 个</div>
</aside>
<div class="main">
<!-- 默认:选中代码的实例档案区 -->
<div id="code-view">
<div class="codehead">
<span class="fname" id="cv-name">all_weather.py</span>
<span class="chip pf" id="cv-type">组合</span>
<span class="cls" id="cv-cls">AllWeatherStrategy</span>
<span class="spacer"></span>
<span class="btn sm">编辑代码</span>
<span class="btn sm primary">+ 新实例</span>
</div>
<div class="insts" id="cv-insts"><!-- JS 渲染 --></div>
</div>
<!-- 巡检模式:在跑运行列表 -->
<div id="runs-view" style="display:none">
<div class="codehead">
<span class="fname">在跑运行</span>
<span class="chip pf">跨全部档案</span>
<span class="cls">实时巡检 · 点统计条「在跑运行」返回</span>
</div>
<div class="runsview" id="rv-list"><!-- JS 渲染 --></div>
</div>
</div>
</div>
<div class="hint">原型说明:左栏点代码切换 · 顶部「在跑运行」进入全局巡检模式 · 状态灯/收益为假数据示意</div>
</div>
<script>
const DATA = {
all_weather: { name: 'all_weather.py', type: '组合', cls: 'AllWeatherStrategy', insts: [
{ id: 3, name: '全天候·基准版', sub: 'd · 更新 08-15', params: 'max_pool=30 · benchmark=000300', sym: 'hs300_subset',
ret: '+15.48%', retCls: 'up', retSub: '模拟实走 · 最新',
runs: [['done','回测'], ['','回放'], ['run','实走×2'], ['','实盘']] },
{ id: 5, name: '全天候·小池激进', sub: 'd · 更新 08-14', drift: true, params: 'max_pool=10 · benchmark=000905', sym: 'zz500',
ret: '-2.31%', retCls: 'down', retSub: '回测 · 最新',
runs: [['done','回测'], ['','回放'], ['','实走'], ['','实盘']] },
{ id: 7, name: '全天候·对照(影子)', sub: '15m · 更新 08-15', params: 'max_pool=30 · benchmark=000300', sym: 'hs300_subset',
ret: '+0.42%', retCls: 'up', retSub: '影子 · 最新',
runs: [['done','回测'], ['','回放'], ['run','影子'], ['run','实盘']] },
]},
channel_test: { name: 'channel_test.py', type: '组合', cls: 'ChannelTestStrategy', insts: [
{ id: 8, name: '通路测试·双轨', sub: '15m · 更新 08-15', params: '双轨对账进行中 · live#5 ↔ shadow#39', sym: 'hs300_subset',
ret: '+0.42%', retCls: 'up', retSub: '影子 · 至 08-15',
runs: [['done','回测'], ['','回放'], ['run','影子'], ['run','实盘']] },
]},
momentum_timing: { name: 'momentum_timing.py', type: '组合', cls: 'MomentumTimingStrategy', insts: [
{ id: 4, name: '牛熊动量·标准', sub: 'd · 更新 08-10', params: 'n=20 · 基准=000905', sym: 'zz500',
ret: '+58.59%', retCls: 'up', retSub: '回测 · 08-01',
runs: [['done','回测'], ['','回放'], ['','实走'], ['','实盘']] },
]},
value_selection: { name: 'value_selection.py', type: '组合', cls: 'ValueSelectionStrategy', insts: [
{ id: 6, name: '价值精选·试参', sub: 'd · 更新 08-08', params: 'roe>0.05 · 30 只', sym: 'zz1000',
ret: null, retSub: '未运行过',
runs: [['','回测'], ['','回放'], ['','实走'], ['','实盘']] },
]},
small_cap: { name: 'small_cap.py', type: '组合', cls: 'SmallCapStrategy', insts: [] },
double_ma: { name: 'double_ma.py', type: 'CTA', cls: 'AShareDoubleMaStrategy', insts: [
{ id: 1, name: '双均线·浦发', sub: 'd · 更新 07-30', params: 'fast=5 · slow=10 · next_open', sym: '600000',
ret: '-1.07%', retCls: 'down', retSub: '回测 · 07-30',
runs: [['done','回测'], ['stopped','回放·停'], ['','实走'], ['','实盘']] },
{ id: 2, name: '双均线·茅台', sub: 'd · 更新 07-28', drift: true, params: 'fast=10 · slow=30 · next_open', sym: '600519',
ret: '+3.20%', retCls: 'up', retSub: '回放 · 07-28',
runs: [['done','回测'], ['done','回放'], ['','实走'], ['','实盘']] },
]},
cci: { name: 'cci_strategy.py', type: 'CTA', cls: 'CciStrategy', insts: [] },
};
const RUNNING = [
{ kind: 'live', inst: '全天候·对照(影子)', code: 'all_weather', since: '08-14 起', note: 'miniQMT · live#5', ret: '+0.42%' },
{ kind: 'shadow', inst: '全天候·对照(影子)', code: 'all_weather', since: '08-14 起', note: '影子#39 · 双轨对账', ret: '+0.41%' },
{ kind: 'paper', inst: '全天候·基准版', code: 'all_weather', since: '07-20 起', note: '实走 · 每日 20:30 结算', ret: '+15.48%' },
{ kind: 'paper', inst: '全天候·基准版', code: 'all_weather', since: '08-02 起', note: '实走(第二账户) · 对照', ret: '+15.47%' },
];
const $ = (s) => document.querySelector(s);
function pill([st, label]) {
return `<span class="pill ${st}"><span class="lamp"></span>${label}</span>`;
}
function renderCode(key) {
const d = DATA[key]; if (!d) return;
$('#cv-name').textContent = d.name;
$('#cv-type').textContent = d.type;
$('#cv-type').className = 'chip ' + (d.type === '组合' ? 'pf' : 'cta');
$('#cv-cls').textContent = d.cls;
$('#cv-insts').innerHTML = d.insts.length ? d.insts.map(i => `
<div class="row">
<div class="iname">${i.name}${i.drift ? '<span class="chip drift" style="margin-left:7px">参数已漂移</span>' : ''}<span class="sub">#${i.id} · ${i.sub}</span></div>
<div class="params">${i.params}</div>
<div class="sym">${i.sym}</div>
<div class="ret ${i.retCls || ''}">${i.ret || '—'}<span class="sub">${i.retSub}</span></div>
<div class="runs">${i.runs.map(pill).join('')}</div>
<div class="ops"><span class="btn sm">编辑</span><span class="btn sm primary">发起</span></div>
</div>`).join('')
: `<div class="empty">该代码还没有实例档案 — 点右上「+ 新实例」创建,或直接发起运行(发起即建档)</div>`;
}
function renderRuns() {
const kindLabel = { live: '实盘', shadow: '影子', paper: '模拟' };
$('#rv-list').innerHTML = RUNNING.map(r => `
<div class="runrow">
<span class="tag-kind ${r.kind}">${kindLabel[r.kind]}</span>
<div class="iname">${r.inst}<span class="sub">${r.code}</span></div>
<div class="params">${r.note}</div>
<div class="params">${r.since}</div>
<div class="ret up">${r.ret}<span class="sub">最新收益</span></div>
</div>`).join('');
}
document.querySelectorAll('.item[data-code]').forEach(el => {
el.addEventListener('click', () => {
document.querySelectorAll('.item').forEach(x => x.classList.remove('on'));
el.classList.add('on');
$('#st-running').classList.remove('on');
$('#code-view').style.display = '';
$('#runs-view').style.display = 'none';
renderCode(el.dataset.code);
});
});
$('#st-running').addEventListener('click', () => {
const on = $('#code-view').style.display !== 'none';
$('#code-view').style.display = on ? 'none' : '';
$('#runs-view').style.display = on ? '' : 'none';
$('#st-running').classList.toggle('on', on);
document.querySelectorAll('.item').forEach(x => x.classList.remove('on'));
});
renderCode('all_weather');
renderRuns();
</script>
</body>
</html>