Project Overview
Multi-Agent
Quant Trading Arena
14 autonomous bots compete in real-time on live BTC/USDT data. Each has its own capital, risk DNA, and trading style. The weakest are killed and replaced with genetic mutations of the strongest.
Launch ArenaAt a Glance
Bot Archetypes
Signal Engine
+0.8 (LONG) when short EMA > long EMA and price > short EMA
−0.8 (SHORT) when short EMA < long EMA and price < short EMA
Otherwise 0 (no signal)
+0.7 (LONG) when RSI < 30 (oversold)
−0.7 (SHORT) when RSI > 70 (overbought)
Otherwise 0 (neutral zone)
confidence = (trendSignal × trendWeight + meanRevSignal × meanRevWeight)
/ (trendWeight + meanRevWeight)
// Entry condition
if |confidence| ≥ minConfidence → open position in that direction
// Fast bots: either signal alone can reach threshold (low minConfidence)
// Slow bots: need both signals to align to reach threshold (high minConfidence)
Leverage System
| Confidence | Leverage | Applies to |
|---|---|---|
| ≥ 0.65 | 10x | Fast bots only |
| ≥ 0.55 | 5x | Fast & Medium |
| ≥ 0.45 | 2x | All archetypes |
| below threshold | 1x | No trade opened |
maxMargin = wallet / (atrBase × slMultiplier × leverage)
margin = min(wallet × riskFactor, maxMargin)
// Amount of BTC controlled
amount = (margin × leverage) / price
Position Lifecycle
atr = price × atrBase
tp = price + atr × tpMultiplier (BUY)
sl = price − atr × slMultiplier (BUY)
wallet −= margin
pnl = (exitPrice − entryPrice) × amount
wallet += margin + pnl
if wallet < $800 → isKicked = true
totalValue = wallet + Σ(margin + unrealizedPnL) for each open position
ROI = (totalValue − initialWallet) / initialWallet × 100
Survival of the Fittest
Any bot whose wallet drops below $800 (−20%) is immediately flagged isKicked and removed at the end of that tick.
Replacement bots always inherit the same archetype as the one they replace. Fast bots are replaced by fast bots — diversity is never lost.
New DNA = best same-archetype bot's DNA × (1 ± random × 15%). Each parameter mutates independently.
Mutation clamps TP ≥ SL × 1.5 so no offspring can inherit a statistically losing TP/SL ratio across generations.
DNA Parameters
| Parameter | Description | Fast | Medium | Slow |
|---|---|---|---|---|
| trendWeight | EMA crossover signal weight | 0.60–0.95 | 0.35–0.75 | 0.25–0.60 |
| meanRevWeight | RSI mean reversion weight | 0.05–0.25 | 0.25–0.65 | 0.45–0.80 |
| emaShort | Short EMA period | 4–8 | 8–15 | 20–30 |
| emaLong | Long EMA period | 12–20 | 25–40 | 50–80 |
| rsiPeriod | RSI lookback period | 5–9 | 10–14 | 14–21 |
| minConfidence | Conviction threshold to enter | 0.42–0.54 | 0.45–0.63 | 0.55–0.70 |
| riskFactor | Wallet % used as margin per trade | 5–12% | 10–22% | 15–30% |
| tpMultiplier | Take profit distance in ATR units | 1.5–2.7 | 2.0–3.5 | 2.5–4.5 |
| slMultiplier | Stop loss distance in ATR units | 0.4–0.7 | 0.7–1.2 | 1.2–1.8 |
| maxLeverage | Confidence-scaled leverage cap | 5x or 10x | 2x or 5x | 2x |
| maxPositions | Max concurrent open positions | 3–5 | 2–3 | 1–2 |
Performance Design
EMA and RSI are seeded once from full history, then updated in O(1) per closed candle using Wilder's smoothing. No per-tick history scan.
Each bot's ROI is computed once per tick and cached as _cachedROI. Reused by the sort, the spotlight, the arena list, and the trade log.
State is persisted every 30 ticks (~30 seconds), not every tick. Cloud sync is further rate-limited to once per 30 seconds.
The closed-trade history panel only rebuilds when a position closes, using a _historyDirty flag to skip expensive DOM work.
Buffer capped at 1,000 candles. Overflow uses update() only — never setData(), which would reset the entire chart.
Exponential backoff on disconnect: 1s → 2s → 4s → … → 30s cap. Delay resets to 1s on successful reconnect.
Persistence
Full arena state serialized to btc_arena_state every 30 ticks. Survives page refresh. Indicator cache excluded — re-seeded from live candles on load.
Async upsert to arena_state (id=1) every 30 seconds. Cloud state takes precedence over local on load. Backward-compatible migration for old single-position saves.
Data Sources
Binance WebSocket
wss://stream.binance.com:9443/ws/btcusdt@kline_1m
Each message triggers a full engine tick — position checks, signal evaluation, survival, ranking, and UI update.
Binance REST
GET /api/v3/klines?symbol=BTCUSDT&interval=1m&limit=720
720 candles (≈ 12 hours) fetched on startup with 3-attempt retry and exponential backoff. Seeds indicator state before the first live tick.
Tech Stack
Resources