// 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 */}
{tabs.map((t) => {
const active = t.id === tab;
return (
setTab(t.id)} className="focus-ring" style={{
display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", borderRadius: 8, border: 0,
background: active ? "var(--accent-soft)" : "transparent",
color: active ? "var(--text)" : "var(--text-2)",
fontSize: 13, fontWeight: active ? 500 : 400, textAlign: "left",
transition: "background .15s",
}}
onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.04)"; }}
onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = "transparent"; }}>
{t.icon}
{t.label}
{t.id === "danger" && }
);
})}
{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 (
select(id)} className="focus-ring" style={{
padding: 16, borderRadius: 12, textAlign: "left",
background: active ? "var(--accent-soft)" : "rgba(255,255,255,0.02)",
border: "1px solid " + (active ? "rgba(0,212,170,0.4)" : "var(--line)"),
color: "var(--text)", cursor: "pointer",
display: "flex", flexDirection: "column", gap: 12,
transition: "all .15s",
}}>
{active && }
{e.name}
ACTIVE_EXCHANGE={e.id}
{e.full} · {e.quirks}
Fees {e.fees.maker.toFixed(2)}/{e.fees.taker.toFixed(2)}%
SL {e.stopLoss}
Testnet {e.testnet ? "yes" : "paper-only"}
RTT {e.rttMs}ms
);
})}
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 (
{active && }
{label}
{tag}
{sub}
);
}
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 (
{o}
);
})}
);
}
// =====================================================================
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}
{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
>
}>
Key
Value
Required
Description
{env.map((e) => (
{e.k}
{e.v}
{e.req
? required
: optional }
{e.desc}
))}
);
}
// =====================================================================
function DangerZone() {
return (
);
}
function DangerRow({ title, desc, btn, btnIcon }) {
return (
);
}
Object.assign(window, { SettingsPage });