feat: 策略列表全列可点击排序(升降切换+方向箭头)

This commit is contained in:
hmo
2026-07-29 14:48:11 +08:00
parent f857a14762
commit c570fb9aab
+62 -12
View File
@@ -1959,9 +1959,55 @@ async function loadStrategyList() {
} }
} }
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) { function renderStrategyTable(strategies) {
const el = document.getElementById('strategyList'); const el = document.getElementById('strategyList');
if (!strategies.length) { el.innerHTML = '<div class="text-slate-500">暂无策略版本</div>'; return; } if (!strategies.length) { el.innerHTML = '<div class="text-slate-500">暂无策略版本</div>'; return; }
// 保存数据用于列排序
window._strats = strategies;
if (window._sortKey) {
strategies = sortStrategies(strategies, window._sortKey, window._sortDir);
}
// 非港股版本在前,港股分身沉底且同版本内非港在前(2026-07-29 老爸:v7.1身份不被港股分身混淆) // 非港股版本在前,港股分身沉底且同版本内非港在前(2026-07-29 老爸:v7.1身份不被港股分身混淆)
strategies.sort((a, b) => { strategies.sort((a, b) => {
const am = (a.market || 'all') === 'hk' ? 1 : 0; const am = (a.market || 'all') === 'hk' ? 1 : 0;
@@ -2027,19 +2073,23 @@ function renderStrategyTable(strategies) {
const sty = bar(c, v, invert); const sty = bar(c, v, invert);
return `<td class="${cls}" style="${sty}">${v != null ? fmt(v) : '—'}</td>`; return `<td class="${cls}" style="${sty}">${v != null ? fmt(v) : '—'}</td>`;
}; };
const th = (key, label, cls, title) => {
const arrow = window._sortKey === key ? (window._sortDir === 'desc' ? '▼' : '▲') : '';
return `<th class="${cls || 'text-right'} px-2 py-1.5 cursor-pointer hover:text-slate-300 select-none" onclick="sortBy('${key}')" title="${title || '点击排序'}">${label}${arrow ? ' ' + arrow : ''}</th>`;
};
let html = '<div class="overflow-x-auto"><table class="w-full text-xs whitespace-nowrap"><thead><tr class="text-slate-500 border-b border-slate-700">' + let html = '<div class="overflow-x-auto"><table class="w-full text-xs whitespace-nowrap"><thead><tr class="text-slate-500 border-b border-slate-700">' +
'<th class="text-left px-2 py-1.5">版本</th><th class="text-left px-2 py-1.5">名称</th>' + th('version', '版本', 'text-left') + th('name', '名称', 'text-left') +
'<th class="text-center px-2 py-1.5" title="该策略实证最优的仓位模型:波段/结构出场适合大仓少股,固定出场适合小仓多股">仓位</th>' + th('sizing_slots', '仓位', 'text-center') +
'<th class="text-right px-2 py-1.5 text-amber-300">综合</th>' + th('composite', '综合', 'text-right') +
'<th class="text-right px-2 py-1.5">普适</th>' + th('universality', '普适', 'text-right') +
'<th class="text-right px-2 py-1.5 text-amber-400">总收益</th>' + th('total_return', '总收益', 'text-right') +
'<th class="text-right px-2 py-1.5 text-amber-400">最终资产</th>' + th('capital_final', '最终资产', 'text-right') +
'<th class="text-right px-2 py-1.5">年化</th>' + th('cagr', '年化', 'text-right') +
'<th class="text-right px-2 py-1.5">交易数</th>' + th('trades', '交易数', 'text-right') +
'<th class="text-right px-2 py-1.5">均持仓</th>' + th('hold', '均持仓', 'text-right') +
'<th class="text-right px-2 py-1.5">胜率</th>' + th('win_rate', '胜率', 'text-right') +
'<th class="text-right px-2 py-1.5">平均收益</th><th class="text-right px-2 py-1.5">夏普</th>' + th('avg_profit', '平均收益', 'text-right') + th('sharpe', '夏普', 'text-right') +
'<th class="text-right px-2 py-1.5">盈亏比</th><th class="text-right px-2 py-1.5">资产回撤</th>' + th('profit_factor', '盈亏比', 'text-right') + th('max_dd', '资产回撤', 'text-right') +
'<th class="text-center px-2 py-1.5">操作</th></tr></thead><tbody>'; '<th class="text-center px-2 py-1.5">操作</th></tr></thead><tbody>';
for (const s of strategies) { for (const s of strategies) {
const st = s.summary_stats || {}; const st = s.summary_stats || {};