← back to Ken
Ken: add real-money-only dashboard toggle (Paper Arena ⟷ Real Money)
8ba89727c19dfe7aa0447ed1d8d4a60e67989d80 · 2026-08-04 10:13:03 -0700 · Steve Abrams
- New GET /api/dashboard/real endpoint: same shape as /api/dashboard but
computed from ken_trades (actual Kalshi orders) + live account balance,
not the 99-bot paper arena. Paper path untouched.
- Dashboard.jsx: persistent realMode toggle button in header, dynamic
labels/subtitle, refetches on toggle. Rebuilt bundle.
Files touched
M kalshi-dash/server.jsM kalshi-dash/src/pages/Dashboard.jsx
Diff
commit 8ba89727c19dfe7aa0447ed1d8d4a60e67989d80
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 10:13:03 2026 -0700
Ken: add real-money-only dashboard toggle (Paper Arena ⟷ Real Money)
- New GET /api/dashboard/real endpoint: same shape as /api/dashboard but
computed from ken_trades (actual Kalshi orders) + live account balance,
not the 99-bot paper arena. Paper path untouched.
- Dashboard.jsx: persistent realMode toggle button in header, dynamic
labels/subtitle, refetches on toggle. Rebuilt bundle.
---
kalshi-dash/server.js | 126 ++++++++++++++++++++++++++++++++++++
kalshi-dash/src/pages/Dashboard.jsx | 39 +++++++++--
2 files changed, 161 insertions(+), 4 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index c6f833b..ff48f9d 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -2182,6 +2182,132 @@ const routes = {
}
},
+ // ════════════════════════════════════════
+ // REAL-MONEY DASHBOARD — same shape as /api/dashboard but computed from
+ // ken_trades (actual Kalshi orders) + live account balance, NOT the paper
+ // arena. Feeds the "Real money only" toggle on the Dashboard page.
+ // ════════════════════════════════════════
+ 'GET /api/dashboard/real': async (req, res) => {
+ try {
+ // Live account cash (cents). Best-effort — fall back to bankroll if the
+ // Kalshi balance call rate-limits.
+ let cashCents = 0;
+ try {
+ const bal = await kalshiFetch('GET', '/portfolio/balance');
+ cashCents = Number(bal?.balance || 0);
+ } catch {
+ const { rows: [rs] } = await q('SELECT bankroll FROM risk_state ORDER BY updated_at DESC LIMIT 1');
+ cashCents = Math.round(Number(rs?.bankroll || 0) * 100);
+ }
+ // real safe_mode
+ const { rows: [rsCfg] } = await q('SELECT config FROM risk_state ORDER BY updated_at DESC LIMIT 1');
+ const safeMode = rsCfg?.config?.safe_mode ?? true;
+
+ // Aggregates over REAL trades
+ const { rows: [agg] } = await kenQ(`
+ SELECT
+ COALESCE(SUM(cost_cents) FILTER (WHERE status='open'), 0) AS capital_at_risk,
+ COUNT(*) FILTER (WHERE status='open') AS open_count,
+ COUNT(DISTINCT ticker) FILTER (WHERE status='open') AS open_markets,
+ COALESCE(SUM(cost_cents), 0) AS total_invested,
+ COALESCE(SUM(pnl_cents) FILTER (WHERE status!='open'), 0) AS realized_pnl,
+ COALESCE(SUM(cost_cents + COALESCE(pnl_cents,0)) FILTER (WHERE status!='open'), 0) AS total_returned,
+ COALESCE(SUM(pnl_cents) FILTER (WHERE status!='open' AND closed_at::date = CURRENT_DATE), 0) AS daily_pnl
+ FROM ken_trades
+ `);
+ const capitalAtRisk = Number(agg?.capital_at_risk || 0);
+ const realizedPnl = Number(agg?.realized_pnl || 0);
+ const balance = cashCents + capitalAtRisk; // equity = cash + open cost basis
+ const seedMoney = cashCents + capitalAtRisk - realizedPnl; // seed ≡ balance − realized
+ const roi = seedMoney > 0 ? (realizedPnl / seedMoney * 100) : 0;
+
+ // Daily economics (resolved real trades, last 7 days)
+ const { rows: spendRows } = await kenQ(`
+ SELECT closed_at::date AS day, COUNT(*) AS trades,
+ SUM(CASE WHEN pnl_cents>0 THEN 1 ELSE 0 END) AS wins,
+ SUM(CASE WHEN pnl_cents<0 THEN 1 ELSE 0 END) AS losses,
+ SUM(pnl_cents) AS net_pnl,
+ SUM(CASE WHEN pnl_cents>0 THEN pnl_cents ELSE 0 END) AS gross_wins,
+ SUM(CASE WHEN pnl_cents<0 THEN ABS(pnl_cents) ELSE 0 END) AS gross_losses,
+ SUM(cost_cents) AS capital_deployed
+ FROM ken_trades WHERE status!='open' AND closed_at IS NOT NULL
+ GROUP BY closed_at::date ORDER BY day DESC LIMIT 7
+ `);
+ // Today
+ const { rows: [today] } = await kenQ(`
+ SELECT COUNT(*) AS trades,
+ SUM(CASE WHEN pnl_cents>0 THEN 1 ELSE 0 END) AS wins,
+ SUM(CASE WHEN pnl_cents<0 THEN 1 ELSE 0 END) AS losses
+ FROM ken_trades WHERE created_at::date = CURRENT_DATE
+ `);
+ // Recent fills (open + resolved), newest first
+ const { rows: fills } = await kenQ(`
+ SELECT ticker, title, side, count, price_cents, pnl_cents, status
+ FROM ken_trades ORDER BY id DESC LIMIT 10
+ `);
+ // P&L history (resolved, last 14d)
+ const { rows: pnlHistory } = await kenQ(`
+ SELECT closed_at::date AS day, SUM(pnl_cents) AS daily_pnl
+ FROM ken_trades WHERE status!='open' AND closed_at > NOW() - INTERVAL '14 days'
+ GROUP BY closed_at::date ORDER BY day ASC
+ `);
+ // Per-ticker performance
+ const { rows: byTicker } = await kenQ(`
+ SELECT ticker, MAX(title) AS title, COUNT(*) AS trades,
+ SUM(CASE WHEN pnl_cents>0 THEN 1 ELSE 0 END) AS wins,
+ SUM(CASE WHEN pnl_cents<0 THEN 1 ELSE 0 END) AS losses,
+ SUM(COALESCE(pnl_cents,0)) AS pnl
+ FROM ken_trades GROUP BY ticker ORDER BY pnl DESC
+ `);
+ const mkMarket = (m) => {
+ const t = m.ticker || ''; const et = t.replace(/-[^-]+$/, '') || t; const st = et.replace(/-[^-]+$/, '') || et;
+ return { ticker: m.ticker, title: m.title, trades: Number(m.trades), wins: Number(m.wins),
+ losses: Number(m.losses), pnl: Number(m.pnl), edge: '', url: `https://kalshi.com/markets/${st.toLowerCase()}/${et.toLowerCase()}` };
+ };
+
+ json(res, {
+ env: KALSHI_ENV,
+ real_mode: true,
+ safe_mode: safeMode,
+ balance,
+ seed_money: seedMoney,
+ net_pnl_from_seed: realizedPnl,
+ roi_pct: parseFloat(roi.toFixed(2)),
+ total_invested: Number(agg?.total_invested || 0),
+ total_returned: Number(agg?.total_returned || 0),
+ daily_budget: 1000,
+ num_portfolios: 1,
+ capital_at_risk: capitalAtRisk,
+ open_positions_cost: Number(agg?.open_count || 0),
+ daily_economics: spendRows.map(r => ({
+ day: r.day, trades: Number(r.trades), wins: Number(r.wins), losses: Number(r.losses),
+ net_pnl: Number(r.net_pnl), gross_wins: Number(r.gross_wins), gross_losses: Number(r.gross_losses),
+ capital_deployed: Number(r.capital_deployed),
+ })),
+ position_count: Number(agg?.open_count || 0),
+ open_orders: Number(agg?.open_markets || 0),
+ daily_pnl: Number(agg?.daily_pnl || 0),
+ total_pnl: realizedPnl,
+ markets_tracked: cachedActiveMarkets.length,
+ today_trades: Number(today?.trades || 0),
+ today_wins: Number(today?.wins || 0),
+ today_losses: Number(today?.losses || 0),
+ recent_fills: fills.map(f => ({
+ ticker: f.ticker, title: f.title, side: f.side,
+ count: Number(f.count), yes_price: Number(f.price_cents),
+ pnl: f.pnl_cents == null ? null : Number(f.pnl_cents), status: f.status,
+ })),
+ pnl_history: pnlHistory.map(p => Number(p.daily_pnl) / 100),
+ top_markets: byTicker.filter(m => Number(m.pnl) >= 0).slice(0, 10).map(mkMarket),
+ worst_markets: byTicker.filter(m => Number(m.pnl) < 0).slice(0, 5).map(mkMarket),
+ strategies: [],
+ top_opportunities: [],
+ });
+ } catch (err) {
+ json(res, { error: err.message }, 500);
+ }
+ },
+
// ════════════════════════════════════════
// SCAN CONFIG (User-adjustable intervals + risk params)
// ════════════════════════════════════════
diff --git a/kalshi-dash/src/pages/Dashboard.jsx b/kalshi-dash/src/pages/Dashboard.jsx
index 9d66324..56b046f 100644
--- a/kalshi-dash/src/pages/Dashboard.jsx
+++ b/kalshi-dash/src/pages/Dashboard.jsx
@@ -1266,16 +1266,27 @@ function CollapsibleSection({ title, defaultOpen = true, children }) {
export default function Dashboard() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
+ // Real-money-only toggle — persists across reloads.
+ const [realMode, setRealMode] = useState(() => localStorage.getItem('ken_real_mode') === '1');
useEffect(() => {
loadDashboard();
const i = setInterval(loadDashboard, 30000);
return () => clearInterval(i);
- }, []);
+ }, [realMode]);
+
+ function toggleReal() {
+ setRealMode(prev => {
+ const next = !prev;
+ localStorage.setItem('ken_real_mode', next ? '1' : '0');
+ return next;
+ });
+ setLoading(true);
+ }
async function loadDashboard() {
try {
- const r = await api('/dashboard');
+ const r = await api(realMode ? '/dashboard/real' : '/dashboard');
setData(r);
} catch {}
setLoading(false);
@@ -1300,7 +1311,7 @@ export default function Dashboard() {
const statCards = [
{
- label: 'Paper Balance',
+ label: realMode ? 'Real Balance' : 'Paper Balance',
rawValue: d.balance || 0,
displayValue: d.balance ? `$${(d.balance / 100).toLocaleString(undefined, { minimumFractionDigits: 2 })}` : '—',
color: 'var(--green)',
@@ -1361,9 +1372,29 @@ export default function Dashboard() {
LIVE
</span>
</div>
- <p className="section-subtitle">Ken event contracts overview</p>
+ <p className="section-subtitle">
+ {realMode ? 'Live real-money account — actual Kalshi orders only' : 'Ken event contracts overview (paper arena)'}
+ </p>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
+ {/* ── Paper ⟷ Real-money toggle ── */}
+ <button
+ onClick={toggleReal}
+ title="Toggle real-money-only view (actual Kalshi orders, not the 99-bot paper arena)"
+ className="badge"
+ style={{
+ cursor: 'pointer',
+ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 700,
+ border: `1px solid ${realMode ? 'rgba(255,92,92,0.35)' : 'var(--border)'}`,
+ background: realMode ? 'var(--red-dim)' : 'rgba(107,114,128,0.12)',
+ color: realMode ? 'var(--red)' : 'var(--text-muted)',
+ }}
+ >
+ <span style={{ width: 8, height: 8, borderRadius: '50%', display: 'inline-block',
+ background: realMode ? 'var(--red)' : 'var(--text-muted)',
+ boxShadow: realMode ? '0 0 6px var(--red)' : 'none' }} />
+ {realMode ? 'REAL MONEY' : 'PAPER ARENA'}
+ </button>
<span
className="badge"
style={d.env === 'prod'
← 2bec9fc auto-save: 2026-08-04T09:32:01 (1 files) — kalshi-dash/place
·
back to Ken
·
Ken: make real-money dashboard mode deep-linkable via ?real= aa47a3b →