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.order_id, sequence, ts_event);
214    }
215
216    /// Removes an order by its ID from the ladder.
217    pub fn remove_order(&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    /// Removes an entire price level from the ladder and returns it.
254    pub fn remove_level(&mut self, price: BookPrice) -> Option<BookLevel> {
255        if let Some(level) = self.levels.remove(&price) {
256            // Remove all orders in this level from the cache
257            for order_id in level.orders.keys() {
258                self.cache.remove(order_id);
259            }
260
261            debug_assert_eq!(
262                self.cache.len(),
263                self.levels.values().map(|level| level.len()).sum::<usize>(),
264                "Cache size should equal total orders across all levels"
265            );
266
267            Some(level)
268        } else {
269            None
270        }
271    }
272
273    /// Returns the total size of all orders in the ladder.
274    #[must_use]
275    #[allow(dead_code)] // Used in tests
276    pub fn sizes(&self) -> f64 {
277        self.levels.values().map(BookLevel::size).sum()
278    }
279
280    /// Returns the total value exposure (price * size) of all orders in the ladder.
281    #[must_use]
282    #[allow(dead_code)] // Used in tests
283    pub fn exposures(&self) -> f64 {
284        self.levels.values().map(BookLevel::exposure).sum()
285    }
286
287    /// Returns the best price level in the ladder.
288    #[must_use]
289    pub fn top(&self) -> Option<&BookLevel> {
290        match self.levels.iter().next() {
291            Some((_, l)) => Option::Some(l),
292            None => Option::None,
293        }
294    }
295
296    /// Simulates fills for an order against this ladder's liquidity.
297    /// Returns a list of (price, size) tuples representing the simulated fills.
298    #[must_use]
299    pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
300        let is_reversed = self.side == OrderSideSpecified::Buy;
301        let mut fills = Vec::new();
302        let mut cumulative_denominator = Quantity::zero(order.size.precision);
303        let target = order.size;
304
305        for level in self.levels.values() {
306            if (is_reversed && level.price.value < order.price)
307                || (!is_reversed && level.price.value > order.price)
308            {
309                break;
310            }
311
312            for book_order in level.orders.values() {
313                let current = book_order.size;
314                if cumulative_denominator + current >= target {
315                    // This order has filled us, add fill and return
316                    let remainder = target - cumulative_denominator;
317                    if remainder.is_positive() {
318                        fills.push((book_order.price, remainder));
319                    }
320                    return fills;
321                }
322
323                // Add this fill and continue
324                fills.push((book_order.price, current));
325                cumulative_denominator += current;
326            }
327        }
328
329        fills
330    }
331}
332
333impl Display for BookLadder {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        writeln!(f, "{}(side={})", stringify!(BookLadder), self.side)?;
336        for (price, level) in &self.levels {
337            writeln!(f, "  {} -> {} orders", price, level.len())?;
338        }
339        Ok(())
340    }
341}
342
343////////////////////////////////////////////////////////////////////////////////
344// Tests
345////////////////////////////////////////////////////////////////////////////////
346#[cfg(test)]
347mod tests {
348    use rstest::rstest;
349
350    use crate::{
351        data::order::BookOrder,
352        enums::{OrderSide, OrderSideSpecified},
353        orderbook::ladder::{BookLadder, BookPrice},
354        types::{Price, Quantity},
355    };
356
357    #[rstest]
358    fn test_is_empty() {
359        let ladder = BookLadder::new(OrderSideSpecified::Buy);
360        assert!(ladder.is_empty(), "A new ladder should be empty");
361    }
362
363    #[rstest]
364    fn test_is_empty_after_add() {
365        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
366        assert!(ladder.is_empty(), "Ladder should start empty");
367        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(100), 1);
368        ladder.add(order);
369        assert!(
370            !ladder.is_empty(),
371            "Ladder should not be empty after adding an order"
372        );
373    }
374
375    #[rstest]
376    fn test_add_bulk_empty() {
377        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
378        ladder.add_bulk(vec![]);
379        assert!(
380            ladder.is_empty(),
381            "Adding an empty vector should leave the ladder empty"
382        );
383    }
384
385    #[rstest]
386    fn test_add_bulk_orders() {
387        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
388        let orders = vec![
389            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1),
390            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2),
391            BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(50), 3),
392        ];
393        ladder.add_bulk(orders);
394        // All orders share the same price, so there should be one price level.
395        assert_eq!(ladder.len(), 1, "Ladder should have one price level");
396        let orders_in_level = ladder.top().unwrap().get_orders();
397        assert_eq!(
398            orders_in_level.len(),
399            3,
400            "Price level should contain all bulk orders"
401        );
402    }
403
404    #[rstest]
405    fn test_book_price_bid_sorting() {
406        let mut bid_prices = [
407            BookPrice::new(Price::from("2.0"), OrderSideSpecified::Buy),
408            BookPrice::new(Price::from("4.0"), OrderSideSpecified::Buy),
409            BookPrice::new(Price::from("1.0"), OrderSideSpecified::Buy),
410            BookPrice::new(Price::from("3.0"), OrderSideSpecified::Buy),
411        ];
412        bid_prices.sort();
413        assert_eq!(bid_prices[0].value, Price::from("4.0"));
414    }
415
416    #[rstest]
417    fn test_book_price_ask_sorting() {
418        let mut ask_prices = [
419            BookPrice::new(Price::from("2.0"), OrderSideSpecified::Sell),
420            BookPrice::new(Price::from("4.0"), OrderSideSpecified::Sell),
421            BookPrice::new(Price::from("1.0"), OrderSideSpecified::Sell),
422            BookPrice::new(Price::from("3.0"), OrderSideSpecified::Sell),
423        ];
424
425        ask_prices.sort();
426        assert_eq!(ask_prices[0].value, Price::from("1.0"));
427    }
428
429    #[rstest]
430    fn test_add_single_order() {
431        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
432        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
433
434        ladder.add(order);
435        assert_eq!(ladder.len(), 1);
436        assert_eq!(ladder.sizes(), 20.0);
437        assert_eq!(ladder.exposures(), 200.0);
438        assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
439    }
440
441    #[rstest]
442    fn test_add_multiple_buy_orders() {
443        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
444        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
445        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 1);
446        let order3 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(50), 2);
447        let order4 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(200), 3);
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(), 2520.0);
453        assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
454    }
455
456    #[rstest]
457    fn test_add_multiple_sell_orders() {
458        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
459        let order1 = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 0);
460        let order2 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(30), 1);
461        let order3 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(50), 2);
462        let order4 = BookOrder::new(
463            OrderSide::Sell,
464            Price::from("13.00"),
465            Quantity::from(200),
466            0,
467        );
468
469        ladder.add_bulk(vec![order1, order2, order3, order4]);
470        assert_eq!(ladder.len(), 3);
471        assert_eq!(ladder.sizes(), 300.0);
472        assert_eq!(ladder.exposures(), 3780.0);
473        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
474    }
475
476    #[rstest]
477    fn test_add_to_same_price_level() {
478        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
479        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
480        let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
481
482        ladder.add(order1);
483        ladder.add(order2);
484
485        assert_eq!(ladder.len(), 1);
486        assert_eq!(ladder.sizes(), 50.0);
487        assert_eq!(ladder.exposures(), 500.0);
488    }
489
490    #[rstest]
491    fn test_add_descending_buy_orders() {
492        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
493        let order1 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(20), 1);
494        let order2 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(30), 2);
495
496        ladder.add(order1);
497        ladder.add(order2);
498
499        assert_eq!(ladder.top().unwrap().price.value, Price::from("9.00"));
500    }
501
502    #[rstest]
503    fn test_add_ascending_sell_orders() {
504        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
505        let order1 = BookOrder::new(OrderSide::Sell, Price::from("8.00"), Quantity::from(20), 1);
506        let order2 = BookOrder::new(OrderSide::Sell, Price::from("9.00"), Quantity::from(30), 2);
507
508        ladder.add(order1);
509        ladder.add(order2);
510
511        assert_eq!(ladder.top().unwrap().price.value, Price::from("8.00"));
512    }
513
514    #[rstest]
515    fn test_update_buy_order_price() {
516        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
517        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
518
519        ladder.add(order);
520        let order = BookOrder::new(OrderSide::Buy, Price::from("11.10"), Quantity::from(20), 1);
521
522        ladder.update(order);
523        assert_eq!(ladder.len(), 1);
524        assert_eq!(ladder.sizes(), 20.0);
525        assert_eq!(ladder.exposures(), 222.0);
526        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
527    }
528
529    #[rstest]
530    fn test_update_sell_order_price() {
531        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
532        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
533
534        ladder.add(order);
535
536        let order = BookOrder::new(OrderSide::Sell, Price::from("11.10"), Quantity::from(20), 1);
537
538        ladder.update(order);
539        assert_eq!(ladder.len(), 1);
540        assert_eq!(ladder.sizes(), 20.0);
541        assert_eq!(ladder.exposures(), 222.0);
542        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
543    }
544
545    #[rstest]
546    fn test_update_buy_order_size() {
547        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
548        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
549
550        ladder.add(order);
551
552        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
553
554        ladder.update(order);
555        assert_eq!(ladder.len(), 1);
556        assert_eq!(ladder.sizes(), 10.0);
557        assert_eq!(ladder.exposures(), 110.0);
558        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
559    }
560
561    #[rstest]
562    fn test_update_sell_order_size() {
563        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
564        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
565
566        ladder.add(order);
567
568        let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(10), 1);
569
570        ladder.update(order);
571        assert_eq!(ladder.len(), 1);
572        assert_eq!(ladder.sizes(), 10.0);
573        assert_eq!(ladder.exposures(), 110.0);
574        assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
575    }
576
577    #[rstest]
578    fn test_delete_non_existing_order() {
579        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
580        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
581
582        ladder.delete(order, 0, 0.into());
583
584        assert_eq!(ladder.len(), 0);
585    }
586
587    #[rstest]
588    fn test_delete_buy_order() {
589        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
590        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
591
592        ladder.add(order);
593
594        let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
595
596        ladder.delete(order, 0, 0.into());
597        assert_eq!(ladder.len(), 0);
598        assert_eq!(ladder.sizes(), 0.0);
599        assert_eq!(ladder.exposures(), 0.0);
600        assert_eq!(ladder.top(), None);
601    }
602
603    #[rstest]
604    fn test_delete_sell_order() {
605        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
606        let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
607
608        ladder.add(order);
609
610        let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
611
612        ladder.delete(order, 0, 0.into());
613        assert_eq!(ladder.len(), 0);
614        assert_eq!(ladder.sizes(), 0.0);
615        assert_eq!(ladder.exposures(), 0.0);
616        assert_eq!(ladder.top(), None);
617    }
618
619    #[rstest]
620    fn test_ladder_sizes_empty() {
621        let ladder = BookLadder::new(OrderSideSpecified::Buy);
622        assert_eq!(
623            ladder.sizes(),
624            0.0,
625            "An empty ladder should have total size 0.0"
626        );
627    }
628
629    #[rstest]
630    fn test_ladder_exposures_empty() {
631        let ladder = BookLadder::new(OrderSideSpecified::Buy);
632        assert_eq!(
633            ladder.exposures(),
634            0.0,
635            "An empty ladder should have total exposure 0.0"
636        );
637    }
638
639    #[rstest]
640    fn test_ladder_sizes() {
641        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
642        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
643        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
644        ladder.add(order1);
645        ladder.add(order2);
646
647        let expected_size = 20.0 + 30.0;
648        assert_eq!(
649            ladder.sizes(),
650            expected_size,
651            "Ladder total size should match the sum of order sizes"
652        );
653    }
654
655    #[rstest]
656    fn test_ladder_exposures() {
657        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
658        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
659        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
660        ladder.add(order1);
661        ladder.add(order2);
662
663        let expected_exposure = 10.00 * 20.0 + 9.50 * 30.0;
664        assert_eq!(
665            ladder.exposures(),
666            expected_exposure,
667            "Ladder total exposure should match the sum of individual exposures"
668        );
669    }
670
671    #[rstest]
672    fn test_iter_returns_fifo() {
673        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
674        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
675        let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
676        ladder.add(order1);
677        ladder.add(order2);
678        let orders: Vec<BookOrder> = ladder.top().unwrap().iter().copied().collect();
679        assert_eq!(
680            orders,
681            vec![order1, order2],
682            "Iterator should return orders in FIFO order"
683        );
684    }
685
686    #[rstest]
687    fn test_update_missing_order_inserts() {
688        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
689        let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
690        // Call update on an order that hasn't been added yet (upsert behavior)
691        ladder.update(order);
692        assert_eq!(
693            ladder.len(),
694            1,
695            "Ladder should have one level after upsert update"
696        );
697        let orders = ladder.top().unwrap().get_orders();
698        assert_eq!(
699            orders.len(),
700            1,
701            "Price level should contain the inserted order"
702        );
703        assert_eq!(orders[0], order, "The inserted order should match");
704    }
705
706    #[rstest]
707    fn test_cache_consistency_after_operations() {
708        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
709        let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
710        let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 2);
711        ladder.add(order1);
712        ladder.add(order2);
713
714        // Ensure that each order in the cache is present in the corresponding price level.
715        for (order_id, price) in &ladder.cache {
716            let level = ladder
717                .levels
718                .get(price)
719                .expect("Every price in the cache should have a corresponding level");
720            assert!(
721                level.orders.contains_key(order_id),
722                "Order id {order_id} should be present in the level for price {price}",
723            );
724        }
725    }
726
727    #[rstest]
728    fn test_simulate_fills_with_empty_book() {
729        let ladder = BookLadder::new(OrderSideSpecified::Buy);
730        let order = BookOrder::new(OrderSide::Buy, Price::max(2), Quantity::from(500), 1);
731
732        let fills = ladder.simulate_fills(&order);
733
734        assert!(fills.is_empty());
735    }
736
737    #[rstest]
738    #[case(OrderSide::Buy, Price::max(2), OrderSideSpecified::Sell)]
739    #[case(OrderSide::Sell, Price::min(2), OrderSideSpecified::Buy)]
740    fn test_simulate_order_fills_with_no_size(
741        #[case] side: OrderSide,
742        #[case] price: Price,
743        #[case] ladder_side: OrderSideSpecified,
744    ) {
745        let ladder = BookLadder::new(ladder_side);
746        let order = BookOrder {
747            price, // <-- Simulate a MARKET order
748            size: Quantity::from(500),
749            side,
750            order_id: 2,
751        };
752
753        let fills = ladder.simulate_fills(&order);
754
755        assert!(fills.is_empty());
756    }
757
758    #[rstest]
759    #[case(OrderSide::Buy, OrderSideSpecified::Sell, Price::from("60.0"))]
760    #[case(OrderSide::Sell, OrderSideSpecified::Buy, Price::from("40.0"))]
761    fn test_simulate_order_fills_buy_when_far_from_market(
762        #[case] order_side: OrderSide,
763        #[case] ladder_side: OrderSideSpecified,
764        #[case] ladder_price: Price,
765    ) {
766        let mut ladder = BookLadder::new(ladder_side);
767
768        ladder.add(BookOrder {
769            price: ladder_price,
770            size: Quantity::from(100),
771            side: ladder_side.as_order_side(),
772            order_id: 1,
773        });
774
775        let order = BookOrder {
776            price: Price::from("50.00"),
777            size: Quantity::from(500),
778            side: order_side,
779            order_id: 2,
780        };
781
782        let fills = ladder.simulate_fills(&order);
783
784        assert!(fills.is_empty());
785    }
786
787    #[rstest]
788    fn test_simulate_order_fills_sell_when_far_from_market() {
789        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
790
791        ladder.add(BookOrder {
792            price: Price::from("100.00"),
793            size: Quantity::from(100),
794            side: OrderSide::Buy,
795            order_id: 1,
796        });
797
798        let order = BookOrder {
799            price: Price::from("150.00"), // <-- Simulate a MARKET order
800            size: Quantity::from(500),
801            side: OrderSide::Buy,
802            order_id: 2,
803        };
804
805        let fills = ladder.simulate_fills(&order);
806
807        assert!(fills.is_empty());
808    }
809
810    #[rstest]
811    fn test_simulate_order_fills_buy() {
812        let mut ladder = BookLadder::new(OrderSideSpecified::Sell);
813
814        ladder.add_bulk(vec![
815            BookOrder {
816                price: Price::from("100.00"),
817                size: Quantity::from(100),
818                side: OrderSide::Sell,
819                order_id: 1,
820            },
821            BookOrder {
822                price: Price::from("101.00"),
823                size: Quantity::from(200),
824                side: OrderSide::Sell,
825                order_id: 2,
826            },
827            BookOrder {
828                price: Price::from("102.00"),
829                size: Quantity::from(400),
830                side: OrderSide::Sell,
831                order_id: 3,
832            },
833        ]);
834
835        let order = BookOrder {
836            price: Price::max(2), // <-- Simulate a MARKET order
837            size: Quantity::from(500),
838            side: OrderSide::Buy,
839            order_id: 4,
840        };
841
842        let fills = ladder.simulate_fills(&order);
843
844        assert_eq!(fills.len(), 3);
845
846        let (price1, size1) = fills[0];
847        assert_eq!(price1, Price::from("100.00"));
848        assert_eq!(size1, Quantity::from(100));
849
850        let (price2, size2) = fills[1];
851        assert_eq!(price2, Price::from("101.00"));
852        assert_eq!(size2, Quantity::from(200));
853
854        let (price3, size3) = fills[2];
855        assert_eq!(price3, Price::from("102.00"));
856        assert_eq!(size3, Quantity::from(200));
857    }
858
859    #[rstest]
860    fn test_simulate_order_fills_sell() {
861        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
862
863        ladder.add_bulk(vec![
864            BookOrder {
865                price: Price::from("102.00"),
866                size: Quantity::from(100),
867                side: OrderSide::Buy,
868                order_id: 1,
869            },
870            BookOrder {
871                price: Price::from("101.00"),
872                size: Quantity::from(200),
873                side: OrderSide::Buy,
874                order_id: 2,
875            },
876            BookOrder {
877                price: Price::from("100.00"),
878                size: Quantity::from(400),
879                side: OrderSide::Buy,
880                order_id: 3,
881            },
882        ]);
883
884        let order = BookOrder {
885            price: Price::min(2), // <-- Simulate a MARKET order
886            size: Quantity::from(500),
887            side: OrderSide::Sell,
888            order_id: 4,
889        };
890
891        let fills = ladder.simulate_fills(&order);
892
893        assert_eq!(fills.len(), 3);
894
895        let (price1, size1) = fills[0];
896        assert_eq!(price1, Price::from("102.00"));
897        assert_eq!(size1, Quantity::from(100));
898
899        let (price2, size2) = fills[1];
900        assert_eq!(price2, Price::from("101.00"));
901        assert_eq!(size2, Quantity::from(200));
902
903        let (price3, size3) = fills[2];
904        assert_eq!(price3, Price::from("100.00"));
905        assert_eq!(size3, Quantity::from(200));
906    }
907
908    #[rstest]
909    fn test_simulate_order_fills_sell_with_size_at_limit_of_precision() {
910        let mut ladder = BookLadder::new(OrderSideSpecified::Buy);
911
912        ladder.add_bulk(vec![
913            BookOrder {
914                price: Price::from("102.00"),
915                size: Quantity::from("100.000000000"),
916                side: OrderSide::Buy,
917                order_id: 1,
918            },
919            BookOrder {
920                price: Price::from("101.00"),
921                size: Quantity::from("200.000000000"),
922                side: OrderSide::Buy,
923                order_id: 2,
924            },
925            BookOrder {
926                price: Price::from("100.00"),
927                size: Quantity::from("400.000000000"),
928                side: OrderSide::Buy,
929                order_id: 3,
930            },
931        ]);
932
933        let order = BookOrder {
934            price: Price::min(2),                  // <-- Simulate a MARKET order
935            size: Quantity::from("699.999999999"), // <-- Size slightly less than total size in ladder
936            side: OrderSide::Sell,
937            order_id: 4,
938        };
939
940        let fills = ladder.simulate_fills(&order);
941
942        assert_eq!(fills.len(), 3);
943
944        let (price1, size1) = fills[0];
945        assert_eq!(price1, Price::from("102.00"));
946        assert_eq!(size1, Quantity::from("100.000000000"));
947
948        let (price2, size2) = fills[1];
949        assert_eq!(price2, Price::from("101.00"));
950        assert_eq!(size2, Quantity::from("200.000000000"));
951
952        let (price3, size3) = fills[2];
953        assert_eq!(price3, Price::from("100.00"));
954        assert_eq!(size3, Quantity::from("399.999999999"));
955    }
956
957    #[rstest]
958    fn test_boundary_prices() {
959        let max_price = Price::max(1);
960        let min_price = Price::min(1);
961
962        let mut ladder_buy = BookLadder::new(OrderSideSpecified::Buy);
963        let mut ladder_sell = BookLadder::new(OrderSideSpecified::Sell);
964
965        let order_buy = BookOrder::new(OrderSide::Buy, min_price, Quantity::from(1), 1);
966        let order_sell = BookOrder::new(OrderSide::Sell, max_price, Quantity::from(1), 1);
967
968        ladder_buy.add(order_buy);
969        ladder_sell.add(order_sell);
970
971        assert_eq!(ladder_buy.top().unwrap().price.value, min_price);
972        assert_eq!(ladder_sell.top().unwrap().price.value, max_price);
973    }
974}