From d20fa4b6786a17625f2b75a6f595a45484bc5323 Mon Sep 17 00:00:00 2001 From: xxm Date: Sat, 15 Aug 2026 13:43:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A0=94=E7=A9=B6Tab=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E6=B8=A9=E5=8C=BA=E6=BF=80=E6=B4=BB=E5=8C=BA=E5=9D=97=E6=8C=89?= =?UTF-8?q?=E6=B8=A9=E5=8C=BA=E5=88=86=E7=BB=84=E2=80=94=E2=80=94=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E6=B8=A9=E5=8C=BA=E6=8E=92=E6=9C=80=E5=89=8D=E9=AB=98?= =?UTF-8?q?=E4=BA=AE,=E7=BB=84=E5=86=85=E6=8C=89=E8=AF=A5=E6=B8=A9?= =?UTF-8?q?=E5=8C=BA=E8=83=9C=E7=8E=87=E9=99=8D=E5=BA=8F,=E6=AF=8F?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=B8=80=E8=A1=8C(=E8=AF=A5=E6=B8=A9?= =?UTF-8?q?=E5=8C=BA=E6=95=B0=E6=8D=AE),API=E8=BF=94=E5=9B=9Eregime=5Fstat?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 3 ++- static/index.html | 67 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index e5d5b5fa..6d38c543 100644 --- a/server.py +++ b/server.py @@ -736,7 +736,8 @@ def api_research_strategies(): _entry['state_reason'] = '当前温区路由激活' _entry['note'] = f"({pt or '当前周期'}无回测记录,显示10y数据)" strats.append(_entry) - return jsonify({'strategies': strats, 'regime_active': sorted(_active_set)}) + return jsonify({'strategies': strats, 'regime_active': sorted(_active_set), + 'regime_state': _weights.get('state', 'unknown')}) except Exception as e: return jsonify({'error': str(e)}), 500 diff --git a/static/index.html b/static/index.html index 7f5420e8..f6a3bf2f 100644 --- a/static/index.html +++ b/static/index.html @@ -2179,6 +2179,7 @@ async function loadStrategyList() { if (data.error) { listEl.innerHTML = '
' + data.error + '
'; return; } // 2026-08-13 温区激活策略集合(供策略表高亮) window._regimeActive = data.regime_active || []; + window._regimeState = data.regime_state || 'unknown'; const mkt = document.getElementById('btMarket')?.value || 'all'; let strats = data.strategies || []; // 市场语义:'all'=未分市场的A股回测(港股回填前跑的),'hk'=港股宇宙,'a'=显式A股 @@ -2496,12 +2497,74 @@ function renderStrategyTable(strategies) { ghtml += ''; return ghtml; }; + // ── 2026-08-15 按温区分组:当前温区激活区块改为温区优先(当前温区排最前高亮), + // 每个温区下列出所有在该温区有数据的策略(按该温区胜率降序)── + const REGIME_ORDER = ['trend_up', 'choppy', 'trend_down']; + const renderRegimeRow = (s, rg, isCur) => { + const rw = (s.regime_winrates || {})[rg] || {}; + const rwPf = rw.portfolio || null; + const wr = rw.win_rate != null ? rw.win_rate : null; + const pnl = rw.avg_pnl != null ? rw.avg_pnl : null; + const tr = rw.trades != null ? rw.trades : null; + const smallSample = tr != null && tr < 40; + let comp = null; + if (rwPf) { + const ret = Math.min(rwPf.total_return_pct || 0, 100) / 100 * 30; + const w = (wr || 0) / 100 * 20; + const sh = Math.min(Math.max((rwPf.sharpe_ratio || 0), 0), 20) / 20 * 20; + const pfc = Math.min((rwPf.profit_factor || 0), 5) / 5 * 15; + const dd = (1 - Math.min(rwPf.portfolio_max_dd_pct || 0, 50) / 50) * 15; + comp = Math.round((ret + w + sh + pfc + dd) * (s._confidence || 1)); + } + const cagr = rwPf ? rwPf.cagr_pct : null; + const ddV = rwPf ? rwPf.portfolio_max_dd_pct : null; + const univ = rw.universality ? rw.universality.score : null; + const univM = rw.universality ? (rw.universality.months + '/' + (rw.universality.regime_total_months || '?')) : null; + return '' + + '' + s.version + (isCur ? ' 激活' : '') + ((s.market && s.market !== 'all') ? ' ' + (s.market === 'hk' ? '港' : 'A') + '' : '') + '' + + '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + '' + + cell('win_rate', wr, v => v + '%', wr != null && wr >= 50 ? 'text-green-400' : 'text-red-400') + + cell('avg_profit_pct', pnl, v => v + '%', (pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400') + + cell('total_trades', tr, v => v, '', false) + + cell('avg_hold_days', rw.avg_hold_days != null ? rw.avg_hold_days : null, v => v + 'd') + + cell('cagr_pct', cagr, v => v + '%', 'font-bold text-amber-300') + + cell('composite', comp, v => v, 'font-bold text-amber-300') + + cell('universality_score', univ, v => v + '/' + (univM || '?') + '月') + + cell('portfolio_max_dd_pct', ddV, v => v + '%', '') + + ''; + }; + const renderGroupByRegime = (strats, currentRegime) => { + const order = [currentRegime, ...REGIME_ORDER.filter(r => r !== currentRegime)]; + let g = ''; + for (const rg of order) { + const metas = strats + .filter(s => (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)); + if (!metas.length) continue; + const isCur = rg === currentRegime; + const meta = REGIME_META[rg] || {icon: '🌐', name: rg}; + g += '
' + + '
' + + meta.icon + ' ' + meta.name + (isCur ? ' 当前温区' : '') + + '(' + metas.length + '个策略)' + + '' + (isCur ? '当前温区匹配即激活' : '该温区下有数据(参考)') + '
' + + '
' + + th('version', '版本', 'text-left') + th('name', '名称', 'text-left') + + th('win_rate', '胜率', 'text-right') + th('avg_profit', '平均收益', 'text-right') + th('trades', '笔数', 'text-right') + + th('hold', '均持仓', 'text-right') + th('cagr', '年化', 'text-right') + th('composite', '综合', 'text-right') + + th('universality', '普适', 'text-right') + th('max_dd', '回撤', 'text-right') + + '' + + metas.map(s => renderRegimeRow(s, rg, isCur)).join('') + + '
操作
'; + } + return g; + }; // 当前区域(展开)+ 历史区域(默认折叠) let html = ''; if (currentStrats.length) { html += '
' + - '
🟢 当前温区激活(' + currentStrats.length + '个)
' + - renderGroup(currentStrats, '当前', '🟢', false) + + '
🟢 当前温区激活(' + currentStrats.length + '个) · 按温区分组 · 当前温区排最前高亮
' + + renderGroupByRegime(currentStrats, window._regimeState) + '
'; } if (availableStrats.length) {