Files
MoFin/static/effectiveness.html
T
xxm 5b9d46efc6 refactor: 归档策略进化模块+新建评估页面+API
- 归档 evolution/ + meta_growth/meta_watchdog/ab_research_daily
- docs/evolution-archive-readme.md: 归档说明(旧模块功能+替代方案)
- server.py: 新增 /api/research/effectiveness + effectiveness/summary + recommendation_log + execution_log
- static/effectiveness.html: 新评估页面(概览/详细评估/推荐记录/执行记录)
- 策略进化改为人驱动闭环(评估→用户决策→调整)
2026-08-21 02:47:38 +08:00

193 lines
8.4 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>策略评估 - MoFin</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0a0a0f; color: #e0e0e0; padding: 20px; }
h1 { color: #fff; font-size: 1.5em; margin-bottom: 20px; }
.tab-bar { display: flex; gap: 8px; margin-bottom: 20px; }
.tab { padding: 8px 16px; border-radius: 6px; cursor: pointer; background: #1a1a2e; color: #888; border: 1px solid #333; }
.tab.active { background: #16213e; color: #4fc3f7; border-color: #4fc3f7; }
.card { background: #1a1a2e; border-radius: 8px; padding: 16px; margin-bottom: 12px; border: 1px solid #333; }
.card h3 { color: #4fc3f7; font-size: 1.1em; margin-bottom: 8px; }
.metric { display: inline-block; margin-right: 16px; margin-bottom: 8px; }
.metric .label { color: #888; font-size: 0.85em; }
.metric .value { color: #fff; font-size: 1.2em; font-weight: bold; }
.metric .value.good { color: #4caf50; }
.metric .value.warn { color: #ff9800; }
.metric .value.bad { color: #f44336; }
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #333; }
th { color: #888; font-size: 0.85em; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.8em; }
.tag-effective { background: #1b5e20; color: #4caf50; }
.tag-ineffective { background: #b71c1c; color: #f44336; }
.tag-neutral { background: #37474f; color: #90a4ae; }
.tag-not_triggered { background: #263238; color: #607d8b; }
.filter { margin-bottom: 16px; }
.filter select, .filter input { background: #1a1a2e; color: #e0e0e0; border: 1px solid #333; padding: 6px 12px; border-radius: 4px; }
#loading { text-align: center; padding: 40px; color: #888; }
</style>
</head>
<body>
<h1>📊 策略评估</h1>
<div class="tab-bar">
<div class="tab active" onclick="showTab('overview')">概览</div>
<div class="tab" onclick="showTab('details')">详细评估</div>
<div class="tab" onclick="showTab('recommendations')">推荐记录</div>
<div class="tab" onclick="showTab('executions')">执行记录</div>
</div>
<div class="filter">
<select id="sourceFilter" onchange="loadData()">
<option value="">全部策略</option>
</select>
<select id="codeFilter" onchange="loadData()">
<option value="">全部股票</option>
</select>
</div>
<div id="loading">加载中...</div>
<div id="content"></div>
<script>
const API = '/api/research';
let currentTab = 'overview';
function showTab(tab) {
currentTab = tab;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
loadData();
}
async function loadData() {
const source = document.getElementById('sourceFilter').value;
const code = document.getElementById('codeFilter').value;
const content = document.getElementById('content');
content.innerHTML = '<div id="loading">加载中...</div>';
try {
if (currentTab === 'overview') {
const res = await fetch(`${API}/effectiveness/summary`);
const data = await res.json();
renderOverview(data);
} else if (currentTab === 'details') {
const params = new URLSearchParams();
if (source) params.set('source', source);
if (code) params.set('code', code);
const res = await fetch(`${API}/effectiveness?${params}`);
const data = await res.json();
renderDetails(data);
} else if (currentTab === 'recommendations') {
const params = new URLSearchParams();
if (code) params.set('code', code);
const res = await fetch(`${API}/recommendation_log?${params}`);
const data = await res.json();
renderRecommendations(data);
} else if (currentTab === 'executions') {
const params = new URLSearchParams();
if (code) params.set('code', code);
const res = await fetch(`${API}/execution_log?${params}`);
const data = await res.json();
renderExecutions(data);
}
} catch (e) {
content.innerHTML = `<div class="card"><h3>加载失败</h3><p>${e.message}</p></div>`;
}
}
function renderOverview(data) {
const content = document.getElementById('content');
if (!data.length) { content.innerHTML = '<div class="card"><h3>暂无评估数据</h3><p>策略到期后会自动生成评估结果</p></div>'; return; }
let html = '';
data.forEach(row => {
const total = row.total || 0;
const buyPct = total ? Math.round(row.buy_effective/total*100) : 0;
const slPct = total ? Math.round(row.sl_effective/total*100) : 0;
const tpPct = total ? Math.round(row.tp_effective/total*100) : 0;
const timePct = Math.round((row.time_score||0)*100);
html += `<div class="card">
<h3>${row.strategy_source || '未知策略'}</h3>
<div class="metric"><div class="label">评估次数</div><div class="value">${total}</div></div>
<div class="metric"><div class="label">买入区有效</div><div class="value ${buyPct>=60?'good':buyPct>=40?'warn':'bad'}">${buyPct}%</div></div>
<div class="metric"><div class="label">止损有效</div><div class="value ${slPct>=60?'good':slPct>=40?'warn':'bad'}">${slPct}%</div></div>
<div class="metric"><div class="label">止盈有效</div><div class="value ${tpPct>=60?'good':tpPct>=40?'warn':'bad'}">${tpPct}%</div></div>
<div class="metric"><div class="label">时间准确</div><div class="value ${timePct>=60?'good':timePct>=40?'warn':'bad'}">${timePct}%</div></div>
</div>`;
});
content.innerHTML = html;
}
function renderDetails(data) {
const content = document.getElementById('content');
if (!data.length) { content.innerHTML = '<div class="card"><h3>暂无数据</h3></div>'; return; }
let html = '<table><tr><th>股票</th><th>策略</th><th>版本</th><th>周期</th><th>买入区</th><th>止损</th><th>止盈</th><th>时间</th><th>评价</th></tr>';
data.forEach(r => {
html += `<tr>
<td>${r.code}</td>
<td>${r.strategy_source||'-'}</td>
<td>v${r.version||'?'}</td>
<td>${(r.period_start||'').slice(0,10)} ~ ${(r.period_end||'').slice(0,10)}</td>
<td><span class="tag tag-${r.buy_zone_accuracy==='effective'?'effective':r.buy_zone_accuracy==='ineffective'?'ineffective':'neutral'}">${r.buy_zone_accuracy||'-'}</span></td>
<td><span class="tag tag-${r.stop_loss_accuracy==='effective'?'effective':r.stop_loss_accuracy==='ineffective'?'ineffective':'neutral'}">${r.stop_loss_accuracy||'-'}</span></td>
<td><span class="tag tag-${r.take_profit_accuracy==='effective'?'effective':r.take_profit_accuracy==='ineffective'?'ineffective':'neutral'}">${r.take_profit_accuracy||'-'}</span></td>
<td><span class="tag tag-${r.time_accuracy==='on_time'?'effective':r.time_accuracy==='late'?'ineffective':'neutral'}">${r.time_accuracy||'-'}</span></td>
<td title="${r.overall_assessment||''}">${(r.overall_assessment||'').slice(0,30)}${(r.overall_assessment||'').length>30?'...':''}</td>
</tr>`;
});
html += '</table>';
content.innerHTML = html;
}
function renderRecommendations(data) {
const content = document.getElementById('content');
if (!data.length) { content.innerHTML = '<div class="card"><h3>暂无推荐记录</h3></div>'; return; }
let html = '<table><tr><th>时间</th><th>股票</th><th>策略</th><th>版本</th><th>动作</th><th>买入区</th><th>止损</th><th>止盈</th></tr>';
data.forEach(r => {
html += `<tr>
<td>${(r.recommend_time||'').slice(0,16)}</td>
<td>${r.code}</td>
<td>${r.strategy_source||'-'}</td>
<td>v${r.version||'?'}</td>
<td>${r.action||'-'}</td>
<td>${r.entry_low||0}~${r.entry_high||0}</td>
<td>${r.stop_loss||'-'}</td>
<td>${r.take_profit||'-'}</td>
</tr>`;
});
html += '</table>';
content.innerHTML = html;
}
function renderExecutions(data) {
const content = document.getElementById('content');
if (!data.length) { content.innerHTML = '<div class="card"><h3>暂无执行记录</h3></div>'; return; }
let html = '<table><tr><th>时间</th><th>股票</th><th>动作</th><th>数量</th><th>价格</th><th>来源</th></tr>';
data.forEach(r => {
html += `<tr>
<td>${(r.execute_time||'').slice(0,16)}</td>
<td>${r.code}</td>
<td>${r.action}</td>
<td>${r.shares}</td>
<td>${r.price}</td>
<td>${r.source||'-'}</td>
</tr>`;
});
html += '</table>';
content.innerHTML = html;
}
// 初始加载
loadData();
</script>
</body>
</html>