fix: dashboard metrics — real drawdown baseline, realized/unrealized split, risk radar fields

- Max drawdown was double-broken: the backend built the equity curve on a
  hardcoded $10k baseline (understating a small account's drawdown ~20x) and
  the frontend multiplied the already-percent value by 100 again (0.87% shown
  as -86.9%). The curve now starts from the trader's real initial balance
  (10k fallback when unknown) and the UI renders the percent once; the demo
  engine and type docs are aligned to percent semantics.
- Header now separates equity-based 'Total P/L (incl. unrealized)' from
  'Realized P/L (closed trades)' so it no longer contradicts profit factor /
  win rate, and the stats strip shows the fee-drag chain
  (gross - fees = net) plus a per-trade-labelled sharpe.
- Risk radar read a non-existent account field (total_unrealized_profit) so
  unrealized PnL always showed $0; small PnLs also render with cents now.
- Nav 'Connect Hyperliquid' turns into a green connected chip when the server
  already holds a fully-authorized exchange, instead of nagging forever from
  a browser without the localStorage flow state.
This commit is contained in:
tinkle-community
2026-07-06 00:01:44 +09:00
parent ee5917adc6
commit 026c5d3089
12 changed files with 116 additions and 28 deletions

View File

@@ -339,14 +339,21 @@ export function TerminalDashboard({
</div>
<div className="tm-rule" />
{/* metric row */}
{/* metric row — "Total P/L" is equity-based (includes unrealized);
"Realized P/L" is closed-trades only and matches PF/win-rate/sharpe,
so the two never read as contradicting each other */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)' }}>
{[
{ l: 'Equity', v: fmtUsd(account?.total_equity), c: 'var(--tm-ink)' },
{ l: 'Total P/L', v: `${fmtUsd(pnl, true)} (${fmtPct(pnlPct)})`, c: up ? 'var(--tm-up)' : 'var(--tm-dn)' },
{ l: 'Win rate', v: fullStats != null ? `${fullStats.win_rate.toFixed(1)}%` : '—', c: 'var(--tm-ink)' },
{ l: 'Total P/L · incl. unrealized', v: `${fmtUsd(pnl, true)} (${fmtPct(pnlPct)})`, c: up ? 'var(--tm-up)' : 'var(--tm-dn)' },
{
l: 'Realized P/L · closed trades',
v: fullStats != null ? fmtUsd(fullStats.total_pnl, true) : '—',
c: fullStats != null && fullStats.total_pnl >= 0 ? 'var(--tm-up)' : 'var(--tm-dn)',
},
{ l: 'Profit factor', v: fullStats != null ? fullStats.profit_factor.toFixed(2) : '—', c: 'var(--tm-ink)' },
{ l: 'Max drawdown', v: fullStats != null ? `-${(fullStats.max_drawdown_pct * 100).toFixed(1)}%` : '—', c: 'var(--tm-dn)' },
// max_drawdown_pct is already a percent (18.5 = -18.5%)
{ l: 'Max drawdown', v: fullStats != null ? `-${fullStats.max_drawdown_pct.toFixed(1)}%` : '—', c: 'var(--tm-dn)' },
].map((m, i) => (
<div key={m.l} style={{ padding: '12px 14px', borderRight: i < 4 ? cellBorder : 'none' }}>
<div className="tm-sc">{m.l}</div>
@@ -361,11 +368,14 @@ export function TerminalDashboard({
<>
<div className="tm-mono" style={{ display: 'flex', gap: 18, padding: '6px 14px', fontSize: 11, color: 'var(--tm-ink-2)', flexWrap: 'wrap' }}>
<span className="tm-sc">trades <b style={{ color: 'var(--tm-ink)' }}>{fullStats.total_trades}</b></span>
<span className="tm-sc tm-up">win {fullStats.win_trades}</span>
<span className="tm-sc tm-up">win {fullStats.win_trades} ({fullStats.win_rate.toFixed(1)}%)</span>
<span className="tm-sc tm-dn">loss {fullStats.loss_trades}</span>
<span className="tm-sc">sharpe <b style={{ color: 'var(--tm-ink)' }}>{fullStats.sharpe_ratio.toFixed(2)}</b></span>
{/* fee-drag chain: gross realized fees = net realized */}
<span className="tm-sc">gross <b style={{ color: 'var(--tm-ink)' }}>{fmtUsd(fullStats.total_pnl + fullStats.total_fee, true)}</b></span>
<span className="tm-sc">fees <b style={{ color: 'var(--tm-ink)' }}>-{fmtUsd(fullStats.total_fee)}</b></span>
<span className="tm-sc">net <b style={{ color: fullStats.total_pnl >= 0 ? 'var(--tm-up)' : 'var(--tm-dn)' }}>{fmtUsd(fullStats.total_pnl, true)}</b></span>
<span className="tm-sc">sharpe/trade <b style={{ color: 'var(--tm-ink)' }}>{fullStats.sharpe_ratio.toFixed(2)}</b></span>
<span className="tm-sc">avg win/loss <b style={{ color: 'var(--tm-ink)' }}>{fullStats.avg_win.toFixed(2)}/{fullStats.avg_loss.toFixed(2)}</b></span>
<span className="tm-sc">fees <b style={{ color: 'var(--tm-ink)' }}>{fmtUsd(fullStats.total_fee)}</b></span>
</div>
<div className="tm-rule" />
</>