Technical Documentation
Order Matching Engine
The order matching engine is the core component of the Open Mandi exchange. It processes incoming orders, maintains the order book, and executes trades using a price-time priority (FIFO) algorithm.
Engine Overview
The matching engine operates on three order books: USDT/USDC, XAU (gold futures), and XAG (silver futures). Each order book is independent and maintains its own bid and ask queues.
The engine is synchronous within a single order book — orders are processed sequentially to prevent race conditions. Cross-book operations (e.g., settling a futures trade that debits a wallet balance) are wrapped in database transactions to ensure atomicity.
Order Types Supported
| Order Type | Behavior |
|---|---|
| Limit Order | Placed at a specific price. Rests on the order book until filled, cancelled, or expired. Eligible for maker fee rate. |
| Market Order | Executed immediately at the best available price(s). Walks the book if necessary to fill the full quantity. Always pays taker fee rate. |
Matching Algorithm: Price-Time Priority (FIFO)
The matching algorithm follows the industry-standard price-time priority model:
- Price priority — orders with better prices are matched first. For bids, higher prices have priority. For asks, lower prices have priority.
- Time priority — among orders at the same price level, the order that was placed first (earliest timestamp) is matched first (First In, First Out).
Incoming BUY order: quantity=10, price=MARKET
Ask side of order book:
Price | Quantity | Time
--------|----------|----------
100.02 | 5 | 10:00:01 ← matched first (best price)
100.02 | 3 | 10:00:05 ← matched second (same price, later time)
100.05 | 20 | 10:00:02 ← matched third (2 units, worse price)
Result: 3 fills → (5@100.02) + (3@100.02) + (2@100.05) = 10 unitsOrder Book Data Structure
Each side of the order book (bids and asks) is maintained as a sorted collection:
- Bids — sorted in descending order by price (highest bid first)
- Asks — sorted in ascending order by price (lowest ask first)
At each price level, orders are maintained in a FIFO queue. The data structure supports O(log n) insertion and O(1) access to the best bid/ask.
OrderBook {
bids: SortedMap<Price, Queue<Order>> // descending by price
asks: SortedMap<Price, Queue<Order>> // ascending by price
}
Order {
id: UUID
userId: string
side: "buy" | "sell"
type: "limit" | "market"
price: Decimal // null for market orders
quantity: Decimal
filledQuantity: Decimal
status: "open" | "partial" | "filled" | "cancelled"
createdAt: Timestamp
}Partial Fills and Order Lifecycle
An order moves through the following states:
- Open — order is placed and resting on the book (limit orders only)
- Partial — order has been partially filled but still has remaining quantity
- Filled — order is completely filled
- Cancelled — order was cancelled by the user or system
Each fill generates a Trade record linking the maker order and taker order, recording the execution price, quantity, and fees for both sides.
Concurrency and Race Condition Handling
The matching engine serializes order processing per order book using a write lock. This ensures that:
- Two orders cannot simultaneously match against the same resting order
- Balance checks and debits are atomic with order placement
- The order book state is always consistent
Balance updates resulting from fills are executed within the same database transaction as the trade record insertion, guaranteeing that funds are never double-spent or lost.
Performance Characteristics
Given the academic nature and low volume of the exchange, the matching engine is optimized for correctness rather than throughput. Expected performance:
- Order placement: < 50ms
- Order matching: < 10ms per match
- Order book depth: supports up to 10,000 resting orders per side