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 TypeBehavior
Limit OrderPlaced at a specific price. Rests on the order book until filled, cancelled, or expired. Eligible for maker fee rate.
Market OrderExecuted 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:

  1. Price priority — orders with better prices are matched first. For bids, higher prices have priority. For asks, lower prices have priority.
  2. 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 units

Order 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:

  1. Open — order is placed and resting on the book (limit orders only)
  2. Partial — order has been partially filled but still has remaining quantity
  3. Filled — order is completely filled
  4. 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