A simplified matching engine written in Rust.
This project is designed as a short, high-signal portfolio project for Rust / trading infrastructure roles. It implements a small but realistic subset of an exchange matching engine:
- limit buy/sell orders
- market buy/sell orders
- price-time priority
- partial fills
- resting order book
- trade log
- order cancellation
- order modification
- deterministic scenario runner
- engine statistics
- simple benchmark command
- indexed order lookup for faster cancellation
- market orders that consume available liquidity without resting in the book
- order modifications with priority rules
- integration tests
A matching engine is the core component of many trading systems. It receives incoming orders, compares them with the opposite side of the order book, executes trades when prices cross, and stores the remaining quantity when an order is not fully filled.
The goal here is not to build a production exchange. The goal is to show clean Rust, strong data-structure choices, and a solid understanding of trading infrastructure basics.
BTreeMap<Price, VecDeque<Order>>for sorted price levels and FIFO time priority.- Limit orders rest in the book when they are not fully matched.
- Market orders consume the best available opposite-side liquidity and never rest in the book.
- Integer price representation in cents/ticks instead of floating-point prices.
- Deterministic order IDs and sequence numbers.
- Maker/taker trade logs.
- Partial fill handling on both incoming and resting orders.
- Scenario runner for reproducible examples.
- Lightweight benchmark using
std::time::Instant. HashMap<OrderId, OrderLocation>index so cancellation and modification can jump directly to the expected side and price level instead of scanning every price level.- Modification rules that preserve time priority only for same-price quantity reductions; price changes and quantity increases are cancel/replace operations with a new sequence.
rust_matching_engine/
├── Cargo.toml
├── README.md
├── examples/
│ └── basic.txt
├── src/
│ ├── engine.rs
│ ├── lib.rs
│ ├── main.rs
│ ├── order.rs
│ ├── order_book.rs
│ ├── stats.rs
│ ├── trade.rs
│ └── types.rs
└── tests/
└── matching_tests.rsEach order has:
- an ID
- a side:
BUYorSELL - a price
- an initial quantity
- a remaining quantity
- a sequence number used for time priority
Prices are represented as integer cents/ticks, not floats.
For example:
100.50 -> 10050
99.01 -> 9901This avoids floating-point comparison bugs, which matter a lot in trading systems.
The engine uses price-time priority:
- Best price wins.
- If prices are equal, the oldest resting order wins.
For a BUY order:
match while best_ask <= buy_priceFor a SELL limit order:
match while best_bid >= sell_priceFor a market order, there is no limit price check: the order keeps consuming the best available opposite-side liquidity until it is fully filled or the opposite book is empty.
For a modification, the engine applies simplified exchange-style priority rules:
- reducing the remaining quantity at the same price keeps the original time priority
- increasing quantity or changing price is treated as cancel/replace, so the order receives a new sequence
- if the modified limit price crosses the opposite side, the modified order can immediately execute as the taker
The execution price is always the maker/resting order price.
cargo run -- demoOr run the example scenario:
cargo run -- scenario examples/basic.txtYou can also submit a single order to a fresh engine:
cargo run -- submit buy 10 100.50Or submit a market order to a fresh engine:
cargo run -- submit-market buy 10Inside a scenario, use:
statsIt prints:
- submitted orders
- resting orders
- total trades
- executed volume
- resting bid/ask volume
- best bid / best ask
- spread
- notional traded
- average trade price
Example:
========== ENGINE STATS ==========
Submitted orders : 4
Resting orders : 1
Total trades : 3
Executed volume : 12
Resting bid volume : 0
Resting ask volume : 1
Best bid : -
Best ask : 102.00
Spread : -
Notional traded : 1204.00
Average trade price : 100.33
==================================Market orders have no limit price. They immediately consume the best available opposite-side liquidity and never rest in the book.
Example:
submit sell 5 100.00
submit sell 5 101.00
submit-market buy 7Expected result:
TRADE taker=3 maker=1 qty=5 price=100.00
TRADE taker=3 maker=2 qty=2 price=101.00If there is not enough liquidity, the remaining market quantity is returned in the response but is not inserted into the order book.
Inside a scenario, use:
modify <order_id> <new_quantity> <new_price>Example:
submit sell 3 101.00
submit buy 5 99.00
modify 2 5 101.00The buy order originally rests at 99.00. After modification to 101.00, it crosses the resting sell order and executes as the taker:
TRADE taker=2 maker=1 qty=3 price=101.00The engine keeps priority only for same-price quantity reductions. Price changes and quantity increases are treated as cancel/replace operations, which gives the modified order a new sequence and places it behind older orders at the same price.
The engine keeps an internal order-location index:
OrderId -> (Side, Price)This avoids scanning the whole order book when cancelling or modifying an order. The engine can directly jump to the expected side and price level, then update or remove the order from the FIFO queue at that level.
The index is updated when:
- a resting order is added to the book
- a resting order is fully filled by an incoming order
- an order is cancelled
- an order is modified and reinserted at a new price/priority
This is still simplified compared to a production exchange, but it is a more realistic design than a full linear scan across all price levels.
Run a deterministic benchmark with 100,000 generated orders:
cargo run --release -- benchmark 100000Or use the default:
cargo run --release -- benchmarkThis prints elapsed time, approximate throughput, and final engine statistics.
submit sell 10 100.00
submit buy 4 101.00
submit-market buy 2
modify 1 4 101.00
book 10
trades
statsSupported scenario commands:
submit <buy|sell> <quantity> <price>
submit-market <buy|sell> <quantity>
modify <order_id> <new_quantity> <new_price>
cancel <order_id>
book [depth]
trades
statscargo testThe tests cover:
- non-crossing orders resting in the book
- buy order crossing best ask
- partial fills
- incoming remainder resting after a partial execution
- price-time priority
- cancellation
- cancellation after full and partial fills
- market buy/sell orders
- unfilled market quantity not resting in the book
- order modification priority rules
- price parsing without floats
- engine statistics
> submit sell 10 100.00
SUBMITTED id=1 side=SELL qty=10 price=100.00 remaining=10 trades=0
> submit buy 4 101.00
SUBMITTED id=2 side=BUY qty=4 price=101.00 remaining=0 trades=1
TRADE taker=2 maker=1 qty=4 price=100.00Built a simplified matching engine in Rust implementing limit and market orders, price-time priority, partial fills, indexed cancellation and modification, trade logs, engine statistics, benchmarking and integration tests.French version:
Développement d’un mini matching engine en Rust : ordres limit et market buy/sell, priorité prix/temps, exécutions partielles, annulation et modification indexées d’ordres, statistiques moteur, benchmark simple et tests d’intégration.Good extensions for a stronger portfolio version:
- Add order status: accepted, partially filled, filled, cancelled, replaced, rejected.
- Add benchmarks with
criterion. - Add CSV scenario input/output.
- Add a simple TCP or REST API.
- Add latency measurements.
- Add multi-symbol support, e.g. BTC-USD, ETH-USD.
- Add persistent event replay.