import { useEffect, useState, useCallback } from 'react' import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, } from 'recharts' const REFRESH_MS = 30_000 function fmt(n, decimals = 2) { if (n == null) return '—' return Number(n).toFixed(decimals) } function fmtUSD(n) { if (n == null) return '—' const abs = Math.abs(n) const sign = n < 0 ? '-' : '' return `${sign}$${abs.toFixed(2)}` } function fmtPct(n) { if (n == null) return '—' return `${(n * 100).toFixed(1)}%` } function fmtTime(ts) { if (!ts) return '—' const d = new Date(ts) return d.toLocaleString('es-ES', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) } function MetricCard({ title, value, subtitle, progress, progressColor }) { return (
{title}
{value}
{subtitle &&
{subtitle}
} {progress != null && (
)}
) } function CustomTooltip({ active, payload, label }) { if (!active || !payload?.length) return null return (
{label}
{payload.map((p) => (
P&L: {fmtUSD(p.value)}
))}
) } export default function App() { const [summary, setSummary] = useState(null) const [trades, setTrades] = useState([]) const [history, setHistory] = useState([]) const [lastUpdate, setLastUpdate] = useState(null) const [error, setError] = useState(null) const fetchAll = useCallback(async () => { try { const [sumRes, tradesRes, metricsRes] = await Promise.all([ fetch('/api/summary'), fetch('/api/trades?limit=50'), fetch('/api/metrics'), ]) if (!sumRes.ok || !tradesRes.ok || !metricsRes.ok) throw new Error('API error') const [sumData, tradesData, metricsData] = await Promise.all([ sumRes.json(), tradesRes.json(), metricsRes.json(), ]) setSummary(sumData) setTrades(tradesData.trades || []) const raw = (metricsData.history || []).slice().reverse() setHistory( raw.map((r) => ({ date: new Date(r.timestamp).toLocaleDateString('es-ES', { month: 'short', day: 'numeric' }), pnl: r.total_pnl, })) ) setLastUpdate(new Date()) setError(null) } catch (e) { setError(e.message) } }, []) useEffect(() => { fetchAll() const id = setInterval(fetchAll, REFRESH_MS) return () => clearInterval(id) }, [fetchAll]) if (!summary && !error) { return (
Cargando...
) } if (error && !summary) { return (
Error conectando con la API: {error}
) } const pnlColor = summary.total_pnl >= 0 ? 'var(--green)' : 'var(--red)' return (
{/* Header */}

Polymarket Bot {summary.paper_mode && PAPER}

{lastUpdate && ( Actualizado: {lastUpdate.toLocaleTimeString('es-ES')} )}
{/* Promotion banner */} {summary.promotion_ready && (
🚀
Listo para producción — El bot cumple los criterios de rendimiento. Considera desactivar PAPER_MODE.
)} {/* Metrics grid */}
{fmtUSD(summary.total_pnl)}} subtitle={`${fmtPct(summary.total_pnl / summary.paper_bankroll)} sobre bankroll`} progress={summary.total_pnl / summary.paper_bankroll + 0.5} progressColor={pnlColor} /> = 0.52 ? 'var(--green)' : 'var(--amber)'} /> = 0.5 ? 'var(--green)' : 'var(--amber)'} /> = 0.7 ? 'var(--green)' : 'var(--amber)'} />
{/* Performance chart */}

Performance Over Time

{history.length === 0 ? (
Sin datos históricos aún
) : (
`$${v}`} tick={{ fill: 'var(--text-muted)', fontSize: 11 }} axisLine={false} tickLine={false} width={60} /> } />
)}
{/* Recent trades table */}

Recent Trades

{trades.length === 0 ? (
Sin trades registrados aún
) : (
{trades.map((t) => ( ))}
Time Market Dir Size Price Shares Fee
{fmtTime(t.timestamp)} {t.question} {t.direction} {fmtUSD(t.size_usdc)} {fmt(t.entry_price, 3)} {fmt(t.shares, 2)} {fmtUSD(t.fee_usdc)}
)}
) }