Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
62e3a36
refactor: stopped calling os clock. using rdtsc() based timing
K-Finger Aug 8, 2026
7b159d0
added: lock-free MPMC ConcurrentQueue for order ingest
K-Finger Aug 8, 2026
d0fe35d
added: ConcurrentQueue why comments and CMake wiring for its tests
K-Finger Aug 9, 2026
b69ffe5
added: unit and multithreaded stress tests for ConcurrentQueue
K-Finger Aug 9, 2026
4bfe623
added: mutex vs lock-free contention benchmark for ConcurrentQueue
K-Finger Aug 9, 2026
22ae3db
added: N producer to 1 consumer ingest pipeline benchmark
K-Finger Aug 9, 2026
35d73a7
removed: symmetric push-pop ConcurrentQueue bench, contention pattern…
K-Finger Aug 9, 2026
6f2a9a3
fixed: depth benchmarks taking 80+ seconds from auto-calibrated itera…
K-Finger Aug 9, 2026
b2e997b
ci: added linux-tsan job for the ConcurrentQueue race tests
K-Finger Aug 9, 2026
2e506c0
perf: packed Order into one cache line (72 to 64 bytes), cache-aligne…
K-Finger Aug 9, 2026
ac16d8a
perf: moved Trade into tradeBuffer_ instead of copying it
K-Finger Aug 9, 2026
d6bc3f8
perf: fused OrderIndex duplicate check and insert into one hash and p…
K-Finger Aug 9, 2026
7ee1626
perf: order construction now uses the shared TSC clock instead of sys…
K-Finger Aug 9, 2026
6850854
perf: fused OrderIndex find and erase in the cancel path into one has…
K-Finger Aug 9, 2026
a9e63d5
perf: fused canMatch and getBestOppositeLevel so matchOrder's loop lo…
K-Finger Aug 9, 2026
ac5621d
refactor: OrderIndex insert delegates to findSlot and insertAt instea…
K-Finger Aug 10, 2026
2664f4c
refactor: moved list ops to PriceLevel, added side-dispatch helpers, …
K-Finger Aug 10, 2026
a48f21b
v3: Performance fixes, Concurrent Queue tests, update docs
K-Finger Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ jobs:
UBSAN_OPTIONS: print_stacktrace=1,halt_on_error=1
run: ctest --test-dir build --output-on-failure

linux-tsan:
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4

- name: Install toolchain
run: |
sudo apt-get update -y
sudo apt-get install -y clang

- name: Configure CMake
env:
CC: clang
CXX: clang++
run: >
cmake -B build -DCMAKE_BUILD_TYPE=Debug
-DORDERBOOK_WARNINGS_AS_ERRORS=ON
-DORDERBOOK_SANITIZERS=thread

- name: Build
run: cmake --build build -j

- name: Run tests under TSan
env:
TSAN_OPTIONS: halt_on_error=1
run: ctest --test-dir build --output-on-failure

clang-tidy:
runs-on: ubuntu-24.04

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ compile_commands.json

# VSCode
PLAN.md
V3.md
v3-info.md
architecture-deep-dive.md
.vscode/

# Visual Studio
Expand Down
13 changes: 9 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(LimitOrderBook
VERSION 2.3.0
VERSION 3.0.0
LANGUAGES CXX
)

Expand Down Expand Up @@ -95,6 +95,8 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
include(FetchContent)
enable_testing()

find_package(Threads REQUIRED)

FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
Expand Down Expand Up @@ -122,10 +124,11 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
tests/order_pool_tests.cpp
tests/book_printer_tests.cpp
tests/order_index_tests.cpp
tests/concurrent_queue_tests.cpp
)

add_executable(orderbook_tests ${TEST_SOURCES})
target_link_libraries(orderbook_tests PRIVATE orderbook GTest::gtest_main)
target_link_libraries(orderbook_tests PRIVATE orderbook GTest::gtest_main Threads::Threads)
orderbook_apply_hardening(orderbook_tests)
if(ipo_ok)
set_target_properties(orderbook_tests PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
Expand All @@ -138,12 +141,13 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
bench/cancel_order_bench.cpp
bench/depth_bench.cpp
bench/level_info_bench.cpp
bench/ingest_pipeline_bench.cpp
)

foreach(BENCH_SRC ${BENCH_SOURCES})
get_filename_component(BENCH_NAME ${BENCH_SRC} NAME_WE)
add_executable(${BENCH_NAME} ${BENCH_SRC})
target_link_libraries(${BENCH_NAME} PRIVATE orderbook benchmark::benchmark)
target_link_libraries(${BENCH_NAME} PRIVATE orderbook benchmark::benchmark Threads::Threads)
target_include_directories(${BENCH_NAME} PRIVATE bench)
orderbook_apply_hardening(${BENCH_NAME})
if(ipo_ok)
Expand All @@ -161,8 +165,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
bench/cancel_order_bench.cpp
bench/depth_bench.cpp
bench/level_info_bench.cpp
bench/ingest_pipeline_bench.cpp
)
target_link_libraries(orderbook_bench_all PRIVATE orderbook benchmark::benchmark_main)
target_link_libraries(orderbook_bench_all PRIVATE orderbook benchmark::benchmark_main Threads::Threads)
target_include_directories(orderbook_bench_all PRIVATE bench)
target_compile_definitions(orderbook_bench_all PRIVATE BENCHMARK_COMBINED)
orderbook_apply_hardening(orderbook_bench_all)
Expand Down
93 changes: 43 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,61 @@
# liborderbook

A high-performance C++20 limit order book matching engine with sub-100ns add-order latency.

[![Build](https://github.com/K-Finger/liborderbook/actions/workflows/ci.yml/badge.svg)](https://github.com/K-Finger/liborderbook/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

## Features
A C++20 limit order book matching engine with sub-50ns operations on Linux.

## Properties

- Supports GTC, market, IOC, and FOK orders
- Ingests orders through a lock free queue when multiple threads submit at once
- Matches by price time priority
- Never allocates on the hot path
- Indexes price levels directly by price instead of using a tree

- **Sub-100ns latency** on the critical path
- **Order types**: GTC, Market, IOC, FOK
- **Price-time priority** matching (FIFO)
- **Strong types** for compile-time safety
- **Slab allocator** for cache locality
- **Zero dependencies** beyond STL
## What's New in v3.0.0

## Quick Start
Adds a lock free queue for concurrent order ingest. Also cuts add and cancel latency further. The public API has not changed. Details and numbers are in [docs/BENCHMARKS.md](docs/BENCHMARKS.md).

### Build
## Build

```sh
cmake -S . -B build
cmake --build build --config Release
```

### Integrate
## Integrate

```cmake
add_subdirectory(liborderbook)
target_link_libraries(your_app PRIVATE orderbook::orderbook)
```

### Use
## Use

```cpp
#include <iostream>

#include "OrderBook.h"
#include "BookPrinter.h"

using namespace orderbook;

OrderBook book;

// Place limit orders
book.addOrder(OrderBuilder{}.id(OrderId{1}).buy().goodTillCancel()
.price(Price{100}).quantity(Quantity{10}).build());
book.addOrder(OrderBuilder{}.id(OrderId{2}).sell().goodTillCancel()
.price(Price{101}).quantity(Quantity{20}).build());

// Market order crosses the spread
// Market order crosses the spread and returns the trades it produced.
auto trades = book.addOrder(OrderBuilder{}.id(OrderId{3}).buy().market()
.quantity(Quantity{5}).build());

book.cancelOrder(OrderId{1});
book.printBook();
printBook(std::cout, book);
```

Output:

```
=== ASKS ===
101 | 15 (1)
Expand All @@ -64,8 +65,6 @@ Output:

## API

### OrderBuilder

```cpp
OrderBuilder{}
.id(OrderId{42})
Expand All @@ -74,60 +73,54 @@ OrderBuilder{}
.price(Price{100}) // omit for market orders
.quantity(Quantity{10})
.build();
```

### OrderBook

```cpp
std::vector<Trade> addOrder(Order order); // Match + rest remainder
bool cancelOrder(OrderId id); // Remove resting order
std::size_t size() const; // Live order count
OrderBookLevelInfos getLevelInfos() const; // Aggregated depth
const std::vector<Trade>& getTrades() const; // Trade history
void printBook(); // Debug output
std::span<const Trade> addOrder(Order order); // Match against the book, rest the remainder
bool cancelOrder(OrderId id); // Remove a resting order by id
std::size_t size() const; // Live resting order count
OrderBookLevelInfos getLevelInfos() const; // Aggregated depth, both sides
```

`Order`, `OrderId`, `Price`, and `Quantity` are strong types. A price cannot be passed where a quantity is expected.

## Benchmarks

Single thread, MSVC Release with IPO and AVX2:
Single core, Linux, GCC Release `-O3`:

| Operation | Latency |
|-----------|---------|
| Add, no match | 67 ns |
| Add, single match | 143 ns |
| Add into 10k levels | 58 ns |
| Cancel | 75 ns |
| Add, no match | 37 ns |
| Add, single match | 37.5 ns |
| Add into 10k price levels | 34 ns |
| Cancel | 20 ns |

```sh
./build/Release/orderbook_bench_all
./build/orderbook_bench_all
```

See [BENCHMARKS.md](docs/BENCHMARKS.md) for history.
Full history in [docs/BENCHMARKS.md](docs/BENCHMARKS.md).

## Tests

```sh
cmake --build build --target orderbook_tests
./build/Release/orderbook_tests
./build/orderbook_tests
```

CI runs the suite on GCC and Clang, in Release and Debug, and under AddressSanitizer, UndefinedBehaviorSanitizer, and ThreadSanitizer.

## Documentation

| Document | Description |
|----------|-------------|
| [Introduction](docs/introduction.md) | Architecture and design |
| [Installation](docs/installation.md) | Build and integration |
| [Guide](docs/guide.md) | Core concepts and operations |
| [Examples](docs/examples.md) | Usage patterns |
| [Support](docs/support.md) | FAQ, contributing, license |
- [User Guide](docs/user_guide.md)
- [Benchmarks](docs/BENCHMARKS.md)

## References

- [How to Build a Fast Limit Order Book](https://web.archive.org/web/20110219163448/http://howtohft.wordpress.com/2011/02/15/how-to-build-a-fast-limit-order-book/)
- [Limit Order Book in C++](https://alexabosi.wordpress.com/2014/08/28/limit-order-book-implementation-for-low-latency-trading-in-c/) — Abosi
- [brprojects/Limit-Order-Book](https://github.com/brprojects/Limit-Order-Book)
- [Strong types with CRTP](https://youtu.be/fWcnp7Bulc8) — Fluent C++
- [TomaszRewak/cpp-allocator](https://github.com/TomaszRewak/cpp-allocator) — slab allocator
- [How to Build a Fast Limit Order Book](https://web.archive.org/web/20110219163448/http://howtohft.wordpress.com/2011/02/15/how-to-build-a-fast-limit-order-book/) - the array indexed price level technique this book uses
- [Bounded MPMC queue](https://web.archive.org/web/20160310160656/http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue) - Dmitry Vyukov, the design `ConcurrentQueue` is built on
- [Limit Order Book in C++](https://alexabosi.wordpress.com/2014/08/28/limit-order-book-implementation-for-low-latency-trading-in-c/) - Abosi
- [Bit Twiddling Hacks](https://graphics.stanford.edu/~seander/bithacks.html) - Sean Eron Anderson, Stanford. The bit scan technique behind the occupancy bitset
- [TomaszRewak/cpp-allocator](https://github.com/TomaszRewak/cpp-allocator) - slab allocator
- [Strong types with CRTP](https://youtu.be/fWcnp7Bulc8) - Fluent C++

## License

Expand Down
9 changes: 6 additions & 3 deletions bench/depth_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ static void BM_Depth_AddAtBest(benchmark::State& state) {
benchmark::DoNotOptimize(trades);
}
}
BENCHMARK(BM_Depth_AddAtBest)->Arg(0)->Arg(100)->Arg(1000)->Arg(10000);
// Iterations are capped. The refill above is O(depth). Without this cap,
// auto calibration picks iteration counts in the millions, which turns
// that refill cost into minutes of wall time.
BENCHMARK(BM_Depth_AddAtBest)->Arg(0)->Arg(100)->Arg(1000)->Arg(10000)->Iterations(20000);

// Add orders at random prices across the price range.
// Forces tree traversal on every add. Measures worst-case map descent.
Expand All @@ -56,7 +59,7 @@ static void BM_Depth_AddScatter(benchmark::State& state) {
benchmark::DoNotOptimize(trades);
}
}
BENCHMARK(BM_Depth_AddScatter)->Arg(100)->Arg(1000)->Arg(10000);
BENCHMARK(BM_Depth_AddScatter)->Arg(100)->Arg(1000)->Arg(10000)->Iterations(20000);

// Matching that sweeps through multiple price levels.
// Aggressive buy crosses spread and walks up the ask ladder.
Expand Down Expand Up @@ -106,7 +109,7 @@ static void BM_Depth_SweepLevels(benchmark::State& state) {
benchmark::DoNotOptimize(trades);
}
}
BENCHMARK(BM_Depth_SweepLevels)->Arg(2)->Arg(5)->Arg(10)->Arg(20);
BENCHMARK(BM_Depth_SweepLevels)->Arg(2)->Arg(5)->Arg(10)->Arg(20)->Iterations(20000);

#ifndef BENCHMARK_COMBINED
BENCHMARK_MAIN();
Expand Down
Loading
Loading