1use std::fmt::Display;
17
18use nautilus_core::{UnixNanos, correctness::check_slice_not_empty};
19use serde::{Deserialize, Serialize};
20
21use super::{Order, OrderAny};
22use crate::identifiers::{InstrumentId, OrderListId, StrategyId};
23
24#[derive(Clone, Eq, Debug, Serialize, Deserialize)]
25#[cfg_attr(
26 feature = "python",
27 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
28)]
29pub struct OrderList {
30 pub id: OrderListId,
31 pub instrument_id: InstrumentId,
32 pub strategy_id: StrategyId,
33 pub orders: Vec<OrderAny>,
34 pub ts_init: UnixNanos,
35}
36
37impl OrderList {
38 pub fn new(
44 order_list_id: OrderListId,
45 instrument_id: InstrumentId,
46 strategy_id: StrategyId,
47 orders: Vec<OrderAny>,
48 ts_init: UnixNanos,
49 ) -> Self {
50 check_slice_not_empty(orders.as_slice(), stringify!(orders)).unwrap();
51 for order in &orders {
52 assert_eq!(instrument_id, order.instrument_id());
53 assert_eq!(strategy_id, order.strategy_id());
54 }
55 Self {
56 id: order_list_id,
57 instrument_id,
58 strategy_id,
59 orders,
60 ts_init,
61 }
62 }
63}
64
65impl PartialEq for OrderList {
66 fn eq(&self, other: &Self) -> bool {
67 self.id == other.id
68 }
69}
70
71impl Display for OrderList {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(
74 f,
75 "OrderList(\
76 id={}, \
77 instrument_id={}, \
78 strategy_id={}, \
79 orders={:?}, \
80 ts_init={}\
81 )",
82 self.id, self.instrument_id, self.strategy_id, self.orders, self.ts_init,
83 )
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use rstest::rstest;
90
91 use super::*;
92 use crate::{
93 enums::{OrderSide, OrderType},
94 identifiers::{OrderListId, StrategyId},
95 instruments::{CurrencyPair, stubs::*},
96 orders::OrderTestBuilder,
97 types::{Price, Quantity},
98 };
99
100 #[rstest]
101 fn test_new_and_display(audusd_sim: CurrencyPair) {
102 let order1 = OrderTestBuilder::new(OrderType::Limit)
103 .instrument_id(audusd_sim.id)
104 .side(OrderSide::Buy)
105 .price(Price::from("1.00000"))
106 .quantity(Quantity::from(100_000))
107 .build();
108 let order2 = OrderTestBuilder::new(OrderType::Limit)
109 .instrument_id(audusd_sim.id)
110 .side(OrderSide::Buy)
111 .price(Price::from("1.00000"))
112 .quantity(Quantity::from(100_000))
113 .build();
114 let order3 = OrderTestBuilder::new(OrderType::Limit)
115 .instrument_id(audusd_sim.id)
116 .side(OrderSide::Buy)
117 .price(Price::from("1.00000"))
118 .quantity(Quantity::from(100_000))
119 .build();
120
121 let orders = vec![order1, order2, order3];
122
123 let order_list = OrderList::new(
124 OrderListId::from("OL-001"),
125 audusd_sim.id,
126 StrategyId::default(),
127 orders,
128 UnixNanos::default(),
129 );
130
131 assert!(order_list.to_string().starts_with(
132 "OrderList(id=OL-001, instrument_id=AUD/USD.SIM, strategy_id=S-001, orders="
133 ));
134 }
135
136 #[rstest]
137 #[should_panic(expected = "assertion `left == right` failed")]
138 fn test_order_list_creation_with_mismatched_instrument_id(audusd_sim: CurrencyPair) {
139 let order1 = OrderTestBuilder::new(OrderType::Limit)
140 .instrument_id(audusd_sim.id)
141 .side(OrderSide::Buy)
142 .price(Price::from("1.00000"))
143 .quantity(Quantity::from(100_000))
144 .build();
145 let order2 = OrderTestBuilder::new(OrderType::Limit)
146 .instrument_id(InstrumentId::from("EUR/USD.SIM"))
147 .side(OrderSide::Sell)
148 .price(Price::from("1.01000"))
149 .quantity(Quantity::from(50_000))
150 .build();
151
152 let orders = vec![order1, order2];
153
154 OrderList::new(
156 OrderListId::from("OL-003"),
157 audusd_sim.id,
158 StrategyId::default(),
159 orders,
160 UnixNanos::default(),
161 );
162 }
163
164 #[rstest]
165 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: the 'orders' slice")]
166 fn test_order_list_creation_with_empty_orders(audusd_sim: CurrencyPair) {
167 let orders: Vec<OrderAny> = vec![];
168
169 OrderList::new(
171 OrderListId::from("OL-004"),
172 audusd_sim.id,
173 StrategyId::default(),
174 orders,
175 UnixNanos::default(),
176 );
177 }
178
179 #[rstest]
180 fn test_order_list_equality(audusd_sim: CurrencyPair) {
181 let order1 = OrderTestBuilder::new(OrderType::Limit)
182 .instrument_id(audusd_sim.id)
183 .side(OrderSide::Buy)
184 .price(Price::from("1.00000"))
185 .quantity(Quantity::from(100_000))
186 .build();
187
188 let orders = vec![order1];
189
190 let order_list1 = OrderList::new(
191 OrderListId::from("OL-006"),
192 audusd_sim.id,
193 StrategyId::default(),
194 orders.clone(),
195 UnixNanos::default(),
196 );
197
198 let order_list2 = OrderList::new(
199 OrderListId::from("OL-006"),
200 audusd_sim.id,
201 StrategyId::default(),
202 orders,
203 UnixNanos::default(),
204 );
205
206 assert_eq!(order_list1, order_list2);
207 }
208
209 #[rstest]
210 fn test_order_list_inequality(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-007"),
222 audusd_sim.id,
223 StrategyId::default(),
224 orders.clone(),
225 UnixNanos::default(),
226 );
227
228 let order_list2 = OrderList::new(
229 OrderListId::from("OL-008"),
230 audusd_sim.id,
231 StrategyId::default(),
232 orders,
233 UnixNanos::default(),
234 );
235
236 assert_ne!(order_list1, order_list2);
237 }
238}