feat: 温区加权综合/普适——整体行综合/普适按温区时长占比加权各温区表现(业务:策略只在特定温区激活,评估限定温区范围),温区行用温区级组合
This commit is contained in:
@@ -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')):
|
||||
|
||||
+7
-7
@@ -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 += '<tr class="' + rowCls + '" onclick="showStrategyDetail(\'' + s.version + '\')">' +
|
||||
'<td class="px-2 py-1.5 whitespace-nowrap">' +
|
||||
|
||||
Reference in New Issue
Block a user