From a3cdb7bce311d9addb9cf6eca6fddcf39579ebd5 Mon Sep 17 00:00:00 2001 From: will chandler Date: Wed, 8 Apr 2026 06:39:15 -0400 Subject: [PATCH] [Matcher] Add deterministic replay tests and rollout flags --- README.md | 17 ++++++++++ atra-ob/benches/matching_engine_benchmark.rs | 10 ++++++ atra-ob/tests/order_book_tests.rs | 35 +++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 789a232..2571067 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ cargo nextest run cargo run --bin server ``` +Deterministic sequencing flags: + +```bash +# number of stable processing lanes (parallel across lanes, serial in-lane) +export ATRA_LANE_COUNT=4 + +# strict sequence gate: reject stale/gap/out-of-order sequence numbers +export ATRA_STRICT_SEQUENCE_VALIDATION=true +``` + ### CLI (Python) ```bash cd atra-cli @@ -77,6 +87,13 @@ mix compile iex -S mix ``` +Gateway deterministic ingress flags: + +```bash +export ATRA_GATEWAY_LANE_COUNT=4 +export ATRA_GATEWAY_STRICT_SEQUENCE_VALIDATION=true +``` + ## Project Structure ``` ├── atra-ob/ # Rust matching engine diff --git a/atra-ob/benches/matching_engine_benchmark.rs b/atra-ob/benches/matching_engine_benchmark.rs index 5d29efc..a628bf1 100644 --- a/atra-ob/benches/matching_engine_benchmark.rs +++ b/atra-ob/benches/matching_engine_benchmark.rs @@ -44,6 +44,8 @@ fn run_benchmarks(c: &mut Criterion) { for i in 0..size { engine.place_order(Order::new( i as u64, + 1, + i as u64 + 1, dec!(100.0) + Decimal::new((i % 100) as i64, 2), dec!(1.0), Side::Bid, @@ -55,6 +57,8 @@ fn run_benchmarks(c: &mut Criterion) { let start = Instant::now(); let order = Order::new( size as u64 + 1, + 1, + size as u64 + 2, dec!(100.0), dec!(1.0), Side::Ask, @@ -84,6 +88,8 @@ fn run_benchmarks(c: &mut Criterion) { for i in 0..1000 { engine.place_order(Order::new( i, + 1, + i + 1, dec!(100.0) + Decimal::new((i % 100) as i64, 2), dec!(1.0), Side::Bid, @@ -96,6 +102,8 @@ fn run_benchmarks(c: &mut Criterion) { let start = Instant::now(); engine.place_order(Order::new( 1000 + i, + 1, + 1001 + i, dec!(100.0), dec!(1.0), Side::Ask, @@ -113,6 +121,8 @@ fn run_benchmarks(c: &mut Criterion) { for i in 0..*batch_size { engine.place_order(Order::new( i as u64, + 1, + i as u64 + 1, dec!(100.0), dec!(1.0), if i % 2 == 0 { Side::Bid } else { Side::Ask }, diff --git a/atra-ob/tests/order_book_tests.rs b/atra-ob/tests/order_book_tests.rs index ffd571e..5f858ab 100644 --- a/atra-ob/tests/order_book_tests.rs +++ b/atra-ob/tests/order_book_tests.rs @@ -4,7 +4,7 @@ use atra_ob::core::{OrderBook, Order, Side, OrderType, OrderStatus, MatchingEngi fn create_test_order(id: u64, price: rust_decimal::Decimal, quantity: rust_decimal::Decimal, side: Side, order_type: OrderType) -> Order { - Order::new(id, price, quantity, side, order_type) + Order::new(id, 1, id, price, quantity, side, order_type) } @@ -191,3 +191,36 @@ fn test_price_time_priority() { assert_eq!(trades[2].price, dec!(100.0)); assert_eq!(trades[2].quantity, dec!(10.0)); } + +#[test] +fn test_replay_is_deterministic() { + let mut run1 = MatchingEngine::new(); + let mut run2 = MatchingEngine::new(); + let stream = vec![ + create_test_order(1, dec!(100.0), dec!(10.0), Side::Bid, OrderType::Limit), + create_test_order(2, dec!(99.0), dec!(4.0), Side::Bid, OrderType::Limit), + create_test_order(3, dec!(100.0), dec!(3.0), Side::Ask, OrderType::Limit), + create_test_order(4, dec!(100.0), dec!(7.0), Side::Ask, OrderType::Limit), + ]; + for order in &stream { + run1.place_order(order.clone()); + run2.place_order(order.clone()); + } + assert_eq!(run1.get_order_book(10), run2.get_order_book(10)); + assert_eq!(run1.get_trade_history(None), run2.get_trade_history(None)); +} + +#[test] +fn test_duplicate_sequence_orders_still_fifo_by_arrival() { + let mut book = MatchingEngine::new(); + let mut first = create_test_order(1, dec!(100.0), dec!(5.0), Side::Bid, OrderType::Limit); + first.sequence = 42; + let mut second = create_test_order(2, dec!(100.0), dec!(5.0), Side::Bid, OrderType::Limit); + second.sequence = 42; + book.place_order(first); + book.place_order(second); + book.place_order(create_test_order(3, dec!(100.0), dec!(10.0), Side::Ask, OrderType::Limit)); + let trades = book.get_trade_history(None); + assert_eq!(trades[0].maker_order_id, 1); + assert_eq!(trades[1].maker_order_id, 2); +}