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 = "posei_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)]
91mod tests {
92 use rstest::rstest;
93
94 use super::*;
95 use crate::{
96 enums::{OrderSide, OrderType},
97 identifiers::{OrderListId, StrategyId},
98 instruments::{CurrencyPair, stubs::*},
99 orders::OrderTestBuilder,
100 types::{Price, Quantity},
101 };
102
103 #[rstest]
104 fn test_new_and_display(audusd_sim: CurrencyPair) {
105 let order1 = OrderTestBuilder::new(OrderType::Limit)
106 .instrument_id(audusd_sim.id)
107 .side(OrderSide::Buy)
108 .price(Price::from("1.00000"))
109 .quantity(Quantity::from(100_000))
110 .build();
111 let order2 = OrderTestBuilder::new(OrderType::Limit)
112 .instrument_id(audusd_sim.id)
113 .side(OrderSide::Buy)
114 .price(Price::from("1.00000"))
115 .quantity(Quantity::from(100_000))
116 .build();
117 let order3 = OrderTestBuilder::new(OrderType::Limit)
118 .instrument_id(audusd_sim.id)
119 .side(OrderSide::Buy)
120 .price(Price::from("1.00000"))
121 .quantity(Quantity::from(100_000))
122 .build();
123
124 let orders = vec![order1, order2, order3];
125
126 let order_list = OrderList::new(
127 OrderListId::from("OL-001"),
128 audusd_sim.id,
129 StrategyId::default(),
130 orders,
131 UnixNanos::default(),
132 );
133
134 assert!(order_list.to_string().starts_with(
135 "OrderList(id=OL-001, instrument_id=AUD/USD.SIM, strategy_id=S-001, orders="
136 ));
137 }
138
139 #[rstest]
140 #[should_panic(expected = "assertion `left == right` failed")]
141 fn test_order_list_creation_with_mismatched_instrument_id(audusd_sim: CurrencyPair) {
142 let order1 = OrderTestBuilder::new(OrderType::Limit)
143 .instrument_id(audusd_sim.id)
144 .side(OrderSide::Buy)
145 .price(Price::from("1.00000"))
146 .quantity(Quantity::from(100_000))
147 .build();
148 let order2 = OrderTestBuilder::new(OrderType::Limit)
149 .instrument_id(InstrumentId::from("EUR/USD.SIM"))
150 .side(OrderSide::Sell)
151 .price(Price::from("1.01000"))
152 .quantity(Quantity::from(50_000))
153 .build();
154
155 let orders = vec![order1, order2];
156
157 OrderList::new(
159 OrderListId::from("OL-003"),
160 audusd_sim.id,
161 StrategyId::default(),
162 orders,
163 UnixNanos::default(),
164 );
165 }
166
167 #[rstest]
168 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: the 'orders' slice")]
169 fn test_order_list_creation_with_empty_orders(audusd_sim: CurrencyPair) {
170 let orders: Vec<OrderAny> = vec![];
171
172 OrderList::new(
174 OrderListId::from("OL-004"),
175 audusd_sim.id,
176 StrategyId::default(),
177 orders,
178 UnixNanos::default(),
179 );
180 }
181
182 #[rstest]
183 fn test_order_list_equality(audusd_sim: CurrencyPair) {
184 let order1 = OrderTestBuilder::new(OrderType::Limit)
185 .instrument_id(audusd_sim.id)
186 .side(OrderSide::Buy)
187 .price(Price::from("1.00000"))
188 .quantity(Quantity::from(100_000))
189 .build();
190
191 let orders = vec![order1];
192
193 let order_list1 = OrderList::new(
194 OrderListId::from("OL-006"),
195 audusd_sim.id,
196 StrategyId::default(),
197 orders.clone(),
198 UnixNanos::default(),
199 );
200
201 let order_list2 = OrderList::new(
202 OrderListId::from("OL-006"),
203 audusd_sim.id,
204 StrategyId::default(),
205 orders,
206 UnixNanos::default(),
207 );
208
209 assert_eq!(order_list1, order_list2);
210 }
211
212 #[rstest]
213 fn test_order_list_inequality(audusd_sim: CurrencyPair) {
214 let order1 = OrderTestBuilder::new(OrderType::Limit)
215 .instrument_id(audusd_sim.id)
216 .side(OrderSide::Buy)
217 .price(Price::from("1.00000"))
218 .quantity(Quantity::from(100_000))
219 .build();
220
221 let orders = vec![order1];
222
223 let order_list1 = OrderList::new(
224 OrderListId::from("OL-007"),
225 audusd_sim.id,
226 StrategyId::default(),
227 orders.clone(),
228 UnixNanos::default(),
229 );
230
231 let order_list2 = OrderList::new(
232 OrderListId::from("OL-008"),
233 audusd_sim.id,
234 StrategyId::default(),
235 orders,
236 UnixNanos::default(),
237 );
238
239 assert_ne!(order_list1, order_list2);
240 }
241}