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