1use std::{
17 fmt::Display,
18 hash::{Hash, Hasher},
19};
20
21use nautilus_core::{UnixNanos, correctness::check_slice_not_empty};
22use serde::{Deserialize, Serialize};
23
24use super::{Order, OrderAny};
25use crate::identifiers::{InstrumentId, OrderListId, StrategyId};
26
27#[derive(Clone, Eq, Debug, Serialize, Deserialize)]
28#[cfg_attr(
29 feature = "python",
30 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
31)]
32pub struct OrderList {
33 pub id: OrderListId,
34 pub instrument_id: InstrumentId,
35 pub strategy_id: StrategyId,
36 pub orders: Vec<OrderAny>,
37 pub ts_init: UnixNanos,
38}
39
40impl OrderList {
41 pub fn new(
47 order_list_id: OrderListId,
48 instrument_id: InstrumentId,
49 strategy_id: StrategyId,
50 orders: Vec<OrderAny>,
51 ts_init: UnixNanos,
52 ) -> Self {
53 check_slice_not_empty(orders.as_slice(), stringify!(orders)).unwrap();
54 for order in &orders {
55 assert_eq!(instrument_id, order.instrument_id());
56 assert_eq!(strategy_id, order.strategy_id());
57 }
58 Self {
59 id: order_list_id,
60 instrument_id,
61 strategy_id,
62 orders,
63 ts_init,
64 }
65 }
66
67 #[must_use]
69 pub fn first(&self) -> Option<&OrderAny> {
70 self.orders.first()
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.orders.len()
77 }
78
79 #[must_use]
81 pub fn is_empty(&self) -> bool {
82 self.orders.is_empty()
83 }
84}
85
86impl PartialEq for OrderList {
87 fn eq(&self, other: &Self) -> bool {
88 self.id == other.id
89 }
90}
91
92impl Hash for OrderList {
93 fn hash<H: Hasher>(&self, state: &mut H) {
94 self.id.hash(state);
95 }
96}
97
98impl Display for OrderList {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 write!(
101 f,
102 "OrderList(\
103 id={}, \
104 instrument_id={}, \
105 strategy_id={}, \
106 orders={:?}, \
107 ts_init={}\
108 )",
109 self.id, self.instrument_id, self.strategy_id, self.orders, self.ts_init,
110 )
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use std::collections::hash_map::DefaultHasher;
117
118 use rstest::rstest;
119
120 use super::*;
121 use crate::{
122 enums::{OrderSide, OrderType},
123 identifiers::{OrderListId, StrategyId},
124 instruments::{CurrencyPair, stubs::*},
125 orders::OrderTestBuilder,
126 stubs::TestDefault,
127 types::{Price, Quantity},
128 };
129
130 #[rstest]
131 fn test_new_and_display(audusd_sim: CurrencyPair) {
132 let order1 = OrderTestBuilder::new(OrderType::Limit)
133 .instrument_id(audusd_sim.id)
134 .side(OrderSide::Buy)
135 .price(Price::from("1.00000"))
136 .quantity(Quantity::from(100_000))
137 .build();
138 let order2 = OrderTestBuilder::new(OrderType::Limit)
139 .instrument_id(audusd_sim.id)
140 .side(OrderSide::Buy)
141 .price(Price::from("1.00000"))
142 .quantity(Quantity::from(100_000))
143 .build();
144 let order3 = OrderTestBuilder::new(OrderType::Limit)
145 .instrument_id(audusd_sim.id)
146 .side(OrderSide::Buy)
147 .price(Price::from("1.00000"))
148 .quantity(Quantity::from(100_000))
149 .build();
150
151 let orders = vec![order1, order2, order3];
152
153 let order_list = OrderList::new(
154 OrderListId::from("OL-001"),
155 audusd_sim.id,
156 StrategyId::test_default(),
157 orders,
158 UnixNanos::default(),
159 );
160
161 assert!(order_list.to_string().starts_with(
162 "OrderList(id=OL-001, instrument_id=AUD/USD.SIM, strategy_id=S-001, orders="
163 ));
164 }
165
166 #[rstest]
167 #[should_panic(expected = "assertion `left == right` failed")]
168 fn test_order_list_creation_with_mismatched_instrument_id(audusd_sim: CurrencyPair) {
169 let order1 = OrderTestBuilder::new(OrderType::Limit)
170 .instrument_id(audusd_sim.id)
171 .side(OrderSide::Buy)
172 .price(Price::from("1.00000"))
173 .quantity(Quantity::from(100_000))
174 .build();
175 let order2 = OrderTestBuilder::new(OrderType::Limit)
176 .instrument_id(InstrumentId::from("EUR/USD.SIM"))
177 .side(OrderSide::Sell)
178 .price(Price::from("1.01000"))
179 .quantity(Quantity::from(50_000))
180 .build();
181
182 let orders = vec![order1, order2];
183
184 OrderList::new(
186 OrderListId::from("OL-003"),
187 audusd_sim.id,
188 StrategyId::test_default(),
189 orders,
190 UnixNanos::default(),
191 );
192 }
193
194 #[rstest]
195 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: the 'orders' slice")]
196 fn test_order_list_creation_with_empty_orders(audusd_sim: CurrencyPair) {
197 let orders: Vec<OrderAny> = vec![];
198
199 OrderList::new(
201 OrderListId::from("OL-004"),
202 audusd_sim.id,
203 StrategyId::test_default(),
204 orders,
205 UnixNanos::default(),
206 );
207 }
208
209 #[rstest]
210 fn test_order_list_equality(audusd_sim: CurrencyPair) {
211 let order1 = OrderTestBuilder::new(OrderType::Limit)
212 .instrument_id(audusd_sim.id)
213 .side(OrderSide::Buy)
214 .price(Price::from("1.00000"))
215 .quantity(Quantity::from(100_000))
216 .build();
217
218 let orders = vec![order1];
219
220 let order_list1 = OrderList::new(
221 OrderListId::from("OL-006"),
222 audusd_sim.id,
223 StrategyId::test_default(),
224 orders.clone(),
225 UnixNanos::default(),
226 );
227
228 let order_list2 = OrderList::new(
229 OrderListId::from("OL-006"),
230 audusd_sim.id,
231 StrategyId::test_default(),
232 orders,
233 UnixNanos::default(),
234 );
235
236 assert_eq!(order_list1, order_list2);
237 }
238
239 #[rstest]
240 fn test_order_list_inequality(audusd_sim: CurrencyPair) {
241 let order1 = OrderTestBuilder::new(OrderType::Limit)
242 .instrument_id(audusd_sim.id)
243 .side(OrderSide::Buy)
244 .price(Price::from("1.00000"))
245 .quantity(Quantity::from(100_000))
246 .build();
247
248 let orders = vec![order1];
249
250 let order_list1 = OrderList::new(
251 OrderListId::from("OL-007"),
252 audusd_sim.id,
253 StrategyId::test_default(),
254 orders.clone(),
255 UnixNanos::default(),
256 );
257
258 let order_list2 = OrderList::new(
259 OrderListId::from("OL-008"),
260 audusd_sim.id,
261 StrategyId::test_default(),
262 orders,
263 UnixNanos::default(),
264 );
265
266 assert_ne!(order_list1, order_list2);
267 }
268
269 #[rstest]
270 fn test_order_list_first(audusd_sim: CurrencyPair) {
271 let order1 = OrderTestBuilder::new(OrderType::Limit)
272 .instrument_id(audusd_sim.id)
273 .side(OrderSide::Buy)
274 .price(Price::from("1.00000"))
275 .quantity(Quantity::from(100_000))
276 .build();
277 let order2 = OrderTestBuilder::new(OrderType::Limit)
278 .instrument_id(audusd_sim.id)
279 .side(OrderSide::Sell)
280 .price(Price::from("1.01000"))
281 .quantity(Quantity::from(50_000))
282 .build();
283
284 let first_order_id = order1.client_order_id();
285 let orders = vec![order1, order2];
286
287 let order_list = OrderList::new(
288 OrderListId::from("OL-009"),
289 audusd_sim.id,
290 StrategyId::test_default(),
291 orders,
292 UnixNanos::default(),
293 );
294
295 let first = order_list.first();
296 assert!(first.is_some());
297 assert_eq!(first.unwrap().client_order_id(), first_order_id);
298 }
299
300 #[rstest]
301 fn test_order_list_len(audusd_sim: CurrencyPair) {
302 let order1 = OrderTestBuilder::new(OrderType::Limit)
303 .instrument_id(audusd_sim.id)
304 .side(OrderSide::Buy)
305 .price(Price::from("1.00000"))
306 .quantity(Quantity::from(100_000))
307 .build();
308 let order2 = OrderTestBuilder::new(OrderType::Limit)
309 .instrument_id(audusd_sim.id)
310 .side(OrderSide::Sell)
311 .price(Price::from("1.01000"))
312 .quantity(Quantity::from(50_000))
313 .build();
314 let order3 = OrderTestBuilder::new(OrderType::Limit)
315 .instrument_id(audusd_sim.id)
316 .side(OrderSide::Buy)
317 .price(Price::from("0.99000"))
318 .quantity(Quantity::from(75_000))
319 .build();
320
321 let orders = vec![order1, order2, order3];
322
323 let order_list = OrderList::new(
324 OrderListId::from("OL-010"),
325 audusd_sim.id,
326 StrategyId::test_default(),
327 orders,
328 UnixNanos::default(),
329 );
330
331 assert_eq!(order_list.len(), 3);
332 assert!(!order_list.is_empty());
333 }
334
335 #[rstest]
336 fn test_order_list_hash(audusd_sim: CurrencyPair) {
337 let order1 = OrderTestBuilder::new(OrderType::Limit)
338 .instrument_id(audusd_sim.id)
339 .side(OrderSide::Buy)
340 .price(Price::from("1.00000"))
341 .quantity(Quantity::from(100_000))
342 .build();
343
344 let orders = vec![order1];
345
346 let order_list1 = OrderList::new(
347 OrderListId::from("OL-011"),
348 audusd_sim.id,
349 StrategyId::test_default(),
350 orders.clone(),
351 UnixNanos::default(),
352 );
353
354 let order_list2 = OrderList::new(
355 OrderListId::from("OL-011"),
356 audusd_sim.id,
357 StrategyId::test_default(),
358 orders,
359 UnixNanos::default(),
360 );
361
362 let mut hasher1 = DefaultHasher::new();
363 let mut hasher2 = DefaultHasher::new();
364 order_list1.hash(&mut hasher1);
365 order_list2.hash(&mut hasher2);
366
367 assert_eq!(hasher1.finish(), hasher2.finish());
368 }
369}