nautilus_model/orderbook/
ladder.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Represents a ladder of price levels for one side of an order book.
17
18use std::{
19    cmp::Ordering,
20    collections::{BTreeMap, HashMap},
21    fmt::{Debug, Display, Formatter},
22};
23
24use nautilus_core::UnixNanos;
25
26use crate::{
27    data::order::{BookOrder, OrderId},
28    enums::OrderSideSpecified,
29    orderbook::BookLevel,
30    types::{Price, Quantity},
31};
32
33/// Represents a price level with a specified side in an order books ladder.
34#[derive(Clone, Copy, Debug, Eq)]
35#[cfg_attr(
36    feature = "python",
37    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
38)]
39pub struct BookPrice {
40    pub value: Price,
41    pub side: OrderSideSpecified,
42}
43
44impl BookPrice {
45    /// Creates a new [`BookPrice`] instance.
46    #[must_use]
47    pub fn new(value: Price, side: OrderSideSpecified) -> Self {
48        Self { value, side }
49    }
50}
51
52impl PartialOrd for BookPrice {
53    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
54        Some(self.cmp(other))
55    }
56}
57
58impl PartialEq for BookPrice {
59    fn eq(&self, other: &Self) -> bool {
60        self.value == other.value
61    }
62}
63
64impl Ord for BookPrice {
65    fn cmp(&self, other: &Self) -> Ordering {
66        match self.side {
67            OrderSideSpecified::Buy => other.value.cmp(&self.value),
68            OrderSideSpecified::Sell => self.value.cmp(&other.value),
69        }
70    }
71}
72
73impl Display for BookPrice {
74    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.value)
76    }
77}
78
79/// Represents a ladder of price levels for one side of an order book.
80#[derive(Clone, Debug)]
81pub(crate) struct BookLadder {
82    pub side: OrderSideSpecified,
83    pub levels: BTreeMap<BookPrice, BookLevel>,
84    pub cache: HashMap<u64, BookPrice>,
85}
86
87impl BookLadder {
88    /// Creates a new [`Ladder`] instance.
89    #[must_use]
90    pub fn new(side: OrderSideSpecified) -> Self {
91        Self {
92            side,
93            levels: BTreeMap::new(),
94            cache: HashMap::new(),
95        }
96    }
97
98    /// Returns the number of price levels in the ladder.
99    #[must_use]
100    pub fn len(&self) -> usize {
101        self.levels.len()
102    }
103
104    /// Returns true if the ladder has no price levels.
105    #[must_use]
106    #[allow(dead_code)] // Used in tests
107    pub fn is_empty(&self) -> bool {
108        self.levels.is_empty()
109    }
110
111    #[allow(dead_code)] // Used in tests
112    /// Adds multiple orders to the ladder.
113    pub fn add_bulk(&mut self, orders: Vec<BookOrder>) {
114        for order in orders {
115            self.add(order);
116        }
117    }
118
119    /// Removes all orders and price levels from the ladder.
120    pub fn clear(&mut self) {
121        self.levels.clear();
122        self.cache.clear();
123    }
124
125    /// Adds an order to the ladder at its price level.
126    pub fn add(&mut self, order: BookOrder) {
127        let book_price = order.to_book_price();
128        self.cache.insert(order.order_id, book_price);
129
130        match self.levels.get_mut(&book_price) {
131            Some(level) => {
132                level.add(order);
133            }
134            None => {
135                let level = BookLevel::from_order(order);
136                self.levels.insert(book_price, level);
137            }
138        }
139    }
140
141    /// Updates an existing order in the ladder, moving it to a new price level if needed.
142    pub fn update(&mut self, order: BookOrder) {
143        let price = self.cache.get(&order.order_id).copied();
144        if let Some(price) = price
145            && let Some(level) = self.levels.get_mut(&price)
146        {
147            if order.price == level.price.value {
148                // Update at current price level
149                let level_len_before = level.len();
150                level.update(order);
151
152                // If level.update removed the order due to zero size, remove from cache too
153                if order.size.raw == 0 {
154                    self.cache.remove(&order.order_id);
155                    debug_assert_eq!(
156                        level.len(),
157                        level_len_before - 1,
158                        "Level should have one less order after zero-size update"
159                    );
160                } else {
161                    debug_assert!(
162                        self.cache.contains_key(&order.order_id),
163                        "Cache should still contain order {0} after update",
164                        order.order_id
165                    );
166                }
167
168                // Remove empty price level
169                if level.is_empty() {
170                    self.levels.remove(&price);
171                    debug_assert!(
172                        !self.cache.values().any(|p| *p == price),
173                        "Cache should not contain removed price level {price:?}"
174                    );
175                }
176
177                // Validate cache consistency after same-price update
178                debug_assert_eq!(
179                    self.cache.len(),
180                    self.levels.values().map(|level| level.len()).sum::<usize>(),
181                    "Cache size should equal total orders across all levels"
182                );
183                return;
184            }
185
186            // Price update: delete and insert at new level
187            self.cache.remove(&order.order_id);
188            level.delete(&order);
189            if level.is_empty() {
190                self.levels.remove(&price);
191                debug_assert!(
192                    !self.cache.values().any(|p| *p == price),
193                    "Cache should not contain removed price level {price:?}"
194                );
195            }
196        }
197
198        // Only add if the order has positive size
199        if order.size.is_positive() {
200            self.add(order);
201        }
202
203        // Validate cache consistency after update
204        debug_assert_eq!(
205            self.cache.len(),
206            self.levels.values().map(|level| level.len()).sum::<usize>(),
207            "Cache size should equal total orders across all levels"
208        );
209    }
210
211    /// Deletes an order from the ladder.
212    pub fn delete(&mut self, order: BookOrder, sequence: u64, ts_event: UnixNanos) {
213        self.remove(order.order_id, sequence, ts_event);
214    }
215
216    /// Removes an order by its ID from the ladder.
217    pub fn remove(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
218        if let Some(price) = self.cache.get(&order_id).copied()
219            && let Some(level) = self.levels.get_mut(&price)
220        {
221            // Check if order exists in level before modifying cache
222            if level.orders.contains_key(&order_id) {
223                let level_len_before = level.len();
224
225                // Now safe to remove from cache since we know order exists in level
226                self.cache.remove(&order_id);
227                level.remove_by_id(order_id, sequence, ts_event);
228
229                debug_assert_eq!(
230                    level.len(),
231                    level_len_before - 1,
232                    "Level should have exactly one less order after removal"
233                );
234
235                if level.is_empty() {
236                    self.levels.remove(&price);
237                    debug_assert!(
238                        !self.cache.values().any(|p| *p == price),
239                        "Cache should not contain removed price level {price:?}"
240                    );
241                }
242            }
243        }
244
245        // Validate cache consistency after removal
246        debug_assert_eq!(
247            self.cache.len(),
248            self.levels.values().map(|level| level.len()).sum::<usize>(),
249            "Cache size should equal total orders across all levels"
250        );
251    }
252
253    /// Returns the total size of all orders in the ladder.
254    #[must_use]
255    #[allow(dead_code)] // Used in tests
256    pub fn sizes(&self) -> f64 {
257        self.levels.values().map(BookLevel::size).sum()
258    }
259
260    /// Returns the total value exposure (price * size) of all orders in the ladder.
261    #[must_use]
262    #[allow(dead_code)] // Used in tests
263    pub fn exposures(&self) -> f64 {
264        self.levels.values().map(BookLevel::exposure).sum()
265    }
266
267    /// Returns the best price level in the ladder.
268    #[must_use]
269    pub fn top(&self) -> Option<&BookLevel> {
270        match self.levels.iter().next() {
271            Some((_, l)) => Option::Some(l),
272            None => Option::None,
273        }
274    }
275
276    /// Simulates fills for an order against this ladder's liquidity.
277    /// Returns a list of (price, size) tuples representing the simulated fills.
278    #[must_use]
279    pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
280        let is_reversed = self.side == OrderSideSpecified::Buy;
281        let mut fills = Vec::new();
282        let mut cumulative_denominator = Quantity::zero(order.size.precision);
283        let target = order.size;
284
285        for level in self.levels.values() {
286            if (is_reversed && level.price.value < order.price)
287                || (!is_reversed && level.price.value > order.price)
288            {
289                break;
290            }
291
292            for book_order in level.orders.values() {
293                let current = book_order.size;
294                if cumulative_denominator + current >= target {
295                    // This order has filled us, add fill and return
296                    let remainder = target - cumulative_denominator;
297                    if remainder.is_positive() {
298                        fills.push((book_order.price, remainder));
299                    }
300                    return fills;
301                }
302
303                // Add this fill and continue
304                fills.push((book_order.price, current));
305                cumulative_denominator += current;
306            }
307        }
308
309        fills
310    }
311}
312
313impl Display for BookLadder {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        writeln!(f, "{}(side={})", stringify!(BookLadder), self.side)?;
316        for (price, level) in &self.levels {
317            writeln!(f, "  {} -> {} orders", price, level.len())?;
318        }
319        Ok(())
320    }
321}
322
323////////////////////////////////////////////////////////////////////////////////
324// Tests
325////////////////////////////////////////////////////////////////////////////////
326#[cfg(test)]
327mod tests {
328    use rstest::rstest;
329
330    use crate::{
331        data::order::BookOrder,
332        enums::{OrderSide, OrderSideSpecified},
333        orderbook::ladder::{BookLadder, BookPrice},
334        types::{Price, Quantity},
335    };
336
337    #[rstest]
338    fn test_is_empty() {
339        let ladder = BookLadder::new(OrderSideSpecified::Buy);
340        assert!(ladder.is_empty(), "A new ladder should be empty");
341    }
342
343    #[rstest]
344    fn test_is_empty_after_add() {
345        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
346        assert!(ladder.is_empty(), "Ladder should start empty");
347        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(100), 1);
348        ladder.add(order);
349        assert!(
350            !ladder.is_empty(),
351            "Ladder should not be empty after adding an order"
352        );
353    }
354
355    #[rstest]
356    fn test_add_bulk_empty() {
357        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
358        ladder.add_bulk(vec![]);
359        assert!(
360            ladder.is_empty(),
361            "Adding an empty vector should leave the ladder empty"
362        );
363    }
364
365    #[rstest]
366    fn test_add_bulk_orders() {
367        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
368        let orders = vec![
369            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1),
370            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2),
371            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(50), 3),
372        ];
373        ladder.add_bulk(orders);
374        // All orders share the same price, so there should be one price level.
375        assert_eq!(ladder.len(), 1, "Ladder should have one price level");
376        let orders_in_level = ladder.top().unwrap().get_orders();
377        assert_eq!(
378            orders_in_level.len(),
379            3,
380            "Price level should contain all bulk orders"
381        );
382    }
383
384    #[rstest]
385    fn test_book_price_bid_sorting() {
386        let mut bid_prices = [
387            BookPrice::new(Price::from("2.0"), OrderSideSpecified::Buy),
388            BookPrice::new(Price::from("4.0"), OrderSideSpecified::Buy),
389            BookPrice::new(Price::from("1.0"), OrderSideSpecified::Buy),
390            BookPrice::new(Price::from("3.0"), OrderSideSpecified::Buy),
391        ];
392        bid_prices.sort();
393        assert_eq!(bid_prices[0].value, Price::from("4.0"));
394    }
395
396    #[rstest]
397    fn test_book_price_ask_sorting() {
398        let mut ask_prices = [
399            BookPrice::new(Price::from("2.0"), OrderSideSpecified::Sell),
400            BookPrice::new(Price::from("4.0"), OrderSideSpecified::Sell),
401            BookPrice::new(Price::from("1.0"), OrderSideSpecified::Sell),
402            BookPrice::new(Price::from("3.0"), OrderSideSpecified::Sell),
403        ];
404
405        ask_prices.sort();
406        assert_eq!(ask_prices[0].value, Price::from("1.0"));
407    }
408
409    #[rstest]
410    fn test_add_single_order() {
411        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
412        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
413
414        ladder.add(order);
415        assert_eq!(ladder.len(), 1);
416        assert_eq!(ladder.sizes(), 20.0);
417        assert_eq!(ladder.exposures(), 200.0);
418        assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
419    }
420
421    #[rstest]
422    fn test_add_multiple_buy_orders() {
423        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
424        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
425        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 1);
426        let order3 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(50), 2);
427        let order4 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(200), 3);
428
429        ladder.add_bulk(vec![order1, order2, order3, order4]);
430        assert_eq!(ladder.len(), 3);
431        assert_eq!(ladder.sizes(), 300.0);
432        assert_eq!(ladder.exposures(), 2520.0);
433        assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
434    }
435
436    #[rstest]
437    fn test_add_multiple_sell_orders() {
438        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
439        let order1 = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 0);
440        let order2 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(30), 1);
441        let order3 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(50), 2);
442        let order4 = BookOrder::new(
443            OrderSide::Sell,
444            Price::from("13.00"),
445            Quantity::from(200),
446            0,
447        );
448
449        ladder.add_bulk(vec![order1, order2, order3, order4]);
450        assert_eq!(ladder.len(), 3);
451        assert_eq!(ladder.sizes(), 300.0);
452        assert_eq!(ladder.exposures(), 3780.0);
453        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
454    }
455
456    #[rstest]
457    fn test_add_to_same_price_level() {
458        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
459        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
460        let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
461
462        ladder.add(order1);
463        ladder.add(order2);
464
465        assert_eq!(ladder.len(), 1);
466        assert_eq!(ladder.sizes(), 50.0);
467        assert_eq!(ladder.exposures(), 500.0);
468    }
469
470    #[rstest]
471    fn test_add_descending_buy_orders() {
472        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
473        let order1 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(20), 1);
474        let order2 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(30), 2);
475
476        ladder.add(order1);
477        ladder.add(order2);
478
479        assert_eq!(ladder.top().unwrap().price.value, Price::from("9.00"));
480    }
481
482    #[rstest]
483    fn test_add_ascending_sell_orders() {
484        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
485        let order1 = BookOrder::new(OrderSide::Sell, Price::from("8.00"), Quantity::from(20), 1);
486        let order2 = BookOrder::new(OrderSide::Sell, Price::from("9.00"), Quantity::from(30), 2);
487
488        ladder.add(order1);
489        ladder.add(order2);
490
491        assert_eq!(ladder.top().unwrap().price.value, Price::from("8.00"));
492    }
493
494    #[rstest]
495    fn test_update_buy_order_price() {
496        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
497        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
498
499        ladder.add(order);
500        let order = BookOrder::new(OrderSide::Buy, Price::from("11.10"), Quantity::from(20), 1);
501
502        ladder.update(order);
503        assert_eq!(ladder.len(), 1);
504        assert_eq!(ladder.sizes(), 20.0);
505        assert_eq!(ladder.exposures(), 222.0);
506        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
507    }
508
509    #[rstest]
510    fn test_update_sell_order_price() {
511        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
512        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
513
514        ladder.add(order);
515
516        let order = BookOrder::new(OrderSide::Sell, Price::from("11.10"), Quantity::from(20), 1);
517
518        ladder.update(order);
519        assert_eq!(ladder.len(), 1);
520        assert_eq!(ladder.sizes(), 20.0);
521        assert_eq!(ladder.exposures(), 222.0);
522        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
523    }
524
525    #[rstest]
526    fn test_update_buy_order_size() {
527        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
528        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
529
530        ladder.add(order);
531
532        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
533
534        ladder.update(order);
535        assert_eq!(ladder.len(), 1);
536        assert_eq!(ladder.sizes(), 10.0);
537        assert_eq!(ladder.exposures(), 110.0);
538        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
539    }
540
541    #[rstest]
542    fn test_update_sell_order_size() {
543        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
544        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
545
546        ladder.add(order);
547
548        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(10), 1);
549
550        ladder.update(order);
551        assert_eq!(ladder.len(), 1);
552        assert_eq!(ladder.sizes(), 10.0);
553        assert_eq!(ladder.exposures(), 110.0);
554        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
555    }
556
557    #[rstest]
558    fn test_delete_non_existing_order() {
559        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
560        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
561
562        ladder.delete(order, 0, 0.into());
563
564        assert_eq!(ladder.len(), 0);
565    }
566
567    #[rstest]
568    fn test_delete_buy_order() {
569        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
570        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
571
572        ladder.add(order);
573
574        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
575
576        ladder.delete(order, 0, 0.into());
577        assert_eq!(ladder.len(), 0);
578        assert_eq!(ladder.sizes(), 0.0);
579        assert_eq!(ladder.exposures(), 0.0);
580        assert_eq!(ladder.top(), None);
581    }
582
583    #[rstest]
584    fn test_delete_sell_order() {
585        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
586        let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
587
588        ladder.add(order);
589
590        let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
591
592        ladder.delete(order, 0, 0.into());
593        assert_eq!(ladder.len(), 0);
594        assert_eq!(ladder.sizes(), 0.0);
595        assert_eq!(ladder.exposures(), 0.0);
596        assert_eq!(ladder.top(), None);
597    }
598
599    #[rstest]
600    fn test_ladder_sizes_empty() {
601        let ladder = BookLadder::new(OrderSideSpecified::Buy);
602        assert_eq!(
603            ladder.sizes(),
604            0.0,
605            "An empty ladder should have total size 0.0"
606        );
607    }
608
609    #[rstest]
610    fn test_ladder_exposures_empty() {
611        let ladder = BookLadder::new(OrderSideSpecified::Buy);
612        assert_eq!(
613            ladder.exposures(),
614            0.0,
615            "An empty ladder should have total exposure 0.0"
616        );
617    }
618
619    #[rstest]
620    fn test_ladder_sizes() {
621        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
622        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
623        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
624        ladder.add(order1);
625        ladder.add(order2);
626
627        let expected_size = 20.0 + 30.0;
628        assert_eq!(
629            ladder.sizes(),
630            expected_size,
631            "Ladder total size should match the sum of order sizes"
632        );
633    }
634
635    #[rstest]
636    fn test_ladder_exposures() {
637        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
638        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
639        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
640        ladder.add(order1);
641        ladder.add(order2);
642
643        let expected_exposure = 10.00 * 20.0 + 9.50 * 30.0;
644        assert_eq!(
645            ladder.exposures(),
646            expected_exposure,
647            "Ladder total exposure should match the sum of individual exposures"
648        );
649    }
650
651    #[rstest]
652    fn test_iter_returns_fifo() {
653        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
654        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
655        let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
656        ladder.add(order1);
657        ladder.add(order2);
658        let orders: Vec<BookOrder> = ladder.top().unwrap().iter().copied().collect();
659        assert_eq!(
660            orders,
661            vec![order1, order2],
662            "Iterator should return orders in FIFO order"
663        );
664    }
665
666    #[rstest]
667    fn test_update_missing_order_inserts() {
668        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
669        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
670        // Call update on an order that hasn't been added yet (upsert behavior)
671        ladder.update(order);
672        assert_eq!(
673            ladder.len(),
674            1,
675            "Ladder should have one level after upsert update"
676        );
677        let orders = ladder.top().unwrap().get_orders();
678        assert_eq!(
679            orders.len(),
680            1,
681            "Price level should contain the inserted order"
682        );
683        assert_eq!(orders[0], order, "The inserted order should match");
684    }
685
686    #[rstest]
687    fn test_cache_consistency_after_operations() {
688        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
689        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
690        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 2);
691        ladder.add(order1);
692        ladder.add(order2);
693
694        // Ensure that each order in the cache is present in the corresponding price level.
695        for (order_id, price) in &ladder.cache {
696            let level = ladder
697                .levels
698                .get(price)
699                .expect("Every price in the cache should have a corresponding level");
700            assert!(
701                level.orders.contains_key(order_id),
702                "Order id {order_id} should be present in the level for price {price}",
703            );
704        }
705    }
706
707    #[rstest]
708    fn test_simulate_fills_with_empty_book() {
709        let ladder = BookLadder::new(OrderSideSpecified::Buy);
710        let order = BookOrder::new(OrderSide::Buy, Price::max(2), Quantity::from(500), 1);
711
712        let fills = ladder.simulate_fills(&order);
713
714        assert!(fills.is_empty());
715    }
716
717    #[rstest]
718    #[case(OrderSide::Buy, Price::max(2), OrderSideSpecified::Sell)]
719    #[case(OrderSide::Sell, Price::min(2), OrderSideSpecified::Buy)]
720    fn test_simulate_order_fills_with_no_size(
721        #[case] side: OrderSide,
722        #[case] price: Price,
723        #[case] ladder_side: OrderSideSpecified,
724    ) {
725        let ladder = BookLadder::new(ladder_side);
726        let order = BookOrder {
727            price, // <-- Simulate a MARKET order
728            size: Quantity::from(500),
729            side,
730            order_id: 2,
731        };
732
733        let fills = ladder.simulate_fills(&order);
734
735        assert!(fills.is_empty());
736    }
737
738    #[rstest]
739    #[case(OrderSide::Buy, OrderSideSpecified::Sell, Price::from("60.0"))]
740    #[case(OrderSide::Sell, OrderSideSpecified::Buy, Price::from("40.0"))]
741    fn test_simulate_order_fills_buy_when_far_from_market(
742        #[case] order_side: OrderSide,
743        #[case] ladder_side: OrderSideSpecified,
744        #[case] ladder_price: Price,
745    ) {
746        let mut ladder = BookLadder::new(ladder_side);
747
748        ladder.add(BookOrder {
749            price: ladder_price,
750            size: Quantity::from(100),
751            side: ladder_side.as_order_side(),
752            order_id: 1,
753        });
754
755        let order = BookOrder {
756            price: Price::from("50.00"),
757            size: Quantity::from(500),
758            side: order_side,
759            order_id: 2,
760        };
761
762        let fills = ladder.simulate_fills(&order);
763
764        assert!(fills.is_empty());
765    }
766
767    #[rstest]
768    fn test_simulate_order_fills_sell_when_far_from_market() {
769        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
770
771        ladder.add(BookOrder {
772            price: Price::from("100.00"),
773            size: Quantity::from(100),
774            side: OrderSide::Buy,
775            order_id: 1,
776        });
777
778        let order = BookOrder {
779            price: Price::from("150.00"), // <-- Simulate a MARKET order
780            size: Quantity::from(500),
781            side: OrderSide::Buy,
782            order_id: 2,
783        };
784
785        let fills = ladder.simulate_fills(&order);
786
787        assert!(fills.is_empty());
788    }
789
790    #[rstest]
791    fn test_simulate_order_fills_buy() {
792        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
793
794        ladder.add_bulk(vec![
795            BookOrder {
796                price: Price::from("100.00"),
797                size: Quantity::from(100),
798                side: OrderSide::Sell,
799                order_id: 1,
800            },
801            BookOrder {
802                price: Price::from("101.00"),
803                size: Quantity::from(200),
804                side: OrderSide::Sell,
805                order_id: 2,
806            },
807            BookOrder {
808                price: Price::from("102.00"),
809                size: Quantity::from(400),
810                side: OrderSide::Sell,
811                order_id: 3,
812            },
813        ]);
814
815        let order = BookOrder {
816            price: Price::max(2), // <-- Simulate a MARKET order
817            size: Quantity::from(500),
818            side: OrderSide::Buy,
819            order_id: 4,
820        };
821
822        let fills = ladder.simulate_fills(&order);
823
824        assert_eq!(fills.len(), 3);
825
826        let (price1, size1) = fills[0];
827        assert_eq!(price1, Price::from("100.00"));
828        assert_eq!(size1, Quantity::from(100));
829
830        let (price2, size2) = fills[1];
831        assert_eq!(price2, Price::from("101.00"));
832        assert_eq!(size2, Quantity::from(200));
833
834        let (price3, size3) = fills[2];
835        assert_eq!(price3, Price::from("102.00"));
836        assert_eq!(size3, Quantity::from(200));
837    }
838
839    #[rstest]
840    fn test_simulate_order_fills_sell() {
841        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
842
843        ladder.add_bulk(vec![
844            BookOrder {
845                price: Price::from("102.00"),
846                size: Quantity::from(100),
847                side: OrderSide::Buy,
848                order_id: 1,
849            },
850            BookOrder {
851                price: Price::from("101.00"),
852                size: Quantity::from(200),
853                side: OrderSide::Buy,
854                order_id: 2,
855            },
856            BookOrder {
857                price: Price::from("100.00"),
858                size: Quantity::from(400),
859                side: OrderSide::Buy,
860                order_id: 3,
861            },
862        ]);
863
864        let order = BookOrder {
865            price: Price::min(2), // <-- Simulate a MARKET order
866            size: Quantity::from(500),
867            side: OrderSide::Sell,
868            order_id: 4,
869        };
870
871        let fills = ladder.simulate_fills(&order);
872
873        assert_eq!(fills.len(), 3);
874
875        let (price1, size1) = fills[0];
876        assert_eq!(price1, Price::from("102.00"));
877        assert_eq!(size1, Quantity::from(100));
878
879        let (price2, size2) = fills[1];
880        assert_eq!(price2, Price::from("101.00"));
881        assert_eq!(size2, Quantity::from(200));
882
883        let (price3, size3) = fills[2];
884        assert_eq!(price3, Price::from("100.00"));
885        assert_eq!(size3, Quantity::from(200));
886    }
887
888    #[rstest]
889    fn test_simulate_order_fills_sell_with_size_at_limit_of_precision() {
890        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
891
892        ladder.add_bulk(vec![
893            BookOrder {
894                price: Price::from("102.00"),
895                size: Quantity::from("100.000000000"),
896                side: OrderSide::Buy,
897                order_id: 1,
898            },
899            BookOrder {
900                price: Price::from("101.00"),
901                size: Quantity::from("200.000000000"),
902                side: OrderSide::Buy,
903                order_id: 2,
904            },
905            BookOrder {
906                price: Price::from("100.00"),
907                size: Quantity::from("400.000000000"),
908                side: OrderSide::Buy,
909                order_id: 3,
910            },
911        ]);
912
913        let order = BookOrder {
914            price: Price::min(2),                  // <-- Simulate a MARKET order
915            size: Quantity::from("699.999999999"), // <-- Size slightly less than total size in ladder
916            side: OrderSide::Sell,
917            order_id: 4,
918        };
919
920        let fills = ladder.simulate_fills(&order);
921
922        assert_eq!(fills.len(), 3);
923
924        let (price1, size1) = fills[0];
925        assert_eq!(price1, Price::from("102.00"));
926        assert_eq!(size1, Quantity::from("100.000000000"));
927
928        let (price2, size2) = fills[1];
929        assert_eq!(price2, Price::from("101.00"));
930        assert_eq!(size2, Quantity::from("200.000000000"));
931
932        let (price3, size3) = fills[2];
933        assert_eq!(price3, Price::from("100.00"));
934        assert_eq!(size3, Quantity::from("399.999999999"));
935    }
936
937    #[rstest]
938    fn test_boundary_prices() {
939        let max_price = Price::max(1);
940        let min_price = Price::min(1);
941
942        let mut ladder_buy = BookLadder::new(OrderSideSpecified::Buy);
943        let mut ladder_sell = BookLadder::new(OrderSideSpecified::Sell);
944
945        let order_buy = BookOrder::new(OrderSide::Buy, min_price, Quantity::from(1), 1);
946        let order_sell = BookOrder::new(OrderSide::Sell, max_price, Quantity::from(1), 1);
947
948        ladder_buy.add(order_buy);
949        ladder_sell.add(order_sell);
950
951        assert_eq!(ladder_buy.top().unwrap().price.value, min_price);
952        assert_eq!(ladder_sell.top().unwrap().price.value, max_price);
953    }
954}