Building a Basic Order Book in C++
Order books are fundamental data structures in trading systems. They maintain lists of buy and sell orders, match them, and provide real-time market depth information. In this article, we'll build a basic order book implementation in C++ that demonstrates core trading system concepts.
Overview
We'll create:
- An
Orderstruct to represent individual orders - An
OrderBookclass to manage bids and asks - Methods to add/remove orders and query market data
This implementation provides a foundation for more advanced trading systems like DEX market makers and execution engines.
Project Structure
Required Headers
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <iomanip>
iostream: Input/output operationsstring: String handlingmap: Ordered data structures for price levelsvector: Collections of ordersiomanip: Output formatting
The Order Struct
struct Order {
int id;
double price;
int quantity;
bool is_buy;
Order(int i, double p, int q, bool buy)
: id(i), price(p), quantity(q), is_buy(buy) {}
};
Fields:
id: Unique identifier for the orderprice: Limit price (floating-point)quantity: Number of units to buy/sellis_buy: Boolean flag (true = BUY, false = SELL)
Why boolean instead of string? Booleans are faster and use less memory than string comparisons.
The OrderBook Class
Private Members
class OrderBook {
private:
std::map<double, int, std::greater<double>> bids;
std::map<double, int> asks;
Key design decisions:
bidsusesstd::greater<double>to sort prices in descending order (highest bid first)asksuses default ascending order (lowest ask first)- Both maps associate prices with total quantities at that price level
Adding Orders
void addOrder(const Order& order) {
if (order.is_buy) {
bids[order.price] += order.quantity;
} else {
asks[order.price] += order.quantity;
}
}
The bracket operator ([]) automatically creates entries if they don't exist, or adds to existing quantities. This aggregates orders at the same price level.
Removing Orders
void removeOrder(double price, int quantity, bool is_buy) {
if (is_buy) {
bids[price] -= quantity;
if (bids[price] <= 0) {
bids.erase(price);
}
} else {
asks[price] -= quantity;
if (asks[price] <= 0) {
asks.erase(price);
}
}
}
After subtracting quantity, we check if the price level is empty and remove it if necessary.
Querying Market Data
double getBestBid() const {
if (bids.empty()) {
return 0.0;
}
return bids.begin()->first;
}
double getBestAsk() const {
if (asks.empty()) {
return 0.0;
}
return asks.begin()->first;
}
double getSpread() const {
double bid = getBestBid();
double ask = getBestAsk();
if (bid == 0.0 || ask == 0.0) {
return 0.0;
}
return ask - bid;
}
Best bid/ask: Since bids is sorted descending and asks ascending, begin()->first gives us the best prices.
Spread: The difference between best ask and best bid—a key market metric.
Displaying the Order Book
void printTopOfBook() const {
std::cout << "\n=== ORDER BOOK ===\n";
std::cout << std::fixed << std::setprecision(2);
int count = 0;
for (const auto& [price, qty] : bids) {
if (count >= 5) break;
std::cout << "BID: $" << price << " @ " << qty << "\n";
count++;
}
count = 0;
for (const auto& [price, qty] : asks) {
if (count >= 5) break;
std::cout << "ASK: $" << price << " @ " << qty << "\n";
count++;
}
std::cout << "\nBest Bid: $" << getBestBid() << "\n";
std::cout << "Best Ask: $" << getBestAsk() << "\n";
std::cout << "Spread: $" << getSpread() << "\n";
}
This displays the top 5 price levels on each side, plus best bid/ask and spread.
Complete Example
int main() {
OrderBook book;
std::vector<Order> orders = {
Order(1, 123.50, 100, true), // Buy order
Order(2, 124.00, 150, false), // Sell order
Order(3, 123.75, 200, true), // Buy order
Order(4, 124.25, 75, false), // Sell order
Order(5, 123.25, 50, true) // Buy order
};
// Add all orders
for (const Order& order : orders) {
book.addOrder(order);
}
book.printTopOfBook();
// Remove an order
book.removeOrder(123.50, 100, true);
std::cout << "\nAfter removing order:\n";
book.printTopOfBook();
return 0;
}
Understanding the Output
=== ORDER BOOK ===
BID: $123.75 @ 200
BID: $123.50 @ 100
BID: $123.25 @ 50
ASK: $124.00 @ 150
ASK: $124.25 @ 75
Best Bid: $123.75
Best Ask: $124.00
Spread: $0.25
The order book shows:
- Bids sorted highest to lowest (best bid first)
- Asks sorted lowest to highest (best ask first)
- Spread of $0.25 between best bid and ask
Key Concepts
Price-Time Priority
In real exchanges, orders at the same price are prioritized by time (FIFO). Our simplified version aggregates by price only. A production system would use a more complex structure like:
std::map<double, std::queue<Order>> bids;
Market Depth
The order book provides "market depth"—visibility into liquidity at different price levels. This is crucial for:
- Market makers: Understanding where to place orders
- Traders: Assessing slippage for large orders
- Analytics: Calculating order book imbalance
Order Matching
While this basic implementation doesn't match orders, a full system would:
- Check if new order crosses the spread
- Match against opposite side at best price
- Execute trades and update order book
Extending the Implementation
Adding Order IDs to Price Levels
std::map<double, std::vector<int>> bids; // Track which orders are at each price
Implementing Order Matching
std::vector<Trade> matchOrder(const Order& order) {
std::vector<Trade> trades;
// Match against opposite side
// Return filled trades
}
Adding Timestamps
struct Order {
// ... existing fields
std::chrono::system_clock::time_point timestamp;
};
Performance Considerations
- Map vs. Unordered Map:
std::mapmaintains order (needed for best bid/ask), butstd::unordered_mapis faster for lookups - Memory: Aggregating by price reduces memory usage vs. storing individual orders
- Locking: In multi-threaded systems, consider using
std::shared_mutexfor concurrent reads
Common Pitfalls
- Empty order book: Always check
empty()before accessingbegin() - Price precision: Floating-point prices can cause precision issues—consider using fixed-point arithmetic
- Integer overflow: Large quantities might overflow—use
int64_tfor production
Conclusion
This basic order book implementation demonstrates core concepts:
- Data structure design for efficient price-level access
- Order aggregation at price levels
- Market data queries (best bid/ask, spread)
From here, you can extend it with:
- Order matching logic
- Trade execution
- Market data feeds
- Performance optimizations
The foundation is solid for building more advanced trading systems, which we'll explore in the next article on order execution engines.