feat: 温区加权综合/普适——整体行综合/普适按温区时长占比加权各温区表现(业务:策略只在特定温区激活,评估限定温区范围),温区行用温区级组合

This commit is contained in:
hmo
2026-08-15 10:03:46 +08:00
parent 13790e0cb6
commit 7db2585c15
2 changed files with 48 additions and 7 deletions
+41
View File
@@ -629,6 +629,47 @@ def api_research_strategies():
except Exception: except Exception:
pass pass
for s in strats: 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'], {}) s['description'] = STRATEGY_DESCRIPTIONS.get(s['version'], {})
# 2026-08-15STRATEGY_DESCRIPTIONS 缺策略描述时,从 strategy_research 的 name/summary 生成简化描述(说明列不再空) # 2026-08-15STRATEGY_DESCRIPTIONS 缺策略描述时,从 strategy_research 的 name/summary 生成简化描述(说明列不再空)
if not s['description'] and (s.get('name') or s.get('summary')): if not s['description'] and (s.get('name') or s.get('summary')):
+7 -7
View File
@@ -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 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 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); const pfShow = rw && rw.profit_factor != null ? rw.profit_factor : (isOverall ? st.profit_factor : null);
// 组合级列:整体行用整体组合;温区行用温区级组合(rw.portfolio+ 温区级 composite + 温区级 universality // 组合级列:整体行用温区加权综合(s.regime_weighted2026-08-15 按温区时长占比加权各温区表现);温区行用温区级组合(rw.portfolio+ 温区级 composite + 温区级 universality
const rwPf = rw && rw.portfolio ? rw.portfolio : null; const rwPf = rw && rw.portfolio ? rw.portfolio : null;
const rwUniv = rw && rw.universality ? rw.universality : null; const rwUniv = rw && rw.universality ? rw.universality : null;
// 温区级 composite(用温区 portfolio 的 cagr/total_return + 温区胜率/夏普/盈亏比/回撤,对齐整体综合分逻辑 // 温区加权整体综合(regime_weighted.composite);温区级 composite(用温区 portfolio 算
let rgComposite = null; let rgComposite = null;
if (rwPf) { if (rwPf) {
const rgRet = Math.min(rwPf.total_return_pct || 0, 100) / 100 * 30; 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; 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)); rgComposite = Math.round((rgRet + rgWr + rgSh + rgPfc + rgDd) * (s._confidence || 1));
} }
const compositeShow = isOverall ? s._composite : rgComposite; const compositeShow = isOverall ? (s.regime_weighted && s.regime_weighted.composite != null ? s.regime_weighted.composite : s._composite) : rgComposite;
// 温区级 universality(温区内信号月份分散度 + 温区总月份占比 // 温区加权普适(用温区时长占比加权各温区月份分散度);温区级普适(rw.universality
const universalityShow = isOverall ? (st.universality || {}).score : (rwUniv ? rwUniv.score : null); 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 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 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 ? (st.portfolio_full||pf).portfolio_max_dd_pct : (rwPf ? rwPf.portfolio_max_dd_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' : ''); 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 += '<tr class="' + rowCls + '" onclick="showStrategyDetail(\'' + s.version + '\')">' + ghtml += '<tr class="' + rowCls + '" onclick="showStrategyDetail(\'' + s.version + '\')">' +
'<td class="px-2 py-1.5 whitespace-nowrap">' + '<td class="px-2 py-1.5 whitespace-nowrap">' +