alphax/static/iteration.html
2026-05-14 11:21:21 +08:00

196 lines
26 KiB
HTML
Raw 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 title %}AlphaX Agent Crypto — 策略进化{% endblock %}
{% block nav_links %}
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
<a class="sidebar-link" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
<a class="sidebar-link" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>推荐</a>
<div class="sidebar-section-label admin-link" style="display:none">研发</div>
<a class="sidebar-link admin-link" href="/pipeline" style="display:none"><svg class="link-icon"><use href="#svg-pipeline"/></svg>链路日志</a>
<a class="sidebar-link admin-link" href="/strategy" style="display:none"><svg class="link-icon"><use href="#svg-target"/></svg>策略</a>
<a class="sidebar-link active admin-link" href="/iteration" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>迭代</a>
<a class="sidebar-link admin-link" href="/admin.html" style="display:none"><svg class="link-icon"><use href="#svg-admin"/></svg>管理</a>
{% endblock %}
{% block extra_head_css %}
<style>
.shell { width:min(100% - 40px,1180px); margin:0 auto; padding:28px 0 48px; }
.hero { display:flex; justify-content:space-between; align-items:flex-start; gap:20px; margin-bottom:18px; }
h2 { font-size:26px; font-weight:900; margin:0 0 8px; color:var(--ink); }
.subtitle { color:var(--stone); font-size:14px; line-height:1.7; max-width:780px; }
.badge { display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border-radius:999px; font-size:12px; font-weight:800; border:1px solid var(--hairline-soft); background:var(--canvas); color:var(--slate); white-space:nowrap; }
.badge.release { background:var(--green-light); color:var(--green); border-color:rgba(29,166,122,.2); }
.badge.gray { background:rgba(66,98,255,.08); color:var(--blue); border-color:rgba(66,98,255,.18); }
.badge.hold { background:var(--yellow-light); color:var(--yellow-dark); border-color:rgba(245,158,11,.18); }
.badge.reject { background:rgba(216,75,75,.08); color:var(--red); border-color:rgba(216,75,75,.18); }
.toolbar { display:flex; gap:10px; flex-wrap:wrap; }
.btn { border:1px solid var(--hairline-soft); background:var(--canvas); color:var(--ink); border-radius:12px; min-height:44px; padding:9px 12px; font-size:13px; font-weight:800; cursor:pointer; }
.btn:hover { border-color:var(--hairline); box-shadow:0 4px 12px rgba(5,0,56,.05); }
.grid { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin:18px 0; }
.kpi { background:var(--canvas); border:1px solid var(--hairline-soft); border-radius:18px; padding:16px; }
.kpi .label { color:var(--muted); font-size:12px; font-weight:700; margin-bottom:8px; }
.kpi .value { color:var(--ink); font-size:28px; font-weight:900; letter-spacing:-.04em; }
.kpi .note { color:var(--stone); font-size:12px; margin-top:6px; line-height:1.55; }
.gate-card { background:linear-gradient(135deg, rgba(66,98,255,.08), rgba(255,208,47,.12)); border:1px solid var(--hairline-soft); border-radius:20px; padding:16px; margin:14px 0 18px; }
.gate-head { display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:10px; }
.gate-title { font-size:15px; font-weight:900; color:var(--ink); }
.gate-text { color:var(--slate); font-size:13px; line-height:1.7; }
.gate-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-top:12px; }
.gate-mini { background:rgba(255,255,255,.7); border:1px solid var(--hairline-soft); border-radius:14px; padding:10px; }
.gate-mini span { display:block; color:var(--muted); font-size:11px; font-weight:800; margin-bottom:4px; }
.gate-mini b { color:var(--ink); font-size:16px; }
.tabs { display:flex; gap:8px; margin:22px 0 14px; flex-wrap:wrap; }
.tab { padding:9px 13px; border-radius:999px; border:1px solid var(--hairline-soft); background:var(--surface); color:var(--slate); font-size:13px; font-weight:900; cursor:pointer; min-height:44px; }
.tab.active { background:var(--primary); color:white; border-color:var(--primary); }
.panel { display:none; }
.panel.active { display:block; }
.board { display:grid; grid-template-columns:1.05fr .95fr; gap:14px; }
.card { background:var(--canvas); border:1px solid var(--hairline-soft); border-radius:20px; padding:18px; margin-bottom:14px; }
.card-title { font-size:15px; font-weight:900; margin-bottom:12px; color:var(--ink); display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; }
.timeline { position:relative; padding-left:34px; }
.timeline::before { content:""; position:absolute; left:13px; top:0; bottom:0; width:2px; background:var(--hairline); }
.iter { position:relative; border:1px solid var(--hairline-soft); background:var(--canvas); border-radius:18px; padding:16px; margin-bottom:12px; }
.iter::before { content:""; position:absolute; left:-26px; top:22px; width:10px; height:10px; border-radius:50%; background:var(--yellow); border:3px solid var(--canvas); box-shadow:0 0 0 1px var(--hairline); }
.iter.release::before { background:var(--green); }
.iter.gray::before { background:var(--blue); }
.iter-head { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.ver { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:13px; font-weight:900; color:var(--primary); background:var(--yellow-light); padding:3px 9px; border-radius:10px; }
.title { font-size:14px; font-weight:900; color:var(--ink); }
.time { margin-left:auto; color:var(--muted); font-size:12px; }
.metrics { display:flex; gap:12px; flex-wrap:wrap; margin:12px 0; color:var(--slate); font-size:12px; }
.metric b { color:var(--ink); }
.summary { color:var(--slate); font-size:13px; line-height:1.7; }
.detail { margin-top:12px; padding-top:12px; border-top:1px solid var(--hairline-soft); display:none; }
.iter.open .detail { display:block; }
.section { margin-top:10px; }
.section-label { font-size:11px; font-weight:900; color:var(--muted); letter-spacing:.04em; margin-bottom:7px; }
.item { color:var(--slate); font-size:13px; line-height:1.65; padding:6px 0 6px 13px; position:relative; }
.item::before { content:""; position:absolute; left:0; top:15px; width:4px; height:4px; border-radius:50%; background:var(--hairline-strong); }
.item.good { color:var(--green); } .item.warn { color:var(--red); }
.table { width:100%; border-collapse:separate; border-spacing:0 8px; }
.table th { color:var(--muted); font-size:11px; text-align:left; padding:0 8px; font-weight:900; }
.table td { background:var(--surface); border-top:1px solid var(--hairline-soft); border-bottom:1px solid var(--hairline-soft); padding:10px 8px; font-size:12px; color:var(--slate); vertical-align:top; }
.table td:first-child { border-left:1px solid var(--hairline-soft); border-radius:12px 0 0 12px; }
.table td:last-child { border-right:1px solid var(--hairline-soft); border-radius:0 12px 12px 0; }
.rule-name { color:var(--ink); font-weight:900; max-width:380px; white-space:normal; line-height:1.55; }
.reason { max-width:360px; white-space:normal; line-height:1.55; color:var(--slate); }
.score { font-weight:900; color:var(--primary); }
.failure-chip { display:inline-flex; margin:3px; padding:6px 9px; border-radius:999px; background:rgba(216,75,75,.08); color:var(--red); font-size:12px; font-weight:800; }
.empty,.loading { text-align:center; padding:44px 20px; color:var(--stone); font-size:14px; }
@media(max-width:860px){ .shell{width:min(100% - 24px,1180px); padding-top:20px;} .hero{display:block;} .toolbar{margin-top:12px;} .grid,.gate-grid{grid-template-columns:repeat(2,1fr);} .board{grid-template-columns:1fr;} .time{margin-left:0;width:100%;} .table{display:block; overflow-x:auto; white-space:nowrap;} .rule-name,.reason{min-width:260px;} }
@media(max-width:480px){ .grid,.gate-grid{grid-template-columns:1fr;} .kpi .value{font-size:24px;} .tabs{gap:6px;} .tab{padding:8px 10px;} .timeline{padding-left:26px;} .timeline::before{left:9px;} .iter::before{left:-22px;} }
/* ===== USER REPORT VIEW ===== */
.summary-report { display:grid; grid-template-columns:1.1fr .9fr; gap:14px; margin:16px 0 18px; }
.report-card { background:var(--canvas); border:1px solid var(--hairline-soft); border-radius:22px; padding:18px; }
.report-title { font-size:15px; font-weight:900; color:var(--ink); margin-bottom:10px; display:flex; align-items:center; justify-content:space-between; gap:10px; }
.report-answer { font-size:28px; font-weight:900; letter-spacing:-.04em; color:var(--ink); margin:8px 0; }
.report-answer.release { color:var(--green); } .report-answer.gray { color:var(--blue); } .report-answer.hold { color:var(--yellow-dark); } .report-answer.reject { color:var(--red); }
.report-text { color:var(--slate); font-size:13px; line-height:1.75; }
.report-list { display:flex; flex-direction:column; gap:8px; margin-top:12px; }
.report-item { border:1px solid var(--hairline-soft); background:var(--surface); border-radius:14px; padding:10px 12px; }
.report-item b { display:block; color:var(--ink); font-size:13px; margin-bottom:4px; }
.report-item span { color:var(--stone); font-size:12px; line-height:1.55; }
.user-tabs-note { color:var(--stone); font-size:12px; margin:-6px 0 8px; line-height:1.6; }
.rule-quality { display:inline-flex; padding:4px 8px; border-radius:999px; font-size:11px; font-weight:900; }
.rule-quality.good { background:var(--green-light); color:var(--green); }
.rule-quality.wait { background:var(--yellow-light); color:var(--yellow-dark); }
.rule-quality.bad { background:var(--red-light); color:var(--red); }
@media(max-width:860px){ .summary-report{grid-template-columns:1fr;} }
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="hero">
<div>
<h2>策略进化</h2>
<p class="subtitle">这里展示 AlphaX Agent Crypto 策略是否真的在变聪明:本轮有没有发布、为什么没发布、哪些规律还在观察、哪些错误正在减少。</p>
</div>
<div class="toolbar">
<button class="btn" onclick="refreshCandidates()">刷新规则表现</button>
<button class="btn" onclick="loadAll()">刷新页面</button>
</div>
</div>
<div class="grid" id="kpis"><div class="loading">加载中…</div></div>
<div id="gateBox" class="gate-card"><div class="loading">加载发布闸门…</div></div>
<div id="userReport" class="summary-report"><div class="loading">生成用户版进化报告…</div></div>
<div class="tabs">
<button class="tab active" data-tab="timeline" onclick="switchTab('timeline')">本轮结论</button>
<button class="tab" data-tab="candidates" onclick="switchTab('candidates')">发现的规律</button>
<button class="tab" data-tab="dryrun" onclick="switchTab('dryrun')">发布预演</button>
<button class="tab" data-tab="failures" onclick="switchTab('failures')">错误复盘</button>
<button class="tab" data-tab="versions" onclick="switchTab('versions')">版本表现</button>
</div>
<div class="panel active" id="panel-timeline"><div class="timeline" id="timeline"><div class="loading">加载中…</div></div></div>
<div class="panel" id="panel-candidates"><div class="card"><div class="card-title">发现的规律 <span class="badge hold">未达标不发布</span></div><div class="user-tabs-note">这些是系统复盘后发现的可能规律。只有样本、成功率、收益和稳定性都达标,才会进入线上策略。</div><div id="candidates"></div></div></div>
<div class="panel" id="panel-dryrun"><div class="card"><div class="card-title">发布预演 <span class="badge hold">只读评估,不改线上策略</span></div><div id="dryrun"></div></div></div>
<div class="panel" id="panel-failures"><div class="board"><div class="card"><div class="card-title">主要失败原因</div><div id="failureSummary"></div></div><div class="card"><div class="card-title">失败样本</div><div id="failures"></div></div></div></div>
<div class="panel" id="panel-versions"><div class="card"><div class="card-title">版本表现</div><div id="versions"></div></div></div>
</div>
{% endblock %}
{% block extra_script %}
<script>
var API = '';
var $ = function(id){ return document.getElementById(id); };
var state = { data:null };
function esc(v){ return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];}); }
function fmtTime(t){ if(!t)return '--'; var d=new Date(t); return (d.getMonth()+1)+'/'+d.getDate()+' '+('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2); }
function badge(status){ var cls=status==='release'||status==='active'?'release':status==='gray'?'gray':status==='rejected'?'reject':'hold'; var txt={release:'正式发布',gray:'灰度观察',hold:'只研究不发布',candidate:'候选研究',active:'正式生效',rejected:'已淘汰',blocked:'发布阻断',unknown:'旧日志',dirty_history:'污染历史'}[status]||status||'研究中'; return '<span class="badge '+cls+'">'+esc(txt)+'</span>'; }
function switchTab(tab){ document.querySelectorAll('.tab').forEach(function(x){x.classList.toggle('active',x.dataset.tab===tab);}); document.querySelectorAll('.panel').forEach(function(x){x.classList.remove('active');}); $('panel-'+tab).classList.add('active'); }
async function loadUser(){ try{ var r=await fetch(API+'/api/auth/me'); if(!r.ok)return; var d=await r.json(); var email=(d.user&&d.user.email)||''; if(!email)return; $('userInitial').textContent=email.charAt(0).toUpperCase(); $('userEmailShort').textContent=email.length>14?email.slice(0,12)+'…':email; $('ddEmail').textContent=email; }catch(e){} }
function toggleUserMenu(){ $('userDropdown').classList.toggle('show'); }
document.addEventListener('click',function(e){ if(!e.target.closest('.sidebar-user')&&!e.target.closest('.user-dropdown')) $('userDropdown').classList.remove('show'); });
async function doLogout(){ await fetch(API+'/api/auth/logout',{method:'POST'}); location.href='/auth'; }
async function refreshCandidates(){ var old=document.querySelector('.toolbar .btn'); try{ old.textContent='刷新中…'; await fetch(API+'/api/strategy/candidates/refresh',{method:'POST'}); await loadAll(); }catch(e){ alert('刷新失败'); } finally{ old.textContent='刷新候选评分'; } }
async function loadAll(){ try{ var r=await fetch(API+'/api/strategy/lifecycle?days=60'); var d=await r.json(); state.data=d; renderAll(d); }catch(e){ $('timeline').innerHTML='<div class="empty">加载失败</div>'; } }
function renderAll(d){ renderKpis(d); renderGate(d); renderUserReport(d); renderTimeline(d.logs||[]); renderCandidates(d.candidates||[], d.dry_run||{}); renderDryRun(d.dry_run||{}); renderFailures(d); renderVersions(((d.summary||{}).version_stats)||[]); }
function decisionText(decision){
var map={release:'正式发布新策略',gray:'进入灰度观察',hold:'只研究,不发布',blocked:'暂不发布',rejected:'暂不采纳',unknown:'等待复盘'};
return map[decision]||map.unknown;
}
function decisionClass(decision){ return decision==='release'?'release':decision==='gray'?'gray':(decision==='blocked'||decision==='rejected')?'reject':'hold'; }
function renderUserReport(d){
var ov=d.overview||{}, dry=d.dry_run||{}, ds=ov.dry_run_summary||{};
var decision=ov.latest_release_decision || (dry.would_bump_version?'release':'hold');
var reason=ov.latest_release_reason || dry.release_reason || '样本仍在积累,暂不改变线上策略。';
var candidates=(d.candidates||[]).slice(0,3);
var failures=((ov.failure_type_counts)||[]).slice(0,3);
var candHtml=candidates.length?candidates.map(function(c){
var name=c.rule_description||c.signal_name||'待验证规律';
var conf=Number(c.confidence_score||0);
var q=conf>=70?'good':conf>=40?'wait':'bad';
var qtxt=conf>=70?'接近可用':conf>=40?'继续观察':'证据不足';
return '<div class="report-item"><b>'+esc(name)+'</b><span><span class="rule-quality '+q+'">'+qtxt+'</span> 样本 '+esc(c.sample_size||0)+' · 置信 '+esc(c.confidence_score||0)+' · 平均表现 '+esc(c.avg_pnl||0)+'</span></div>';
}).join(''):'<div class="report-item"><b>暂无新规律</b><span>当前没有足够证据支持策略改动。</span></div>';
var failHtml=failures.length?failures.map(function(f){return '<div class="report-item"><b>'+esc(f.type||'失败模式')+'</b><span>出现 '+esc(f.count||0)+' 次,后续复盘会重点观察是否重复发生。</span></div>';}).join(''):'<div class="report-item"><b>暂无集中失败模式</b><span>当前失败样本不足,先继续观察。</span></div>';
$('userReport').innerHTML = '<div class="report-card"><div class="report-title">本轮策略结论 '+badge(decision)+'</div><div class="report-answer '+decisionClass(decision)+'">'+decisionText(decision)+'</div><div class="report-text">'+esc(reason)+'</div><div class="report-list">'+candHtml+'</div></div>' +
'<div class="report-card"><div class="report-title">最近最该关注的错误</div><div class="report-text">系统不只看成功因子,也会记录反复导致失败的原因,避免下一轮继续犯同样的错。</div><div class="report-list">'+failHtml+'</div></div>';
}
function renderKpis(d){ var ov=d.overview||{}, st=ov.candidate_status_counts||{}, rd=ov.release_decision_counts||{}, dry=ov.dry_run_summary||{}, latest=(d.logs&&d.logs[0]&&d.logs[0].metrics)||{}; $('kpis').innerHTML=[
['有效计权', latest.effective_review_count!=null?latest.effective_review_count:(dry.review_sample_count||0), '只统计有48h窗口的交易样本'],
['样本不足', latest.insufficient_tracking_count||0, '缺少price_tracking时只记录不调权'],
['待验证规律', ov.candidate_count||0, '观察中 '+(st.candidate||0)+' / 灰度 '+(st.gray||0)+' / 旧样本参考 '+(dry.dirty_history_candidate_count||0)],
['可灰度规律', dry.gray_ready_count||0, '达到门槛才会进入灰度'],
['正式发布', (rd.release||0), '真正改变线上策略的次数']
].map(function(k){return '<div class="kpi"><div class="label">'+k[0]+'</div><div class="value">'+k[1]+'</div><div class="note">'+k[2]+'</div></div>';}).join(''); }
function renderGate(d){ var ov=d.overview||{}, dry=(d.dry_run||{}), ds=ov.dry_run_summary||{}; var latest=ov.latest_release_decision||'hold'; $('gateBox').innerHTML='<div class="gate-head"><div class="gate-title">本轮是否发布</div>'+badge(latest)+'</div><div class="gate-text"><b>干净样本起点:</b>'+esc(ds.clean_started_at||dry.clean_started_at||'未设置')+';样本窗口:'+esc(ds.sample_window||dry.sample_window||'all_history')+'。旧污染样本只作解释,不会直接改变线上策略。</div><div class="gate-text"><b>最近发布原因:</b>'+esc(ov.latest_release_reason||'暂无发布决策说明')+'</div><div class="gate-text"><b>预演结论:</b>'+esc(dry.release_reason||ds.release_reason||'只读评估,不写库、不升版')+'</div><div class="gate-grid"><div class="gate-mini"><span>干净复盘样本</span><b>'+esc(ds.review_sample_count||dry.review_sample_count||0)+'</b></div><div class="gate-mini"><span>污染历史候选</span><b>'+esc(ds.dirty_history_candidate_count||dry.dirty_history_candidate_count||0)+'</b></div><div class="gate-mini"><span>可灰度</span><b>'+esc(ds.gray_ready_count||dry.gray_ready_count||0)+'</b></div><div class="gate-mini"><span>是否发布</span><b>'+(dry.would_bump_version?'是':'否')+'</b></div></div>'; }
function renderTimeline(items){ if(!items.length){$('timeline').innerHTML='<div class="empty">暂无迭代记录</div>';return;} $('timeline').innerHTML=items.map(function(it){ var decision=it.release_decision||'unknown'; var metrics=it.metrics||{}; var cls=decision==='release'?' release':decision==='gray'?' gray':''; return '<div class="iter'+cls+'" onclick="this.classList.toggle(\'open\')"><div class="iter-head"><span class="ver">'+esc(it.strategy_version||'--')+'</span><span class="title">'+esc(it.title||'复盘迭代')+'</span>'+badge(decision)+'<span class="time">'+fmtTime(it.created_at)+'</span></div><div class="metrics"><span class="metric">有效 <b>'+(metrics.effective_review_count!=null?metrics.effective_review_count:((metrics.hit_count||0)+(metrics.flat_count||0)+(metrics.fail_count||0)))+'</b></span><span class="metric">爆发 <b>'+(metrics.hit_count||0)+'</b></span><span class="metric">横盘 <b>'+(metrics.flat_count||0)+'</b></span><span class="metric">失败 <b>'+(metrics.fail_count||0)+'</b></span><span class="metric">样本不足 <b>'+(metrics.insufficient_tracking_count||0)+'</b></span><span class="metric">候选 <b>'+((it.candidate_rules||[]).length)+'</b></span><span class="metric">置信 <b>'+esc(it.confidence_level||'--')+'</b></span></div><div class="summary">'+esc((it.release_reason||it.version_change_summary||it.summary||'').slice(0,280))+'</div><div class="detail">'+renderSection('成功因子',(it.success_analysis&&it.success_analysis.top_success_factors)||[],'good')+renderSection('失败模式',(it.failure_analysis&&it.failure_analysis.failure_types)||[],'warn')+renderCandidateMini(it.candidate_rules||[])+renderSection('动作',it.actions||[],'')+renderSection('问题',it.problems||[],'warn')+'</div></div>'; }).join(''); }
function renderSection(label,items,cls){ if(!items||!items.length)return ''; return '<div class="section"><div class="section-label">'+label+'</div>'+items.slice(0,10).map(function(x){ var t=typeof x==='string'?x:(x.label||x.type||x.signal||x.description||JSON.stringify(x)); var c=x.count?(' · '+x.count):''; return '<div class="item '+cls+'">'+esc(t+c)+'</div>'; }).join('')+'</div>'; }
function renderCandidateMini(items){ if(!items.length)return ''; return '<div class="section"><div class="section-label">本轮候选规则</div>'+items.slice(0,8).map(function(x){return '<div class="item">'+esc(x.description||x.signal||'候选规则')+' · 置信 '+esc(x.confidence_score||0)+' · 样本 '+esc(x.sample_size||0)+' · '+esc(x.status||'candidate')+'</div>';}).join('')+'</div>'; }
function sourceLabel(c){ var s=String(c.source||''); if(s==='reverse_analysis')return '涨幅榜逆向'; if(s.indexOf('dual_attribution_success')===0)return '成功复盘'; if(s.indexOf('dual_attribution_failure')===0)return '失败复盘'; if(s.indexOf('signal_deprecation')===0)return '低绩效信号'; if(s.indexOf('dirty_history')===0)return '历史参考'; return s||'研究池'; }
function renderCandidates(items,dry){ if(!items.length){$('candidates').innerHTML='<div class="empty">暂无待验证规律</div>';return;} var dryMap={}; (dry.evaluated_candidates||[]).forEach(function(x){dryMap[x.id]=x;}); $('candidates').innerHTML='<table class="table"><thead><tr><th>来源</th><th>当前阶段</th><th>预演结论</th><th>规律</th><th>样本</th><th>成功/失败</th><th>可信度</th><th>平均表现</th><th>为什么还没发布</th></tr></thead><tbody>'+items.map(function(c){var d=dryMap[c.id]||{};return '<tr><td><span class="tag">'+esc(sourceLabel(c))+'</span></td><td>'+badge(c.status||'candidate')+'</td><td>'+badge(d.dry_run_status||c.status||'candidate')+'</td><td class="rule-name">'+esc(c.rule_description||c.signal_name||'--')+'</td><td>'+esc(d.sample_size!=null?d.sample_size:(c.sample_size||0))+'</td><td>'+esc(d.success_count!=null?d.success_count:(c.success_count||0))+' / '+esc(d.fail_count!=null?d.fail_count:(c.fail_count||0))+'</td><td class="score">'+esc(d.confidence_score!=null?d.confidence_score:(c.confidence_score||0))+'</td><td>'+esc(d.avg_pnl!=null?d.avg_pnl:(c.avg_pnl||0))+'</td><td class="reason">'+esc(d.gate_reason||'等待样本验证')+'</td></tr>';}).join('')+'</tbody></table>'; }
function renderDryRun(dry){ var items=dry.evaluated_candidates||[]; if(!items.length){$('dryrun').innerHTML='<div class="empty">暂无待验证规律可评估</div>';return;} $('dryrun').innerHTML='<div class="gate-text">当前版本 '+esc(dry.current_version||'--')+';干净样本起点 '+esc(dry.clean_started_at||'未设置')+';干净复盘样本 '+esc(dry.review_sample_count||0)+';污染历史候选 '+esc(dry.dirty_history_candidate_count||0)+';可灰度 '+esc(dry.gray_ready_count||0)+';是否发布:'+(dry.would_bump_version?'是':'否')+'。</div><div class="gate-text"><b>灰度标准:</b>'+esc((dry.gate_policy&&dry.gate_policy.gray)||'--')+'</div><table class="table"><thead><tr><th>预演结论</th><th>规律</th><th>样本</th><th>成功/失败</th><th>可信度</th><th>平均表现</th><th>原因</th></tr></thead><tbody>'+items.map(function(x){return '<tr><td>'+badge(x.dry_run_status||'candidate')+'</td><td class="rule-name">'+esc(x.rule_description||x.signal_name||'--')+'</td><td>'+esc(x.sample_size||0)+'</td><td>'+esc(x.success_count||0)+' / '+esc(x.fail_count||0)+'</td><td class="score">'+esc(x.confidence_score||0)+'</td><td>'+esc(x.avg_pnl||0)+'</td><td class="reason">'+esc(x.gate_reason||'--')+'</td></tr>';}).join('')+'</tbody></table>'; }
function renderFailures(d){ var fs=(d.overview&&d.overview.failure_type_counts)||[]; $('failureSummary').innerHTML=fs.length?fs.map(function(f){return '<span class="failure-chip">'+esc(f.type)+' · '+esc(f.count)+'</span>';}).join(''):'<div class="empty">暂无失败模式</div>'; var items=d.failures||[]; $('failures').innerHTML=items.length?items.slice(0,30).map(function(f){return '<div class="item warn"><b>'+esc(f.symbol||'--')+'</b> · '+esc(f.failure_type||'未分类')+' · '+esc((f.failure_reason||'').slice(0,90))+' · PnL '+esc(f.pnl_pct||0)+'</div>';}).join(''):'<div class="empty">暂无失败样本</div>'; }
function renderVersions(items){ if(!items.length){$('versions').innerHTML='<div class="empty">暂无版本表现</div>';return;} $('versions').innerHTML='<table class="table"><thead><tr><th>版本</th><th>推荐数</th><th>成功</th><th>失败</th><th>待观察</th><th>成功率</th><th>均值收益</th></tr></thead><tbody>'+items.map(function(v){return '<tr><td class="rule-name">'+esc(v.strategy_version)+'</td><td>'+esc(v.recommendation_count)+'</td><td>'+esc(v.success_count)+'</td><td>'+esc(v.failed_count)+'</td><td>'+esc(v.pending_count)+'</td><td class="score">'+esc(v.success_rate_pct)+'</td><td>'+esc(v.avg_pnl_pct)+'</td></tr>';}).join('')+'</tbody></table>'; }
loadUser(); loadAll();
</script>
{% endblock %}