diff --git a/server.py b/server.py index eaa4fbfe..27d2a008 100644 --- a/server.py +++ b/server.py @@ -550,10 +550,16 @@ def api_research_strategies(): _c = _sq.connect(str(DATA_DIR / "mofin.db"), timeout=10) _c.execute("PRAGMA busy_timeout=10000") for _r in _c.execute( - "SELECT strategy, regime, trades, win_rate, avg_pnl FROM strategy_regime_perf" + "SELECT strategy, regime, trades, win_rate, avg_pnl, avg_hold_days, " + "total_return_pct, cagr_pct, portfolio_max_dd_pct, capital_final, " + "positions_taken, sharpe_ratio, profit_factor FROM strategy_regime_perf" ).fetchall(): _regime_winrates.setdefault(_r[0], {})[_r[1]] = { - "trades": _r[2], "win_rate": _r[3], "avg_pnl": _r[4] + "trades": _r[2], "win_rate": _r[3], "avg_pnl": _r[4], + "avg_hold_days": _r[5], "total_return_pct": _r[6], + "cagr_pct": _r[7], "max_dd_pct": _r[8], "capital_final": _r[9], + "positions_taken": _r[10], "sharpe_ratio": _r[11], + "profit_factor": _r[12], } _c.close() except Exception: diff --git a/static/index.html b/static/index.html index ec773bf7..af5d3d76 100644 --- a/static/index.html +++ b/static/index.html @@ -2384,12 +2384,19 @@ function renderStrategyTable(strategies) { const rw = rws[rg]; const isOverall = rg === 'overall'; const rgMeta = REGIME_META[rg] || {icon:'🌐', name: rg === 'overall' ? '整体' : rg}; - const rgActive = !isOverall && isCurrent && (window._regimeActive || []).includes(s.version); - // 温区行数据:该温区胜率/均盈/样本(有则覆盖,无则用整体) + const rgActive = !isOverall && isCurrent; + // ── 温区行数据:所有列用该温区数据(rw 有则用,无则"—")── const wrShow = rw && rw.win_rate != null ? rw.win_rate : (isOverall ? st.win_rate : null); const pnlShow = rw && rw.avg_pnl != null ? rw.avg_pnl : (isOverall ? st.avg_profit_pct : null); const tradesShow = rw && rw.trades != null ? rw.trades : (isOverall ? st.total_trades : null); - const rowCls = 'border-b border-slate-800/50 hover:bg-slate-800/30 cursor-pointer' + (isCurrent && !isOverall ? ' bg-emerald-900/20 border-l-2 border-l-emerald-400' : ''); + const holdShow = rw && rw.avg_hold_days != null ? rw.avg_hold_days : (isOverall ? st.avg_hold_days : null); + const cagrShow = rw && rw.cagr_pct != null ? rw.cagr_pct : (isOverall ? (st.portfolio_full||pf).cagr_pct : null); + const retShow = rw && rw.total_return_pct != null ? rw.total_return_pct : (isOverall ? (st.portfolio_full||{}).total_return_pct : null); + const ddShow = rw && rw.max_dd_pct != null ? rw.max_dd_pct : (isOverall ? (st.portfolio_full||pf).portfolio_max_dd_pct : null); + const capShow = rw && rw.capital_final != null ? rw.capital_final : (isOverall ? (st.portfolio_full||pf).capital_final : 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 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 += '' + '' + (isOverall ? '整体' : @@ -2398,22 +2405,22 @@ function renderStrategyTable(strategies) { (rw && rw.avg_pnl != null ? ' ' + (rw.avg_pnl >= 0 ? '+' : '') + rw.avg_pnl + '%' : '') + (rw && rw.trades != null ? ' ' + rw.trades + '笔' : '') + '' + - '' + s.version + (isCurrent ? ' 激活' : '') + (deprecatedReason ? ' 已证伪' : '') + ((s.market && s.market !== 'all') ? ' ' + (s.market === 'hk' ? '港' : 'A') + '' : '') + '' + + '' + s.version + (rgActive ? ' 激活' : '') + (deprecatedReason ? ' 已证伪' : '') + ((s.market && s.market !== 'all') ? ' ' + (s.market === 'hk' ? '港' : 'A') + '' : '') + '' + '' + (s.name || '') + (smallSample ? ' ⚠️' : '') + '' + '' + (st.sizing_slots ? st.sizing_slots + '仓' : '10仓') + (st.conviction_model ? '' : '') + '' + '' + ((s.description && s.description.title) ? '' : '') + '' + - cell('composite', s._composite, v => v + (s._confidence != null && s._confidence < 1 ? '×' + s._confidence.toFixed(2) + '' : ''), 'font-bold text-amber-300') + - cell('universality_score', (st.universality || {}).score, v => v + '/' + (st.universality || {}).months + '月') + - cell('full_return_pct', (st.portfolio_full || {}).total_return_pct, v => v + '%' + ((st.portfolio_full||{}).std ? '±' + (st.portfolio_full||{}).std + '' : ''), 'font-bold text-amber-300') + - cell('capital_final', (st.portfolio_full||pf).capital_final, v => '¥' + (v/10000).toFixed(0) + '万') + - cell('cagr_pct', (st.portfolio_full||pf).cagr_pct, v => v + '%', hl((st.portfolio_full||pf).cagr_pct, best.cagr_pct)) + - cell('total_trades', tradesShow, v => (v > 999 ? Math.round(v/1000) + 'k' : v) + (isOverall ? '' : ''), '', false) + - cell('avg_hold_days', st.avg_hold_days, v => v + 'd') + + cell('composite', isOverall ? s._composite : null, v => v + (s._confidence != null && s._confidence < 1 ? '×' + s._confidence.toFixed(2) + '' : ''), 'font-bold text-amber-300') + + cell('universality_score', isOverall ? (st.universality || {}).score : null, v => v + '/' + (st.universality || {}).months + '月') + + cell('full_return_pct', retShow, v => v + '%', 'font-bold text-amber-300') + + cell('capital_final', capShow, v => '¥' + (v/10000).toFixed(0) + '万') + + cell('cagr_pct', cagrShow, v => v + '%', hl(cagrShow, best.cagr_pct)) + + cell('total_trades', tradesShow, v => (v > 999 ? Math.round(v/1000) + 'k' : v), '', false) + + cell('avg_hold_days', holdShow, v => v + 'd') + cell('win_rate', wrShow, v => v + '%', (isOverall ? hl(wrShow, best.win_rate) : (wrShow >= 50 ? 'text-green-400' : 'text-red-400'))) + cell('avg_profit_pct', pnlShow, v => v + '%', (pnlShow || 0) >= 0 ? 'text-green-400' : 'text-red-400') + - cell('sharpe_ratio', st.sharpe_ratio, v => v, hl(st.sharpe_ratio, best.sharpe_ratio)) + - cell('profit_factor', st.profit_factor, v => v, hl(st.profit_factor, best.profit_factor)) + - cell('portfolio_max_dd_pct', (st.portfolio_full||pf).portfolio_max_dd_pct, v => v + '%', hl((st.portfolio_full||pf).portfolio_max_dd_pct, best.portfolio_max_dd_pct, true), true) + + cell('sharpe_ratio', sharpeShow, v => v, hl(sharpeShow, best.sharpe_ratio)) + + cell('profit_factor', pfShow, v => v, hl(pfShow, best.profit_factor)) + + cell('portfolio_max_dd_pct', ddShow, v => v + '%', hl(ddShow, best.portfolio_max_dd_pct, true), true) + '' + '' + '';