fix: 恢复sortBy/sortStrategies(重构时被删,点列头报ReferenceError)+温区表改温区感知排序(读regime_winrates[rg],尊重_sortKey)
This commit is contained in:
+83
-2
@@ -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') + '">' +
|
||||
|
||||
Reference in New Issue
Block a user