fix: 恢复sortBy/sortStrategies(重构时被删,点列头报ReferenceError)+温区表改温区感知排序(读regime_winrates[rg],尊重_sortKey)

This commit is contained in:
xxm
2026-08-16 00:39:58 +08:00
parent 4987d6b56e
commit 1193a61510
+83 -2
View File
@@ -2252,6 +2252,47 @@ async function loadEvolutionBox() {
new MutationObserver(()=>{if(!document.getElementById('tab-research')?.classList?.contains('hidden'))loadEvolutionBox();}).observe(document.body,{attributes:true,subtree:true});
function sortBy(key) {
if (window._sortKey === key) {
window._sortDir = window._sortDir === 'desc' ? 'asc' : 'desc';
} else {
window._sortKey = key;
window._sortDir = 'desc';
}
renderStrategyTable(window._strats || []);
}
function sortStrategies(strategies, key, dir) {
const get = (s) => {
const st = s.summary_stats || {}; const pf = st.portfolio || {};
switch(key) {
case 'version': return s.version || '';
case 'name': return s.name || '';
case 'sizing_slots': return st.sizing_slots || 10;
case 'composite': return s._composite != null ? s._composite : -999;
case 'universality': return (st.universality || {}).score || 0;
case 'total_return': return pf.total_return_pct != null ? pf.total_return_pct : -999;
case 'capital_final': return pf.capital_final || 0;
case 'cagr': return pf.cagr_pct != null ? pf.cagr_pct : -999;
case 'trades': return st.total_trades || 0;
case 'hold': return st.avg_hold_days || 0;
case 'win_rate': return st.win_rate != null ? st.win_rate : -999;
case 'avg_profit': return st.avg_profit_pct != null ? st.avg_profit_pct : -999;
case 'sharpe': return st.sharpe_ratio != null ? st.sharpe_ratio : -999;
case 'profit_factor': return st.profit_factor != null ? st.profit_factor : -999;
case 'max_dd': return pf.portfolio_max_dd_pct != null ? pf.portfolio_max_dd_pct : 999;
default: return 0;
}
};
const arr = [...strategies];
arr.sort((a, b) => {
const va = get(a), vb = get(b);
let cmp = (typeof va === 'string') ? va.localeCompare(vb) : va - vb;
return dir === 'desc' ? -cmp : cmp;
});
return arr;
}
function renderStrategyTable(strategies) {
const el = document.getElementById('strategyList');
if (!strategies.length) { el.innerHTML = '<div class="text-slate-500">暂无策略版本</div>'; return; }
@@ -2482,9 +2523,49 @@ function renderStrategyTable(strategies) {
};
const renderRegimeTable = (strats, rg, isCur, marketLabel, tempHtml) => {
// 2026-08-15 方案A:策略只在适应温区(best_regime)表显示一行——所有列都是该策略在适应温区的数据
// 2026-08-16 温区感知排序:尊重 window._sortKey(点列头排序),字段从 regime_winrates[rg] 取
const metas = strats
.filter(s => s.best_regime === rg && (s.regime_winrates || {})[rg] && (s.regime_winrates || {})[rg].trades != null)
.sort((x, y) => ((y.regime_winrates || {})[rg].win_rate || -1) - ((x.regime_winrates || {})[rg].win_rate || -1));
.filter(s => s.best_regime === rg && (s.regime_winrates || {})[rg] && (s.regime_winrates || {})[rg].trades != null);
const regimeGet = (s, key) => {
const rw = (s.regime_winrates || {})[rg] || {};
const pf = rw.portfolio || {};
switch (key) {
case 'version': return s.version || '';
case 'name': return s.name || '';
case 'win_rate': return rw.win_rate != null ? rw.win_rate : -999;
case 'avg_profit': return rw.avg_pnl != null ? rw.avg_pnl : -999;
case 'trades': return rw.trades || 0;
case 'hold': return rw.avg_hold_days != null ? rw.avg_hold_days : 0;
case 'cagr': return pf.cagr_pct != null ? pf.cagr_pct : -999;
case 'sharpe': return rw.sharpe_ratio != null ? rw.sharpe_ratio : -999;
case 'profit_factor': return rw.profit_factor != null ? rw.profit_factor : -999;
case 'composite': {
// 复用温区行综合分公式(与 renderRegimeRow 一致)
const ret = Math.min(pf.total_return_pct || 0, 100) / 100 * 30;
const w = (rw.win_rate || 0) / 100 * 20;
const sh = Math.min(Math.max((pf.sharpe_ratio || 0), 0), 20) / 20 * 20;
const pfc = Math.min((pf.profit_factor || 0), 5) / 5 * 15;
const dd = (1 - Math.min(pf.portfolio_max_dd_pct || 0, 50) / 50) * 15;
const rn = rw.trades || 0;
const rm = rw.universality ? (rw.universality.months || 0) : 0;
const rconf = Math.min(1, rn / 40) * (rm > 0 ? Math.min(1, rm / 6) : 1);
return Math.round((ret + w + sh + pfc + dd) * rconf);
}
case 'universality': return (rw.universality || {}).score || 0;
case 'max_dd': return pf.portfolio_max_dd_pct != null ? pf.portfolio_max_dd_pct : 999;
default: return 0;
}
};
if (window._sortKey) {
const key = window._sortKey, dir = window._sortDir;
metas.sort((a, b) => {
const va = regimeGet(a, key), vb = regimeGet(b, key);
let cmp = (typeof va === 'string') ? va.localeCompare(vb) : va - vb;
return dir === 'desc' ? -cmp : cmp;
});
} else {
metas.sort((x, y) => ((y.regime_winrates || {})[rg].win_rate || -1) - ((x.regime_winrates || {})[rg].win_rate || -1));
}
if (!metas.length) return '';
const meta = REGIME_META[rg] || {icon: '🌐', name: rg};
return '<div class="mb-3 rounded-lg border ' + (isCur ? 'border-emerald-700/60 bg-emerald-900/10' : 'border-slate-700') + '">' +