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 asyncSQLite WALDockerTestnet defaultDubai region
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 (
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 (
Read ACTIVE_EXCHANGE from config; instantiate that client.
Query open positions via get_open_positions().
Cross-reference with last-known DB state.
If orphan position found → log WARNING, send Telegram alert, halt. User decides.
Load daily P&L from DB so the daily-loss limit picks up where it left off.
Verify the trading pair is still available on the active exchange.
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 (
Clone the repo and copy .env.example → .env.
Fill in ACTIVE_EXCHANGE, the matching API credentials, Telegram tokens, and webhook secret.
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 });