C++ SDK
OrderBook
Lock-free order book data structures, SIMD price matching, and real-time updates
OrderBook
The OrderBook class provides a lock-free, cache-optimized representation of market depth with wait-free reads and obstruction-free writes, designed for HFT applications requiring sub-microsecond access.
Architecture
+----------------------------------------------------------+
| OrderBook |
| +------------------------+ +------------------------+ |
| | Bid Side | | Ask Side | |
| | (Sorted Descending) | | (Sorted Ascending) | |
| | +------------------+ | | +------------------+ | |
| | | Level 0 (Best) | | | | Level 0 (Best) | | |
| | | Price: 50000.00 | | | | Price: 50000.01 | | |
| | | Qty: 10.5 | | | | Qty: 8.2 | | |
| | | Orders: 15 | | | | Orders: 12 | | |
| | +------------------+ | | +------------------+ | |
| | | Level 1 | | | | Level 1 | | |
| | | Price: 49999.99 | | | | Price: 50000.02 | | |
| | +------------------+ | | +------------------+ | |
| | | ... | | | | ... | | |
| +------------------------+ +------------------------+ |
+----------------------------------------------------------+Basic Usage
Creating an OrderBook
#include <lxdex/orderbook.hpp>
using namespace lxdex;
// Create order book with default capacity
OrderBook book("BTC-USD");
// Create with specified depth
OrderBook book("BTC-USD", {
.max_levels = 1000,
.use_simd = true,
.cache_aligned = true
});Subscribing to Order Book Updates
Client client({.endpoints = {"wss://api.lux.network/ws"}});
client.connect();
// Subscribe with callback
client.subscribe_orderbook("BTC-USD", [](const OrderBook& book) {
// Called on I/O thread with lock-free read access
auto [bid, ask] = book.best_bid_ask();
if (bid && ask) {
auto spread = ask->price - bid->price;
auto mid = (bid->price + ask->price) / 2;
std::cout << "Spread: " << spread.to_string()
<< " Mid: " << mid.to_string() << '\n';
}
});Accessing Book Data
// Best bid/ask (O(1))
auto [bid, ask] = book.best_bid_ask();
// Specific level (O(1))
auto level = book.bid_level(5); // 5th best bid
// All bids/asks (O(n))
for (const auto& level : book.bids()) {
std::cout << level.price.to_string() << " x "
<< level.quantity.to_string() << '\n';
}
// Top N levels
auto top_bids = book.top_bids(10);
auto top_asks = book.top_asks(10);Price Level Structure
namespace lxdex {
struct PriceLevel {
Price price;
Quantity quantity;
uint32_t order_count;
Timestamp last_update;
// Calculated fields
[[nodiscard]] Price notional() const noexcept {
return price * quantity;
}
};
// Cache-line aligned for performance
static_assert(sizeof(PriceLevel) <= 64);
static_assert(alignof(PriceLevel) == 64);
} // namespace lxdexLock-Free Implementation
Wait-Free Reads
// Multiple reader threads can access simultaneously
// No locks, no contention, deterministic latency
std::thread reader1([&book] {
while (running) {
auto bid = book.best_bid(); // Wait-free O(1)
process_bid(bid);
}
});
std::thread reader2([&book] {
while (running) {
auto spread = book.spread(); // Wait-free O(1)
process_spread(spread);
}
});Obstruction-Free Writes
// Single writer thread applies updates
// Uses atomic compare-and-swap for consistency
void apply_update(OrderBook& book, const BookUpdate& update) {
switch (update.type) {
case UpdateType::Set:
book.set_level(update.side, update.price, update.quantity);
break;
case UpdateType::Delete:
book.delete_level(update.side, update.price);
break;
}
}Memory Ordering
// SeqLock pattern for consistent snapshots
class OrderBook {
mutable std::atomic<uint64_t> sequence_{0};
std::array<PriceLevel, MAX_LEVELS> bids_;
std::array<PriceLevel, MAX_LEVELS> asks_;
public:
// Reader: retry until consistent read
std::optional<PriceLevel> best_bid() const noexcept {
PriceLevel level;
uint64_t seq1, seq2;
do {
seq1 = sequence_.load(std::memory_order_acquire);
if (seq1 & 1) continue; // Write in progress
level = bids_[0]; // Read
std::atomic_thread_fence(std::memory_order_acquire);
seq2 = sequence_.load(std::memory_order_relaxed);
} while (seq1 != seq2);
if (level.quantity.raw() == 0) return std::nullopt;
return level;
}
// Writer: increment sequence before and after
void set_level(Side side, Price price, Quantity qty) noexcept {
sequence_.fetch_add(1, std::memory_order_release); // Odd = writing
std::atomic_thread_fence(std::memory_order_release);
// Perform update...
std::atomic_thread_fence(std::memory_order_release);
sequence_.fetch_add(1, std::memory_order_release); // Even = done
}
};SIMD Price Matching
AVX-512 Implementation
#include <lxdex/simd/avx512.hpp>
// Find price level using SIMD (8 prices per comparison)
std::optional<size_t> find_price_level_avx512(
const std::array<int64_t, 1024>& prices,
int64_t target_price,
size_t count
) {
__m512i target = _mm512_set1_epi64(target_price);
for (size_t i = 0; i < count; i += 8) {
__m512i prices_vec = _mm512_load_epi64(&prices[i]);
__mmask8 mask = _mm512_cmpeq_epi64_mask(prices_vec, target);
if (mask != 0) {
return i + __builtin_ctz(mask);
}
}
return std::nullopt;
}NEON Implementation (ARM)
#include <lxdex/simd/neon.hpp>
// Find price level using NEON (2 prices per comparison)
std::optional<size_t> find_price_level_neon(
const std::array<int64_t, 1024>& prices,
int64_t target_price,
size_t count
) {
int64x2_t target = vdupq_n_s64(target_price);
for (size_t i = 0; i < count; i += 2) {
int64x2_t prices_vec = vld1q_s64(&prices[i]);
uint64x2_t cmp = vceqq_s64(prices_vec, target);
if (vgetq_lane_u64(cmp, 0)) return i;
if (vgetq_lane_u64(cmp, 1)) return i + 1;
}
return std::nullopt;
}Automatic Dispatch
// Runtime SIMD detection
OrderBook book("BTC-USD", {
.use_simd = true, // Auto-detect best SIMD
.simd_hint = SimdHint::Auto // Or: AVX512, AVX2, NEON, Scalar
});
// Manual dispatch
#if defined(__AVX512F__)
auto idx = find_price_level_avx512(prices, target, count);
#elif defined(__AVX2__)
auto idx = find_price_level_avx2(prices, target, count);
#elif defined(__ARM_NEON)
auto idx = find_price_level_neon(prices, target, count);
#else
auto idx = find_price_level_scalar(prices, target, count);
#endifBook Snapshots
Full Snapshot
// Get immutable snapshot (copy)
auto snapshot = book.snapshot();
// Process snapshot on different thread
std::thread processor([snapshot = std::move(snapshot)] {
for (const auto& level : snapshot.bids()) {
analyze_level(level);
}
});Incremental Updates
// Subscribe to incremental updates
client.subscribe_orderbook_incremental("BTC-USD",
// Snapshot callback (called first)
[&book](const OrderBookSnapshot& snap) {
book.apply_snapshot(snap);
},
// Delta callback (called for each update)
[&book](const OrderBookDelta& delta) {
book.apply_delta(delta);
}
);Delta Structure
struct OrderBookDelta {
std::string symbol;
uint64_t sequence;
Timestamp timestamp;
struct Change {
Side side;
Price price;
Quantity quantity; // 0 = delete level
};
std::vector<Change> changes;
};
// Apply delta
void OrderBook::apply_delta(const OrderBookDelta& delta) {
if (delta.sequence != expected_sequence_) {
// Gap detected - request full snapshot
return;
}
for (const auto& change : delta.changes) {
if (change.quantity.raw() == 0) {
delete_level(change.side, change.price);
} else {
set_level(change.side, change.price, change.quantity);
}
}
++expected_sequence_;
}Market Metrics
Spread and Mid-Price
// Spread (O(1))
auto spread = book.spread();
std::cout << "Spread: " << spread.to_string() << '\n';
// Mid-price (O(1))
auto mid = book.mid_price();
std::cout << "Mid: " << mid.to_string() << '\n';
// Weighted mid-price (O(1))
auto wmid = book.weighted_mid_price();
std::cout << "Weighted Mid: " << wmid.to_string() << '\n';Market Depth
// Total quantity at price levels
auto bid_depth = book.bid_depth(10); // Sum of top 10 bid levels
auto ask_depth = book.ask_depth(10);
// Cumulative depth up to price
auto depth_to_price = book.cumulative_depth_to_price(
Side::Bid,
Price::from_string("49000.00")
);
// Depth required to move price by X%
auto depth_to_move = book.depth_to_move_price(
Side::Ask,
0.01 // 1%
);Order Book Imbalance
// Imbalance ratio: (bid_qty - ask_qty) / (bid_qty + ask_qty)
auto imbalance = book.imbalance(5); // Top 5 levels
// Returns: [-1.0, 1.0], positive = more bids
// Volume-weighted imbalance
auto vwap_imbalance = book.vwap_imbalance(5);Order Book Views
Aggregated View
// Aggregate by price bucket
auto aggregated = book.aggregate(Price::from_string("10.00")); // $10 buckets
for (const auto& [price_bucket, total_qty] : aggregated.bids()) {
std::cout << price_bucket.to_string() << " : "
<< total_qty.to_string() << '\n';
}Filtered View
// Filter by minimum quantity
auto large_orders = book.filter([](const PriceLevel& level) {
return level.quantity >= Quantity::from_string("10.0");
});
// Filter by price range
auto range_view = book.price_range(
Price::from_string("49000.00"),
Price::from_string("51000.00")
);Cache-Friendly Design
Memory Layout
// Cache-line aligned price levels
struct alignas(64) PriceLevel {
int64_t price; // 8 bytes
int64_t quantity; // 8 bytes
uint32_t orders; // 4 bytes
uint32_t flags; // 4 bytes
int64_t timestamp; // 8 bytes
char padding[32]; // Pad to 64 bytes
};
// Contiguous storage for cache efficiency
class OrderBook {
// Hot data (accessed every tick)
alignas(64) std::array<PriceLevel, 16> hot_bids_;
alignas(64) std::array<PriceLevel, 16> hot_asks_;
// Warm data (accessed occasionally)
alignas(64) std::vector<PriceLevel> bids_;
alignas(64) std::vector<PriceLevel> asks_;
};Prefetching
// Manual prefetch for known access patterns
void OrderBook::prefetch_top_levels() const noexcept {
// Prefetch best bid/ask into L1 cache
__builtin_prefetch(&hot_bids_[0], 0, 3); // Read, high locality
__builtin_prefetch(&hot_asks_[0], 0, 3);
// Prefetch next levels into L2 cache
__builtin_prefetch(&hot_bids_[1], 0, 2);
__builtin_prefetch(&hot_asks_[1], 0, 2);
}
// Use before accessing book
book.prefetch_top_levels();
auto [bid, ask] = book.best_bid_ask();Benchmarks
Microbenchmarks
#include <benchmark/benchmark.h>
#include <lxdex/orderbook.hpp>
static void BM_BestBidAsk(benchmark::State& state) {
lxdex::OrderBook book("BTC-USD");
// Setup book with 1000 levels...
for (auto _ : state) {
auto [bid, ask] = book.best_bid_ask();
benchmark::DoNotOptimize(bid);
benchmark::DoNotOptimize(ask);
}
}
BENCHMARK(BM_BestBidAsk);
static void BM_ApplyDelta(benchmark::State& state) {
lxdex::OrderBook book("BTC-USD");
lxdex::OrderBookDelta delta{/* ... */};
for (auto _ : state) {
book.apply_delta(delta);
}
}
BENCHMARK(BM_ApplyDelta);
BENCHMARK_MAIN();Performance Numbers
| Operation | Latency (p50) | Latency (p99) | Notes |
|---|---|---|---|
best_bid_ask() | 12ns | 45ns | Lock-free |
spread() | 15ns | 50ns | Lock-free |
mid_price() | 18ns | 55ns | Lock-free |
top_bids(10) | 89ns | 210ns | Copy 10 levels |
apply_delta() | 45ns | 120ns | Single update |
find_price_simd() | 8ns | 25ns | AVX-512, 1000 levels |
Thread Safety Summary
| Operation | Read Thread | Write Thread |
|---|---|---|
best_bid() | Wait-free | - |
best_ask() | Wait-free | - |
spread() | Wait-free | - |
bids() | Wait-free | - |
asks() | Wait-free | - |
snapshot() | Wait-free | - |
set_level() | - | Single writer |
delete_level() | - | Single writer |
apply_delta() | - | Single writer |
Example: Market Making
#include <lxdex/client.hpp>
#include <lxdex/orderbook.hpp>
class MarketMaker {
Client& client_;
OrderBook book_;
Price spread_target_{100000}; // 0.001 in fixed-point
Quantity quote_size_{1'00000000}; // 1.0
public:
MarketMaker(Client& client, std::string_view symbol)
: client_(client), book_(symbol) {}
void on_book_update(const OrderBook& book) {
book_ = book; // Lock-free copy
requote();
}
void requote() {
auto mid = book_.mid_price();
if (!mid) return;
Price bid_price = *mid - spread_target_ / 2;
Price ask_price = *mid + spread_target_ / 2;
// Cancel existing orders
client_.cancel_all_orders();
// Place new quotes
client_.place_order(OrderBuilder()
.symbol(book_.symbol())
.side(Side::Buy)
.type(OrderType::Limit)
.price(bid_price)
.quantity(quote_size_)
.build());
client_.place_order(OrderBuilder()
.symbol(book_.symbol())
.side(Side::Sell)
.type(OrderType::Limit)
.price(ask_price)
.quantity(quote_size_)
.build());
}
};
int main() {
Client client({.endpoints = {"wss://api.lux.network/ws"}});
client.connect();
MarketMaker mm(client, "BTC-USD");
client.subscribe_orderbook("BTC-USD", [&mm](const OrderBook& book) {
mm.on_book_update(book);
});
// Run forever
std::this_thread::sleep_for(std::chrono::hours{24 * 365});
}