diff --git a/benches/price_level/checked_arithmetic.rs b/benches/price_level/checked_arithmetic.rs new file mode 100644 index 0000000..70d57a2 --- /dev/null +++ b/benches/price_level/checked_arithmetic.rs @@ -0,0 +1,158 @@ +use criterion::Criterion; +use pricelevel::{ + Hash32, Id, MatchResult, OrderType, Price, PriceLevel, Quantity, Side, TimeInForce, + TimestampMs, Trade, UuidGenerator, +}; +use std::hint::black_box; +use uuid::Uuid; + +/// Register benchmarks for checked arithmetic APIs and error-path performance. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Checked Arithmetic"); + + let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + let id_gen = UuidGenerator::new(namespace); + + // Benchmark total_quantity (checked add of visible + hidden) + group.bench_function("total_quantity", |b| { + let level = setup_mixed_level(200); + b.iter(|| { + black_box(level.total_quantity().unwrap()); + }) + }); + + // Benchmark executed_quantity on a MatchResult with many trades + group.bench_function("executed_quantity_many_trades", |b| { + let level = setup_standard_level(100); + let result = level.match_order(500, Id::from_u64(9999), &id_gen); + b.iter(|| { + black_box(result.executed_quantity().unwrap()); + }) + }); + + // Benchmark executed_value on a MatchResult with many trades + group.bench_function("executed_value_many_trades", |b| { + let level = setup_standard_level(100); + let result = level.match_order(500, Id::from_u64(9999), &id_gen); + b.iter(|| { + black_box(result.executed_value().unwrap()); + }) + }); + + // Benchmark average_price computation + group.bench_function("average_price", |b| { + let level = setup_standard_level(100); + let result = level.match_order(500, Id::from_u64(9999), &id_gen); + b.iter(|| { + black_box(result.average_price().unwrap()); + }) + }); + + // Benchmark MatchResult::new + add_trade sequence + group.bench_function("match_result_add_trades", |b| { + b.iter(|| { + let mut mr = MatchResult::new(Id::from_u64(1), 1000); + for i in 0..50_u64 { + let trade = Trade::new( + Id::from_u64(100 + i), + Id::from_u64(1), + Id::from_u64(i), + Price::new(10000), + Quantity::new(20), + Side::Buy, + ); + let _ = mr.add_trade(trade); + } + black_box(mr); + }) + }); + + // Benchmark empty match result (zero trades) arithmetic + group.bench_function("empty_match_arithmetic", |b| { + let empty_level = PriceLevel::new(10000); + let result = empty_level.match_order(100, Id::from_u64(1), &id_gen); + b.iter(|| { + black_box(result.executed_quantity().unwrap()); + black_box(result.executed_value().unwrap()); + black_box(result.average_price().unwrap()); + }) + }); + + // Scaling: executed_quantity with increasing trade counts + for trade_count in [10, 50, 100].iter() { + let tc = *trade_count; + group.bench_function(format!("executed_quantity_{tc}_trades"), |b| { + let level = setup_standard_level(tc); + let result = level.match_order(tc * 10, Id::from_u64(9999), &id_gen); + b.iter(|| { + black_box(result.executed_quantity().unwrap()); + }) + }); + } + + group.finish(); +} + +/// Set up a price level with standard orders. +fn setup_standard_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::Standard { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }); + } + level +} + +/// Set up a price level with a mix of standard, iceberg, and reserve orders. +fn setup_mixed_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + let order = match i % 3 { + 0 => OrderType::Standard { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(100), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + 1 => OrderType::IcebergOrder { + id: Id::from_u64(i), + price: Price::new(10000), + visible_quantity: Quantity::new(20), + hidden_quantity: Quantity::new(80), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + _ => OrderType::ReserveOrder { + id: Id::from_u64(i), + price: Price::new(10000), + visible_quantity: Quantity::new(15), + hidden_quantity: Quantity::new(60), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + replenish_threshold: Quantity::new(5), + replenish_amount: Some(Quantity::new(10)), + auto_replenish: true, + extra_fields: (), + }, + }; + level.add_order(order); + } + level +} diff --git a/benches/price_level/lifecycle.rs b/benches/price_level/lifecycle.rs new file mode 100644 index 0000000..dd644bf --- /dev/null +++ b/benches/price_level/lifecycle.rs @@ -0,0 +1,190 @@ +use criterion::Criterion; +use pricelevel::{ + Hash32, Id, OrderType, OrderUpdate, Price, PriceLevel, Quantity, Side, TimeInForce, + TimestampMs, UuidGenerator, +}; +use std::hint::black_box; +use uuid::Uuid; + +/// Register benchmarks for full order lifecycle: add → update → cancel → match → stats. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Lifecycle"); + + let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + let id_gen = UuidGenerator::new(namespace); + + // Full lifecycle: add → update quantity → match → cancel remainder → stats + group.bench_function("full_lifecycle", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + // Phase 1: Add 100 orders + for i in 0..100_u64 { + level.add_order(create_standard_order(i, 10000, 50)); + } + + // Phase 2: Update quantity on 20 orders + for i in 10..30_u64 { + let _ = level.update_order(OrderUpdate::UpdateQuantity { + order_id: Id::from_u64(i), + new_quantity: Quantity::new(25), + }); + } + + // Phase 3: Cancel 10 orders + for i in 50..60_u64 { + let _ = level.update_order(OrderUpdate::Cancel { + order_id: Id::from_u64(i), + }); + } + + // Phase 4: Match 500 units + let result = level.match_order(500, Id::from_u64(9999), &id_gen); + let _ = black_box(result.executed_quantity()); + + // Phase 5: Read stats + let stats = level.stats(); + black_box(stats.orders_added()); + black_box(stats.orders_removed()); + black_box(stats.orders_executed()); + black_box(stats.quantity_executed()); + }) + }); + + // Add-heavy lifecycle: 80% add, 20% match + group.bench_function("add_heavy_lifecycle", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + for i in 0..200_u64 { + if i % 5 == 0 { + // Match every 5th iteration + black_box(level.match_order(10, Id::from_u64(10000 + i), &id_gen)); + } else { + // Add order + level.add_order(create_standard_order(i, 10000, 20)); + } + } + black_box(level.order_count()); + }) + }); + + // Cancel-heavy lifecycle: many adds followed by many cancels + group.bench_function("cancel_heavy_lifecycle", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + // Add 200 orders + for i in 0..200_u64 { + level.add_order(create_standard_order(i, 10000, 10)); + } + + // Cancel 150 of them + for i in 0..150_u64 { + let _ = level.update_order(OrderUpdate::Cancel { + order_id: Id::from_u64(i), + }); + } + + black_box(level.order_count()); + black_box(level.visible_quantity()); + }) + }); + + // Match-drain lifecycle: fill level then drain completely + group.bench_function("match_drain_lifecycle", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + // Add 100 orders with quantity 10 each = 1000 total + for i in 0..100_u64 { + level.add_order(create_standard_order(i, 10000, 10)); + } + + // Drain completely + let result = level.match_order(1000, Id::from_u64(9999), &id_gen); + black_box(result.is_complete()); + black_box(result.filled_order_ids().len()); + }) + }); + + // Replace-heavy lifecycle: add orders then replace them + group.bench_function("replace_heavy_lifecycle", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + // Add 100 orders + for i in 0..100_u64 { + level.add_order(create_standard_order(i, 10000, 50)); + } + + // Replace 50 orders (same price = quantity update) + for i in 0..50_u64 { + let _ = level.update_order(OrderUpdate::Replace { + order_id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(30), + side: Side::Buy, + }); + } + + // Replace 25 orders (different price = removal) + for i in 50..75_u64 { + let _ = level.update_order(OrderUpdate::Replace { + order_id: Id::from_u64(i), + price: Price::new(10100), + quantity: Quantity::new(30), + side: Side::Buy, + }); + } + + black_box(level.order_count()); + }) + }); + + // Lifecycle with stats query interleaved + group.bench_function("lifecycle_with_stats_queries", |b| { + b.iter(|| { + let level = PriceLevel::new(10000); + + for i in 0..100_u64 { + level.add_order(create_standard_order(i, 10000, 20)); + + // Query stats every 10 adds + if i % 10 == 0 { + black_box(level.visible_quantity()); + black_box(level.hidden_quantity()); + let _ = black_box(level.total_quantity()); + black_box(level.order_count()); + let stats = level.stats(); + black_box(stats.orders_added()); + } + } + + // Match and query stats + let result = level.match_order(100, Id::from_u64(9999), &id_gen); + let _ = black_box(result.executed_quantity()); + + let stats = level.stats(); + black_box(stats.average_execution_price()); + black_box(stats.average_waiting_time()); + black_box(stats.time_since_last_execution()); + }) + }); + + group.finish(); +} + +/// Create a standard limit order. +fn create_standard_order(id: u64, price: u128, quantity: u64) -> OrderType<()> { + OrderType::Standard { + id: Id::from_u64(id), + price: Price::new(price), + quantity: Quantity::new(quantity), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + id), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + } +} diff --git a/benches/price_level/mod.rs b/benches/price_level/mod.rs index a62f9a9..bf8105a 100644 --- a/benches/price_level/mod.rs +++ b/benches/price_level/mod.rs @@ -1,7 +1,13 @@ // benches/price_level/mod.rs pub mod add_orders; +pub mod checked_arithmetic; +pub mod lifecycle; pub mod match_orders; pub mod mixed_operations; +pub mod newtypes; +pub mod serialization; +pub mod snapshot_recovery; +pub mod special_orders; pub mod update_orders; // Import common benchmarks into the main bench group @@ -10,4 +16,10 @@ pub fn register_benchmarks(c: &mut criterion::Criterion) { match_orders::register_benchmarks(c); update_orders::register_benchmarks(c); mixed_operations::register_benchmarks(c); + snapshot_recovery::register_benchmarks(c); + checked_arithmetic::register_benchmarks(c); + serialization::register_benchmarks(c); + newtypes::register_benchmarks(c); + special_orders::register_benchmarks(c); + lifecycle::register_benchmarks(c); } diff --git a/benches/price_level/newtypes.rs b/benches/price_level/newtypes.rs new file mode 100644 index 0000000..390cd27 --- /dev/null +++ b/benches/price_level/newtypes.rs @@ -0,0 +1,132 @@ +use criterion::Criterion; +use pricelevel::{Id, Price, Quantity, TimestampMs}; +use std::hint::black_box; +use std::str::FromStr; + +/// Register benchmarks for domain newtype construction, parsing, and Display/FromStr. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Newtypes"); + + // --- Price --- + + group.bench_function("price_new", |b| { + b.iter(|| { + black_box(Price::new(10000)); + }) + }); + + group.bench_function("price_display_fromstr", |b| { + let p = Price::new(12345); + let s = p.to_string(); + b.iter(|| { + let displayed = p.to_string(); + let parsed = Price::from_str(&s).unwrap(); + black_box((displayed, parsed)); + }) + }); + + group.bench_function("price_serde_roundtrip", |b| { + let p = Price::new(99999); + let json = serde_json::to_string(&p).unwrap(); + b.iter(|| { + let ser = serde_json::to_string(&p).unwrap(); + let de: Price = serde_json::from_str(&json).unwrap(); + black_box((ser, de)); + }) + }); + + // --- Quantity --- + + group.bench_function("quantity_new", |b| { + b.iter(|| { + black_box(Quantity::new(500)); + }) + }); + + group.bench_function("quantity_display_fromstr", |b| { + let q = Quantity::new(500); + let s = q.to_string(); + b.iter(|| { + let displayed = q.to_string(); + let parsed = Quantity::from_str(&s).unwrap(); + black_box((displayed, parsed)); + }) + }); + + group.bench_function("quantity_serde_roundtrip", |b| { + let q = Quantity::new(12345); + let json = serde_json::to_string(&q).unwrap(); + b.iter(|| { + let ser = serde_json::to_string(&q).unwrap(); + let de: Quantity = serde_json::from_str(&json).unwrap(); + black_box((ser, de)); + }) + }); + + // --- TimestampMs --- + + group.bench_function("timestamp_new", |b| { + b.iter(|| { + black_box(TimestampMs::new(1_616_823_000_000)); + }) + }); + + group.bench_function("timestamp_display_fromstr", |b| { + let ts = TimestampMs::new(1_616_823_000_000); + let s = ts.to_string(); + b.iter(|| { + let displayed = ts.to_string(); + let parsed = TimestampMs::from_str(&s).unwrap(); + black_box((displayed, parsed)); + }) + }); + + // --- Id --- + + group.bench_function("id_from_u64", |b| { + b.iter(|| { + black_box(Id::from_u64(42)); + }) + }); + + group.bench_function("id_from_uuid", |b| { + let uuid = uuid::Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + b.iter(|| { + black_box(Id::from_uuid(uuid)); + }) + }); + + group.bench_function("id_display_fromstr", |b| { + let id = Id::from_u64(42); + let s = id.to_string(); + b.iter(|| { + let displayed = id.to_string(); + let parsed = Id::from_str(&s).unwrap(); + black_box((displayed, parsed)); + }) + }); + + group.bench_function("id_serde_roundtrip", |b| { + let id = Id::from_u64(999); + let json = serde_json::to_string(&id).unwrap(); + b.iter(|| { + let ser = serde_json::to_string(&id).unwrap(); + let de: Id = serde_json::from_str(&json).unwrap(); + black_box((ser, de)); + }) + }); + + // Batch construction scaling + for count in [100, 1000, 10000].iter() { + let n = *count; + group.bench_function(format!("id_from_u64_batch_{n}"), |b| { + b.iter(|| { + for i in 0..n as u64 { + black_box(Id::from_u64(i)); + } + }) + }); + } + + group.finish(); +} diff --git a/benches/price_level/serialization.rs b/benches/price_level/serialization.rs new file mode 100644 index 0000000..ddad44f --- /dev/null +++ b/benches/price_level/serialization.rs @@ -0,0 +1,142 @@ +use criterion::Criterion; +use pricelevel::{ + Hash32, Id, MatchResult, OrderType, Price, PriceLevel, Quantity, Side, TimeInForce, + TimestampMs, Trade, TradeList, UuidGenerator, +}; +use std::hint::black_box; +use std::str::FromStr; +use uuid::Uuid; + +/// Register benchmarks for Trade, TradeList, and MatchResult serialization roundtrips. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Serialization"); + + // --- Trade benchmarks --- + + let trade = Trade::with_timestamp( + Id::from_u64(100), + Id::from_u64(1), + Id::from_u64(2), + Price::new(9500), + Quantity::new(42), + Side::Buy, + TimestampMs::new(1_616_823_000_000), + ); + + // Trade Display + group.bench_function("trade_display", |b| { + b.iter(|| { + black_box(trade.to_string()); + }) + }); + + // Trade FromStr + let trade_str = trade.to_string(); + group.bench_function("trade_from_str", |b| { + b.iter(|| { + black_box(Trade::from_str(&trade_str).unwrap()); + }) + }); + + // Trade serde JSON serialize + group.bench_function("trade_serde_serialize", |b| { + b.iter(|| { + black_box(serde_json::to_string(&trade).unwrap()); + }) + }); + + // Trade serde JSON deserialize + let trade_json = serde_json::to_string(&trade).unwrap(); + group.bench_function("trade_serde_deserialize", |b| { + b.iter(|| { + black_box(serde_json::from_str::(&trade_json).unwrap()); + }) + }); + + // --- TradeList benchmarks --- + + let mut trade_list = TradeList::new(); + for i in 0..20_u64 { + trade_list.add(Trade::with_timestamp( + Id::from_u64(100 + i), + Id::from_u64(1), + Id::from_u64(i), + Price::new(10000), + Quantity::new(10), + Side::Buy, + TimestampMs::new(1_616_823_000_000 + i), + )); + } + + // TradeList Display + group.bench_function("trade_list_display_20", |b| { + b.iter(|| { + black_box(trade_list.to_string()); + }) + }); + + // TradeList FromStr + let tl_str = trade_list.to_string(); + group.bench_function("trade_list_from_str_20", |b| { + b.iter(|| { + black_box(TradeList::from_str(&tl_str).unwrap()); + }) + }); + + // --- MatchResult benchmarks --- + + let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + let id_gen = UuidGenerator::new(namespace); + let level = setup_standard_level(50); + let match_result = level.match_order(200, Id::from_u64(9999), &id_gen); + + // MatchResult Display + group.bench_function("match_result_display", |b| { + b.iter(|| { + black_box(match_result.to_string()); + }) + }); + + // MatchResult FromStr + let mr_str = match_result.to_string(); + group.bench_function("match_result_from_str", |b| { + b.iter(|| { + black_box(MatchResult::from_str(&mr_str).unwrap()); + }) + }); + + // MatchResult serde JSON serialize + group.bench_function("match_result_serde_serialize", |b| { + b.iter(|| { + black_box(serde_json::to_string(&match_result).unwrap()); + }) + }); + + // MatchResult serde JSON deserialize + let mr_json = serde_json::to_string(&match_result).unwrap(); + group.bench_function("match_result_serde_deserialize", |b| { + b.iter(|| { + black_box(serde_json::from_str::(&mr_json).unwrap()); + }) + }); + + group.finish(); +} + +/// Set up a price level with standard orders. +fn setup_standard_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::Standard { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }); + } + level +} diff --git a/benches/price_level/snapshot_recovery.rs b/benches/price_level/snapshot_recovery.rs new file mode 100644 index 0000000..196080c --- /dev/null +++ b/benches/price_level/snapshot_recovery.rs @@ -0,0 +1,129 @@ +use criterion::Criterion; +use pricelevel::{ + Hash32, Id, OrderType, Price, PriceLevel, PriceLevelSnapshotPackage, Quantity, Side, + TimeInForce, TimestampMs, +}; +use std::hint::black_box; + +/// Register benchmarks for snapshot checksum creation, JSON roundtrip, and recovery. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Snapshot Recovery"); + + // Benchmark snapshot_package creation (includes checksum computation) + group.bench_function("snapshot_package_creation", |b| { + let price_level = setup_mixed_level(200); + b.iter(|| { + black_box(price_level.snapshot_package().unwrap()); + }) + }); + + // Benchmark snapshot to JSON serialization + group.bench_function("snapshot_to_json", |b| { + let price_level = setup_mixed_level(200); + b.iter(|| { + black_box(price_level.snapshot_to_json().unwrap()); + }) + }); + + // Benchmark JSON deserialization + validation + group.bench_function("snapshot_from_json_validate", |b| { + let price_level = setup_mixed_level(200); + let json = price_level.snapshot_to_json().unwrap(); + b.iter(|| { + let pkg = PriceLevelSnapshotPackage::from_json(&json).unwrap(); + pkg.validate().unwrap(); + black_box(pkg); + }) + }); + + // Benchmark full roundtrip: snapshot → JSON → restore PriceLevel + group.bench_function("snapshot_full_roundtrip", |b| { + let price_level = setup_mixed_level(200); + let json = price_level.snapshot_to_json().unwrap(); + b.iter(|| { + let restored = PriceLevel::from_snapshot_json(&json).unwrap(); + black_box(restored); + }) + }); + + // Benchmark snapshot_package validation only (checksum verify) + group.bench_function("snapshot_checksum_validate", |b| { + let price_level = setup_mixed_level(200); + let pkg = price_level.snapshot_package().unwrap(); + let json = pkg.to_json().unwrap(); + let deserialized = PriceLevelSnapshotPackage::from_json(&json).unwrap(); + b.iter(|| { + deserialized.validate().unwrap(); + black_box(()); + }) + }); + + // Benchmark from_snapshot_package (validation + reconstruction) + group.bench_function("from_snapshot_package", |b| { + let price_level = setup_mixed_level(200); + let json = price_level.snapshot_to_json().unwrap(); + b.iter(|| { + let pkg = PriceLevelSnapshotPackage::from_json(&json).unwrap(); + let restored = PriceLevel::from_snapshot_package(pkg).unwrap(); + black_box(restored); + }) + }); + + // Scaling: snapshot creation with increasing order counts + for order_count in [50, 200, 500].iter() { + group.bench_function(format!("snapshot_package_{order_count}_orders"), |b| { + let price_level = setup_mixed_level(*order_count); + b.iter(|| { + black_box(price_level.snapshot_package().unwrap()); + }) + }); + } + + group.finish(); +} + +/// Set up a price level with a mix of standard, iceberg, and reserve orders. +fn setup_mixed_level(order_count: u64) -> PriceLevel { + let price_level = PriceLevel::new(10000); + for i in 0..order_count { + let order = match i % 3 { + 0 => OrderType::Standard { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(100), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + 1 => OrderType::IcebergOrder { + id: Id::from_u64(i), + price: Price::new(10000), + visible_quantity: Quantity::new(20), + hidden_quantity: Quantity::new(80), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + _ => OrderType::ReserveOrder { + id: Id::from_u64(i), + price: Price::new(10000), + visible_quantity: Quantity::new(15), + hidden_quantity: Quantity::new(60), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + replenish_threshold: Quantity::new(5), + replenish_amount: Some(Quantity::new(10)), + auto_replenish: true, + extra_fields: (), + }, + }; + price_level.add_order(order); + } + price_level +} diff --git a/benches/price_level/special_orders.rs b/benches/price_level/special_orders.rs new file mode 100644 index 0000000..176d7a0 --- /dev/null +++ b/benches/price_level/special_orders.rs @@ -0,0 +1,202 @@ +use criterion::{BenchmarkId, Criterion}; +use pricelevel::{ + Hash32, Id, OrderType, PegReferenceType, Price, PriceLevel, Quantity, Side, TimeInForce, + TimestampMs, UuidGenerator, +}; +use std::hint::black_box; +use uuid::Uuid; + +/// Register benchmarks for matching against each special order type. +pub fn register_benchmarks(c: &mut Criterion) { + let mut group = c.benchmark_group("PriceLevel - Special Order Matching"); + + let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + let id_gen = UuidGenerator::new(namespace); + + // Benchmark matching against PostOnly orders + group.bench_function("match_post_only", |b| { + b.iter(|| { + let level = setup_post_only_level(100); + black_box(level.match_order(50, Id::from_u64(9999), &id_gen)); + }) + }); + + // Benchmark matching against TrailingStop orders + group.bench_function("match_trailing_stop", |b| { + b.iter(|| { + let level = setup_trailing_stop_level(100); + black_box(level.match_order(50, Id::from_u64(9999), &id_gen)); + }) + }); + + // Benchmark matching against Pegged orders + group.bench_function("match_pegged", |b| { + b.iter(|| { + let level = setup_pegged_level(100); + black_box(level.match_order(50, Id::from_u64(9999), &id_gen)); + }) + }); + + // Benchmark matching against MarketToLimit orders + group.bench_function("match_market_to_limit", |b| { + b.iter(|| { + let level = setup_market_to_limit_level(100); + black_box(level.match_order(50, Id::from_u64(9999), &id_gen)); + }) + }); + + // Benchmark matching against all special types mixed + group.bench_function("match_all_special_mixed", |b| { + b.iter(|| { + let level = setup_all_special_mixed(100); + black_box(level.match_order(100, Id::from_u64(9999), &id_gen)); + }) + }); + + // Scaling: special order matching with different queue depths + for order_count in [25, 100, 500].iter() { + group.bench_with_input( + BenchmarkId::new("match_special_mixed_scaling", order_count), + order_count, + |b, &order_count| { + b.iter(|| { + let level = setup_all_special_mixed(order_count); + black_box(level.match_order(order_count / 2, Id::from_u64(9999), &id_gen)); + }) + }, + ); + } + + group.finish(); +} + +/// Set up a price level with PostOnly orders. +fn setup_post_only_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::PostOnly { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }); + } + level +} + +/// Set up a price level with TrailingStop orders. +fn setup_trailing_stop_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::TrailingStop { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + trail_amount: Quantity::new(100), + last_reference_price: Price::new(10100), + extra_fields: (), + }); + } + level +} + +/// Set up a price level with Pegged orders. +fn setup_pegged_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::PeggedOrder { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + reference_price_offset: -50, + reference_price_type: PegReferenceType::BestAsk, + extra_fields: (), + }); + } + level +} + +/// Set up a price level with MarketToLimit orders. +fn setup_market_to_limit_level(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + level.add_order(OrderType::MarketToLimit { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }); + } + level +} + +/// Set up a price level with a mix of all special order types. +fn setup_all_special_mixed(order_count: u64) -> PriceLevel { + let level = PriceLevel::new(10000); + for i in 0..order_count { + let order = match i % 4 { + 0 => OrderType::PostOnly { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + 1 => OrderType::TrailingStop { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + trail_amount: Quantity::new(100), + last_reference_price: Price::new(10100), + extra_fields: (), + }, + 2 => OrderType::PeggedOrder { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + reference_price_offset: -50, + reference_price_type: PegReferenceType::BestAsk, + extra_fields: (), + }, + _ => OrderType::MarketToLimit { + id: Id::from_u64(i), + price: Price::new(10000), + quantity: Quantity::new(10), + side: Side::Buy, + user_id: Hash32::zero(), + timestamp: TimestampMs::new(1_616_823_000_000 + i), + time_in_force: TimeInForce::Gtc, + extra_fields: (), + }, + }; + level.add_order(order); + } + level +}