Skip to content

yetiwannacode/cpp-matching-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C++ Matching Engine Simulator

A simplified matching engine and limit order book simulator built in C++.

The project implements price-time priority matching, partial fills, order cancellation, order modification, trade logging, command-based input, and synthetic order-flow benchmarking.

Why I Built This

I built this project to understand the core data structures and matching logic used in electronic trading systems, and to practice performance-conscious C++ design.

The focus of this project is on:

  • C++ and object-oriented design
  • Data structures and algorithms
  • Order book mechanics
  • Price-time priority matching
  • Efficient order lookup for cancel/modify operations
  • Basic latency benchmarking using synthetic order flow

Features

  • Add buy and sell limit orders
  • Match orders using price-time priority
  • Support partial fills
  • Cancel existing orders
  • Modify existing orders as cancel-and-replace
  • Print current order book state
  • Maintain executed trade logs
  • Run single and multi-run synthetic order-flow benchmarks using std::chrono

Project Outcome

The simulator currently supports the complete flow of a simplified limit order book:

  • Adding buy and sell limit orders
  • Matching crossed orders using price-time priority
  • Handling partial fills
  • Cancelling resting orders
  • Modifying existing orders as cancel-and-replace
  • Printing the current order book
  • Logging executed trades
  • Running synthetic order-flow benchmarks

On a sample single-run benchmark of 100,000 synthetic orders, the engine processed the orders in 29,916 microseconds, averaging 0.2992 microseconds per order on my system.

In a 5-run benchmark with 100,000 synthetic orders per run, the engine recorded a best time of 20,394 microseconds, an average time of 25,012.40 microseconds, and a worst time of 30,128 microseconds, averaging 0.2501 microseconds per order.

These benchmark numbers are machine-dependent and are intended only to measure this simplified in-memory implementation. They should not be interpreted as production-level HFT latency.

Supported Commands

ADD <orderId> <BUY/SELL> <price> <quantity>
CANCEL <orderId>
MODIFY <orderId> <newPrice> <newQuantity>
PRINT
TRADES
BENCHMARK <numberOfOrders>
BENCHMARK_MULTI <numberOfOrders> <runs>
EXIT

Example Input

ADD 1 BUY 100 10
ADD 2 SELL 99 5
ADD 3 SELL 101 7
ADD 4 BUY 101 10
PRINT
TRADES
CANCEL 1
PRINT
MODIFY 4 102 8
PRINT
TRADES
BENCHMARK 100000
BENCHMARK_MULTI 100000 5
EXIT

Example Output

C++ Matching Engine Simulator
Supported commands:
ADD <orderId> <BUY/SELL> <price> <quantity>
CANCEL <orderId>
MODIFY <orderId> <newPrice> <newQuantity>
PRINT
TRADES
BENCHMARK <numberOfOrders>
BENCHMARK_MULTI <numberOfOrders> <runs>
EXIT

TRADE: BuyOrder=1 SellOrder=2 Price=100 Quantity=5
TRADE: BuyOrder=4 SellOrder=3 Price=101 Quantity=7

===== ORDER BOOK =====
SELL ORDERS:

BUY ORDERS:
Price 101: [ID=4, Qty=3]
Price 100: [ID=1, Qty=5]
======================

===== TRADES =====
BuyOrder=1 SellOrder=2 Price=100 Quantity=5
BuyOrder=4 SellOrder=3 Price=101 Quantity=7
==================

Cancelled order: 1

===== ORDER BOOK =====
SELL ORDERS:

BUY ORDERS:
Price 101: [ID=4, Qty=3]
======================

Cancelled order: 4
Modified order: 4 NewPrice=102 NewQuantity=8

===== ORDER BOOK =====
SELL ORDERS:

BUY ORDERS:
Price 102: [ID=4, Qty=8]
======================

===== TRADES =====
BuyOrder=1 SellOrder=2 Price=100 Quantity=5
BuyOrder=4 SellOrder=3 Price=101 Quantity=7
==================

===== BENCHMARK =====
Processed orders: 100000
Total time: 29916 microseconds
Average processing time: 0.2992 microseconds/order
Trades generated: 79280
=====================

===== MULTI-RUN BENCHMARK =====
Runs: 5
Orders per run: 100000
Best time: 20394 microseconds
Average time: 25012.40 microseconds
Worst time: 30128 microseconds
Average processing time: 0.2501 microseconds/order
Average trades generated: 79244.60
===============================

Benchmark results are machine-dependent and may vary across systems.

Assumptions and Design Choices

This project intentionally models a simplified single-instrument matching engine.

The following assumptions are used:

  • All orders are limit orders.
  • Prices and quantities are represented as integers.
  • The engine handles one instrument/order book at a time.
  • Active resting order IDs are assumed to be unique. Fully matched incoming orders are recorded in the trade log but are not retained in the active order map.
  • Matching happens immediately when an incoming order crosses the opposite side of the book.
  • Within the same price level, orders follow FIFO ordering to preserve time priority.
  • The trade price is taken from the resting order already present in the book.
  • A modified order is treated as a cancel followed by a new add, so it receives a new timestamp and loses its earlier time priority.
  • Benchmarking uses synthetic randomly generated orders and does not represent real exchange latency.

This project does not attempt to model real-world trading infrastructure completely. It focuses on the core matching logic and data-structure design.

Data Structures Used

Buy Book

std::map<Price, OrderQueue, std::greater<Price>>

The buy book is sorted in descending price order, so the highest bid is always available first.

Sell Book

std::map<Price, OrderQueue>

The sell book is sorted in ascending price order, so the lowest ask is always available first.

Order Queues

std::list<Order>

Each price level stores orders in FIFO order to preserve time priority.

Order Lookup

std::unordered_map<int, OrderLocation>

This allows efficient lookup of orders for cancellation and modification.

The OrderLocation stores the order side, price level, and iterator to the order inside the corresponding price-level queue.

Matching Logic

Buy orders match against the lowest available sell price when:

buy price >= best sell price

Sell orders match against the highest available buy price when:

sell price <= best buy price

Within the same price level, older orders are matched first. This preserves price-time priority.

If an incoming order is only partially filled, its remaining quantity continues matching until no more eligible opposite-side orders exist. If some quantity still remains after matching, it is added to the book.

Complexity Analysis

Let P be the number of active price levels and K be the number of orders or price levels matched by an incoming order.

Operation Complexity
Add order to book O(log P)
Access best bid/ask O(1) using map.begin()
Match order O(K log P) in the worst case due to price-level removals
Cancel order O(1) order lookup + O(log P) price-level lookup + O(1) list erase
Modify order Cancel + Add

Benchmarking

The benchmark command generates synthetic buy and sell orders with randomized prices and quantities.

Example:

BENCHMARK 100000

The project also supports repeated benchmark runs:

BENCHMARK_MULTI 100000 5

This runs the benchmark multiple times and reports the best, average, and worst processing time across runs.

The benchmark reports:

  • Number of processed orders
  • Total processing time
  • Average processing time per order
  • Number of trades generated

This benchmark is useful for comparing changes within this project, but it should not be interpreted as production-level HFT latency. It does not include network delays, exchange connectivity, kernel bypass, risk checks, persistence, logging overhead, or real market-data feed handling.

How to Build

Windows PowerShell

Compile:

g++ -std=c++17 -O2 -Wall -Wextra -Iinclude src/main.cpp src/OrderBook.cpp -o matching_engine.exe

Run with sample input:

Get-Content sample_input.txt | .\matching_engine.exe

Linux / Git Bash

Compile:

g++ -std=c++17 -O2 -Wall -Wextra -Iinclude src/main.cpp src/OrderBook.cpp -o matching_engine

Run with sample input:

./matching_engine < sample_input.txt

Project Structure

cpp-matching-engine/
│
├── include/
│   ├── Order.hpp
│   ├── Trade.hpp
│   └── OrderBook.hpp
│
├── src/
│   ├── OrderBook.cpp
│   └── main.cpp
│
├── tests/
│   └── sample_test.txt
│
├── sample_input.txt
├── Makefile
├── .gitignore
└── README.md

Current Limitations

This is a simplified educational matching engine. It does not include:

  • Multiple instruments
  • Market orders
  • Stop orders
  • Tick-size validation
  • Real exchange connectivity
  • Network feed handling
  • Risk checks
  • Persistent storage
  • Multithreaded market data ingestion
  • Production-grade latency measurement
  • p50, p95, or p99 latency distribution tracking

Future Improvements

  • Add socket-based order input
  • Add p50, p95, and p99 latency tracking
  • Add object pooling to reduce dynamic allocations
  • Replace STL containers with more cache-friendly structures
  • Add multithreaded market data simulation
  • Add unit tests for matching, cancellation, modification, and edge cases
  • Support multiple instruments using separate order books
  • Add stricter command validation and error handling

About

A C++ matching engine simulator with price-time priority matching, order management, and latency benchmarking.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors