Skip to main content

nautilus_model/data/
order.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
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", from_py_object)
54)]
55#[cfg_attr(
56    feature = "python",
57    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
58)]
59pub struct BookOrder {
60    /// The order side.
61    pub side: OrderSide,
62    /// The order price.
63    pub price: Price,
64    /// The order size.
65    pub size: Quantity,
66    /// The order ID.
67    pub order_id: OrderId,
68}
69
70impl BookOrder {
71    /// Creates a new [`BookOrder`] instance.
72    #[must_use]
73    pub fn new(side: OrderSide, price: Price, size: Quantity, order_id: OrderId) -> Self {
74        Self {
75            side,
76            price,
77            size,
78            order_id,
79        }
80    }
81
82    /// Returns a [`BookPrice`] from this order.
83    #[must_use]
84    pub fn to_book_price(&self) -> BookPrice {
85        BookPrice::new(self.price, self.side.as_specified())
86    }
87
88    /// Returns the order exposure as an `f64`.
89    #[must_use]
90    pub fn exposure(&self) -> f64 {
91        self.price.as_f64() * self.size.as_f64()
92    }
93
94    /// Returns the signed order size as `f64`, positive for buys, negative for sells.
95    ///
96    /// # Panics
97    ///
98    /// Panics if `self.side` is `NoOrderSide`.
99    #[must_use]
100    pub fn signed_size(&self) -> f64 {
101        match self.side {
102            OrderSide::Buy => self.size.as_f64(),
103            OrderSide::Sell => -(self.size.as_f64()),
104            _ => panic!("{}", BookIntegrityError::NoOrderSide),
105        }
106    }
107}
108
109impl Default for BookOrder {
110    /// Creates a NULL [`BookOrder`] instance.
111    fn default() -> Self {
112        NULL_ORDER
113    }
114}
115
116impl PartialEq for BookOrder {
117    fn eq(&self, other: &Self) -> bool {
118        self.order_id == other.order_id
119    }
120}
121
122impl Hash for BookOrder {
123    fn hash<H: Hasher>(&self, state: &mut H) {
124        self.order_id.hash(state);
125    }
126}
127
128impl Debug for BookOrder {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(
131            f,
132            "{}(side={}, price={}, size={}, order_id={})",
133            stringify!(BookOrder),
134            self.side,
135            self.price,
136            self.size,
137            self.order_id,
138        )
139    }
140}
141
142impl Display for BookOrder {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(
145            f,
146            "{},{},{},{}",
147            self.side, self.price, self.size, self.order_id,
148        )
149    }
150}
151
152impl Serializable for BookOrder {}
153
154#[cfg(test)]
155mod tests {
156    use rstest::rstest;
157
158    use super::*;
159
160    #[rstest]
161    fn test_new() {
162        let price = Price::from("100.00");
163        let size = Quantity::from("10");
164        let side = OrderSide::Buy;
165        let order_id = 123_456;
166
167        let order = BookOrder::new(side, price, size, order_id);
168
169        assert_eq!(order.price, price);
170        assert_eq!(order.size, size);
171        assert_eq!(order.side, side);
172        assert_eq!(order.order_id, order_id);
173    }
174
175    #[rstest]
176    fn test_to_book_price() {
177        let price = Price::from("100.00");
178        let size = Quantity::from("10");
179        let side = OrderSide::Buy;
180        let order_id = 123_456;
181
182        let order = BookOrder::new(side, price, size, order_id);
183        let book_price = order.to_book_price();
184
185        assert_eq!(book_price.value, price);
186        assert_eq!(book_price.side, side.as_specified());
187    }
188
189    #[rstest]
190    fn test_exposure() {
191        let price = Price::from("100.00");
192        let size = Quantity::from("10");
193        let side = OrderSide::Buy;
194        let order_id = 123_456;
195
196        let order = BookOrder::new(side, price, size, order_id);
197        let exposure = order.exposure();
198
199        assert_eq!(exposure, 100.00 * 10.0);
200    }
201
202    #[rstest]
203    fn test_signed_size() {
204        let price = Price::from("100.00");
205        let size = Quantity::from("10");
206        let order_id = 123_456;
207
208        let order_buy = BookOrder::new(OrderSide::Buy, price, size, order_id);
209        let signed_size_buy = order_buy.signed_size();
210        assert_eq!(signed_size_buy, 10.0);
211
212        let order_sell = BookOrder::new(OrderSide::Sell, price, size, order_id);
213        let signed_size_sell = order_sell.signed_size();
214        assert_eq!(signed_size_sell, -10.0);
215    }
216
217    #[rstest]
218    fn test_debug() {
219        let price = Price::from("100.00");
220        let size = Quantity::from(10);
221        let side = OrderSide::Buy;
222        let order_id = 123_456;
223        let order = BookOrder::new(side, price, size, order_id);
224        let result = format!("{order:?}");
225        let expected = "BookOrder(side=BUY, price=100.00, size=10, order_id=123456)";
226        assert_eq!(result, expected);
227    }
228
229    #[rstest]
230    fn test_display() {
231        let price = Price::from("100.00");
232        let size = Quantity::from(10);
233        let side = OrderSide::Buy;
234        let order_id = 123_456;
235        let order = BookOrder::new(side, price, size, order_id);
236        let result = format!("{order}");
237        let expected = "BUY,100.00,10,123456";
238        assert_eq!(result, expected);
239    }
240}