From 3245c3b8513eea644ab93ec6b84f2b09c99d5710 Mon Sep 17 00:00:00 2001 From: xxm Date: Mon, 17 Aug 2026 08:19:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B8=A9=E5=8C=BA=E8=A1=A8=E5=88=86?= =?UTF-8?q?=E5=8C=BA(=E5=B7=B2=E6=BF=80=E6=B4=BB=E4=B8=8A=E5=8C=BA+?= =?UTF-8?q?=E5=A4=87=E9=80=89=E4=B8=8B=E5=8C=BA=E6=8A=98=E5=8F=A0)+?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E8=AF=84=E5=88=86=E5=88=86=E7=BA=A7(?= =?UTF-8?q?=E4=B8=8D=E5=90=88=E6=A0=BC/=E5=BE=85=E8=A7=82=E5=AF=9F/?= =?UTF-8?q?=E5=90=88=E6=A0=BC/=E8=89=AF=E5=A5=BD/=E4=BC=98=E7=A7=80)+?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=AF=84=E5=88=86=E5=88=86=E7=A6=BB+?= =?UTF-8?q?=E7=BB=BC=E5=90=88=E6=8E=92=E5=90=8D=E6=95=88=E7=8E=87=E6=83=A9?= =?UTF-8?q?=E7=BD=9A=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/index.html | 93 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/static/index.html b/static/index.html index d13e942d..63a37fe5 100644 --- a/static/index.html +++ b/static/index.html @@ -2595,7 +2595,8 @@ function renderStrategyTable(strategies) { (rw && rw.trades != null ? ' ' + rw.trades + '笔' : '') + '' + '' + s.version + (rgActive ? ' 激活' : '') + (s.state === 'available' && !rgActive ? ' 可用' : '') + (deprecatedReason ? ' 已证伪' : '') + ((s.market && s.market !== 'all') ? ' ' + (s.market === 'hk' ? '港' : 'A') + '' : '') + '' + - '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + '' + + '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + ' ' + + (rGrade ? '' + GRADE_STD[rGrade].label + '' : '') + '' + '' + (st.sizing_slots ? st.sizing_slots + '仓' : '10仓') + (st.conviction_model ? '' : '') + '' + '' + ((s.description && s.description.title) ? '' : '') + '' + cell('composite', compositeShow, v => v + (s._confidence != null && s._confidence < 1 ? '×' + s._confidence.toFixed(2) + '' : ''), 'font-bold text-amber-300') + @@ -2623,6 +2624,29 @@ function renderStrategyTable(strategies) { // ── 2026-08-15 按温区分组:当前温区激活区块改为温区优先(当前温区排最前高亮), // 每个温区下列出所有在该温区有数据的策略(按该温区胜率降序)── const REGIME_ORDER = ['trend_up', 'choppy', 'trend_down']; + + // ── 2026-08-17 策略评分分级(老莫):基于资格叉数 + 综合分排名 ── + // 不合格=≥2个叉 / 待观察=1个叉 / 合格/良好/优秀=3勾+综合分区间 + const GRADE_STD = { + poor: {label: '不合格', cls: 'bg-red-500/20 text-red-300', need: '叉≥2'}, + watch: {label: '待观察', cls: 'bg-amber-500/20 text-amber-300', need: '叉=1'}, + ok: {label: '合格', cls: 'bg-slate-500/20 text-slate-300', need: '3勾+综合60-74'}, + good: {label: '良好', cls: 'bg-emerald-500/25 text-emerald-200', need: '3勾+综合75-84'}, + great: {label: '优秀', cls: 'bg-emerald-400/30 text-emerald-100', need: '3勾+综合≥85'}, + }; + const gradeOf = (qg, comp) => { + if (!qg) return null; + const ok3 = qg.long_ok && qg.mid_ok && qg.short_ok; + const xCount = [qg.long_ok, qg.mid_ok, qg.short_ok].filter(x => !x).length; + if (!ok3) { + if (xCount >= 2) return 'poor'; + if (xCount === 1) return 'watch'; + return null; + } + if (comp >= 85) return 'great'; + if (comp >= 75) return 'good'; + return 'ok'; + }; const renderRegimeRow = (s, rg) => { // 2026-08-15 行级激活按策略自身(后端已按所属市场温区算好 s.current),不再按表格 isCur const rw = (s.regime_winrates || {})[rg] || {}; @@ -2660,15 +2684,27 @@ function renderStrategyTable(strategies) { const r3 = rLong && rMid && rShort; // 三项全过 const rNearOk = rMid || rShort; // 近期有亮点(含长期不过但近期过:❌❌✅/❌✅✅) const rManual = s.manual_available !== false; - let rState, rStateCls, rStateBadge, rRowBg; - if (!rManual) { rState = 'manual_off'; rStateCls = 'text-slate-500'; rStateBadge = '手动停用'; rRowBg = 'opacity-50'; } - else if (r3) { rState = 'ok3'; rStateCls = 'text-emerald-400'; rStateBadge = (s.current === true) ? '已激活' : '可激活'; rRowBg = 'bg-emerald-900/10'; } - else if (rNearOk) { rState = 'ok2'; rStateCls = 'text-blue-400'; rStateBadge = '可用'; rRowBg = ''; } - else { rState = 'bad'; rStateCls = 'text-red-400/80'; rStateBadge = '不合格'; rRowBg = 'opacity-60'; } - const isActive = rState === 'ok3'; + // ── 2026-08-17 状态与评分分离(老莫): + // 状态 = 已激活(current=true,实盘跑)/未激活(current=false,不跑) + // 评分 = 不合格(≥2叉)/待观察(1叉)/合格/良好/优秀(3勾+综合分区间) + const rGrade = gradeOf(qg, comp); + let rStateCls, rStateBadge, rRowBg; + if (!rManual) { rStateCls = 'text-slate-500'; rStateBadge = '手动停用'; rRowBg = 'opacity-50'; } + else if (s.current === true) { + rStateCls = 'text-emerald-400'; + rStateBadge = '已激活'; + rRowBg = 'bg-emerald-900/15'; + } else { + rStateCls = 'text-blue-400'; + rStateBadge = '未激活'; + rRowBg = ''; + } + const rGradeBadge = rGrade && rGrade !== 'ok' && rGrade !== 'good' && rGrade !== 'great' ? '' : ''; + const isActive = s.current === true; return '' + '' + s.version + ' ' + rStateBadge + ((s.market && s.market !== 'all') ? ' ' + (s.market === 'hk' ? '港' : 'A') + '' : '') + '' + - '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + '' + + '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + ' ' + + (rGrade ? '' + GRADE_STD[rGrade].label + '' : '') + '' + // 2026-08-16 资格列:长期/近期/当下 三项达标徽章 '' + (s.qualification && s.qualification[rg] ? ( '' + (s.qualification[rg].long_ok ? '✅' : '❌') + '' + @@ -2724,7 +2760,11 @@ function renderStrategyTable(strategies) { 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); + // 2026-08-17 效率惩罚(与 renderRegimeRow 一致)——否则排序分≠显示分,排名错乱 + const _sig = rn, _pos = rw.positions_taken || 0; + const _ratio = _pos > 0 ? _sig / _pos : 99; + const _eff = _ratio <= 2 ? 1.0 : _ratio <= 5 ? 0.9 : _ratio <= 10 ? 0.75 : 0.5; + return Math.round((ret + w + sh + pfc + dd) * rconf * _eff); } case 'universality': return (rw.universality || {}).score || 0; case 'max_dd': return pf.portfolio_max_dd_pct != null ? pf.portfolio_max_dd_pct : 999; @@ -2762,9 +2802,38 @@ function renderStrategyTable(strategies) { th('hold', '均持仓', 'text-right', '平均持仓天数', 'hold') + th('cagr', '年化', 'text-right', '年化收益率', 'cagr') + th('composite', '综合', 'text-right', '多维度综合评分', 'composite') + th('sharpe', '夏普', 'text-right', '每单位波动超额收益', 'sharpe') + th('profit_factor', '盈亏比', 'text-right', '总盈利/总亏损', 'profit_factor') + th('universality', '普适', 'text-right', '跨独立年份有效性(非特例)', 'universality') + th('max_dd', '回撤', 'text-right', '组合模拟最大回撤', 'max_dd') + - '操作' + - metas.map(s => renderRegimeRow(s, rg)).join('') + - ''; + '操作' + + // ── 2026-08-17 分区(老莫):上区=已激活,下区=备选(资格达标未激活),默认折叠 ── + (() => { + const active = metas.filter(s => s.current === true); + const backup = metas.filter(s => s.current !== true); + let html = ''; + if (active.length) { + html += '✅ 已激活 (' + active.length + ')'; + html += active.map(s => renderRegimeRow(s, rg)).join(''); + } + if (backup.length) { + html += '' + + ''; + html += '' + + '' + + '' + backup.map(s => renderRegimeRow(s, rg)).join('') + '
' + + ''; + } + html += ''; + return html; + })(); + }; + // ── 2026-08-17 备选区展开/折叠(老莫)── + window.toggleBackup = function(key) { + const body = document.getElementById('bk-body-' + key); + const arrow = document.getElementById('bk-arrow-' + key); + if (body) { + const hidden = body.classList.contains('hidden'); + body.classList.toggle('hidden'); + if (arrow) arrow.textContent = hidden ? '▼' : '▶'; + } }; // ── 2026-08-15 按市场×温区展示:6表格 = A股当前市 → 港股当前市 → A股其他2市 → 港股其他2市, // 每表格列出该市场在该温区有数据的所有可用策略(含未激活,按该温区胜率降序),激活策略高亮;温度并入当前市标题尾部