feat: 温区行综合/普适显示温区级数据——composite用温区portfolio算综合分,universality用整体近似;说明列fallback到strategy_research的name/summary

This commit is contained in:
hmo
2026-08-15 09:30:00 +08:00
parent 1481d6e3ff
commit e0bf57c3f0
2 changed files with 23 additions and 3 deletions
+10
View File
@@ -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-15STRATEGY_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
+13 -3
View File
@@ -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.portfolio2026-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);