From 1193a61510c41c6fbc1252da11e3a1be53d47b35 Mon Sep 17 00:00:00 2001 From: xxm Date: Sun, 16 Aug 2026 00:39:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8DsortBy/sortStrategies(?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=97=B6=E8=A2=AB=E5=88=A0,=E7=82=B9?= =?UTF-8?q?=E5=88=97=E5=A4=B4=E6=8A=A5ReferenceError)+=E6=B8=A9=E5=8C=BA?= =?UTF-8?q?=E8=A1=A8=E6=94=B9=E6=B8=A9=E5=8C=BA=E6=84=9F=E7=9F=A5=E6=8E=92?= =?UTF-8?q?=E5=BA=8F(=E8=AF=BBregime=5Fwinrates[rg],=E5=B0=8A=E9=87=8D=5Fs?= =?UTF-8?q?ortKey)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/index.html | 85 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/static/index.html b/static/index.html index d4561dd1..a8c8cfbd 100644 --- a/static/index.html +++ b/static/index.html @@ -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 = '
暂无策略版本
'; 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 '
' +