fix: dashboard live price + 24h % + chart tick + null-pnl safety
Five bugs in the same blast radius (places we render a price/percent).
DashboardClient.tsx
• Hero price read lastCandle.close (REST candle, frozen until user
changes asset/tf). WebSocket livePrices was set but never read.
Now: livePrice ?? lastCandle.close.
• "+21.84% · 4H" was (last_close - first_open) / first_open over
the entire candle window. limit=200 + tf=4H = 33 days shown as
"4H". Find candle closest to now-24h, label "24h" consistently.
ChartPanel.tsx
• Chart only re-rendered on candles prop change. Live WS ticks
ignored. Added useEffect calling series.update() with
livePrices[asset] on every tick.
analytics/page.tsx
• reduce(sum + trade.pnl_usd) when pnl_usd is null → NaN poison.
• Math.max/min over null cast to 0 → bogus "best/worst trade".
• Win-rate denominator counted null-pnl trades as losses.
• calcDrawdownPct had the same null poisoning.
Fix: gate aggregations on priced subset only.
trades/page.tsx
• ROI could divide by 0/null; entry/exit crashed on null.toLocaleString().
Added guards.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -738,7 +738,12 @@ export default function TradesPage() {
|
||||
)}
|
||||
{filtered.map(t => {
|
||||
const tp = posts.find(p => p.id === t.trigger_post_id)
|
||||
const roi = ((t.exit_price - t.entry_price) / t.entry_price) * 100 * (t.side === 'long' ? 1 : -1)
|
||||
// Guard: entry_price may be 0 on a corrupt row; exit_price may
|
||||
// be null for externally-closed trades. Both → NaN otherwise.
|
||||
const roi =
|
||||
t.entry_price && t.exit_price != null
|
||||
? ((t.exit_price - t.entry_price) / t.entry_price) * 100 * (t.side === 'long' ? 1 : -1)
|
||||
: 0
|
||||
return (
|
||||
<tr key={t.id}>
|
||||
<td>
|
||||
@@ -748,8 +753,8 @@ export default function TradesPage() {
|
||||
</div>
|
||||
</td>
|
||||
<td><span className={`side-pill ${t.side}`}>{t.side==='long'?'↗ LONG':'↘ SHORT'}</span></td>
|
||||
<td className="mono">${t.entry_price.toLocaleString()}</td>
|
||||
<td className="mono">${t.exit_price.toLocaleString()}</td>
|
||||
<td className="mono">{t.entry_price ? '$' + t.entry_price.toLocaleString() : '—'}</td>
|
||||
<td className="mono">{t.exit_price != null ? '$' + t.exit_price.toLocaleString() : '—'}</td>
|
||||
<td className="mono" style={{ color:'var(--ink-2)' }}>{fmtHold(t.hold_seconds)}</td>
|
||||
<td style={{ maxWidth:260 }}>
|
||||
{tp ? (
|
||||
|
||||
Reference in New Issue
Block a user