This project implements a minimal exchange matching engine in Rust using:
- Actix Web for HTTP + WebSocket APIs
- Tokio for async runtime and channels
- A single-threaded matching engine for correctness
The backend supports:
- Submitting orders via HTTP (
POST /orders) - Fetching orderbook snapshots (
GET /orderbook) - Streaming fills over WebSocket (
/ws)
HTTP / WS Layer (Actix)
↓
mpsc channel
↓
Matching Engine (single task)
↓
broadcast channel
↓
WebSocket clients
All state lives inside the matching engine task
The system avoids double-matching by design:
- The matching engine runs as a single Tokio task
- All API instances communicate via
mpsc::Sender<EngineCommand> - Orders are processed sequentially in one place
Even with multiple API servers:
- They do not share state
- They send commands to the same engine
- The engine is the single source of truth
This only works if:
All API instances connect to the same matching engine process
If we run multiple engines independently:
API 1 → Engine 1
API 2 → Engine 2
We will get:
- Split liquidity
- Inconsistent orderbooks
- Double matching risk
Run the matching engine as a separate service:
API Servers (many)
↓
Network (TCP / gRPC)
↓
Central Matching Engine
- Multiple API servers scale horizontally
- Engine remains single-writer
Scale by market / symbol:
BTC-USD → Engine A
ETH-USD → Engine B
SOL-USD → Engine C
- Each engine handles one orderbook
- Still single-threaded per market
- Massive horizontal scalability
we can use broker like Redis / Kafka / NATS:
API → Message Queue → Matching Engine
Benefits:
- Durable message flow
- Replay capability
- Decoupled services
- Better backpressure handling
BTreeMap<u64, VecDeque<Order>>BTreeMap<price → orders>VecDequefor FIFO at each price level
-
Maintains sorted prices
-
Enables:
- Best bid →
next_back() - Best ask →
next()
- Best bid →
-
Required for price-time priority
-
Efficient queue operations:
push_back()→ enqueuepop_front()→ dequeue
-
Guarantees FIFO within same price
- CPU saturation
- Increased latency
mpscchannel fills up- Requests block or fail
- Message cloning per client
- High memory + CPU usage
- Crash = total data loss
- Slow cancel/modify operations
- Inefficient throughput
- Order ID map (
HashMap) - Cancel / replace support
- Snapshot on connect
- Incremental updates (diffs)
- Return fills in response
- Proper validation
- Batching in engine loop
- Metrics (latency, throughput)
- Snapshot to disk
- Append-only log
- Partition engines by market
cargo runcurl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{"id":1,"side":"buy","price":100,"qty":10}'curl http://localhost:8080/orderbook