nautilus_model/data/
order.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//! A `BookOrder` for use with the `OrderBook` and `OrderBookDelta` data type.
17
18use std::{
19    fmt::{Debug, Display},
20    hash::{Hash, Hasher},
21};
22
23use nautilus_core::serialization::Serializable;
24use serde::{Deserialize, Serialize};
25
26use crate::{
27    enums::OrderSide,
28    orderbook::{BookIntegrityError, BookPrice},
29    types::{Price, Quantity},
30};
31
32pub type OrderId = u64;
33
34/// Represents a NULL book order (used with the `Clear` action or where an order is not specified).
35pub const NULL_ORDER: BookOrder = BookOrder {
36    side: OrderSide::NoOrderSide,
37    price: Price {
38        raw: 0,
39        precision: 0,
40    },
41    size: Quantity {
42        raw: 0,
43        precision: 0,
44    },
45    order_id: 0,
46};
47
48/// Represents an order in a book.
49#[repr(C)]
50#[derive(Clone, Copy, Eq, Serialize, Deserialize)]
51#[cfg_attr(
52    feature = "python",
53    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
54)]
55pub struct BookOrder {
56    /// The order side.
57    pub side: OrderSide,
58    /// The order price.
59    pub price: Price,
60    /// The order size.
61    pub size: Quantity,
62    /// The order ID.
63    pub order_id: OrderId,
64}
65
66impl BookOrder {
67    /// Creates a new [`BookOrder`] instance.
68    #[must_use]
69    pub fn new(side: OrderSide, price: Price, size: Quantity, order_id: OrderId) -> Self {
70        Self {
71            side,
72            price,
73            size,
74            order_id,
75        }
76    }
77
78    /// Returns a [`BookPrice`] from this order.
79    #[must_use]
80    pub fn to_book_price(&self) -> BookPrice {
81        BookPrice::new(self.price, self.side.as_specified())
82    }
83
84    /// Returns the order exposure as an `f64`.
85    #[must_use]
86    pub fn exposure(&self) -> f64 {
87        self.price.as_f64() * self.size.as_f64()
88    }
89
90    /// Returns the signed order size as `f64`, positive for buys, negative for sells.
91    ///
92    /// # Panics
93    ///
94    /// Panics if `self.side` is `NoOrderSide`.
95    #[must_use]
96    pub fn signed_size(&self) -> f64 {
97        match self.side {
98            OrderSide::Buy => self.size.as_f64(),
99            OrderSide::Sell => -(self.size.as_f64()),
100            _ => panic!("{}", BookIntegrityError::NoOrderSide),
101        }
102    }
103}
104
105impl Default for BookOrder {
106    /// Creates a NULL [`BookOrder`] instance.
107    fn default() -> Self {
108        NULL_ORDER
109    }
110}
111
112impl PartialEq for BookOrder {
113    fn eq(&self, other: &Self) -> bool {
114        self.order_id == other.order_id
115    }
116}
117
118impl Hash for BookOrder {
119    fn hash<H: Hasher>(&self, state: &mut H) {
120        self.order_id.hash(state);
121    }
122}
123
124impl Debug for BookOrder {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(
127            f,
128            "{}(side={}, price={}, size={}, order_id={})",
129            stringify!(BookOrder),
130            self.side,
131            self.price,
132            self.size,
133            self.order_id,
134        )
135    }
136}
137
138impl Display for BookOrder {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        write!(
141            f,
142            "{},{},{},{}",
143            self.side, self.price, self.size, self.order_id,
144        )
145    }
146}
147
148impl Serializable for BookOrder {}
149
150#[cfg(test)]
151mod tests {
152    use rstest::rstest;
153
154    use super::*;
155
156    #[rstest]
157    fn test_new() {
158        let price = Price::from("100.00");
159        let size = Quantity::from("10");
160        let side = OrderSide::Buy;
161        let order_id = 123_456;
162
163        let order = BookOrder::new(side, price, size, order_id);
164
165        assert_eq!(order.price, price);
166        assert_eq!(order.size, size);
167        assert_eq!(order.side, side);
168        assert_eq!(order.order_id, order_id);
169    }
170
171    #[rstest]
172    fn test_to_book_price() {
173        let price = Price::from("100.00");
174        let size = Quantity::from("10");
175        let side = OrderSide::Buy;
176        let order_id = 123_456;
177
178        let order = BookOrder::new(side, price, size, order_id);
179        let book_price = order.to_book_price();
180
181        assert_eq!(book_price.value, price);
182        assert_eq!(book_price.side, side.as_specified());
183    }
184
185    #[rstest]
186    fn test_exposure() {
187        let price = Price::from("100.00");
188        let size = Quantity::from("10");
189        let side = OrderSide::Buy;
190        let order_id = 123_456;
191
192        let order = BookOrder::new(side, price, size, order_id);
193        let exposure = order.exposure();
194
195        assert_eq!(exposure, 100.00 * 10.0);
196    }
197
198    #[rstest]
199    fn test_signed_size() {
200        let price = Price::from("100.00");
201        let size = Quantity::from("10");
202        let order_id = 123_456;
203
204        let order_buy = BookOrder::new(OrderSide::Buy, price, size, order_id);
205        let signed_size_buy = order_buy.signed_size();
206        assert_eq!(signed_size_buy, 10.0);
207
208        let order_sell = BookOrder::new(OrderSide::Sell, price, size, order_id);
209        let signed_size_sell = order_sell.signed_size();
210        assert_eq!(signed_size_sell, -10.0);
211    }
212
213    #[rstest]
214    fn test_debug() {
215        let price = Price::from("100.00");
216        let size = Quantity::from(10);
217        let side = OrderSide::Buy;
218        let order_id = 123_456;
219        let order = BookOrder::new(side, price, size, order_id);
220        let result = format!("{order:?}");
221        let expected = "BookOrder(side=BUY, price=100.00, size=10, order_id=123456)";
222        assert_eq!(result, expected);
223    }
224
225    #[rstest]
226    fn test_display() {
227        let price = Price::from("100.00");
228        let size = Quantity::from(10);
229        let side = OrderSide::Buy;
230        let order_id = 123_456;
231        let order = BookOrder::new(side, price, size, order_id);
232        let result = format!("{order}");
233        let expected = "BUY,100.00,10,123456";
234        assert_eq!(result, expected);
235    }
236}