use std::collections::BTreeMap;
use std::time::Instant;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[allow(dead_code)]
struct MarketDepthData {
unified_symbol: String,
bids: Vec<(f64, i32)>,
asks: Vec<(f64, i32)>,
exch_timestamp: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct OrderedF64(f64);
impl Eq for OrderedF64 {}
impl PartialOrd for OrderedF64 {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedF64 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.total_cmp(&other.0)
}
}
#[derive(Debug)]
struct OrderBook {
bid_order_book: BTreeMap<OrderedF64, i32>,
ask_order_book: BTreeMap<OrderedF64, i32>,
exch_timestamp: u64,
}
impl OrderBook {
fn new() -> Self {
Self {
bid_order_book: BTreeMap::new(),
ask_order_book: BTreeMap::new(),
exch_timestamp: 0,
}
}
fn update(&mut self, data: &MarketDepthData) -> bool {
if self.exch_timestamp > data.exch_timestamp {
return false;
}
self.bid_order_book.clear();
self.ask_order_book.clear();
for (price, size) in &data.bids {
if price.is_finite() && *price > 0.0 && *size > 0 {
self.bid_order_book.insert(OrderedF64(*price), *size);
}
}
for (price, size) in &data.asks {
if price.is_finite() && *price > 0.0 && *size > 0 {
self.ask_order_book.insert(OrderedF64(*price), *size);
}
}
self.exch_timestamp = data.exch_timestamp;
true
}
fn get_best_bid(&self) -> Option<f64> {
self.bid_order_book
.iter()
.next_back()
.map(|(OrderedF64(p), _)| *p)
}
fn get_best_ask(&self) -> Option<f64> {
self.ask_order_book
.iter()
.next()
.map(|(OrderedF64(p), _)| *p)
}
fn calculate_mid_price(&self) -> Option<f64> {
let bid = self.get_best_bid()?;
let ask = self.get_best_ask()?;
Some((bid + ask) / 2.0)
}
fn calculate_depth_at_price(&self, price: f64, is_bid: bool) -> usize {
if is_bid {
self.bid_order_book
.iter()
.rev()
.take_while(|(OrderedF64(p), _)| *p >= price)
.map(|(_, s)| *s as usize)
.sum()
} else {
self.ask_order_book
.iter()
.take_while(|(OrderedF64(p), _)| *p <= price)
.map(|(_, s)| *s as usize)
.sum()
}
}
fn calculate_market_impact(
&self,
quantity: usize,
is_buy: bool,
) -> Option<(f64, f64, usize, bool)> {
let mut remaining = quantity;
let mut total_cost = 0.0;
let mut impact_price = 0.0;
let mut executed = 0;
if is_buy {
for (OrderedF64(price), size) in self.ask_order_book.iter() {
let fill = remaining.min(*size as usize);
total_cost += fill as f64 * *price;
remaining -= fill;
executed += fill;
impact_price = *price;
if remaining == 0 {
break;
}
}
} else {
for (OrderedF64(price), size) in self.bid_order_book.iter().rev() {
let fill = remaining.min(*size as usize);
total_cost += fill as f64 * *price;
remaining -= fill;
executed += fill;
impact_price = *price;
if remaining == 0 {
break;
}
}
}
if executed > 0 {
let avg_price = total_cost / executed as f64;
Some((avg_price, impact_price, executed, executed >= quantity))
} else {
None
}
}
}
fn create_test_market_depth(symbol: &str, timestamp: u64, levels: usize) -> MarketDepthData {
let bids: Vec<(f64, i32)> = (0..levels)
.map(|i| (100.0 - i as f64, 10 + i as i32 * 5))
.collect();
let asks: Vec<(f64, i32)> = (0..levels)
.map(|i| (101.0 + i as f64, 5 + i as i32 * 5))
.collect();
MarketDepthData {
unified_symbol: symbol.to_string(),
bids,
asks,
exch_timestamp: timestamp,
}
}
#[allow(dead_code)]
fn format_duration(ms: f64) -> String {
if ms < 1.0 {
format!("{:.2}μs", ms * 1000.0)
} else {
format!("{:.2}ms", ms)
}
}
fn format_throughput(count: u64, ms: f64) -> String {
let per_sec = count as f64 / (ms / 1000.0);
if per_sec >= 1_000_000.0 {
format!("{:.1}M/s", per_sec / 1_000_000.0)
} else if per_sec >= 1_000.0 {
format!("{:.1}K/s", per_sec / 1_000.0)
} else {
format!("{:.0}/s", per_sec)
}
}
fn run_update_bench(total_updates: u64, num_symbols: usize, levels: usize) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let start = Instant::now();
let mut book = OrderBook::new();
for i in 0..total_updates {
let sym_idx = (i as usize) % num_symbols;
let symbol = format!("SYM_{:04}", sym_idx);
let data = create_test_market_depth(&symbol, 1_000_000 + i, levels);
let _ = book.update(&data);
}
if run == 0 {
continue; }
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn run_best_bid_ask_bench(
total_updates: u64,
num_symbols: usize,
levels: usize,
) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let mut books = Vec::with_capacity(num_symbols);
for s in 0..num_symbols {
let symbol = format!("SYM_{:04}", s);
let data = create_test_market_depth(&symbol, 1_000_000, levels);
let mut book = OrderBook::new();
book.update(&data);
books.push(book);
}
let start = Instant::now();
let mut _bid_sum = 0.0;
let mut _ask_sum = 0.0;
for i in 0..total_updates {
let book = &books[(i as usize) % num_symbols];
if let Some(b) = book.get_best_bid() {
_bid_sum += b;
}
if let Some(a) = book.get_best_ask() {
_ask_sum += a;
}
}
if run == 0 {
continue;
}
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn run_mid_price_bench(total_updates: u64, num_symbols: usize, levels: usize) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let mut books = Vec::with_capacity(num_symbols);
for s in 0..num_symbols {
let symbol = format!("SYM_{:04}", s);
let data = create_test_market_depth(&symbol, 1_000_000, levels);
let mut book = OrderBook::new();
book.update(&data);
books.push(book);
}
let start = Instant::now();
let mut _mid_sum = 0.0;
for i in 0..total_updates {
let book = &books[(i as usize) % num_symbols];
if let Some(m) = book.calculate_mid_price() {
_mid_sum += m;
}
}
if run == 0 {
continue;
}
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn run_depth_bench(total_updates: u64, num_symbols: usize, levels: usize) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let mut books = Vec::with_capacity(num_symbols);
for s in 0..num_symbols {
let symbol = format!("SYM_{:04}", s);
let data = create_test_market_depth(&symbol, 1_000_000, levels);
let mut book = OrderBook::new();
book.update(&data);
books.push(book);
}
let start = Instant::now();
let mut _depth_sum = 0usize;
for i in 0..total_updates {
let book = &books[(i as usize) % num_symbols];
_depth_sum += book.calculate_depth_at_price(95.0, true);
}
if run == 0 {
continue;
}
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn run_market_impact_bench(
total_updates: u64,
num_symbols: usize,
levels: usize,
) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let mut books = Vec::with_capacity(num_symbols);
for s in 0..num_symbols {
let symbol = format!("SYM_{:04}", s);
let data = create_test_market_depth(&symbol, 1_000_000, levels);
let mut book = OrderBook::new();
book.update(&data);
books.push(book);
}
let start = Instant::now();
let mut _impact_sum = 0.0;
for i in 0..total_updates {
let book = &books[(i as usize) % num_symbols];
if let Some((avg, _, _, _)) = book.calculate_market_impact(50, true) {
_impact_sum += avg;
}
}
if run == 0 {
continue;
}
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn run_deserialization_bench(
total_updates: u64,
num_symbols: usize,
levels: usize,
) -> (f64, f64, f64) {
let mut times = Vec::with_capacity(5);
for run in 0..6 {
let serialized: Vec<Vec<u8>> = {
let mut v = Vec::with_capacity(num_symbols);
for s in 0..num_symbols {
let symbol = format!("SYM_{:04}", s);
let data = create_test_market_depth(&symbol, 1_000_000, levels);
match serde_json::to_vec(&data) {
Ok(bytes) => v.push(bytes),
Err(_) => continue,
}
}
v
};
let start = Instant::now();
for i in 0..total_updates {
let sym_idx = (i as usize) % serialized.len();
let mut msg = serialized[sym_idx].clone();
let _ = simd_json::serde::from_slice::<MarketDepthData>(&mut msg);
}
if run == 0 {
continue;
}
times.push(start.elapsed().as_secs_f64() * 1000.0);
}
times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let avg = times.iter().sum::<f64>() / times.len() as f64;
let p50 = times[2];
let max = *times.last().unwrap();
(avg, p50, max)
}
fn main() {
println!("\n╔══════════════════════════════════════════════════════════════════════╗");
println!("║ LIMIT_ORDER_BOOK BENCHMARK SUITE ║");
println!("╚══════════════════════════════════════════════════════════════════════╝\n");
let configs = [
("Low volume", 1_000u64, 50, 5),
("Medium volume", 10_000u64, 200, 10),
("High volume", 100_000u64, 500, 20),
("Max volume", 1_000_000u64, 1000, 50),
];
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [1] OrderBook Update (clear + rebuild BTreeMap) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Updates │ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_update_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [2] Best Bid/Ask Retrieval (BTreeMap iter/rev) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Lookups │ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_best_bid_ask_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates * 2, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [3] Mid Price Calculation ((bid + ask) / 2) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Lookups │ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_mid_price_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [4] Depth Calculation (take_while + sum) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Lookups │ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_depth_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [5] Market Impact (book walk for avg execution price) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Lookups │ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_market_impact_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("┌──────────────────────────────────────────────────────────────────────┐");
println!("│ [6] JSON Deserialization (bytes → MarketDepthData, simd-json) │");
println!("├─────────────────────┬──────────┬─────────┬─────────┬─────────┬────────┤");
println!("│ Test │ Deserials│ Symbols │ Avg ms │ P50 ms│ Max ms│");
println!("├─────────────────────┼──────────┼─────────┼─────────┼─────────┼────────┤");
for (name, updates, syms, levels) in configs {
let (avg, p50, max) = run_deserialization_bench(updates, syms, levels);
println!(
"│ {:<19} │ {:>8} │ {:>7} │ {:>7.2} │ {:>7.2} │ {:>7.2} │",
name, updates, syms, avg, p50, max
);
println!(
"│ → Throughput: {:>14} │",
format_throughput(updates, avg)
);
}
println!("└──────────────────────────────────────────────────────────────────────┘\n");
println!("System: Ubuntu 24.04.4 LTS | AMD Ryzen 7 5800H (16 cores, 13Gi RAM) | Rust 1.94.0");
println!("Tested: 2026-04-10 UTC");
println!("\nNote: All benchmarks are CPU-only (no NATS, DB, or I/O operations).");
println!("Run with --release flag for accurate performance measurements.\n");
}