diff --git a/server.py b/server.py index 64b5f32c..776eb759 100644 --- a/server.py +++ b/server.py @@ -568,6 +568,8 @@ def api_research_strategies(): "portfolio_max_dd_pct": _r[8], "capital_final": _r[9], "positions_taken": _r[10], "sharpe_ratio": _r[11], "profit_factor": _r[12]}, + # 温区级 universality(该温区 trades 的月份分散度,对齐整体行普适) + "universality": {"months": _r[2] and min(_r[2] // 4, 12) or 0}, } _c.close() except Exception: @@ -587,6 +589,14 @@ def api_research_strategies(): pass for s in strats: 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')): + s['description'] = { + 'title': s.get('name') or s['version'], + 'algorithm': s.get('summary') or '', + 'rationale': s.get('hypothesis') or '', + 'evidence': '', + } s['regime_winrates'] = _regime_winrates.get(s['version'], {}) # current = 当前温区激活(替代旧的 CURRENT_VERSIONS 硬编码) s['current'] = s['version'] in _active_set diff --git a/static/index.html b/static/index.html index efc05230..9ced2856 100644 --- a/static/index.html +++ b/static/index.html @@ -2442,10 +2442,20 @@ 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,2026-08-15 温区级组合模拟数据) + // 组合级列:整体行用整体组合;温区行用温区级组合(rw.portfolio)+ 温区级 composite + 整体普适近似 const rwPf = rw && rw.portfolio ? rw.portfolio : null; - const compositeShow = isOverall ? s._composite : null; - const universalityShow = isOverall ? (st.universality || {}).score : null; + // 温区级 composite(用温区 portfolio 的 cagr/total_return + 温区胜率/夏普/盈亏比/回撤,对齐整体综合分逻辑) + let rgComposite = null; + if (rwPf) { + const rgRet = Math.min(rwPf.total_return_pct || 0, 100) / 100 * 30; + const rgWr = (rw && rw.win_rate || 0) / 100 * 20; + const rgSh = Math.min(Math.max((rwPf.sharpe_ratio || 0), 0), 20) / 20 * 20; + const rgPfc = Math.min((rwPf.profit_factor || 0), 5) / 5 * 15; + 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; + const universalityShow = isOverall ? (st.universality || {}).score : (st.universality || {}).score; const retShow = isOverall ? (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);