// Dashboard page — single BTC/USDT bot overview. const { useState: ucS, useEffect: ucE, useMemo: ucM } = React; // =================== KPI tile =================== function Kpi({ label, value, sub, tone = "text", icon, span }) { const c = { text: "var(--text)", profit: "var(--profit)", loss: "var(--loss)", warn: "var(--warn)", accent: "var(--accent)" }[tone]; return (
{label}
{icon &&
{icon}
}
{value}
{sub &&
{sub}
}
); } // =================== Open position card =================== function OpenPositionCard({ accent, livePrices, activeExchange = "binance" }) { const trade = TRADES.find((t) => t.status === "open"); const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const [price, dir] = livePrices ? useTicker(64210.50, { volatility: 0.0008, intervalMs: 1500 }) : [64210.50, 0]; const pnlPct = trade ? ((price - trade.entry) / trade.entry) * 100 : 0; const pnl = trade ? (price - trade.entry) * trade.qty * 10 : 0; return (
Open position
BTC/USDT · LONG · entered {fmtRel(trade.t)} · {ex.name}
● LIVE
= 0 ? "rgba(25,229,166,0.08)" : "rgba(242,63,92,0.08)", border: "1px solid " + (pnl >= 0 ? "rgba(25,229,166,0.2)" : "rgba(242,63,92,0.2)"), display: "flex", alignItems: "center", gap: 14, }}>
Unrealized P&L
= 0 ? "var(--profit)" : "var(--loss)" }}> {fmtUsd(pnl, { sign: true })} · {fmtPct(pnlPct)}
{/* Risk ladder visualization */}
} style={{ height: 40 }}>Close at market
); } function Field({ label, value, sub, tone, accent, tickDir }) { const c = tone === "loss" ? "var(--loss)" : tone === "profit" ? "var(--profit)" : "var(--text)"; return (
{label}
0 ? "tick-up mono" : tickDir < 0 ? "tick-dn mono" : "mono"} style={{ fontSize: 18, fontWeight: 600, marginTop: 4, color: accent ? "var(--accent)" : c, padding: tickDir ? "2px 6px" : 0, margin: tickDir ? "4px -6px 0" : "4px 0 0", borderRadius: 4, transition: "background .5s" }}> {value}
{sub &&
{sub}
}
); } function RiskLadder({ entry, mark, stop }) { // Visual showing distance: stop ← entry ← mark, scaled to fit const range = entry - stop; // total risk distance const upRange = range * 1.5; // top of visual const total = range + upRange; const stopPct = (range / total) * 100; const entryPct = ((entry - stop) / total) * 100; const markPct = Math.max(0, Math.min(100, ((mark - stop) / total) * 100)); return (
{/* fill from stop to mark */}
= entry ? "linear-gradient(180deg, var(--profit), rgba(25,229,166,0.2))" : "linear-gradient(180deg, var(--warn), rgba(245,166,35,0.2))", borderRadius: 6, transition: "top .4s", }} /> {/* entry tick */}
{/* mark dot */}
); } // =================== Equity + daily P&L composite =================== function EquityAndDailyCard({ accent, scenario }) { const [range, setRange] = ucS("1M"); const points = { "1D": 24, "1W": 60, "1M": 90, "3M": 120, "1Y": 180, "All": 240 }[range]; const driftMap = { winning: 0.0024, losing: -0.0015, volatile: 0.0008 }; const equity = ucM(() => generateSeries(points, { seed: 11 + range.length * 7, start: 10000, drift: driftMap[scenario] || 0.002, vol: scenario === "volatile" ? 0.018 : 0.010, }), [points, range, scenario]); const benchmark = ucM(() => generateSeries(points, { seed: 99 + range.length, start: 10000, drift: 0.0008, vol: 0.014, }), [points, range]); const totalRet = ((equity[equity.length-1] - equity[0]) / equity[0]) * 100; const benchRet = ((benchmark[benchmark.length-1] - benchmark[0]) / benchmark[0]) * 100; return (
Equity curve
Bot equity BTC HODL
{["1D","1W","1M","3M","1Y","All"].map((r) => ( ))}
"$" + (v / 1000).toFixed(1) + "k"} />
= 0 ? "profit" : "loss"} /> = 0 ? "profit" : "loss"} /> = 0 ? "profit" : "loss"} />
); } function SummaryStat({ label, value, delta }) { const color = delta === "profit" ? "var(--profit)" : delta === "loss" ? "var(--loss)" : "var(--text)"; return (
{label}
{value}
); } // =================== Daily P&L bars + key stats =================== function DailyPnLCard({ accent }) { return ( 7-day net: +$612.40}> ); } // =================== Signal feed (live) =================== function SignalFeedCard({ accent }) { return ( e.preventDefault()} style={{ color: accent.base, fontSize: 12, fontWeight: 500, textDecoration: "none" }}>Open log →} padded={false}>
{SIGNALS.slice(0, 7).map((s, i) => )}
); } function SignalRow({ sig, last }) { const statusCfg = { executed: { c: "var(--profit)", lbl: "EXECUTED" }, deduplicated: { c: "var(--text-3)", lbl: "DEDUP" }, rejected: { c: "var(--loss)", lbl: "REJECTED" }, stopped_out: { c: "var(--warn)", lbl: "STOPPED" }, }[sig.status] || { c: "var(--text-3)", lbl: "—" }; return (
{fmtRel(sig.t)}
{sig.side}
BTC/USDT @ ${sig.price.toLocaleString()}
{sig.id} · {sig.reason || "ok"}
● {statusCfg.lbl}
{sig.latencyMs}ms
); } // =================== Bot config sidebar card =================== function BotConfigCard({ accent, activeExchange = "binance", onSwap }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const rows = [ { l: "Pair", v: "BTC/USDT" }, { l: "Market", v: "SPOT" }, { l: "Timeframe", v: "5m" }, { l: "Strategy", v: "EMA(9/21) + RSI(14)" }, { l: "Signal src", v: "TradingView · Pine v5" }, { l: "Risk/trade", v: "1.0% equity" }, { l: "Stop loss", v: "2.0% · " + ex.stopLoss }, { l: "Taker fee", v: ex.fees.taker.toFixed(2) + "%" }, { l: "Take profit", v: "—", muted: true }, { l: "Max position", v: "1 (no pyramid)" }, ]; return ( Edit →}>
{ex.full}
ccxt:{ex.ccxtId} · {ex.nativeSymbol} · {ex.rttMs}ms RTT
{rows.map((r) => (
{r.l} {r.v}
))}
Bot is running in {ex.testnet ? "TESTNET" : "PAPER"} mode on {ex.name}. {ex.testnet ? " No real funds at risk." : " Kraken has no public testnet — paper-trade simulates against live market data."}
); } // =================== Risk overview (compact) =================== function RiskOverviewCard({ accent, scenario }) { const s = SCENARIOS[scenario] || SCENARIOS.winning; const maxDD = 15; const dailyMax = 3.0; const dailyUsed = s.dailyLoss; return (
Current drawdown {s.drawdown}% / {maxDD}% kill
Daily loss budget = dailyMax * 0.7 ? "var(--warn)" : "var(--text)", fontWeight: 600 }}>−{dailyUsed.toFixed(2)}% / −{dailyMax}% cap
= dailyMax * 0.7 ? "linear-gradient(90deg, var(--warn), var(--loss))" : "linear-gradient(90deg, var(--profit), var(--accent))", borderRadius: 999, transition: "width .5s", }} />
resets 00:00 UTC · in 11h 42m
); } function Metric({ label, value, hint }) { return (
{label}
{value}
{hint &&
{hint}
}
); } // =================== /health endpoint preview =================== function HealthEndpointCard({ activeExchange = "binance", scenario, mode = "testnet" }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const s = SCENARIOS[scenario] || SCENARIOS.winning; return ( 200 OK}>
{"{\n"}
{"  "}"status":             "ok",{"\n"}
{"  "}"uptime_seconds":     184302,{"\n"}
{"  "}"active_exchange":    "{ex.id}",{"\n"}
{"  "}"exchange_connected": true,{"\n"}
{"  "}"last_signal_time":   "2026-05-15T08:14:32Z",{"\n"}
{"  "}"open_positions":     1,{"\n"}
{"  "}"daily_pnl_pct":      = 0 ? "var(--profit)" : "var(--loss)" }}>{s.changePct.toFixed(2)},{"\n"}
{"  "}"trading_mode":       "{mode}"{"\n"}
{"}"}
      
} style={{ flex: 1, justifyContent: "center" }}>Copy curl } style={{ flex: 1, justifyContent: "center" }}>Probe now
); } // =================== Exchange comparison + picker =================== function ExchangeComparisonCard({ activeExchange = "binance", onSelect }) { const rows = [ ["CCXT id", (ex) => {ex.ccxtId}], ["Native symbol", (ex) => {ex.nativeSymbol}], ["Maker / Taker", (ex) => {ex.fees.maker.toFixed(2)}% / {ex.fees.taker.toFixed(2)}%], ["Rate limit", (ex) => {ex.rateLimit}], ["Stop loss", (ex) => {ex.stopLoss}], ["Testnet", (ex) => ex.testnet ? ● available : paper-trade only], ["Round-trip", (ex) => {ex.rttMs}ms], ]; return (
{["binance", "kraken"].map((k) => { const ex = EXCHANGES[k]; const isActive = k === activeExchange; return ( ); })} {rows.map(([label, render], i) => (
{label}
{["binance", "kraken"].map((k) => { const ex = EXCHANGES[k]; const isActive = k === activeExchange; return (
{render(ex)}
); })}
))}
); } // =================== System health card =================== function SystemHealthCard({ activeExchange = "binance" }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const services = [ { name: "FastAPI", status: "ok", val: "200 OK", sub: "8000/health · 12ms" }, { name: ex.name + " API", status: "ok", val: "Connected", sub: "spot · " + (ex.testnet ? "testnet" : "paper") + " · ccxt:" + ex.ccxtId }, { name: "TradingView", status: "ok", val: "Listening", sub: "/webhook · 10/min limit" }, { name: "Telegram", status: "ok", val: "Bot active", sub: "@AutoTraderBot" }, { name: "SQLite", status: "ok", val: "WAL mode", sub: "trades.db · 1.4 MB" }, ]; return (
{services.map((s) => (
{s.name}
{s.sub}
{s.val}
))}
); } // =================== Dashboard page composition =================== function DashboardPage({ scenario, accent, livePrices, activeExchange = "binance", setActiveExchange, mode = "testnet" }) { const s = SCENARIOS[scenario] || SCENARIOS.winning; const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const isUp = s.change24h >= 0; const [equity, dir] = livePrices ? useTicker(s.equity, { volatility: 0.0004, intervalMs: 2000, bias: isUp ? 0.0002 : -0.0002 }) : [s.equity, 0]; const animEquity = useCountUp(equity, 600); const modeTone = mode === "live" ? "loss" : mode === "paper" ? "accent" : "warn"; return (
{/* KPI strip */}
} /> } /> } /> } /> } />
{/* Open position */} {/* Equity + side col */}
{/* Daily PnL bars + signal feed */}
{/* Exchange routing + health endpoint (new in v3) */}
{/* Bot config + system health */}
setActiveExchange && setActiveExchange(activeExchange === "binance" ? "kraken" : "binance")} />
); } Object.assign(window, { DashboardPage, Kpi, OpenPositionCard, EquityAndDailyCard, DailyPnLCard, SignalFeedCard, BotConfigCard, RiskOverviewCard, SystemHealthCard, Metric, HealthEndpointCard, ExchangeComparisonCard, });