'use client'; import { useState, useEffect } from 'react'; const GREEN = '#16A34A'; const RED = '#DC2626'; const BLUE = '#2563EB'; export default function PaperDashboard() { const [data, setData] = useState(null); const [trades, setTrades] = useState({}); const [loading, setLoading] = useState(true); const [activeStrat, setActiveStrat] = useState(null); const [resetting, setResetting] = useState(false); useEffect(() => { const fetchState = async () => { try { const res = await fetch('/api/state'); const json = await res.json(); setData(json); if (!activeStrat && json.strategies?.length) { setActiveStrat(json.strategies[0].name); } setLoading(false); } catch (e) { console.error('State fetch error:', e); } }; fetchState(); const interval = setInterval(fetchState, 2000); return () => clearInterval(interval); }, [activeStrat]); useEffect(() => { if (!activeStrat) return; const fetchTrades = async () => { try { const res = await fetch(`/api/trades?strategy=${encodeURIComponent(activeStrat)}`); const json = await res.json(); setTrades(prev => ({ ...prev, [activeStrat]: json.trades || [] })); } catch (e) { console.error('Trades fetch error:', e); } }; fetchTrades(); const interval = setInterval(fetchTrades, 10000); return () => clearInterval(interval); }, [activeStrat]); const handleReset = async () => { if (!confirm('Reset ALL paper trading data? This clears all history, stats, and open positions for every strategy.')) return; setResetting(true); try { await fetch('/api/reset', { method: 'POST' }); setTrades({}); } catch (e) { console.error('Reset error:', e); } setTimeout(() => setResetting(false), 2000); }; if (loading) { return (
Loading Paper Trading...
); } const market = data?.market; const strategies = data?.strategies || []; const paperByStrategy = data?.paperByStrategy || {}; const activeStratData = strategies.find(s => s.name === activeStrat); const activeStats = paperByStrategy[activeStrat]; const activeTrades = trades[activeStrat] || []; return (

Kalbot

PAPER
{data?.lastUpdate ? 'Live' : 'Offline'}
{strategies.map(s => ( ))}
{activeStrat && activeStratData && ( )}
Worker: {formatUptime(data?.workerUptime)} {data?.lastUpdate ? new Date(data.lastUpdate).toLocaleTimeString() : 'never'}
); } function MarketCardCompact({ market }) { if (!market) { return (

No active market โ€” waiting...

); } const timeLeft = market.closeTime ? getTimeLeft(market.closeTime) : null; return (

BTC Up or Down

15 min

{timeLeft && ( โฑ {timeLeft} )} โ‚ฟ
Up {market.yesPct}%
Down {market.noPct}%
${(market.volume || 0).toLocaleString()} vol {market.ticker}
); } function StrategyDetailView({ strategy, stats, trades }) { const s = stats || { balance: 1000, totalPnL: 0, wins: 0, losses: 0, winRate: 0, totalTrades: 0, openPositions: [] }; const pnlColor = s.totalPnL >= 0 ? GREEN : RED; return (

{strategy.name}

{strategy.mode}
= 0 ? '+' : ''}$${s.totalPnL}`} color={pnlColor} /> = 50 ? GREEN : RED} />
{strategy.config && Object.entries(strategy.config).map(([k, v]) => (
{k} {typeof v === 'number' ? v : String(v)}
))} {strategy.consecutiveLosses !== undefined && (
Consecutive Losses 0 ? 'text-red-600' : 'text-gray-700'}> {strategy.consecutiveLosses}
)} {strategy.currentBetSize !== undefined && (
Next Bet ${strategy.currentBetSize}
)} {strategy.round !== undefined && (
Cycle Round {strategy.round}/{strategy.maxRounds}
)} {strategy.cycleWins !== undefined && (
Cycles Won/Lost {strategy.cycleWins}W / {strategy.cycleLosses}L
)} {strategy.cycleWinRate !== undefined && (
Cycle Win Rate = 50 ? 'text-green-600' : 'text-red-600'}> {strategy.cycleWinRate}%
)} {strategy.paused && (
โš ๏ธ PAUSED โ€” max losses reached
)}
{s.openPositions.length > 0 && (

Open Positions ({s.openPositions.length})

{s.openPositions.map((t, i) => )}
)}

Trade History ({trades.length})

{trades.length === 0 ? (

No settled trades yet.

) : ( trades.map((t, i) => ) )}
); } function AllStrategiesOverview({ paperByStrategy, strategies }) { const entries = strategies.map(s => ({ name: s.name, stats: paperByStrategy[s.name] || { balance: 1000, totalPnL: 0, winRate: 0, totalTrades: 0 }, enabled: s.enabled, paused: s.paused })); if (!entries.length) return null; entries.sort((a, b) => b.stats.totalPnL - a.stats.totalPnL); return (

๐Ÿ“Š Strategy Leaderboard

{entries.map((e, i) => { const pnlColor = e.stats.totalPnL >= 0 ? GREEN : RED; return (
{i + 1}. {e.name}
{e.stats.totalTrades} trades {e.stats.winRate}% wr {e.stats.totalPnL >= 0 ? '+' : ''}${e.stats.totalPnL}
); })}
); } function StatBox({ label, value, color }) { return (

{label}

{value}

); } function TradeRow({ trade, isOpen }) { // Fix: Check side vs result for actual win condition, not strictly PNL > 0 const won = trade.result && trade.side.toLowerCase() === trade.result.toLowerCase(); const isNeutral = trade.result === 'cancelled' || trade.result === 'expired'; const pnlColor = trade.pnl == null ? 'text-gray-400' : trade.pnl > 0 ? 'text-green-600' : trade.pnl < 0 ? 'text-red-600' : 'text-gray-600'; return (
{isOpen ? ( ) : ( {isNeutral ? 'โž–' : won ? 'โœ…' : 'โŒ'} )} {trade.side} @ {trade.price}ยข ${trade.size}
{trade.pnl != null ? `${trade.pnl >= 0 ? '+' : ''}$${trade.pnl}` : 'open'}
{trade.reason}
{trade.result && !isOpen && ( Result: {trade.result} )} {!trade.result && } {trade.entryTime ? new Date(trade.entryTime).toLocaleTimeString() : ''}
); } function getTimeLeft(closeTime) { const diff = new Date(closeTime).getTime() - Date.now(); if (diff <= 0) return 'Closing...'; const mins = Math.floor(diff / 60000); const secs = Math.floor((diff % 60000) / 1000); return `${mins}:${secs.toString().padStart(2, '0')}`; } function formatUptime(seconds) { if (!seconds) return '0s'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); if (h > 0) return `${h}h ${m}m`; if (m > 0) return `${m}m ${s}s`; return `${s}s`; }