Skip to main content

nautilus_model/python/orderbook/
level.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
16use std::{
17    collections::hash_map::DefaultHasher,
18    hash::{Hash, Hasher},
19};
20
21use nautilus_core::python::IntoPyObjectNautilusExt;
22use pyo3::{prelude::*, pyclass::CompareOp};
23
24use crate::{
25    data::order::BookOrder,
26    enums::OrderSide,
27    orderbook::BookLevel,
28    types::{price::Price, quantity::QuantityRaw},
29};
30
31#[pymethods]
32#[pyo3_stub_gen::derive::gen_stub_pymethods]
33impl BookLevel {
34    fn __repr__(&self) -> String {
35        format!("{self:?}")
36    }
37
38    fn __str__(&self) -> String {
39        // TODO: Return debug string for now
40        format!("{self:?}")
41    }
42
43    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
44        match op {
45            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
46            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
47            CompareOp::Ge if self.side() == other.side() => self.ge(other).into_py_any_unwrap(py),
48            CompareOp::Gt if self.side() == other.side() => self.gt(other).into_py_any_unwrap(py),
49            CompareOp::Le if self.side() == other.side() => self.le(other).into_py_any_unwrap(py),
50            CompareOp::Lt if self.side() == other.side() => self.lt(other).into_py_any_unwrap(py),
51            CompareOp::Ge | CompareOp::Gt | CompareOp::Le | CompareOp::Lt => py.NotImplemented(),
52        }
53    }
54
55    fn __hash__(&self) -> isize {
56        let mut hasher = DefaultHasher::new();
57        self.side().hash(&mut hasher);
58        self.price.value.hash(&mut hasher);
59        hasher.finish() as isize
60    }
61
62    #[getter]
63    #[pyo3(name = "price")]
64    fn py_price(&self) -> Price {
65        self.price.value
66    }
67
68    #[getter]
69    #[pyo3(name = "side")]
70    fn py_side(&self) -> OrderSide {
71        self.side()
72    }
73
74    /// Returns the number of orders at this price level.
75    #[pyo3(name = "len")]
76    fn py_len(&self) -> usize {
77        self.len()
78    }
79
80    /// Returns true if this price level has no orders.
81    #[pyo3(name = "is_empty")]
82    fn py_is_empty(&self) -> bool {
83        self.is_empty()
84    }
85
86    /// Returns the total size of all orders at this price level as a float.
87    #[pyo3(name = "size")]
88    fn py_size(&self) -> f64 {
89        self.size()
90    }
91
92    /// Returns the total size of all orders at this price level as raw integer units.
93    #[pyo3(name = "size_raw")]
94    fn py_size_raw(&self) -> QuantityRaw {
95        self.size_raw()
96    }
97
98    /// Returns the total exposure (price * size) of all orders at this price level as a float.
99    #[pyo3(name = "exposure")]
100    fn py_exposure(&self) -> f64 {
101        self.exposure()
102    }
103
104    /// Returns the total exposure (price * size) of all orders at this price level as raw integer units.
105    ///
106    /// Fixed-scale orders contribute `price.raw * size.raw / FIXED_SCALAR`.
107    /// Native DeFi scales are normalized to the same fixed-scale result.
108    /// Division truncates toward zero.
109    /// Non-positive prices contribute zero.
110    /// Saturates at `QuantityRaw::MAX` if the total exposure would overflow.
111    #[pyo3(name = "exposure_raw")]
112    fn py_exposure_raw(&self) -> QuantityRaw {
113        self.exposure_raw()
114    }
115
116    #[pyo3(name = "first")]
117    fn py_fist(&self) -> Option<BookOrder> {
118        self.first().copied()
119    }
120
121    /// Returns all orders at this price level in FIFO insertion order.
122    #[pyo3(name = "get_orders")]
123    fn py_get_orders(&self) -> Vec<BookOrder> {
124        self.get_orders()
125    }
126}