// Documentation page — comprehensive reference sourced from spec v3.0 const { useState: udS, useEffect: udE, useRef: udR } = React; const DOCS_SECTIONS = [ { id: "overview", label: "Overview", icon: "01" }, { id: "architecture", label: "Architecture", icon: "02" }, { id: "flow", label: "Trading flow", icon: "03" }, { id: "strategy", label: "Strategy & signals", icon: "04" }, { id: "webhook", label: "Webhook payload", icon: "05" }, { id: "execution", label: "Order execution", icon: "06" }, { id: "exchanges", label: "Exchanges", icon: "07" }, { id: "risk", label: "Risk management", icon: "08" }, { id: "alerts", label: "Telegram alerts", icon: "09" }, { id: "api", label: "API endpoints", icon: "10" }, { id: "schema", label: "Database schema", icon: "11" }, { id: "env", label: "Environment vars", icon: "12" }, { id: "stack", label: "Tech stack", icon: "13" }, { id: "structure", label: "Folder structure", icon: "14" }, { id: "security", label: "Security", icon: "15" }, { id: "recovery", label: "State recovery", icon: "16" }, { id: "setup", label: "Setup & deploy", icon: "17" }, ]; function DocsPage({ accent, activeExchange = "binance" }) { const [active, setActive] = udS("overview"); // Scroll-spy via IntersectionObserver udE(() => { const els = DOCS_SECTIONS.map((s) => document.getElementById("docs-" + s.id)).filter(Boolean); if (!els.length) return; const obs = new IntersectionObserver((entries) => { const top = entries .filter((e) => e.isIntersecting) .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0]; if (top) setActive(top.target.id.replace("docs-", "")); }, { rootMargin: "-120px 0px -60% 0px" }); els.forEach((el) => obs.observe(el)); return () => obs.disconnect(); }, []); const goto = (id) => { const el = document.getElementById("docs-" + id); if (el) window.scrollTo({ top: el.offsetTop - 100, behavior: "smooth" }); setActive(id); }; return (
{/* Sticky TOC */} {/* Content */}
); } // ========================================================= // Helpers // ========================================================= function DocSection({ id, eyebrow, title, lede, children }) { return (
{eyebrow}

{title}

{lede &&

{lede}

}
{children}
); } function Prose({ children }) { return

{children}

; } function Callout({ kind = "info", title, children }) { const cfg = { info: { bg: "rgba(96,165,250,0.07)", bd: "rgba(96,165,250,0.25)", c: "#93C5FD", Icon: IconAlert }, warn: { bg: "rgba(245,166,35,0.08)", bd: "rgba(245,166,35,0.25)", c: "#FBBF24", Icon: IconAlert }, danger: { bg: "rgba(242,63,92,0.08)", bd: "rgba(242,63,92,0.25)", c: "#FCA5A5", Icon: IconAlert }, success: { bg: "rgba(25,229,166,0.07)", bd: "rgba(25,229,166,0.25)", c: "var(--profit)", Icon: IconCheck }, }[kind]; return (
{title &&
{title}
}
{children}
); } function CodeBox({ lang, children }) { return (
{lang && (
{lang}
)}
{children}
); } function DefList({ items }) { return (
{items.map((it, i) => (
{it.k}
{it.v}
))}
); } // ========================================================= // Hero // ========================================================= function DocsHero() { return (
Documentation · v3.0 spec · MVP V1.1

Auto-Trader — reference

Production-ready automated crypto trading bot. Receives signals from TradingView via webhook, validates through risk management, executes on Binance or Kraken (one active per deploy), and reports via Telegram.

FastAPI · Python 3.11+ CCXT async SQLite WAL Docker Testnet default Dubai region
); } // ========================================================= // Sections // ========================================================= function DocOverview() { return (
AI Crypto Trading Platform }, { k: "Repository", v: aminbassam/Auto-Trader }, { k: "Deployment", v: "Dubai · Hetzner / DigitalOcean / Vultr" }, { k: "Exchanges", v: "Binance · Kraken (single-active per deploy)" }, { k: "Pair", v: BTC/USDT }, { k: "Timeframe", v: "5-minute candles" }, { k: "Target uptime", v: "99.9%" }, { k: "Signal→fill", v: "< 2s" }, ]} />
); } function PrincipleCard({ n, t, d }) { return (
{n}
{t}
{d}
); } function DocArchitecture() { const layers = [ { name: "TradingView", role: "Strategy + indicator calculation (Pine v5)", color: "#2962FF" }, { name: "Webhook (FastAPI)", role: "POST /webhook · secret validation · 10/min rate-limit", color: "#60A5FA" }, { name: "Signal handler", role: "Pydantic parse · 60s dedup by signal_id", color: "#A78BFA" }, { name: "Risk manager", role: "Pre-trade VETO · position sizing · circuit breaker", color: "var(--accent)" }, { name: "Exchange factory", role: "Reads ACTIVE_EXCHANGE · returns BinanceClient or KrakenClient", color: "var(--profit)" }, { name: "CCXT async", role: "Spot orders + exchange-side stop loss · retry x3", color: "#F472B6" }, { name: "SQLite + Telegram", role: "Trade log (WAL) · alerts + /commands", color: "var(--warn)" }, ]; return (
{layers.map((l, i) => (
{String(i + 1).padStart(2, "0")}
{l.name}
{l.role}
{i < layers.length - 1 && (
)}
))}
{`TradingView Pine Script │ (alert fires) ▼ HTTPS POST → FastAPI /webhook [X-Webhook-Secret header] │ (Pydantic parse) ▼ SignalHandler.dedup(signal_id, 60s window) │ ▼ RiskManager.validate(signal) [7 pre-trade checks] │ (pass) ▼ ExchangeFactory.get_client() [reads ACTIVE_EXCHANGE] │ ▼ ExchangeClient.place_market_order(symbol, side, qty) ExchangeClient.place_stop_loss() [OCO or conditional close] │ ▼ SQLite trade log + Telegram notify`}
); } function DocFlow() { return (
  1. Webhook arrives. POST /webhook with X-Webhook-Secret. Rate-limited at 10/min.
  2. Payload validates. Pydantic checks signal, symbol, price, signal_id. Bad payload → 422.
  3. Deduplication. If same signal_id has been seen in the last 60s, drop silently and log signal_deduplicated.
  4. Pre-trade risk checks. Seven gates — see Risk management.
  5. Position sizing. size = (balance × risk_pct) ÷ stop_pct. Below min notional → reject.
  6. Exchange dispatch. Factory returns BinanceClient or KrakenClient based on ACTIVE_EXCHANGE.
  7. Market order placed. With retry x3 (exponential backoff) on transient errors.
  8. Stop loss attached. OCO (Binance) or conditional close (Kraken). Exchange-side, survives crash.
  9. Persist + notify. SQLite WAL write, Telegram message, daily P&L counter incremented.
Max concurrent positions is locked at 1 in MVP. Additional signals are rejected with reason position_already_open while a trade is live. Pyramiding ships in v2.
); } function DocStrategy() { return (
BUY signal
EMA 9 crosses above EMA 21
AND
RSI(14) > 50
action: open LONG
SELL signal
EMA 9 crosses below EMA 21
AND
RSI(14) < 50
action: close position
5m }, { k: "Pair", v: BTC/USDT }, { k: "Indicators", v: "EMA(9), EMA(21), RSI(14)" }, { k: "Take profit", v: Deferred for MVP — exit via reverse signal or stop loss only. v2 }, ]} />
); } function DocWebhook() { return ( {`{ "signal": "BUY", "symbol": "BTC/USDT", "price": {{close}}, "timestamp": {{timenow}}, "strategy": "ema_crossover", "signal_id": {{strategy.order.id}} }`} "BUY" or "SELL" }, { k: "symbol", v: CCXT-normalised, e.g. "BTC/USDT" }, { k: "price", v: "Numeric. Used for logging/audit; bot uses live exchange price for sizing." }, { k: "timestamp", v: "TradingView server time at alert fire." }, { k: "strategy", v: "Free-form identifier — logged for analytics." }, { k: "signal_id", v: REQUIRED for dedup. Must be unique per signal — bot drops same-id within 60s. }, ]} /> Every request must include X-Webhook-Secret: $WEBHOOK_SECRET. Missing or wrong → 401. Rotate the secret periodically. ); } function DocExecution({ activeExchange }) { const ex = EXCHANGES[activeExchange] || EXCHANGES.binance; return ( {`position_size = (account_balance × risk_pct) ÷ stop_loss_distance_pct # Example with $10,000 equity, 1% risk, 2% stop: position_size = (10000 × 0.01) ÷ 0.02 = $5,000 notional ≈ 0.078 BTC at $64,210 # Risk per trade (USD) $ at risk = account_balance × risk_pct = $100`} {ex.oco ? <>Binance supports OCO orders natively. The stop-loss leg is placed exchange-side at order entry — it survives bot crashes, restarts, or network loss. : <>Kraken has no OCO. The bot attaches a conditional close order to the opening order via the close parameter — Kraken's native equivalent. Same survives-crash guarantee.} $10 ({ex.name}) — queried at startup, cached }, { k: "Retry policy", v: "3 retries · exponential backoff on transient errors" }, ]} /> ); } function DocExchanges() { return ( {`class AbstractExchangeClient(ABC): @abstractmethod async def connect(): ... @abstractmethod async def disconnect(): ... @abstractmethod async def get_balance(): ... @abstractmethod async def get_ticker(symbol): ... @abstractmethod async def place_market_order(symbol, side, qty): ... @abstractmethod async def place_oco_order(symbol, side, qty, stop_price): ... @abstractmethod async def get_open_positions(): ... @abstractmethod async def cancel_order(order_id): ... class BinanceClient(AbstractExchangeClient): # spot · OCO · testnet class KrakenClient(AbstractExchangeClient): # spot · conditional close · paper`}
{["binance", "kraken"].map((k) => { const ex = EXCHANGES[k]; return (
{ex.name}
{ex.full}
{ex.ccxtId} }, { k: "Native symbol", v: {ex.nativeSymbol} }, { k: "Maker fee", v: {ex.fees.maker.toFixed(2)}% }, { k: "Taker fee", v: {ex.fees.taker.toFixed(2)}% }, { k: "Rate limit", v: {ex.rateLimit} }, { k: "Stop loss", v: ex.stopLoss + " — " + ex.stopLossNote }, { k: "Testnet", v: ex.testnet ? "Available · " + ex.testnetUrl : "None — use TRADING_MODE=paper" }, { k: "Quirk", v: ex.quirks }, ]} />
); })}
Kraken has no public testnet — the bot ships with a paper mode that simulates fills against live order-book prices. Kraken uses XBT for Bitcoin internally; CCXT normalises everything to BTC. Auth is nonce-based (CCXT handles it). Rate limit is much stricter than Binance — CCXT's enableRateLimit is on by default, with extra delay between calls.
); } function DocRisk() { const checks = [ { n: 1, t: "Max concurrent positions", d: "Reject if a position is already open (MVP cap = 1)." }, { n: 2, t: "Daily loss limit", d: "Reject if cumulative P&L since 00:00 UTC ≤ −3%. Trips circuit breaker." }, { n: 3, t: "Duplicate signal", d: "Reject if signal_id seen in last 60s." }, { n: 4, t: "Min account balance", d: "Reject if balance < $50." }, { n: 5, t: "Active exchange reachable", d: "Health-check ACTIVE_EXCHANGE before every order." }, { n: 6, t: "Min notional ($10)", d: "Position size below exchange minimum → reject. Varies per venue." }, { n: 7, t: "Pair available", d: "Reject if symbol not in active exchange's catalogue." }, ]; return (
{checks.map((c) => (
{c.n}
{c.t}
{c.d}
))}
15.0% — permanent halt, requires manual restart }, { k: "Max consecutive losses", v: "5 → auto-pause" }, { k: "Auto-resume", v: OFF — require manual /resume via Telegram }, ]} /> At 1% risk per trade and a 2% stop, you can lose ~50 consecutive trades before hitting the 15% drawdown kill switch. Robust against noise-driven false signals.
); } function DocAlerts() { const events = [ ["trade_opened", "🟢 OPENED: {side} {symbol} @ {price} | Size: {size} | SL: {stop_loss}"], ["trade_closed", "🔴 CLOSED: {symbol} @ {price} | P&L: {pnl} ({pnl_pct}%)"], ["stop_loss_triggered", "⛔ STOP LOSS: {symbol} @ {price} | Loss: {loss}"], ["daily_limit_hit", "🚨 DAILY LOSS LIMIT HIT: −{daily_loss}% | Trading halted"], ["signal_rejected", "⚠️ SIGNAL REJECTED: {reason}"], ["error", "❌ ERROR: {error_type} — {message}"], ["daily_summary", "📊 DAILY SUMMARY: Trades: {count} | P&L: {pnl} | Win rate: {win_rate}%"], ["bot_startup", "🤖 BOT STARTED: Mode: {mode} | Exchange: {exchange} | Balance: {balance}"], ]; const cmds = [ ["/status", "Bot state, open positions, daily P&L"], ["/balance", "Current account balance from exchange"], ["/today", "Today's trade summary"], ["/stop", "Emergency halt — closes positions, stops trading"], ["/resume", "Resume trading after manual halt"], ]; return (
Event templates
{events.map(([e, tmpl]) => (
{e}
{tmpl}
))}
Commands
{cmds.map(([c, d]) => (
{c} {d}
))}
Only the configured TELEGRAM_CHAT_ID can execute commands. All others are silently ignored.
Daily summary scheduled at 23:59 UTC.
); } function DocAPI() { return (
{`{ "status": "ok", // ok | degraded | halted "uptime_seconds": 184302, "active_exchange": "binance", // binance | kraken "exchange_connected": true, "last_signal_time": "2026-05-15T08:14:32Z", "open_positions": 1, "daily_pnl_pct": 1.13, "trading_mode": "testnet" // testnet | live | paper }`}
); } function EndpointRow({ method, path, auth, desc }) { const c = method === "POST" ? "var(--accent)" : "#60A5FA"; return (
{method} {path} auth: {auth}
{desc}
); } function DocSchema() { return ( ); } function SchemaTable({ name, cols }) { return (
{name} {cols.length} columns
{cols.map(([k, t], i) => ( ))}
{k} {t}
); } function DocEnv() { const groups = [ { title: "Required core", items: [ ["ACTIVE_EXCHANGE", "binance | kraken — picks ExchangeClient at boot"], ["TRADING_MODE", "testnet | live | paper — paper required for Kraken"], ["TELEGRAM_BOT_TOKEN", "From @BotFather"], ["TELEGRAM_CHAT_ID", "Your personal chat ID"], ["WEBHOOK_SECRET", "Random string passed in X-Webhook-Secret"], ], }, { title: "Exchange credentials (only the active one is read)", items: [ ["BINANCE_API_KEY", "Required when ACTIVE_EXCHANGE=binance"], ["BINANCE_API_SECRET", "Treat as password"], ["KRAKEN_API_KEY", "Required when ACTIVE_EXCHANGE=kraken"], ["KRAKEN_API_SECRET", "Base64-encoded private key"], ], }, { title: "Optional tuning (defaults applied)", items: [ ["TRADING_PAIR", "default: BTC/USDT"], ["LOG_LEVEL", "default: INFO"], ["RISK_PER_TRADE_PCT", "default: 1.0"], ["MAX_DAILY_LOSS_PCT", "default: 3.0"], ["MAX_DRAWDOWN_PCT", "default: 15.0"], ["STOP_LOSS_PCT", "default: 2.0"], ["MAX_CONSECUTIVE_LOSSES","default: 5"], ["HOST", "default: 0.0.0.0"], ["PORT", "default: 8000"], ], }, ]; return ( {groups.map((g) => (
{g.title}
{g.items.map(([k, d], i) => ( ))}
{k} {d}
))}
); } function DocStack() { const stack = [ ["Language", "Python 3.11+"], ["Framework", "FastAPI"], ["Async server", "Uvicorn"], ["Exchange library", "ccxt (async mode)"], ["Database", "SQLite with WAL mode"], ["ORM", "SQLAlchemy 2.0 (async)"], ["Validation", "Pydantic v2"], ["Config", "pydantic-settings (.env loader)"], ["Alerts", "python-telegram-bot (async)"], ["Rate limiting", "slowapi"], ["Deployment", "Docker · docker-compose"], ["Hosting", "VPS (DigitalOcean, Hetzner, or Vultr) · Frankfurt or Dubai"], ["Charts / signals", "TradingView Pine Script v5"], ]; return (
{stack.map(([k, v], i) => (
1 ? "1px solid var(--line)" : 0, borderLeft: i % 2 === 1 ? "1px solid var(--line)" : 0, background: i % 2 === 0 ? "rgba(255,255,255,0.012)" : "transparent", }}> {k} {v}
))}
); } function DocStructure() { return ( {`Auto-Trader/ ├── main.py — FastAPI app entry · Uvicorn runner ├── requirements.txt — Pinned deps ├── Dockerfile ├── docker-compose.yml ├── .env.example — Template (committed) ├── .env — Secrets (gitignored) ├── .gitignore ├── README.md │ ├── app/ │ ├── config.py — Pydantic settings, loads .env │ ├── models.py — Trade, Signal, DailyStats │ └── database.py — Async engine, WAL init │ ├── app/api/ │ ├── webhook.py — POST /webhook · dedup · validation │ └── health.py — GET /health │ ├── app/strategy/ │ ├── signal_handler.py — Parse · validate · deduplicate │ └── signal_models.py — Pydantic payload models │ ├── app/execution/ — ⭐ factory pattern (v3) │ ├── base_client.py — AbstractExchangeClient ABC │ ├── exchange_factory.py — Reads ACTIVE_EXCHANGE → client │ ├── binance_client.py — OCO orders · testnet support │ ├── kraken_client.py — Conditional close · paper mode │ └── order_manager.py — Exchange-agnostic dispatch │ ├── app/risk/ — ⭐ VETO power │ ├── risk_manager.py — Pre-trade checks · sizing │ └── circuit_breaker.py — Daily loss · drawdown · cooldown │ ├── app/alerts/ │ ├── telegram_bot.py — Notifications · /commands │ └── templates.py │ ├── app/recovery/ │ └── state_reconciler.py — DB ↔ exchange on boot │ ├── tests/ │ ├── test_risk_manager.py │ ├── test_exchange_factory.py │ ├── test_binance_client.py │ ├── test_kraken_client.py │ ├── test_signal_handler.py │ ├── test_webhook.py │ └── conftest.py — Mock exchanges (both) │ └── logs/ — Runtime logs (gitignored)`} ); } function DocSecurity() { return ( ); } function SecRow({ t, d }) { return (
  • {t}
    {d}
  • ); } function DocRecovery() { return (
    1. Read ACTIVE_EXCHANGE from config; instantiate that client.
    2. Query open positions via get_open_positions().
    3. Cross-reference with last-known DB state.
    4. If orphan position found → log WARNING, send Telegram alert, halt. User decides.
    5. Load daily P&L from DB so the daily-loss limit picks up where it left off.
    6. Verify the trading pair is still available on the active exchange.
    7. Resume normal operation.
    Success metric: 100% of untracked positions are caught on boot. The bot will not auto-close — humans decide whether to keep, modify, or exit an orphan position.
    ); } function DocSetup() { return (
    1. Clone the repo and copy .env.example.env.
    2. Fill in ACTIVE_EXCHANGE, the matching API credentials, Telegram tokens, and webhook secret.
    3. Run docker-compose up -d. The bot starts in testnet/paper by default.
    {`# 1. Clone git clone git@github.com:aminbassam/Auto-Trader.git cd Auto-Trader # 2. Configure cp .env.example .env $EDITOR .env # set ACTIVE_EXCHANGE, keys, Telegram, secret # 3. Boot (testnet by default) docker-compose up -d # 4. Verify curl http://localhost:8000/health | jq # → "status": "ok", "active_exchange": "binance", "trading_mode": "testnet" # 5. Point TradingView at it # Webhook URL: https://your-vps/webhook # Header: X-Webhook-Secret: $WEBHOOK_SECRET # 6. Run 7+ days in testnet before flipping TRADING_MODE=live`} Watch the dashboard, run a backtest in the Backtester tab (v2 preview), and tune your sliders in Settings → Risk. The Telegram bot will alert on every state change. Ship live only after a clean week in testnet.
    ); } Object.assign(window, { DocsPage });