// Settings page — API keys, exchange config, risk params, security, env vars. const { useState: usSet, useMemo: umSet } = React; function SettingsPage({ accent, activeExchange = "binance", setActiveExchange }) { const [tab, setTab] = usSet("exchange"); const tabs = [ { id: "exchange", label: "Exchange", icon: }, { id: "strategy", label: "Strategy", icon: }, { id: "risk", label: "Risk", icon: }, { id: "webhook", label: "Webhook", icon: }, { id: "telegram", label: "Telegram", icon: }, { id: "security", label: "Security", icon: }, { id: "env", label: "Environment", icon: }, { id: "danger", label: "Danger zone", icon: }, ]; return (
{/* Tabs sidebar */}
{tab === "exchange" && } {tab === "strategy" && } {tab === "risk" && } {tab === "webhook" && } {tab === "telegram" && } {tab === "security" && } {tab === "env" && } {tab === "danger" && }
); } // ===================================================================== function SettingsCard({ title, subtitle, children, footer }) { return (
{children}
{footer && (
{footer}
)}
); } // ===================================================================== function ExchangeSettings({ activeExchange = "binance", setActiveExchange }) { const [mode, setMode] = usSet(activeExchange === "kraken" ? "paper" : "testnet"); const [showKey, setShow1] = usSet(false); const [showSec, setShow2] = usSet(false); const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; const select = (id) => { setActiveExchange && setActiveExchange(id); // Auto-select paper when switching to Kraken (no public testnet) if (id === "kraken" && mode === "testnet") setMode("paper"); if (id === "binance" && mode === "paper") setMode("testnet"); }; return (
{/* Active exchange picker — new in v3 */}
{["binance", "kraken"].map((id) => { const e = EXCHANGES[id]; const active = id === activeExchange; return ( ); })}
setMode("testnet")} label="Testnet" sub={ex.testnet ? "Paper trade with simulated funds · " + ex.name + " spot testnet" : "Not available on Kraken — use Paper instead"} tag={ex.testnet ? "Safe · default" : "Unavailable"} tagColor={ex.testnet ? "var(--profit)" : "var(--text-3)"} disabled={!ex.testnet} /> setMode("paper")} label="Paper" sub="Simulated balance + live market data. Required for Kraken; useful as a dry-run on Binance." tag="Safe · simulated" tagColor="var(--info)" /> setMode("live")} label="Live trading" sub={"Real funds on " + ex.name + " · trades execute on the production API"} tag="Risk of capital loss" tagColor="var(--loss)" />
{mode === "live" && (
Live mode is destructive. Confirm the bot has run 7+ days in {ex.testnet ? "testnet" : "paper"}, circuit breakers are armed, and Telegram alerts are working before flipping this switch. You will be prompted to type I-UNDERSTAND-THE-RISK to confirm.
)} {mode === "paper" && (
Paper-trade mode simulates fills against live order-book prices. Used as the default for Kraken because Kraken has no public testnet API. P&L, drawdown, and circuit breakers all behave identically to live — only the orders are never sent.
)}
{/* Credentials — keyed by active exchange */}
{ex.full}
CCXT id · {ex.ccxtId} · spot · native {ex.nativeSymbol}
● active
setShow1(!showKey)}>{showKey ? : }
setShow2(!showSec)}>{showSec ? : }
● Connected spot · {ex.testnet ? "testnet" : "paper"} · balance: $10,000.00 USDT } style={{ marginLeft: "auto" }}>Re-test
{ex.rateLimit} {!ex.oco && stricter — auto extra-delay on Kraken}
); } function ModeCard({ active, onClick, label, sub, tag, tagColor, disabled }) { return ( ); } function PermRow({ label, ok, required }) { const expected = required != null ? !ok : ok; const c = expected ? "var(--profit)" : "var(--loss)"; return (
{expected ? : } {label} {required || (ok ? "enabled" : "disabled")}
); } // ===================================================================== function StrategySettings({ activeExchange = "binance" }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; return (
BTC/USDT
native on {ex.name}: {ex.nativeSymbol} · normalised by CCXT
Spot
TradingView Pine v5
EMA(9/21) Cross + RSI(14) > 50 filter
}>Open script
Disabled · v2 will add fixed & trailing TP
); } function SegmentedRadio({ value, options, disabledOptions = [] }) { return (
{options.map((o) => { const active = o === value; const disabled = disabledOptions.includes(o); return ( ); })}
); } // ===================================================================== function RiskSettings({ activeExchange = "binance" }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; return (
Require manual /resume command via Telegram
); } function SliderSetting({ value, min, max, step, unit, envKey, warn }) { const pct = ((value - min) / (max - min)) * 100; const warnPct = warn != null ? ((warn - min) / (max - min)) * 100 : null; return (
{value}{unit} {envKey}
{warnPct != null && (
)}
{min}{unit} {max}{unit}
); } // ===================================================================== function WebhookSettings() { return (
{["52.89.214.238", "34.212.75.30", "54.218.53.128", "52.32.178.7"].map((ip) => (
{ip} TradingView
))}
{"{\n"} {" "}"signal": "BUY"{" "}// or "SELL"{"\n"} {" "}"symbol": "BTC/USDT"{"\n"} {" "}"price": {"{{close}}"}{"\n"} {" "}"timestamp": {"{{timenow}}"}{"\n"} {" "}"strategy": "ema_crossover"{"\n"} {" "}"signal_id": {"{{strategy.order.id}}"} // REQUIRED for dedup{"\n"} {"}"}
); } // ===================================================================== function TelegramSettings() { return (
● Connected @AutoTraderBot · API v6.9 } style={{ marginLeft: "auto" }}>Send test
); } // ===================================================================== function SecuritySettings({ activeExchange = "binance" }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; return (
● Enabled Regenerate codes
Dubai · Hetzner DC 3.2ms to Binance
185.224.92.18 ● Whitelisted on {ex.name}
● Withdraw OFF · Trade ON · audit passed
trades.db.2026-05-15.bak · 1.4 MB trades.db.2026-05-14.bak · 1.3 MB trades.db.2026-05-13.bak · 1.3 MB
); } // ===================================================================== function EnvSettings({ activeExchange = "binance" }) { const env = [ { k: "ACTIVE_EXCHANGE", v: activeExchange, req: true, desc: "binance | kraken · picks ExchangeClient at boot" }, { k: "TRADING_MODE", v: "testnet", req: true, desc: "testnet | live | paper (paper for Kraken)" }, { k: "TRADING_PAIR", v: "BTC/USDT", req: false, desc: "CCXT-normalised symbol" }, { k: "BINANCE_API_KEY", v: activeExchange === "binance" ? "••••••••" : "—", req: activeExchange === "binance", desc: "Only required when ACTIVE_EXCHANGE=binance" }, { k: "BINANCE_API_SECRET", v: activeExchange === "binance" ? "••••••••" : "—", req: activeExchange === "binance", desc: "Only required when ACTIVE_EXCHANGE=binance" }, { k: "KRAKEN_API_KEY", v: activeExchange === "kraken" ? "••••••••" : "—", req: activeExchange === "kraken", desc: "Only required when ACTIVE_EXCHANGE=kraken" }, { k: "KRAKEN_API_SECRET", v: activeExchange === "kraken" ? "••••••••" : "—", req: activeExchange === "kraken", desc: "Base64-encoded private key" }, { k: "TELEGRAM_BOT_TOKEN", v: "••••••••", req: true, desc: "From @BotFather" }, { k: "TELEGRAM_CHAT_ID", v: "487291842", req: true, desc: "Personal chat ID" }, { k: "WEBHOOK_SECRET", v: "••••••••", req: true, desc: "Validates TradingView requests" }, { k: "LOG_LEVEL", v: "INFO", req: false, desc: "DEBUG | INFO | WARNING" }, { k: "RISK_PER_TRADE_PCT", v: "1.0", req: false, desc: "Default 1.0" }, { k: "MAX_DAILY_LOSS_PCT", v: "3.0", req: false, desc: "Default 3.0" }, { k: "MAX_DRAWDOWN_PCT", v: "15.0", req: false, desc: "Default 15.0" }, { k: "STOP_LOSS_PCT", v: "2.0", req: false, desc: "Default 2.0" }, { k: "MAX_CONSECUTIVE_LOSSES", v: "5", req: false, desc: "Default 5" }, { k: "HOST", v: "0.0.0.0", req: false, desc: "FastAPI bind" }, { k: "PORT", v: "8000", req: false, desc: "FastAPI port" }, ]; return ( }>Download .env.example }>Restart bot to apply }>
{env.map((e) => ( ))}
Key Value Required Description
{e.k} {e.v} {e.req ? required : optional} {e.desc}
); } // ===================================================================== function DangerZone() { return (
} /> } /> } /> } /> } />
); } function DangerRow({ title, desc, btn, btnIcon }) { return (
{title}
{desc}
{btn}
); } Object.assign(window, { SettingsPage });