'use client';
import { useState, useEffect } from 'react';
const GREEN = '#16A34A';
const RED = '#DC2626';
const BLUE = '#2563EB';
const AMBER = '#D97706';
export default function LiveDashboard() {
const [data, setData] = useState(null);
const [trades, setTrades] = useState([]);
const [loading, setLoading] = useState(true);
const [toggling, setToggling] = useState(null);
useEffect(() => {
const fetchState = async () => {
try {
const res = await fetch('/api/live-state');
const json = await res.json();
setData(json);
setLoading(false);
} catch (e) {
console.error('State fetch error:', e);
}
};
fetchState();
const interval = setInterval(fetchState, 2000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const fetchTrades = async () => {
try {
const res = await fetch('/api/live-trades');
const json = await res.json();
setTrades(json.trades || []);
} catch (e) {
console.error('Trades fetch error:', e);
}
};
fetchTrades();
const interval = setInterval(fetchTrades, 10000);
return () => clearInterval(interval);
}, []);
const sendCommand = async (action, strategy = null) => {
setToggling(strategy || action);
try {
await fetch('/api/live-toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, strategy })
});
} catch (e) {
console.error('Command error:', e);
}
setTimeout(() => setToggling(null), 1500);
};
if (loading) {
return (
);
}
const market = data?.market;
const live = data?.live || {};
const strategies = data?.strategies || [];
const enabledSet = new Set(live.enabledStrategies || []);
return (
{/* Header */}
Kalbot
LIVE
📝 Paper
{data?.lastUpdate ? 'Live' : 'Offline'}
{/* Kill Switch + Balance */}
Kalshi Balance
{live.balance != null ? `$${live.balance.toFixed(2)}` : '—'}
{live.portfolioValue != null && (
Portfolio: ${live.portfolioValue.toFixed(2)}
)}
= 0 ? '+' : ''}$${live.totalPnL?.toFixed(2) || '0.00'}`} color={live.totalPnL >= 0 ? GREEN : RED} dark />
= 50 ? GREEN : RED} dark />
0 ? AMBER : null} dark />
{live.paused && (
⚠️ TRADING PAUSED — No new orders will be placed
)}
Per-trade cap: ${live.maxPerTrade?.toFixed(2) || '5.00'}
Daily limit: ${live.maxDailyLoss?.toFixed(2) || '20.00'}
{/* Market Card */}
{/* Strategy Toggles */}
⚡ Strategy Controls
{strategies.map((s, i) => {
const isEnabled = enabledSet.has(s.name);
const isToggling = toggling === s.name;
return (
{s.name}
{s.config && (
${s.config.betSize || 1}/trade • {s.config.cooldownMs ? `${(s.config.cooldownMs / 1000).toFixed(0)}s cd` : ''}
)}
);
})}
{strategies.length === 0 && (
No strategies loaded yet.
)}
{/* Open Orders */}
{live.openOrders?.length > 0 && (
Open Orders ({live.openOrders.length})
{live.openOrders.map((o, i) => (
))}
)}
{/* Kalshi Positions */}
{live.positions?.length > 0 && (
Kalshi Positions ({live.positions.length})
{live.positions.map((p, i) => (
{p.market_ticker || p.ticker}
{p.position_fp || p.position || 0} contracts
{p.realized_pnl_dollars && (
Realized PnL: = 0 ? 'text-green-400' : 'text-red-400'}>
${Number(p.realized_pnl_dollars).toFixed(2)}
)}
))}
)}
{/* Trade History */}
Trade History ({trades.length})
{trades.length === 0 ? (
No settled trades yet. Enable a strategy to start.
) : (
trades.map((t, i) =>
)
)}
{/* Footer */}
Worker: {formatUptime(data?.workerUptime)}
{data?.lastUpdate ? new Date(data.lastUpdate).toLocaleTimeString() : 'never'}
);
}
function MarketCard({ market }) {
if (!market) {
return (
No active market — waiting...
);
}
const timeLeft = market.closeTime ? getTimeLeft(market.closeTime) : null;
return (
{timeLeft && (
⏱ {timeLeft}
)}
₿
${(market.volume || 0).toLocaleString()} vol
{market.ticker}
);
}
function LiveOrderRow({ order, isOpen }) {
const won = order.result && order.side?.toLowerCase() === order.result?.toLowerCase();
const isNeutral = order.result === 'cancelled' || order.result === 'expired';
const rawPnl = order?.pnl != null ? Number(order.pnl) : null;
const pnlVal = Number.isFinite(rawPnl)
? (Number.isInteger(rawPnl) ? rawPnl / 100 : rawPnl)
: null;
const pnlColor = pnlVal == null ? 'text-gray-600' : pnlVal > 0 ? 'text-green-400' : pnlVal < 0 ? 'text-red-400' : 'text-gray-400';
const priceRaw = Number(order.priceCents ?? order.price);
const priceCents = Number.isFinite(priceRaw)
? (priceRaw > 0 && priceRaw <= 1 ? Math.round(priceRaw * 100) : Math.round(priceRaw))
: null;
const contractsRaw = Number(order.contracts ?? 1);
const contracts = Number.isFinite(contractsRaw) && contractsRaw > 0 ? contractsRaw : 1;
const hasExpected = isOpen && priceCents != null && priceCents > 0 && priceCents < 100;
const grossPayout = hasExpected ? contracts : null; // $1 per winning contract
const cost = hasExpected ? (priceCents / 100) * contracts : null;
const winPnL = hasExpected ? grossPayout - cost : null;
const losePnL = hasExpected ? -cost : null;
return (
{isOpen ? (
) : (
{isNeutral ? '➖' : won ? '✅' : '❌'}
)}
{order.side}
@ {order.priceCents || order.price}¢
{order.contracts || 1}x
{pnlVal != null ? `${pnlVal >= 0 ? '+' : ''}$${pnlVal.toFixed(2)}` : 'open'}
{order.reason}
{order.strategy}
{hasExpected && (
If win: +${winPnL.toFixed(2)}
If lose: ${losePnL.toFixed(2)}
Gross payout: ${grossPayout.toFixed(2)}
)}
{order.result && !isOpen && (
Result: {order.result}
{order.settleTime ? new Date(order.settleTime).toLocaleTimeString() : order.createdAt ? new Date(order.createdAt).toLocaleTimeString() : ''}
)}
);
}
function StatBox({ label, value, color, dark }) {
return (
);
}
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`;
}