nautilus_model/orderbook/
level.rs

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