fix: 温区行所有列显示该温区数据——regime_perf v2为每策略x温区跑组合模拟(portfolio_sim 100万/10槽), 表扩展total_return/cagr/max_dd/capital_final/sharpe/profit_factor; server.py返回温区级完整指标; 前端温区行全列用温区数据(v_oversold trend_down年化29.1%/资产323万 vs choppy -0.2%/99万)

This commit is contained in:
hmo
2026-08-13 12:04:36 +08:00
parent 9f1185fc0d
commit 35bd408251
2 changed files with 29 additions and 16 deletions
+21 -14
View File
@@ -2384,12 +2384,19 @@ function renderStrategyTable(strategies) {
const rw = rws[rg];
const isOverall = rg === 'overall';
const rgMeta = REGIME_META[rg] || {icon:'🌐', name: rg === 'overall' ? '整体' : rg};
const rgActive = !isOverall && isCurrent && (window._regimeActive || []).includes(s.version);
// 温区行数据:该温区胜率/均盈/样本(有则覆盖,无则用整体)
const rgActive = !isOverall && isCurrent;
// ── 温区行数据:所有列用该温区数据(rw 有则用,无则"—")──
const wrShow = rw && rw.win_rate != null ? rw.win_rate : (isOverall ? st.win_rate : null);
const pnlShow = rw && rw.avg_pnl != null ? rw.avg_pnl : (isOverall ? st.avg_profit_pct : null);
const tradesShow = rw && rw.trades != null ? rw.trades : (isOverall ? st.total_trades : null);
const rowCls = 'border-b border-slate-800/50 hover:bg-slate-800/30 cursor-pointer' + (isCurrent && !isOverall ? ' bg-emerald-900/20 border-l-2 border-l-emerald-400' : '');
const holdShow = rw && rw.avg_hold_days != null ? rw.avg_hold_days : (isOverall ? st.avg_hold_days : null);
const cagrShow = rw && rw.cagr_pct != null ? rw.cagr_pct : (isOverall ? (st.portfolio_full||pf).cagr_pct : null);
const retShow = rw && rw.total_return_pct != null ? rw.total_return_pct : (isOverall ? (st.portfolio_full||{}).total_return_pct : null);
const ddShow = rw && rw.max_dd_pct != null ? rw.max_dd_pct : (isOverall ? (st.portfolio_full||pf).portfolio_max_dd_pct : null);
const capShow = rw && rw.capital_final != null ? rw.capital_final : (isOverall ? (st.portfolio_full||pf).capital_final : null);
const sharpeShow = rw && rw.sharpe_ratio != null ? rw.sharpe_ratio : (isOverall ? st.sharpe_ratio : null);
const pfShow = rw && rw.profit_factor != null ? rw.profit_factor : (isOverall ? st.profit_factor : null);
const rowCls = 'border-b border-slate-800/50 hover:bg-slate-800/30 cursor-pointer' + (rgActive ? ' bg-emerald-900/20 border-l-2 border-l-emerald-400' : '');
ghtml += '<tr class="' + rowCls + '" onclick="showStrategyDetail(\'' + s.version + '\')">' +
'<td class="px-2 py-1.5 whitespace-nowrap">' +
(isOverall ? '<span class="text-slate-500">整体</span>' :
@@ -2398,22 +2405,22 @@ function renderStrategyTable(strategies) {
(rw && rw.avg_pnl != null ? ' <span class="text-[10px] ' + (rw.avg_pnl >= 0 ? 'text-green-500/70' : 'text-red-500/70') + '">' + (rw.avg_pnl >= 0 ? '+' : '') + rw.avg_pnl + '%</span>' : '') +
(rw && rw.trades != null ? ' <span class="text-[10px] text-slate-600">' + rw.trades + '笔</span>' : '') +
'</td>' +
'<td class="px-2 py-1.5 font-mono font-bold ' + (deprecatedReason ? 'text-red-400/70 line-through' : (isCurrent ? 'text-emerald-400' : 'text-blue-400')) + '">' + s.version + (isCurrent ? ' <span class="text-[10px] bg-emerald-500/20 text-emerald-300 px-1 rounded">激活</span>' : '') + (deprecatedReason ? ' <span class="text-[10px] bg-red-500/20 text-red-300 px-1 rounded" title="' + deprecatedReason.replace(/"/g, '&quot;') + '">已证伪</span>' : '') + ((s.market && s.market !== 'all') ? ' <span class="text-[10px] bg-cyan-500/20 text-cyan-300 px-1 rounded">' + (s.market === 'hk' ? '港' : 'A') + '</span>' : '') + '</td>' +
'<td class="px-2 py-1.5 font-mono font-bold ' + (deprecatedReason ? 'text-red-400/70 line-through' : (rgActive ? 'text-emerald-400' : 'text-blue-400')) + '">' + s.version + (rgActive ? ' <span class="text-[10px] bg-emerald-500/20 text-emerald-300 px-1 rounded">激活</span>' : '') + (deprecatedReason ? ' <span class="text-[10px] bg-red-500/20 text-red-300 px-1 rounded" title="' + deprecatedReason.replace(/"/g, '&quot;') + '">已证伪</span>' : '') + ((s.market && s.market !== 'all') ? ' <span class="text-[10px] bg-cyan-500/20 text-cyan-300 px-1 rounded">' + (s.market === 'hk' ? '港' : 'A') + '</span>' : '') + '</td>' +
'<td class="px-2 py-1.5 whitespace-normal break-words" title="' + (s.hypothesis || s.summary || '').replace(/"/g, '&quot;') + '">' + (s.name || '') + (smallSample ? ' <span class="text-amber-500" title="样本<40笔">⚠️</span>' : '') + '</td>' +
'<td class="text-center px-2 py-1.5 font-mono text-cyan-300">' + (st.sizing_slots ? st.sizing_slots + '仓' : '10仓') + (st.conviction_model ? '<button onclick="event.stopPropagation();showConviction(\'' + s.version + '\')" class="text-amber-400 hover:text-amber-300" title="查看信念分级仓位规则">⚡</button>' : '') + '</td>' +
'<td class="text-center px-2 py-1.5">' + ((s.description && s.description.title) ? '<button onclick="event.stopPropagation();showDesc(\'' + s.version + '\')" class="text-blue-400 hover:text-blue-300" title="查看策略说明">📖</button>' : '<span class="text-slate-700">—</span>') + '</td>' +
cell('composite', s._composite, v => v + (s._confidence != null && s._confidence < 1 ? '<span class="text-slate-500 text-[10px]">×' + s._confidence.toFixed(2) + '</span>' : ''), 'font-bold text-amber-300') +
cell('universality_score', (st.universality || {}).score, v => v + '<span class="text-slate-500">/' + (st.universality || {}).months + '月</span>') +
cell('full_return_pct', (st.portfolio_full || {}).total_return_pct, v => v + '%' + ((st.portfolio_full||{}).std ? '<span class="text-slate-500 text-[10px]">±' + (st.portfolio_full||{}).std + '</span>' : ''), 'font-bold text-amber-300') +
cell('capital_final', (st.portfolio_full||pf).capital_final, v => '¥' + (v/10000).toFixed(0) + '万') +
cell('cagr_pct', (st.portfolio_full||pf).cagr_pct, v => v + '%', hl((st.portfolio_full||pf).cagr_pct, best.cagr_pct)) +
cell('total_trades', tradesShow, v => (v > 999 ? Math.round(v/1000) + 'k' : v) + (isOverall ? '' : ''), '', false) +
cell('avg_hold_days', st.avg_hold_days, v => v + 'd') +
cell('composite', isOverall ? s._composite : null, v => v + (s._confidence != null && s._confidence < 1 ? '<span class="text-slate-500 text-[10px]">×' + s._confidence.toFixed(2) + '</span>' : ''), 'font-bold text-amber-300') +
cell('universality_score', isOverall ? (st.universality || {}).score : null, v => v + '<span class="text-slate-500">/' + (st.universality || {}).months + '月</span>') +
cell('full_return_pct', retShow, v => v + '%', 'font-bold text-amber-300') +
cell('capital_final', capShow, v => '¥' + (v/10000).toFixed(0) + '万') +
cell('cagr_pct', cagrShow, v => v + '%', hl(cagrShow, best.cagr_pct)) +
cell('total_trades', tradesShow, v => (v > 999 ? Math.round(v/1000) + 'k' : v), '', false) +
cell('avg_hold_days', holdShow, v => v + 'd') +
cell('win_rate', wrShow, v => v + '%', (isOverall ? hl(wrShow, best.win_rate) : (wrShow >= 50 ? 'text-green-400' : 'text-red-400'))) +
cell('avg_profit_pct', pnlShow, v => v + '%', (pnlShow || 0) >= 0 ? 'text-green-400' : 'text-red-400') +
cell('sharpe_ratio', st.sharpe_ratio, v => v, hl(st.sharpe_ratio, best.sharpe_ratio)) +
cell('profit_factor', st.profit_factor, v => v, hl(st.profit_factor, best.profit_factor)) +
cell('portfolio_max_dd_pct', (st.portfolio_full||pf).portfolio_max_dd_pct, v => v + '%', hl((st.portfolio_full||pf).portfolio_max_dd_pct, best.portfolio_max_dd_pct, true), true) +
cell('sharpe_ratio', sharpeShow, v => v, hl(sharpeShow, best.sharpe_ratio)) +
cell('profit_factor', pfShow, v => v, hl(pfShow, best.profit_factor)) +
cell('portfolio_max_dd_pct', ddShow, v => v + '%', hl(ddShow, best.portfolio_max_dd_pct, true), true) +
'<td class="text-center px-2 py-1.5" onclick="event.stopPropagation()">' +
'<button onclick="runStrategyBacktest(\'' + s.version + '\')" class="px-2 py-0.5 bg-blue-600/60 hover:bg-blue-500 rounded text-xs">▶回测</button>' +
'</td></tr>';