/* global React, AURA_DATA, Icon, Spark, EquityChart */ const D2 = window.AURA_DATA; const { useState, useEffect, useMemo } = React; function Dashboard({ state }) { const { botStatus, activeAccount, openTrades, balance, equity, dayPnL } = state; const [perf, setPerf] = useState(null); const [signals, setSignals] = useState([]); const [sigQuality, setSigQuality] = useState(null); const [confirmStart, setConfirmStart] = useState(false); const [confirmStop, setConfirmStop] = useState(false); const [toast, setToast] = useState(null); const [actionLoading, setActionLoading] = useState(false); const botActive = botStatus ? botStatus.bot_active : false; const accountId = activeAccount ? activeAccount.id : null; useEffect(() => { if (!accountId) return; window.api.performance(accountId, 60).then(setPerf).catch(() => {}); window.api.signals(accountId, 20).then(setSignals).catch(() => {}); window.api.signalQuality(accountId, 60).then(setSigQuality).catch(() => {}); // Poll signals + bot status every 30 s so the feed stays live const poll = setInterval(() => { window.api.signals(accountId, 20).then(setSignals).catch(() => {}); window.api.botStatus(accountId).then((s) => { if (s) state.setBotStatus(s); }).catch(() => {}); }, 30000); return () => clearInterval(poll); }, [accountId]); const equityCurve = useMemo(() => D2.generateEquityCurve(perf ? perf.equity_curve : []), [perf]); const pnlSpark = useMemo(() => D2.generateSpark(perf ? perf.equity_curve : [], 24), [perf]); const showToast = (msg, type = "success") => { setToast({ msg, type }); setTimeout(() => setToast(null), 2400); }; const startBot = async () => { if (!accountId) return; setActionLoading(true); try { await window.api.startBot(accountId); showToast("Bot started — scanning markets"); setConfirmStart(false); const s = await window.api.botStatus(accountId); if (s) state.setBotStatus(s); } catch (err) { showToast(err.message || "Failed to start bot", "error"); } finally { setActionLoading(false); } }; const stopBot = async () => { if (!accountId) return; setActionLoading(true); try { await window.api.stopBot(accountId); showToast("Stop signal sent — open positions held", "error"); setConfirmStop(false); const s = await window.api.botStatus(accountId); if (s) state.setBotStatus(s); } catch (err) { showToast(err.message || "Failed to stop bot", "error"); } finally { setActionLoading(false); } }; const lastCycle = botStatus && botStatus.last_heartbeat ? Math.round((Date.now() - D2.parseUTC(botStatus.last_heartbeat).getTime()) / 1000) : null; // bot_active can be stale in DB if the worker crashed without calling the finish endpoint const botStale = botStatus && botStatus.bot_active && lastCycle != null && lastCycle > 180; return ( <>

Dashboard

{activeAccount ? `Live · ${activeAccount.nickname || activeAccount.login} · ${activeAccount.server}` : "No account selected"}
{Icon.alert} Trading involves substantial risk of loss. Past performance does not guarantee future results.
{/* Stale bot warning */} {botStale && (
{Icon.alert} Bot worker appears offline — last heartbeat was {Math.round(lastCycle / 60)} min ago. Click Start Trading to restart it, or check the AURA-Worker service on the VPS.
)} {/* Bot control */}
Bot Status
{botActive && !botStale ? "ACTIVE" : botStale ? "STALE" : "IDLE"}v1.0
{activeAccount ? `Account #${activeAccount.login} · ${activeAccount.leverage || "1:?"}` : "No account"}
Strategy
Gold Momentum · EMA 8/21 · M15
Spread Gate
{botStatus ? `${botStatus.spread != null ? botStatus.spread.toFixed(1) : "—"} pips` : "—"}
Last Cycle
180 ? "var(--red)" : "var(--text-2)" }}> {lastCycle != null ? `● ${lastCycle < 60 ? lastCycle + "s" : Math.round(lastCycle/60) + "m"} ago` : botActive ? "● starting…" : "—"}
{!botActive ? ( ) : ( )}
{/* Stats */}
Account Balance {Icon.dollar}
${balance != null ? balance.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : "—"}
Equity ${equity != null ? equity.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : "—"}
= 0 ? "pos" : "neg"}`}>
Today's P&L {Icon.activity}
= 0 ? "pos" : "neg"}`}>{D2.fmtSigned(dayPnL)}
{openTrades.length} open · Unrealized
Win Rate · 30D {Icon.target}
{perf ? perf.win_rate.toFixed(1) : "—"}%
PF {perf ? perf.profit_factor.toFixed(2) : "—"} {perf ? perf.total_trades : "—"} trades
Open Positions {Icon.zap}
{openTrades.length}/ 5 max
Unrealized {D2.fmtSigned(openTrades.reduce((a, b) => a + (b.pnl || 0), 0))}
{/* Equity chart + Strategy telemetry */}
Performance · Last 60 Days
{[ { k: "Net P&L", v: perf ? D2.fmtUSD(perf.net_pnl) : "—", c: perf && perf.net_pnl >= 0 ? "pos" : "neg" }, { k: "Max Drawdown", v: perf ? `-${perf.max_drawdown_pct.toFixed(2)}%` : "—", c: "neg" }, { k: "Avg Win", v: perf ? D2.fmtUSD(perf.avg_win) : "—", c: "pos" }, { k: "Avg Loss", v: perf ? D2.fmtUSD(-perf.avg_loss) : "—", c: "neg" }, ].map((m) => (
{m.k}
{m.v}
))}
Strategy Telemetry
live
ATR (M15){botStatus && botStatus.atr != null ? `${botStatus.atr.toFixed(2)} pts` : "—"}
Spread{botStatus && botStatus.spread != null ? `● ${botStatus.spread.toFixed(1)} pips` : "—"}
Loss Streak{botStatus ? `${botStatus.loss_streak} / 4` : "—"}
Daily P&L= 0 ? "pos" : "neg"}`}>{botStatus ? D2.fmtSigned(botStatus.daily_pnl) : "—"}
Circuit Breaker{botStatus ? (botStatus.circuit_broken ? "● TRIPPED" : "● OK") : "—"}
Cycle Latency{botStatus && botStatus.cycle_latency_ms ? `${botStatus.cycle_latency_ms}ms` : "—"}
Account Status{activeAccount ? `● ${activeAccount.status}` : "—"}
{/* Signal Quality Analysis */} {sigQuality && (
Signal Quality · Last 60 Days
{sigQuality.total_signals} signals
{Object.entries(sigQuality.action_counts || {}).map(([action, count]) => (
{action.replace(/_/g, " ")}
{count}
))}
{sigQuality.by_direction && sigQuality.by_direction.length > 0 && ( <>
Trade Outcomes by Bucket
{[ ...( sigQuality.by_direction || []).map(r => ({ ...r, group: "Direction" })), ...( sigQuality.by_session || []).map(r => ({ ...r, group: "Session" })), ...( sigQuality.by_rsi_bucket || []).map(r => ({ ...r, group: "RSI" })), ...( sigQuality.by_spread_bucket || []).map(r => ({ ...r, group: "Spread" })), ].map((row, i) => ( ))}
BucketGroupTradesWin %PFAvg P&LNet P&L
{row.bucket} {row.group} {row.count} = 50 ? "pos" : "neg"}`}>{row.win_rate.toFixed(1)}% = 1 ? "pos" : "neg"}`}>{row.profit_factor.toFixed(2)} = 0 ? "pos" : "neg"}`}>{D2.fmtSigned(row.avg_pnl)} = 0 ? "pos" : "neg"}`}>{D2.fmtSigned(row.net_pnl)}
)} {(!sigQuality.by_direction || sigQuality.by_direction.length === 0) && (
No executed trades yet — analysis will appear once trades have P&L data
)}
)} {/* Open positions + Signal feed */}
Open Positions
{openTrades.length === 0 ? (
No open positions
) : ( {openTrades.map((p) => ( ))}
TicketDirEntrySL / TPVolP&LDuration
#{p.ticket} {p.direction} {p.entry_price.toFixed(2)} {p.sl.toFixed(2)} / {p.tp.toFixed(2)} {p.volume.toFixed(2)} = 0 ? "pos" : "neg"}`} style={{ fontWeight: 600 }}>{D2.fmtSigned(p.pnl || 0)} {D2.fmtDuration(p.open_time)}
)}
Recent Signals
auto-refresh 30s
{signals.length === 0 ? (
{botStale ? "Bot offline — no signals received" : "No signals yet — bot will log activity here"}
) : (() => { const interesting = signals.filter(s => s.action !== "no_signal"); const idle = signals.filter(s => s.action === "no_signal"); return ( <> {interesting.length === 0 && idle.length > 0 && (
{idle.length} idle cycles — EMA8/21 trending without crossover · last {D2.fmtDateTime(idle[0].created_at)}
)} {interesting.slice(0, 10).map((s, i) => (
{D2.fmtDateTime(s.created_at)} {s.reasoning} {s.direction || s.action}
))} {interesting.length === 0 && (
{idle.slice(0, 3).map((s, i) => (
{D2.fmtDateTime(s.created_at)} {s.reasoning}
))}
)} ); })()}
{confirmStart && (
setConfirmStart(false)}>
e.stopPropagation()}>

Start the bot?

The bot will begin scanning markets and may open XAUUSD positions per your configured strategy.

)} {confirmStop && (
setConfirmStop(false)}>
e.stopPropagation()}>

Stop the bot?

New trades will be paused. Open positions will remain — close them manually from Active Trades.

)} {toast &&
{toast.msg}
} ); } window.Dashboard = Dashboard;