Learn how the Exchange orderbook stores and matches orders using BTreeMap data structures
The orderbook is the heart of the matching engine, maintaining all open buy and sell orders for a trading pair. The Exchange uses Rust’s BTreeMap to ensure orders are automatically sorted by price level.
BTreeMap provides crucial properties for an orderbook:
Automatic sorting: Keys (prices) are kept in sorted order
Efficient range queries: Iterating from best to worst prices is O(n)
Fast insertions: Adding new price levels is O(log n)
Ordered iteration: iter() returns prices in ascending order, iter().rev() in descending order
For bids, we want descending price order (highest first), so we use .rev(). For asks, we want ascending order (lowest first), so we use .iter() directly.
When a sell order arrives, it’s matched against existing buy orders (bids):
orderbook.rs
pub fn match_bids(&mut self, order: &Order) -> ProcessOrderResult { let mut fills: Vec<Fill> = vec![]; let mut executed_quantity: Decimal = dec!(0); for (_price, bids) in self.bids.iter_mut().rev() { for bid in bids.iter_mut() { if order.price <= bid.price && executed_quantity < order.quantity { let filled_quantity = std::cmp::min( bid.quantity - executed_quantity, bid.quantity ); self.trade_id += 1; executed_quantity += filled_quantity; bid.filled_quantity += filled_quantity; fills.push(Fill { price: bid.price, quantity: filled_quantity, trade_id: self.trade_id, other_user_id: bid.user_id.clone(), order_id: bid.order_id.clone(), }) } } // Remove bids that have been completely filled bids.retain(|bid| bid.filled_quantity < bid.quantity); } ProcessOrderResult { fills, executed_quantity, }}
The key difference is that bids are iterated in reverse order (highest to lowest prices) using .rev(), and the matching condition checks if the sell order’s price is less than or equal to the bid price.
pub fn cancel_order(&mut self, cancel_order: CancelOrder) -> Result<Order, ()> { let cancel = |orders_map: &mut BTreeMap<Decimal, Vec<Order>>| { if let Some(orders) = orders_map.get_mut(&cancel_order.price) { if let Some(index) = orders .iter() .position(|order| order.order_id == cancel_order.order_id) { let order = orders.get(index).unwrap().clone(); orders.remove(index); Ok(order) } else { Err(()) } } else { Err(()) } }; match cancel_order.side { OrderSide::BUY => cancel(&mut self.bids), OrderSide::SELL => cancel(&mut self.asks), }}
Cancellation requires knowing the order ID, price, and side. The closure removes the order from the appropriate price level and returns it for balance reconciliation.
If all orders are removed from a price level, the vector becomes empty but the BTreeMap entry remains. This has minimal overhead and could be optimized by removing empty price levels.