Project scope
Proof-of-concept market-making stack
Autonomous bot container connects to Binance public WebSocket, posts/takes quotes inside an in-process simulator
API container exposes live P&L and SSE feeds for dashboards / front-end demos
Entire system spins up withdocker compose up --build
┌──────────────────┐ ticks ┌──────────────────────┐
│ Binance WS │─────────────────►│ bot container │
└──────────────────┘ │ • datafeed │
│ • Fixed-Spread MM │
│ • OrderBookSim │
│ • ExecutionEngine │
└────────┬─────────────┘
│ Trades (+PnL)
SQLite volume
│
┌─────────────────────┐ HTTP / SSE ┌───────────▼───────────┐
│React dashboard (WIP)│◄──────────►│ api container │
└─────────────────────┘ │ • FastAPI ("/pnl") │
│ • /stream/pnl │
└───────────────────────┘
- Two containers, one shared volume (
./data/mm.db) keeps bot/API loosely coupled. - CI:
pyteststage in Dockerfile (11 tests).
| Layer | Implementation highlights |
|---|---|
| Datafeed | Async WebSocket client (websockets) streams BTCUSDT@bookTicker into an asyncio.Queue. |
| OrderBookSim | In-memory bid/ask dictionaries, partial-fill logic, resting_qty() for stealth-fill detection. |
| ExecutionEngine | • Naïve cancel/replace with unique order-ids (itertools.count())• Stealth-fill & partial-fill reconciliation • fill_vs_market() models external top-of-book liquidity• Trades persisted via record_fill() → SQLite. |
| Strategy | FixedSpreadMaker |
| Persistence | SQLAlchemy, Trade rows (price, qty, side, oid-<ts>), plus daily realised/unrealised buckets in DailyPnL. |
| API | FastAPI / Uvicorn. JSON snapshot: /pnl; Server-Sent-Events stream: /stream/pnl; health at /health. |
- Liquidity – Binance best bid/ask treated as infinite; depth & partials only simulatedwithin our own book.
- Latency & slippage – Zero; fills occur in the same tick they’re triggered.
- Order-id uniqueness – Each fill row gets
"<oid>-<epoch_ms>"; underlying order-id can repeat. - Risk limits – Fixed size (0.01 BTC) per leg; no inventory caps yet.
- Decimal precision – Financial math done in
Decimal; realised PnL stored asFLOATfor MVP convenience. - Security – No private keys; bot never sends real orders to an exchange.
# build + launch (two containers + shared volume)
docker compose up --build
# live equity
curl http://localhost:8000/pnl
# -> {"equity":"-0.34","cash":"-1.23","inventory":"0.009"}
# stream (server-sent events)
curl http://localhost:8000/stream/pnlThe repo ships with a working fly.toml and multi-stage Dockerfile.
The container runs the trading bot and the FastAPI service in the same
VM and mounts a 1 GB volume at /data for the SQLite file.
flyctl launch --generate-name
# deploy
flyctl deploy
# open browser
flyctl openKey Fly settings (see fly.toml):
DATABASE_URL– defaults tosqlite:////data/mm.db(shared by bot & API)PORT– exposed by Uvicorn and mapped automatically- Volume
mm_dataattached at/datakeeps fills & PnL between restarts
After deploy
- Live PnL:
https://<app>.fly.dev/pnl - Streams:
https://<app>.fly.dev/stream/{ticker|pnl|orders} - Health:
https://<app>.fly.dev/health
pytest -v (also executed during Docker build)