nautilus_model/orderbook/
level.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 discrete price level in an order book.
17
18use std::cmp::Ordering;
19
20use indexmap::IndexMap;
21use nautilus_core::UnixNanos;
22use rust_decimal::Decimal;
23
24use crate::{
25    data::order::{BookOrder, OrderId},
26    orderbook::{BookIntegrityError, BookPrice},
27    types::{fixed::FIXED_SCALAR, quantity::QuantityRaw},
28};
29
30/// Represents a discrete price level in an order book.
31///
32/// Orders are stored in an [`IndexMap`] which preserves FIFO (insertion) order.
33#[derive(Clone, Debug, Eq)]
34#[cfg_attr(
35    feature = "python",
36    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
37)]
38pub struct BookLevel {
39    pub price: BookPrice,
40    pub(crate) orders: IndexMap<OrderId, BookOrder>,
41}
42
43impl BookLevel {
44    /// Creates a new [`BookLevel`] instance.
45    #[must_use]
46    pub fn new(price: BookPrice) -> Self {
47        Self {
48            price,
49            orders: IndexMap::new(),
50        }
51    }
52
53    /// Creates a new [`BookLevel`] from an order, using the order's price and side.
54    #[must_use]
55    pub fn from_order(order: BookOrder) -> Self {
56        let mut level = Self {
57            price: order.to_book_price(),
58            orders: IndexMap::new(),
59        };
60        level.add(order);
61        level
62    }
63
64    /// Returns the number of orders at this price level.
65    #[must_use]
66    pub fn len(&self) -> usize {
67        self.orders.len()
68    }
69
70    /// Returns true if this price level has no orders.
71    #[must_use]
72    pub fn is_empty(&self) -> bool {
73        self.orders.is_empty()
74    }
75
76    /// Returns a reference to the first order at this price level in FIFO order.
77    #[inline]
78    #[must_use]
79    pub fn first(&self) -> Option<&BookOrder> {
80        self.orders.get_index(0).map(|(_key, order)| order)
81    }
82
83    /// Returns an iterator over the orders at this price level in FIFO order.
84    pub fn iter(&self) -> impl Iterator<Item = &BookOrder> {
85        self.orders.values()
86    }
87
88    /// Returns all orders at this price level in FIFO insertion order.
89    #[must_use]
90    pub fn get_orders(&self) -> Vec<BookOrder> {
91        self.orders.values().copied().collect()
92    }
93
94    /// Returns the total size of all orders at this price level as a float.
95    #[must_use]
96    pub fn size(&self) -> f64 {
97        self.orders.values().map(|o| o.size.as_f64()).sum()
98    }
99
100    /// Returns the total size of all orders at this price level as raw integer units.
101    #[must_use]
102    pub fn size_raw(&self) -> QuantityRaw {
103        self.orders.values().map(|o| o.size.raw).sum()
104    }
105
106    /// Returns the total size of all orders at this price level as a decimal.
107    #[must_use]
108    pub fn size_decimal(&self) -> Decimal {
109        self.orders.values().map(|o| o.size.as_decimal()).sum()
110    }
111
112    /// Returns the total exposure (price * size) of all orders at this price level as a float.
113    #[must_use]
114    pub fn exposure(&self) -> f64 {
115        self.orders
116            .values()
117            .map(|o| o.price.as_f64() * o.size.as_f64())
118            .sum()
119    }
120
121    /// Returns the total exposure (price * size) of all orders at this price level as raw integer units.
122    #[must_use]
123    pub fn exposure_raw(&self) -> QuantityRaw {
124        self.orders
125            .values()
126            .map(|o| ((o.price.as_f64() * o.size.as_f64()) * FIXED_SCALAR) as QuantityRaw)
127            .sum()
128    }
129
130    /// Adds multiple orders to this price level in FIFO order. Orders must match the level's price.
131    pub fn add_bulk(&mut self, orders: Vec<BookOrder>) {
132        for order in orders {
133            self.add(order);
134        }
135    }
136
137    /// Adds an order to this price level. Order must match the level's price.
138    pub fn add(&mut self, order: BookOrder) {
139        debug_assert_eq!(order.price, self.price.value);
140
141        self.orders.insert(order.order_id, order);
142    }
143
144    /// Updates an existing order at this price level. Updated order must match the level's price.
145    /// Removes the order if size becomes zero.
146    pub fn update(&mut self, order: BookOrder) {
147        debug_assert_eq!(order.price, self.price.value);
148
149        if order.size.raw == 0 {
150            self.orders.shift_remove(&order.order_id);
151        } else {
152            self.orders.insert(order.order_id, order);
153        }
154    }
155
156    /// Deletes an order from this price level.
157    pub fn delete(&mut self, order: &BookOrder) {
158        self.orders.shift_remove(&order.order_id);
159    }
160
161    /// Removes an order by its ID. Panics if the order doesn't exist.
162    pub fn remove_by_id(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
163        assert!(
164            self.orders.shift_remove(&order_id).is_some(),
165            "{}",
166            &BookIntegrityError::OrderNotFound(order_id, sequence, ts_event)
167        );
168    }
169}
170
171impl PartialEq for BookLevel {
172    fn eq(&self, other: &Self) -> bool {
173        self.price == other.price
174    }
175}
176
177impl PartialOrd for BookLevel {
178    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
179        Some(self.cmp(other))
180    }
181}
182
183impl Ord for BookLevel {
184    fn cmp(&self, other: &Self) -> Ordering {
185        self.price.cmp(&other.price)
186    }
187}
188
189////////////////////////////////////////////////////////////////////////////////
190// Tests
191////////////////////////////////////////////////////////////////////////////////
192#[cfg(test)]
193mod tests {
194    use rstest::rstest;
195    use rust_decimal_macros::dec;
196
197    use crate::{
198        data::order::BookOrder,
199        enums::{OrderSide, OrderSideSpecified},
200        orderbook::{BookLevel, BookPrice},
201        types::{Price, Quantity, fixed::FIXED_SCALAR, quantity::QuantityRaw},
202    };
203
204    #[rstest]
205    fn test_empty_level() {
206        let level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
207        assert!(level.first().is_none());
208    }
209
210    #[rstest]
211    fn test_level_from_order() {
212        let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
213        let level = BookLevel::from_order(order);
214
215        assert_eq!(level.price.value, Price::from("1.00"));
216        assert_eq!(level.price.side, OrderSideSpecified::Buy);
217        assert_eq!(level.len(), 1);
218        assert_eq!(level.first().unwrap(), &order);
219        assert_eq!(level.size(), 10.0);
220    }
221
222    #[rstest]
223    #[should_panic(expected = "assertion `left == right` failed")]
224    fn test_add_order_incorrect_price_level() {
225        let mut level =
226            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
227        let incorrect_price_order =
228            BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 1);
229        level.add(incorrect_price_order);
230    }
231
232    #[rstest]
233    #[should_panic(expected = "assertion `left == right` failed")]
234    fn test_add_bulk_orders_incorrect_price() {
235        let mut level =
236            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
237        let orders = vec![
238            BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1),
239            BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 2), // Incorrect price
240        ];
241        level.add_bulk(orders);
242    }
243
244    #[rstest]
245    fn test_add_bulk_empty() {
246        let mut level =
247            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
248        level.add_bulk(vec![]);
249        assert!(level.is_empty());
250    }
251
252    #[rstest]
253    fn test_comparisons_bid_side() {
254        let level0 = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
255        let level1 = BookLevel::new(BookPrice::new(Price::from("1.01"), OrderSideSpecified::Buy));
256        assert_eq!(level0, level0);
257        assert!(level0 > level1);
258    }
259
260    #[rstest]
261    fn test_comparisons_ask_side() {
262        let level0 = BookLevel::new(BookPrice::new(
263            Price::from("1.00"),
264            OrderSideSpecified::Sell,
265        ));
266        let level1 = BookLevel::new(BookPrice::new(
267            Price::from("1.01"),
268            OrderSideSpecified::Sell,
269        ));
270        assert_eq!(level0, level0);
271        assert!(level0 < level1);
272    }
273
274    #[rstest]
275    fn test_book_level_sorting() {
276        let mut levels = vec![
277            BookLevel::new(BookPrice::new(
278                Price::from("1.00"),
279                OrderSideSpecified::Sell,
280            )),
281            BookLevel::new(BookPrice::new(
282                Price::from("1.02"),
283                OrderSideSpecified::Sell,
284            )),
285            BookLevel::new(BookPrice::new(
286                Price::from("1.01"),
287                OrderSideSpecified::Sell,
288            )),
289        ];
290        levels.sort();
291        assert_eq!(levels[0].price.value, Price::from("1.00"));
292        assert_eq!(levels[1].price.value, Price::from("1.01"));
293        assert_eq!(levels[2].price.value, Price::from("1.02"));
294    }
295
296    #[rstest]
297    fn test_add_single_order() {
298        let mut level =
299            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
300        let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
301
302        level.add(order);
303        assert!(!level.is_empty());
304        assert_eq!(level.len(), 1);
305        assert_eq!(level.size(), 10.0);
306        assert_eq!(level.first().unwrap(), &order);
307    }
308
309    #[rstest]
310    fn test_add_multiple_orders() {
311        let mut level =
312            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
313        let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
314        let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
315
316        level.add(order1);
317        level.add(order2);
318        assert_eq!(level.len(), 2);
319        assert_eq!(level.size(), 30.0);
320        assert_eq!(level.exposure(), 60.0);
321        assert_eq!(level.first().unwrap(), &order1);
322    }
323
324    #[rstest]
325    fn test_get_orders() {
326        let mut level =
327            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
328        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
329        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
330
331        level.add(order1);
332        level.add(order2);
333
334        let orders = level.get_orders();
335        assert_eq!(orders.len(), 2);
336        assert_eq!(orders[0], order1); // Checks FIFO order maintained
337        assert_eq!(orders[1], order2);
338    }
339
340    #[rstest]
341    fn test_iter_returns_fifo() {
342        let mut level =
343            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
344        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
345        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
346        level.add(order1);
347        level.add(order2);
348
349        let orders: Vec<_> = level.iter().copied().collect();
350        assert_eq!(orders, vec![order1, order2]);
351    }
352
353    #[rstest]
354    fn test_update_order() {
355        let mut level =
356            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
357        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
358        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 0);
359
360        level.add(order1);
361        level.update(order2);
362        assert_eq!(level.len(), 1);
363        assert_eq!(level.size(), 20.0);
364        assert_eq!(level.exposure(), 20.0);
365    }
366
367    #[rstest]
368    fn test_update_inserts_if_missing() {
369        let mut level =
370            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
371        let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
372        level.update(order);
373        assert_eq!(level.len(), 1);
374        assert_eq!(level.first().unwrap(), &order);
375    }
376
377    #[rstest]
378    fn test_update_zero_size_nonexistent() {
379        let mut level =
380            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
381        let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 1);
382        level.update(order);
383        assert_eq!(level.len(), 0);
384    }
385
386    #[rstest]
387    fn test_fifo_order_after_updates() {
388        let mut level =
389            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
390
391        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
392        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
393
394        level.add(order1);
395        level.add(order2);
396
397        // Update order1 size
398        let updated_order1 =
399            BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
400        level.update(updated_order1);
401
402        let orders = level.get_orders();
403        assert_eq!(orders.len(), 2);
404        assert_eq!(orders[0], updated_order1); // First order still first
405        assert_eq!(orders[1], order2); // Second order still second
406    }
407
408    #[rstest]
409    fn test_insertion_order_after_mixed_operations() {
410        let mut level =
411            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
412        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
413        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
414        let order3 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(30), 3);
415
416        level.add(order1);
417        level.add(order2);
418        level.add(order3);
419
420        // Update order2 (should keep its position)
421        let updated_order2 =
422            BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(25), 2);
423        level.update(updated_order2);
424
425        // Remove order1; order2 (updated) should now be first
426        level.delete(&order1);
427
428        let orders = level.get_orders();
429        assert_eq!(orders, vec![updated_order2, order3]);
430    }
431
432    #[rstest]
433    #[should_panic(expected = "assertion `left == right` failed")]
434    fn test_update_order_incorrect_price() {
435        let mut level =
436            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
437
438        // Add initial order at correct price level
439        let initial_order =
440            BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
441        level.add(initial_order);
442
443        // Attempt to update with order at incorrect price level
444        let updated_order =
445            BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
446        level.update(updated_order);
447    }
448
449    #[rstest]
450    fn test_update_order_with_zero_size() {
451        let mut level =
452            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
453        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
454        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 0);
455
456        level.add(order1);
457        level.update(order2);
458        assert_eq!(level.len(), 0);
459        assert_eq!(level.size(), 0.0);
460        assert_eq!(level.exposure(), 0.0);
461    }
462
463    #[rstest]
464    fn test_delete_nonexistent_order() {
465        let mut level =
466            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
467        let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
468        level.delete(&order);
469        assert_eq!(level.len(), 0);
470    }
471
472    #[rstest]
473    fn test_delete_order() {
474        let mut level =
475            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
476        let order1_id = 0;
477        let order1 = BookOrder::new(
478            OrderSide::Buy,
479            Price::from("1.00"),
480            Quantity::from(10),
481            order1_id,
482        );
483        let order2_id = 1;
484        let order2 = BookOrder::new(
485            OrderSide::Buy,
486            Price::from("1.00"),
487            Quantity::from(20),
488            order2_id,
489        );
490
491        level.add(order1);
492        level.add(order2);
493        level.delete(&order1);
494        assert_eq!(level.len(), 1);
495        assert_eq!(level.size(), 20.0);
496        assert!(level.orders.contains_key(&order2_id));
497        assert_eq!(level.exposure(), 20.0);
498    }
499
500    #[rstest]
501    fn test_remove_order_by_id() {
502        let mut level =
503            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
504        let order1_id = 0;
505        let order1 = BookOrder::new(
506            OrderSide::Buy,
507            Price::from("1.00"),
508            Quantity::from(10),
509            order1_id,
510        );
511        let order2_id = 1;
512        let order2 = BookOrder::new(
513            OrderSide::Buy,
514            Price::from("1.00"),
515            Quantity::from(20),
516            order2_id,
517        );
518
519        level.add(order1);
520        level.add(order2);
521        level.remove_by_id(order2_id, 0, 0.into());
522        assert_eq!(level.len(), 1);
523        assert!(level.orders.contains_key(&order1_id));
524        assert_eq!(level.size(), 10.0);
525        assert_eq!(level.exposure(), 10.0);
526    }
527
528    #[rstest]
529    fn test_add_bulk_orders() {
530        let mut level =
531            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
532        let order1_id = 0;
533        let order1 = BookOrder::new(
534            OrderSide::Buy,
535            Price::from("2.00"),
536            Quantity::from(10),
537            order1_id,
538        );
539        let order2_id = 1;
540        let order2 = BookOrder::new(
541            OrderSide::Buy,
542            Price::from("2.00"),
543            Quantity::from(20),
544            order2_id,
545        );
546
547        let orders = vec![order1, order2];
548        level.add_bulk(orders);
549        assert_eq!(level.len(), 2);
550        assert_eq!(level.size(), 30.0);
551        assert_eq!(level.exposure(), 60.0);
552    }
553
554    #[rstest]
555    fn test_maximum_order_id() {
556        let mut level =
557            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
558
559        let order = BookOrder::new(
560            OrderSide::Buy,
561            Price::from("1.00"),
562            Quantity::from(10),
563            u64::MAX,
564        );
565        level.add(order);
566
567        assert_eq!(level.len(), 1);
568        assert_eq!(level.first().unwrap(), &order);
569    }
570
571    #[rstest]
572    #[should_panic(
573        expected = "Integrity error: order not found: order_id=1, sequence=2, ts_event=3"
574    )]
575    fn test_remove_nonexistent_order() {
576        let mut level =
577            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
578        level.remove_by_id(1, 2, 3.into());
579    }
580
581    #[rstest]
582    fn test_size() {
583        let mut level =
584            BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
585        let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
586        let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
587
588        level.add(order1);
589        level.add(order2);
590        assert_eq!(level.size(), 25.0);
591    }
592
593    #[rstest]
594    fn test_size_raw() {
595        let mut level =
596            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
597        let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
598        let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
599
600        level.add(order1);
601        level.add(order2);
602        assert_eq!(
603            level.size_raw(),
604            (30.0 * FIXED_SCALAR).round() as QuantityRaw
605        );
606    }
607
608    #[rstest]
609    fn test_size_decimal() {
610        let mut level =
611            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
612        let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
613        let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
614
615        level.add(order1);
616        level.add(order2);
617        assert_eq!(level.size_decimal(), dec!(30.0));
618    }
619
620    #[rstest]
621    fn test_exposure() {
622        let mut level =
623            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
624        let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
625        let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
626
627        level.add(order1);
628        level.add(order2);
629        assert_eq!(level.exposure(), 60.0);
630    }
631
632    #[rstest]
633    fn test_exposure_raw() {
634        let mut level =
635            BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
636        let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
637        let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
638
639        level.add(order1);
640        level.add(order2);
641        assert_eq!(
642            level.exposure_raw(),
643            (60.0 * FIXED_SCALAR).round() as QuantityRaw
644        );
645    }
646}