nautilus_model/data/
order.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//! 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 = "posei_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////////////////////////////////////////////////////////////////////////////////
151// Tests
152////////////////////////////////////////////////////////////////////////////////
153#[cfg(test)]
154mod tests {
155    use rstest::rstest;
156
157    use super::*;
158
159    #[rstest]
160    fn test_new() {
161        let price = Price::from("100.00");
162        let size = Quantity::from("10");
163        let side = OrderSide::Buy;
164        let order_id = 123_456;
165
166        let order = BookOrder::new(side, price, size, order_id);
167
168        assert_eq!(order.price, price);
169        assert_eq!(order.size, size);
170        assert_eq!(order.side, side);
171        assert_eq!(order.order_id, order_id);
172    }
173
174    #[rstest]
175    fn test_to_book_price() {
176        let price = Price::from("100.00");
177        let size = Quantity::from("10");
178        let side = OrderSide::Buy;
179        let order_id = 123_456;
180
181        let order = BookOrder::new(side, price, size, order_id);
182        let book_price = order.to_book_price();
183
184        assert_eq!(book_price.value, price);
185        assert_eq!(book_price.side, side.as_specified());
186    }
187
188    #[rstest]
189    fn test_exposure() {
190        let price = Price::from("100.00");
191        let size = Quantity::from("10");
192        let side = OrderSide::Buy;
193        let order_id = 123_456;
194
195        let order = BookOrder::new(side, price, size, order_id);
196        let exposure = order.exposure();
197
198        assert_eq!(exposure, 100.00 * 10.0);
199    }
200
201    #[rstest]
202    fn test_signed_size() {
203        let price = Price::from("100.00");
204        let size = Quantity::from("10");
205        let order_id = 123_456;
206
207        let order_buy = BookOrder::new(OrderSide::Buy, price, size, order_id);
208        let signed_size_buy = order_buy.signed_size();
209        assert_eq!(signed_size_buy, 10.0);
210
211        let order_sell = BookOrder::new(OrderSide::Sell, price, size, order_id);
212        let signed_size_sell = order_sell.signed_size();
213        assert_eq!(signed_size_sell, -10.0);
214    }
215
216    #[rstest]
217    fn test_debug() {
218        let price = Price::from("100.00");
219        let size = Quantity::from(10);
220        let side = OrderSide::Buy;
221        let order_id = 123_456;
222        let order = BookOrder::new(side, price, size, order_id);
223        let result = format!("{order:?}");
224        let expected = "BookOrder(side=BUY, price=100.00, size=10, order_id=123456)";
225        assert_eq!(result, expected);
226    }
227
228    #[rstest]
229    fn test_display() {
230        let price = Price::from("100.00");
231        let size = Quantity::from(10);
232        let side = OrderSide::Buy;
233        let order_id = 123_456;
234        let order = BookOrder::new(side, price, size, order_id);
235        let result = format!("{order}");
236        let expected = "BUY,100.00,10,123456";
237        assert_eq!(result, expected);
238    }
239}