Market-Making Bot Overview
The Rocky market-making bot (rocky-bot) is a long-running Python service on EC2. It uses 30 independent accounts to continuously quote and actively trade BTC-PERP / ETH-PERP, so that demo.rocky.exchange shows multi-level real depth and continuous fills.
Current deployment: systemd-user service
rocky-bot, single process running ~61 asyncio tasks. Repo:/Users/ubuntu/Desktop/Rocky/rocky-bot.
1. Why a market-making bot
Rocky is an early-stage demo with no real user volume. Without continuous quoting:
- The order book is empty, looks like a dead market
- No fills, the frontend kline / trade tape is forever blank
- Even if a user submits an order, there's no counterparty — matching and settlement can't be exercised
The bot fills that gap:
- Quote (Maker) — limit orders on both sides, forming multi-level depth
- Active fill (Taker) — periodic aggressive orders, guaranteed fill events
- Trigger on-chain reward — every fill triggers a
MiningRewardcontract creation, demonstrating the MTC mining mechanism (see MTC Mining Reward) - Populate kline / 24h stats — fill data lands in
ledger.trades, feeding the kline chart, 24h volume, recent-trades feed
2. Funnel geometry
30 accounts split by role:
┌─────────────────────────────┐
Anchor (1 account) │ mm-anchor │ best bid ± 1 bps
│ qty BTC: 0.0003 / ETH: 0.006
└─────────────────────────────┘
Ladder L1 (12 accts)│ mm-l1-{buy|sell}-{05..10}bps
│ qty BTC: 0.0005 / ETH: 0.01
Ladder L2 (8 accts) │ mm-l2-{buy|sell}-{15,20,25,30}bps
│ qty BTC: 0.001 / ETH: 0.02
Ladder L3 (4 accts) │ mm-l3-{buy|sell}-{50,100}bps
│ qty BTC: 0.002 / ETH: 0.04
Taker (5 accounts) │ taker-{1..5}
│ qty BTC: 0.0005 / ETH: 0.01
│ cross 50 bps past mid, every 30s±10s
Total: 1 anchor + 24 ladder + 5 taker = 30.
Each account is a separate registered user on demo.rocky.exchange, minted in one shot by scripts/mint-30.sh along with $100 USDC seed each.
2.1 Why the "funnel" shape
- Dense in the center, sparse at the edges — anchor + L1 ladders quote in ±1–10 bps, keeping the inside spread very narrow (looks like good liquidity)
- L2/L3 provide depth — orders at 50–100 bps show "the market can absorb large orders," making the depth chart look healthy
- Takers drive fills — pure quoting wouldn't produce fills; takers actively cross 50 bps so a fill happens roughly every 30s
- Multiple accounts vs single account — each account has its own risk budget ($100 wallet), so a blow-up on one doesn't propagate; also visually showcases "many real users trading"
2.2 Capacity math
$100 USDC seed per account. 30 accounts = $3000 total liquidity.
- Each ladder account carries at most ~$15 position margin ($150 notional × 10x leverage / 10 = $15)
- Each taker, same
- Total locked-margin ceiling ≈ 30 × $30 = $900 (enough to show depth without being swept by the market)
Risk configuration details: see Risk Controls.
3. Architecture
rocky-bot is single-process, multi-asyncio-task:
rocky-bot (systemd-user service on EC2)
│
├── BinanceFeed (1 task)
│ └── WebSocket subscribes btcusdt + ethusdt bookTicker, caches mid
│
├── For each account in .keys.json (30 total):
│ │
│ ├── If role=ladder:
│ │ LadderMakerLoop × 2 symbols = 48 tasks
│ │ └── Every 3s ± 1s: read mid → compute target → diff
│ │ against existing open order → cancel/place
│ │
│ ├── If role=anchor:
│ │ AnchorMakerLoop × 2 symbols = 2 tasks
│ │ └── Every 2s ± 0.5s: read mid → quote both ±1 bps
│ │
│ └── If role=taker:
│ TakerLoop × 2 symbols = 10 tasks
│ └── Every 30s ± 10s: choose side (rebalance from position)
│ → 50 bps aggressive cross
│
└── Per-account CircuitBreaker (30 instances)
└── Tracks wallet PnL, API errors; trips if thresholds breached
Total tasks: 1 feed + 48 ladder + 2 anchor + 10 taker = 61 asyncio tasks.
4. Tech stack
| Part | Choice | Why |
|---|---|---|
| Language | Python 3.12 | Rapid iteration, mature ecosystem |
| Concurrency | asyncio | 60+ I/O-bound tasks; asyncio is far lighter than threading |
| HTTP | httpx (async) | Async, connection reuse, doesn't block the event loop |
| Config | pydantic-settings + custom .keys.json | 30 API key pairs in .env would be ugly |
| Market data | websockets → wss://fstream.binance.com | Free, low latency, doesn't conflict with the demo backend's matcher |
| Deploy | rsync + uv + systemctl --user | Simple, low-dependency, observable |
5. Relationship to rocky-backend
rocky-bot rocky-backend (EC2)
├── calls /fapi/v1/order ─────────────► api-gateway → trading-api → internal-ledger
├── calls /fapi/v1/order DELETE ──────► ...
├── calls /fapi/v1/positionRisk ──────► api-gateway → matching-engine query
└── calls /fapi/v1/balance ───────────► api-gateway → internal-ledger query
Auth: HMAC-SHA256 signing on every request; keys from .keys.json
The bot doesn't go through the /api/perp/* web BFF; it hits /fapi/v1/* directly (the Binance-compatible path) with API key + HMAC auth. These endpoints are designed for programmatic traders and correspond to the backend's auth.api_keys table (fully independent from the User Account API auth.users table).
6. Next
- Strategy details — Ladder / Anchor / Taker loop internals
- Risk controls — CircuitBreaker, RiskCaps, position cap, LEVERAGE_V1
- Deployment & operations — mint-30.sh, deploy.sh, reset.sh, monitoring