fix(dashboard): Strategy page crashed on undefined fields
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled

User reported UI error. Root cause: API GET /api/meet-kevin/strategy/
performance returns {trade_count, closed_trade_count, total_pnl_usd,
win_rate_pct, wins, losses} but the dashboard's StrategyPerformance
type expected {total_return_pct, sharpe_ratio, max_drawdown_pct,
open_positions, alpha_vs_spy_pct, ...}. So performance.open_positions
was undefined and .toString() crashed the whole page render.

Fix: update StrategyPerformance to match the actual API shape and
rewrite the 6-card headline grid to show the metrics the API
actually computes (trade count, closed count, total P&L, win rate,
wins, losses). Defensive ?? 0 fallbacks everywhere so future
schema drift doesn't crash again.

Long-term: extend the API to compute sharpe/max-dd/alpha vs SPY etc.
once there are real Kevin trades to compute them on (currently 0
trades — all 13 emitted signals fired outside market hours and were
correctly rejected by RiskManager).
This commit is contained in:
Viktor Barzin 2026-05-27 18:00:48 +00:00
parent d5359691b1
commit 1aeb6e8587
2 changed files with 27 additions and 29 deletions

View file

@ -123,36 +123,35 @@ export default function MeetKevinStrategy() {
{/* Headline metrics */}
{performance && (
<div className="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-4">
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-4">
<MetricCard
label="Total Return"
value={fmt(performance.total_return_pct)}
color={performance.total_return_pct >= 0 ? 'text-green-400' : 'text-red-400'}
label="Total Trades"
value={(performance.trade_count ?? 0).toString()}
/>
<MetricCard
label="Ann. Return"
value={fmt(performance.annualized_return_pct)}
color={(performance.annualized_return_pct ?? 0) >= 0 ? 'text-green-400' : 'text-red-400'}
label="Closed Trades"
value={(performance.closed_trade_count ?? 0).toString()}
/>
<MetricCard
label="Sharpe"
value={performance.sharpe_ratio != null ? performance.sharpe_ratio.toFixed(2) : '—'}
color={(performance.sharpe_ratio ?? 0) >= 1 ? 'text-green-400' : 'text-yellow-400'}
label="Total P&L"
value={`${(performance.total_pnl_usd ?? 0) >= 0 ? '+' : ''}$${(performance.total_pnl_usd ?? 0).toFixed(2)}`}
color={(performance.total_pnl_usd ?? 0) >= 0 ? 'text-green-400' : 'text-red-400'}
/>
<MetricCard
label="Max Drawdown"
value={performance.max_drawdown_pct != null ? `${performance.max_drawdown_pct.toFixed(2)}%` : '—'}
label="Win Rate"
value={`${(performance.win_rate_pct ?? 0).toFixed(1)}%`}
color={(performance.win_rate_pct ?? 0) >= 50 ? 'text-green-400' : 'text-yellow-400'}
/>
<MetricCard
label="Wins"
value={(performance.wins ?? 0).toString()}
color="text-green-400"
/>
<MetricCard
label="Losses"
value={(performance.losses ?? 0).toString()}
color="text-red-400"
/>
<MetricCard
label="Alpha vs SPY"
value={fmt(performance.alpha_vs_spy_pct)}
color={(performance.alpha_vs_spy_pct ?? 0) >= 0 ? 'text-green-400' : 'text-red-400'}
/>
<MetricCard
label="Open Positions"
value={performance.open_positions.toString()}
/>
</div>
)}

View file

@ -199,13 +199,12 @@ export interface StrategyEquityCurve {
}
export interface StrategyPerformance {
total_return_pct: number;
annualized_return_pct: number | null;
sharpe_ratio: number | null;
max_drawdown_pct: number | null;
win_rate: number | null;
// Live-path metrics — current shape from
// GET /api/meet-kevin/strategy/performance.
trade_count: number;
alpha_vs_spy_pct: number | null;
open_positions: number;
last_backtest_at: string | null;
closed_trade_count: number;
total_pnl_usd: number;
win_rate_pct: number;
wins: number;
losses: number;
}