From 7db2585c15f2976d13896eef27843df8f87cecc3 Mon Sep 17 00:00:00 2001 From: hmo Date: Sat, 15 Aug 2026 10:03:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B8=A9=E5=8C=BA=E5=8A=A0=E6=9D=83?= =?UTF-8?q?=E7=BB=BC=E5=90=88/=E6=99=AE=E9=80=82=E2=80=94=E2=80=94?= =?UTF-8?q?=E6=95=B4=E4=BD=93=E8=A1=8C=E7=BB=BC=E5=90=88/=E6=99=AE?= =?UTF-8?q?=E9=80=82=E6=8C=89=E6=B8=A9=E5=8C=BA=E6=97=B6=E9=95=BF=E5=8D=A0?= =?UTF-8?q?=E6=AF=94=E5=8A=A0=E6=9D=83=E5=90=84=E6=B8=A9=E5=8C=BA=E8=A1=A8?= =?UTF-8?q?=E7=8E=B0(=E4=B8=9A=E5=8A=A1:=E7=AD=96=E7=95=A5=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E7=89=B9=E5=AE=9A=E6=B8=A9=E5=8C=BA=E6=BF=80=E6=B4=BB?= =?UTF-8?q?,=E8=AF=84=E4=BC=B0=E9=99=90=E5=AE=9A=E6=B8=A9=E5=8C=BA?= =?UTF-8?q?=E8=8C=83=E5=9B=B4),=E6=B8=A9=E5=8C=BA=E8=A1=8C=E7=94=A8?= =?UTF-8?q?=E6=B8=A9=E5=8C=BA=E7=BA=A7=E7=BB=84=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 41 +++++++++++++++++++++++++++++++++++++++++ static/index.html | 14 +++++++------- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/server.py b/server.py index d9120d7d..4aa3ae6f 100644 --- a/server.py +++ b/server.py @@ -629,6 +629,47 @@ def api_research_strategies(): except Exception: pass for s in strats: + # 2026-08-15 温区加权综合/普适:整体行的综合/普适 = 按温区时长占比加权各温区表现 + # (业务逻辑:策略只在特定温区激活,评估应该限定在温区范围才值得参考) + _rws = _regime_winrates.get(s['version'], {}) + if _rws: + _total_weight = sum((r.get('portfolio') or {}).get('total_return_pct', 0) or 0 for r in _rws.values()) + if _total_weight > 0: + _w_cagr = sum( + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) / _total_weight * + ((r.get('portfolio') or {}).get('cagr_pct', 0) or 0) + for r in _rws.values()) + _w_ret = sum( + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) / _total_weight * + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) + for r in _rws.values()) + _w_dd = sum( + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) / _total_weight * + ((r.get('portfolio') or {}).get('portfolio_max_dd_pct', 0) or 0) + for r in _rws.values()) + _w_sharpe = sum( + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) / _total_weight * + ((r.get('portfolio') or {}).get('sharpe_ratio', 0) or 0) + for r in _rws.values()) + _w_pf = sum( + ((r.get('portfolio') or {}).get('total_return_pct', 0) or 0) / _total_weight * + ((r.get('portfolio') or {}).get('profit_factor', 0) or 0) + for r in _rws.values()) + # 温区加权综合分(对齐整体综合分公式:ret30+wr20+sharpe20+pf15+dd15) + _w_wr = sum(r.get('win_rate', 0) or 0 for r in _rws.values()) / max(len(_rws), 1) + _w_composite = round( + min(_w_ret, 100) / 100 * 30 + _w_wr / 100 * 20 + + min(max(_w_sharpe, 0), 20) / 20 * 20 + + min(_w_pf, 5) / 5 * 15 + + (1 - min(_w_dd, 50) / 50) * 15) + s['regime_weighted'] = { + 'composite': _w_composite, + 'cagr_pct': round(_w_cagr, 1), + 'total_return_pct': round(_w_ret, 1), + 'portfolio_max_dd_pct': round(_w_dd, 1), + 'sharpe_ratio': round(_w_sharpe, 2), + 'profit_factor': round(_w_pf, 2), + } s['description'] = STRATEGY_DESCRIPTIONS.get(s['version'], {}) # 2026-08-15:STRATEGY_DESCRIPTIONS 缺策略描述时,从 strategy_research 的 name/summary 生成简化描述(说明列不再空) if not s['description'] and (s.get('name') or s.get('summary')): diff --git a/static/index.html b/static/index.html index c5257ac4..ec899efc 100644 --- a/static/index.html +++ b/static/index.html @@ -2442,10 +2442,10 @@ function renderStrategyTable(strategies) { const holdShow = rw && rw.avg_hold_days != null ? rw.avg_hold_days : (isOverall ? st.avg_hold_days : null); const sharpeShow = rw && rw.sharpe_ratio != null ? rw.sharpe_ratio : (isOverall ? st.sharpe_ratio : null); const pfShow = rw && rw.profit_factor != null ? rw.profit_factor : (isOverall ? st.profit_factor : null); - // 组合级列:整体行用整体组合;温区行用温区级组合(rw.portfolio)+ 温区级 composite + 温区级 universality + // 组合级列:整体行用温区加权综合(s.regime_weighted,2026-08-15 按温区时长占比加权各温区表现);温区行用温区级组合(rw.portfolio)+ 温区级 composite + 温区级 universality const rwPf = rw && rw.portfolio ? rw.portfolio : null; const rwUniv = rw && rw.universality ? rw.universality : null; - // 温区级 composite(用温区 portfolio 的 cagr/total_return + 温区胜率/夏普/盈亏比/回撤,对齐整体综合分逻辑) + // 温区加权整体综合(regime_weighted.composite);温区级 composite(用温区 portfolio 算) let rgComposite = null; if (rwPf) { const rgRet = Math.min(rwPf.total_return_pct || 0, 100) / 100 * 30; @@ -2455,13 +2455,13 @@ function renderStrategyTable(strategies) { const rgDd = (1 - Math.min(rwPf.portfolio_max_dd_pct || 0, 50) / 50) * 15; rgComposite = Math.round((rgRet + rgWr + rgSh + rgPfc + rgDd) * (s._confidence || 1)); } - const compositeShow = isOverall ? s._composite : rgComposite; - // 温区级 universality(温区内信号月份分散度 + 温区总月份占比) + const compositeShow = isOverall ? (s.regime_weighted && s.regime_weighted.composite != null ? s.regime_weighted.composite : s._composite) : rgComposite; + // 温区加权普适(用温区时长占比加权各温区月份分散度);温区级普适(rw.universality) const universalityShow = isOverall ? (st.universality || {}).score : (rwUniv ? rwUniv.score : null); - const retShow = isOverall ? (st.portfolio_full||{}).total_return_pct : (rwPf ? rwPf.total_return_pct : null); + const retShow = isOverall ? (s.regime_weighted && s.regime_weighted.total_return_pct != null ? s.regime_weighted.total_return_pct : (st.portfolio_full||{}).total_return_pct) : (rwPf ? rwPf.total_return_pct : null); const capShow = isOverall ? (st.portfolio_full||pf).capital_final : (rwPf ? rwPf.capital_final : null); - const cagrShow = isOverall ? (st.portfolio_full||pf).cagr_pct : (rwPf ? rwPf.cagr_pct : null); - const ddShow = isOverall ? (st.portfolio_full||pf).portfolio_max_dd_pct : (rwPf ? rwPf.portfolio_max_dd_pct : null); + const cagrShow = isOverall ? (s.regime_weighted && s.regime_weighted.cagr_pct != null ? s.regime_weighted.cagr_pct : (st.portfolio_full||pf).cagr_pct) : (rwPf ? rwPf.cagr_pct : null); + const ddShow = isOverall ? (s.regime_weighted && s.regime_weighted.portfolio_max_dd_pct != null ? s.regime_weighted.portfolio_max_dd_pct : (st.portfolio_full||pf).portfolio_max_dd_pct) : (rwPf ? rwPf.portfolio_max_dd_pct : null); const rowCls = 'border-b border-slate-800/50 hover:bg-slate-800/30 cursor-pointer' + (rgActive ? ' bg-emerald-900/20 border-l-2 border-l-emerald-400' : ''); ghtml += '' + '' +