// Operational pages: Signals, Trades, Risk, Backtester, Alerts, Logs.
const { useState: upS, useEffect: upE, useMemo: upM } = React;
// =====================================================================
// SIGNALS PAGE — TradingView webhook log
// =====================================================================
function SignalsPage({ accent }) {
const [filter, setFilter] = upS("All");
const filtered = SIGNALS.filter((s) => filter === "All" || s.status === filter.toLowerCase().replace(" ", "_"));
// Stats
const total = SIGNALS.length;
const executed = SIGNALS.filter((s) => s.status === "executed").length;
const rejected = SIGNALS.filter((s) => s.status === "rejected").length;
const dedup = SIGNALS.filter((s) => s.status === "deduplicated").length;
const stopped = SIGNALS.filter((s) => s.status === "stopped_out").length;
const avgLat = Math.round(SIGNALS.reduce((a, s) => a + s.latencyMs, 0) / SIGNALS.length);
return (
{/* KPI strip */}
{/* Funnel + latency */}
s.latencyMs)} h={140} />
milliseconds · target < 2000ms
p50: 461ms
p95: 689ms
p99: 812ms
POST https://api.helix.trade/webhook {"\n"}
X-Webhook-Secret: •••••••••• {"\n\n"}
{"{\n"}
{" "}"signal" : "BUY" ,{"\n"}
{" "}"symbol" : "BTC/USDT" ,{"\n"}
{" "}"price" : {"{{close}}"} ,{"\n"}
{" "}"signal_id" : {"{{strategy.order.id}}"} {"\n"}
{"}"}
} style={{ flex: 1, justifyContent: "center" }}>Copy URL
} style={{ flex: 1, justifyContent: "center" }}>Rotate secret
{/* Signal log table */}
{["All", "Executed", "Rejected", "Deduplicated", "Stopped out"].map((f) => (
setFilter(f)} style={{
padding: "5px 12px", borderRadius: 6, fontSize: 11, fontWeight: 500, border: 0,
background: filter === f ? "rgba(255,255,255,0.08)" : "transparent",
color: filter === f ? "var(--text)" : "var(--text-2)",
}}>{f}
))}
}
padded={false}>
Received
Signal ID
Side
Price
Status
Reason
Latency
{filtered.map((s, i) => {
const statusCfg = {
executed: { c: "var(--profit)", lbl: "Executed" },
deduplicated: { c: "var(--text-3)", lbl: "Deduplicated" },
rejected: { c: "var(--loss)", lbl: "Rejected" },
stopped_out: { c: "var(--warn)", lbl: "Stopped out" },
}[s.status];
return (
{fmtRel(s.t)}
{s.id}
{s.side}
${s.price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{statusCfg.lbl}
{s.reason || "—"}
700 ? "var(--warn)" : "var(--text-2)" }}>{s.latencyMs}ms
);
})}
);
}
function CodeBlock({ children }) {
return (
{children}
);
}
// =====================================================================
// TRADES PAGE
// =====================================================================
function TradesPage({ accent, activeExchange }) {
const [side, setSide] = upS("All");
const [status, setStatus] = upS("All");
const [exch, setExch] = upS("All");
const filtered = TRADES.filter((t) =>
(side === "All" || t.side === side) &&
(status === "All" || t.status === status.toLowerCase().replace(" ", "_")) &&
(exch === "All" || t.exchange === exch.toLowerCase())
);
const closed = TRADES.filter((t) => t.pnl != null);
const wins = closed.filter((t) => t.pnl > 0).length;
const losses = closed.filter((t) => t.pnl < 0).length;
const totalPnL = closed.reduce((a, t) => a + t.pnl, 0);
const avgWin = closed.filter((t) => t.pnl > 0).reduce((a, t) => a + t.pnl, 0) / Math.max(1, wins);
const avgLoss = closed.filter((t) => t.pnl < 0).reduce((a, t) => a + t.pnl, 0) / Math.max(1, losses);
const profitFactor = Math.abs(closed.filter((t) => t.pnl > 0).reduce((a, t) => a + t.pnl, 0) / closed.filter((t) => t.pnl < 0).reduce((a, t) => a + t.pnl, 0) || 1);
return (
{/* KPIs */}
= 0 ? "profit" : "loss"} />
{/* P&L distribution + Hourly heatmap */}
USD per trade
00:00 UTC
peak window: 13:00–17:00 (London/NY overlap)
23:00 UTC
{/* Trades table */}
}>Export CSV
}
padded={false}>
Opened
Exchange
Side
Qty
Entry
Exit
Stop
P&L
%
Closed by
Status
{filtered.map((t, i) => {
const statusBadge = {
open: { c: "var(--accent)", lbl: "open" },
closed: { c: "var(--text-2)", lbl: "closed" },
stopped_out: { c: "var(--warn)", lbl: "stopped" },
}[t.status];
return (
{fmtRel(t.t)}
{(() => {
const ex = EXCHANGES[t.exchange] || EXCHANGES.binance;
return (
{ex.name}
);
})()}
{t.side}
{t.qty.toFixed(4)}
${t.entry.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{t.exit ? "$" + t.exit.toLocaleString(undefined, { minimumFractionDigits: 2 }) : "—"}
{t.stop ? "$" + t.stop.toLocaleString(undefined, { minimumFractionDigits: 2 }) : "—"}
= 0 ? "var(--profit)" : "var(--loss)", fontWeight: 500 }}>
{t.pnl == null ? "—" : fmtUsd(t.pnl, { sign: true })}
= 0 ? "var(--profit)" : "var(--loss)" }}>{t.pnlPct == null ? "—" : fmtPct(t.pnlPct)}
{t.closedBy || "—"}
{statusBadge.lbl}
);
})}
);
}
function Select({ value, onChange, options }) {
return (
onChange(e.target.value)} className="focus-ring"
style={{
padding: "7px 28px 7px 12px", borderRadius: 8, border: "1px solid var(--line-strong)",
background: "rgba(255,255,255,0.03)", color: "var(--text)", fontSize: 12,
fontFamily: "inherit", outline: "none", appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;utf8, \")",
backgroundRepeat: "no-repeat", backgroundPosition: "right 10px center",
}}>
{options.map((o) => {o} )}
);
}
// =====================================================================
// RISK PAGE — drawdown, daily loss, circuit breaker, kill switches
// =====================================================================
function RiskPage({ accent, scenario, activeExchange = "binance" }) {
const s = SCENARIOS[scenario] || SCENARIOS.winning;
const ex = EXCHANGES[activeExchange] || EXCHANGES.binance;
const checks = [
{ name: "Max concurrent positions", value: "1 of 1", status: "ok", detail: "No pyramiding — new signals queue while open" },
{ name: "Daily loss limit", value: `−${s.dailyLoss}% / −3.0%`, status: s.dailyLoss >= 2.4 ? "warn" : "ok", detail: "Resets 00:00 UTC · trips circuit breaker" },
{ name: "Signal deduplication", value: "60s window", status: "ok", detail: "Drops same signal_id seen within window" },
{ name: "Min account balance", value: "$10,000 / $50", status: "ok", detail: "Rejects trades when below threshold" },
{ name: "Active exchange reachable", value: ex.name + " · " + ex.rttMs + "ms", status: "ok", detail: "Health check against ACTIVE_EXCHANGE before every order" },
{ name: "Pair available on " + ex.name, value: "BTC/USDT", status: "ok", detail: "Symbol catalogue cached at boot · " + ex.nativeSymbol + " native" },
{ name: "Min notional ($10)", value: "$54.30 avg", status: "ok", detail: ex.name + " minimum order value · queried at startup" },
{ name: "Max consecutive losses", value: "1 of 5", status: "ok", detail: "Auto-pauses bot when threshold hit" },
{ name: "Max drawdown (kill switch)", value: `−${s.drawdown}% / −15.0%`, status: s.drawdown >= 10 ? "warn" : "ok", detail: "Permanent halt — requires manual restart" },
{ name: "Stop-loss placement", value: ex.stopLoss, status: ex.oco ? "ok" : "warn", detail: ex.oco ? "OCO survives bot crashes" : "Conditional close — Kraken's OCO equivalent" },
];
return (
{/* Top gauges */}
−{s.drawdown.toFixed(1)}%
peak equity $11,940 · current ${s.equity.toLocaleString()}
● Armed · monitoring
No trip events in last 7 days
} style={{ flex: 1, justifyContent: "center" }}>Manual halt
} style={{ flex: 1, justifyContent: "center" }}>Reset
{/* Pre-trade checks table */}
{checks.map((c) => {
const cfg = { ok: { c: "var(--profit)", icon:
}, warn: { c: "var(--warn)", icon:
}, err: { c: "var(--loss)", icon:
} }[c.status];
return (
{cfg.icon}
{c.name}
{c.value}
{c.detail}
);
})}
{/* Position sizing */}
Formula
position_size = ( account_balance × risk_pct) {"\n"}
{" "}÷ stop_loss_distance_pct{"\n\n"}
# Current values {"\n"}
account_balance = 10000.00 {"\n"}
risk_pct = 0.01 # 1% {"\n"}
stop_loss_pct = 0.02 # 2% {"\n"}
position_size = $5000.00 # ~0.078 BTC
Risk per trade
At 1% risk and 2% stop, you can lose 50 consecutive trades before hitting the 15% drawdown kill switch. Mathematically robust for noise-driven false signals.
);
}
function DailyLossMeter({ used, cap }) {
const pct = Math.min(1, used / cap);
const color = pct < 0.5 ? "var(--profit)" : pct < 0.8 ? "var(--warn)" : "var(--loss)";
return (
{[0.25, 0.5, 0.75].map((t) => (
))}
0%
1.5%
3.0% halt
);
}
function TripRow({ label, armed, count }) {
return (
{label}
{count && {count} }
);
}
function RiskBar({ label, value, cap, unit, color, reverse }) {
const pct = reverse ? Math.min(1, cap / value) : Math.min(1, value / cap);
return (
{label}
{unit === "$" ? unit : ""}{value}{unit !== "$" ? unit : ""} / {unit === "$" ? unit : ""}{cap}{unit !== "$" ? unit : ""} {reverse ? "min" : "max"}
);
}
// =====================================================================
// BACKTESTER (v2 feature — preview)
// =====================================================================
function BacktesterPage({ accent }) {
const equity = upM(() => generateSeries(180, { seed: 5, start: 10000, drift: 0.0024, vol: 0.014 }), []);
const benchmark = upM(() => generateSeries(180, { seed: 99, start: 10000, drift: 0.0010, vol: 0.018 }), []);
return (
v2 PREVIEW
Backtesting is on the v2 roadmap. This is a preview of what historical strategy validation will look like.
{/* Config strip */}
{/* KPI strip */}
{/* Equity */}
"$" + (v / 1000).toFixed(1) + "k"} />
{/* Monthly + drawdown */}
} style={{ marginTop: 8, justifyContent: "center" }}>Run backtest
);
}
function ConfigPill({ label, value }) {
return (
);
}
function ParamSlider({ label, value, min, max, unit }) {
const pct = ((value - min) / (max - min)) * 100;
return (
);
}
// =====================================================================
// ALERTS / TELEGRAM PAGE
// =====================================================================
function AlertsPage({ accent }) {
const events = [
{ evt: "trade_opened", enabled: true, preview: "🟢 OPENED: LONG BTC/USDT @ 64210.50 | Size: 0.0084 | SL: 62926.30" },
{ evt: "trade_closed", enabled: true, preview: "🔴 CLOSED: BTC/USDT @ 64055.40 | P&L: +$28.97 (+0.55%)" },
{ evt: "stop_loss_triggered",enabled: true, preview: "⛔ STOP LOSS: BTC/USDT @ 63812.50 | Loss: −$101.82" },
{ evt: "daily_limit_hit", enabled: true, preview: "🚨 DAILY LOSS LIMIT HIT: −3.02% | Trading halted" },
{ evt: "signal_rejected", enabled: true, preview: "⚠️ SIGNAL REJECTED: Position already open" },
{ evt: "error", enabled: true, preview: "❌ ERROR: ccxt.NetworkError — connection reset" },
{ evt: "daily_summary", enabled: true, preview: "📊 DAILY SUMMARY: Trades: 7 | P&L: +$132.18 | Win rate: 86%" },
{ evt: "bot_startup", enabled: true, preview: "🤖 BOT STARTED: Mode: TESTNET | Exchange: Binance | Balance: $10,000.00" },
];
const cmds = [
{ cmd: "/status", desc: "Bot state, open positions, daily P&L" },
{ cmd: "/balance", desc: "Current account balance from exchange" },
{ cmd: "/today", desc: "Today's trade summary" },
{ cmd: "/stop", desc: "Emergency halt — closes positions, stops trading" },
{ cmd: "/resume", desc: "Resume trading after manual halt" },
];
return (
{/* Connection status */}
● Connected}>
}>Send test message
}>Reconnect
} style={{ marginLeft: "auto" }}>Open chat
{/* Events */}
Only the configured TELEGRAM_CHAT_ID can execute commands. All others are silently ignored.
{/* Daily summary preview */}
AT
Auto-Trader Bot
bot · last seen now
📊 DAILY SUMMARY
2026-05-15 · UTC
Trades: 7
Wins: 6 · Losses: 1
Win rate: 86%
Net P&L: +$132.18 (+1.13%)
Max drawdown: −0.42%
);
}
function Field2({ label, value, mono }) {
return (
);
}
// =====================================================================
// LOGS PAGE — structured JSON viewer
// =====================================================================
function LogsPage({ accent }) {
const [level, setLevel] = upS("All");
const [type, setType] = upS("All");
const types = [...new Set(LOGS.map((l) => l.type))];
const filtered = LOGS.filter((l) =>
(level === "All" || l.level === level.toUpperCase()) &&
(type === "All" || l.type === type)
);
// Mock hourly volume data
const vol = upM(() => Array.from({ length: 24 }, () => Math.floor(Math.random() * 80) + 20), []);
const counts = {
INFO: LOGS.filter((l) => l.level === "INFO").length,
DEBUG: LOGS.filter((l) => l.level === "DEBUG").length,
WARN: LOGS.filter((l) => l.level === "WARN").length,
ERROR: LOGS.filter((l) => l.level === "ERROR").length,
};
return (
{/* Stats + volume */}
{/* Filters */}
}>Download log
}
padded={false}>
{filtered.map((l, i) => )}
);
}
function LogLine({ log }) {
const [open, setOpen] = upS(false);
const levelCfg = {
INFO: { c: "#60A5FA", bg: "rgba(96,165,250,0.10)" },
DEBUG: { c: "var(--text-3)", bg: "rgba(255,255,255,0.03)" },
WARN: { c: "var(--warn)", bg: "rgba(245,166,35,0.10)" },
ERROR: { c: "var(--loss)", bg: "rgba(242,63,92,0.10)" },
}[log.level];
const t = fmtTime(log.t);
return (
setOpen(!open)} style={{ padding: "6px 24px", display: "flex", gap: 12, cursor: "pointer", borderLeft: "2px solid " + levelCfg.c, lineHeight: 1.5 }}>
{t}
{log.level}
{log.type}
{log.msg}
{open && Object.keys(log.ctx).length > 0 && (
{Object.entries(log.ctx).map(([k, v]) => (
{k}: {JSON.stringify(v)}
))}
)}
);
}
// =====================================================================
// ROADMAP / BUILD PAGE — project task board, v1/v2/v3 timeline, changelog
// =====================================================================
function RoadmapPage({ accent }) {
const total = TASKS.length;
const done = TASKS.filter((t) => t.status === "done").length;
const wip = TASKS.filter((t) => t.status === "in-progress").length;
const pend = TASKS.filter((t) => t.status === "pending").length;
const pct = Math.round((done / total) * 100);
return (
{/* Spec banner */}
spec v3.0 · MVP V1.1 · Dubai
aminbassam/Auto-Trader · last updated 2026-05-15 · {total} tasks across {TASK_CATEGORIES.length} categories
}>Open repo
}>Download spec.json
{/* KPI strip with progress card */}
{/* Task board grouped by category */}
{TASK_CATEGORIES.map((cat) => {
const tasks = TASKS.filter((t) => t.cat === cat.id);
if (!tasks.length) return null;
const catDone = tasks.filter((t) => t.status === "done").length;
return (
{cat.label}
{catDone} / {tasks.length}
{tasks.map((t) => )}
);
})}
{/* Roadmap timeline v1/v2/v3 */}
{ROADMAP.map((r) => )}
{/* Changelog */}
{CHANGELOG.map((c, i) => (
{c.version}
{c.date}
{c.agent}
{c.summary}
{c.highlights.map((h, j) => (
·
{h}
))}
))}
);
}
function CategoryProgressStack() {
const total = TASKS.length;
return (
{TASK_CATEGORIES.map((cat) => {
const done = TASKS.filter((t) => t.cat === cat.id && t.status === "done").length;
const w = (done / total) * 100;
if (w === 0) return null;
return
;
})}
{TASK_CATEGORIES.map((cat) => {
const tasks = TASKS.filter((t) => t.cat === cat.id);
if (!tasks.length) return null;
const done = tasks.filter((t) => t.status === "done").length;
return (
{cat.label} {done}/{tasks.length}
);
})}
);
}
function TaskCard({ task, color }) {
const statusCfg = {
"done": { c: "var(--profit)", bg: "rgba(25,229,166,0.06)", lbl: "done", icon: },
"in-progress": { c: "var(--accent)", bg: "rgba(0,212,170,0.08)", lbl: "in flight", icon: },
"pending": { c: "var(--text-3)", bg: "rgba(255,255,255,0.025)", lbl: "pending", icon: },
}[task.status];
return (
{statusCfg.icon}
{task.id}
{statusCfg.lbl}
{task.task}
);
}
function RoadmapColumn({ phase }) {
const accentColor = phase.status === "in-progress" ? "var(--accent)" : phase.status === "planned" ? "#60A5FA" : "#A78BFA";
return (
{phase.v.toUpperCase()}
{phase.name}
{phase.timeline}
{phase.current &&
● NOW }
{phase.features.map((f, i) => (
·
{f}
))}
);
}
Object.assign(window, {
SignalsPage, TradesPage, RiskPage, BacktesterPage, AlertsPage, LogsPage, RoadmapPage,
});