1use std::{collections::HashMap, str::FromStr};
17
18use nautilus_core::{UUID4, UnixNanos};
19
20use super::any::OrderAny;
21use crate::{
22 enums::{LiquiditySide, OrderType},
23 events::{OrderAccepted, OrderCanceled, OrderEventAny, OrderFilled, OrderSubmitted},
24 identifiers::{
25 AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, Venue, VenueOrderId,
26 },
27 instruments::{Instrument, InstrumentAny},
28 orders::{Order, OrderTestBuilder},
29 types::{Money, Price, Quantity},
30};
31
32#[derive(Debug)]
34pub struct TestOrderEventStubs;
35
36impl TestOrderEventStubs {
37 pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
38 let event = OrderSubmitted::new(
39 order.trader_id(),
40 order.strategy_id(),
41 order.instrument_id(),
42 order.client_order_id(),
43 account_id,
44 UUID4::new(),
45 UnixNanos::default(),
46 UnixNanos::default(),
47 );
48 OrderEventAny::Submitted(event)
49 }
50
51 pub fn accepted(
52 order: &OrderAny,
53 account_id: AccountId,
54 venue_order_id: VenueOrderId,
55 ) -> OrderEventAny {
56 let event = OrderAccepted::new(
57 order.trader_id(),
58 order.strategy_id(),
59 order.instrument_id(),
60 order.client_order_id(),
61 venue_order_id,
62 account_id,
63 UUID4::new(),
64 UnixNanos::default(),
65 UnixNanos::default(),
66 false,
67 );
68 OrderEventAny::Accepted(event)
69 }
70
71 pub fn canceled(
72 order: &OrderAny,
73 account_id: AccountId,
74 venue_order_id: Option<VenueOrderId>,
75 ) -> OrderEventAny {
76 let event = OrderCanceled::new(
77 order.trader_id(),
78 order.strategy_id(),
79 order.instrument_id(),
80 order.client_order_id(),
81 UUID4::new(),
82 UnixNanos::default(),
83 UnixNanos::default(),
84 false, venue_order_id,
86 Some(account_id),
87 );
88 OrderEventAny::Canceled(event)
89 }
90
91 #[allow(clippy::too_many_arguments)]
95 pub fn filled(
96 order: &OrderAny,
97 instrument: &InstrumentAny,
98 trade_id: Option<TradeId>,
99 position_id: Option<PositionId>,
100 last_px: Option<Price>,
101 last_qty: Option<Quantity>,
102 liquidity_side: Option<LiquiditySide>,
103 commission: Option<Money>,
104 ts_filled_ns: Option<UnixNanos>,
105 account_id: Option<AccountId>,
106 ) -> OrderEventAny {
107 let venue_order_id = order.venue_order_id().unwrap_or_default();
108 let account_id = account_id
109 .or(order.account_id())
110 .unwrap_or(AccountId::from("SIM-001"));
111 let trade_id = trade_id.unwrap_or(TradeId::new(
112 order.client_order_id().as_str().replace('O', "E").as_str(),
113 ));
114 let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
115 let event = UUID4::new();
116 let position_id = position_id
117 .or_else(|| order.position_id())
118 .unwrap_or(PositionId::new("1"));
119 let commission = commission.unwrap_or(Money::from("2 USD"));
120 let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
121 let last_qty = last_qty.unwrap_or(order.quantity());
122 let event = OrderFilled::new(
123 order.trader_id(),
124 order.strategy_id(),
125 instrument.id(),
126 order.client_order_id(),
127 venue_order_id,
128 account_id,
129 trade_id,
130 order.order_side(),
131 order.order_type(),
132 last_qty,
133 last_px,
134 instrument.quote_currency(),
135 liquidity_side,
136 event,
137 ts_filled_ns.unwrap_or_default(),
138 UnixNanos::default(),
139 false,
140 Some(position_id),
141 Some(commission),
142 );
143 OrderEventAny::Filled(event)
144 }
145}
146
147#[derive(Debug)]
148pub struct TestOrderStubs;
149
150impl TestOrderStubs {
151 pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
155 let mut new_order = order.clone();
156 let accepted_event = TestOrderEventStubs::accepted(
157 &new_order,
158 AccountId::from("SIM-001"),
159 VenueOrderId::from("V-001"),
160 );
161 new_order.apply(accepted_event).unwrap();
162 new_order
163 }
164
165 pub fn make_filled_order(
169 order: &OrderAny,
170 instrument: &InstrumentAny,
171 liquidity_side: LiquiditySide,
172 ) -> OrderAny {
173 let mut accepted_order = TestOrderStubs::make_accepted_order(order);
174 let fill = TestOrderEventStubs::filled(
175 &accepted_order,
176 instrument,
177 None,
178 None,
179 None,
180 None,
181 Some(liquidity_side),
182 None,
183 None,
184 None,
185 );
186 accepted_order.apply(fill).unwrap();
187 accepted_order
188 }
189}
190
191#[derive(Debug)]
192pub struct TestOrdersGenerator {
193 order_type: OrderType,
194 venue_instruments: HashMap<Venue, u32>,
195 orders_per_instrument: u32,
196}
197
198impl TestOrdersGenerator {
199 pub fn new(order_type: OrderType) -> Self {
200 Self {
201 order_type,
202 venue_instruments: HashMap::new(),
203 orders_per_instrument: 5,
204 }
205 }
206
207 pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
208 self.orders_per_instrument = total_orders;
209 }
210
211 pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
212 self.venue_instruments.insert(venue, total_instruments);
213 }
214
215 fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
216 let client_order_id =
217 ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
218 OrderTestBuilder::new(self.order_type)
219 .quantity(Quantity::from("1"))
220 .price(Price::from("1"))
221 .instrument_id(instrument_id)
222 .client_order_id(client_order_id)
223 .build()
224 }
225
226 pub fn build(&self) -> Vec<OrderAny> {
227 let mut orders = Vec::new();
228 for (venue, total_instruments) in self.venue_instruments.iter() {
229 for i in 0..*total_instruments {
230 let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
231 for order_index in 0..self.orders_per_instrument {
232 let order = self.generate_order(instrument_id, order_index);
233 orders.push(order);
234 }
235 }
236 }
237 orders
238 }
239}
240
241pub fn create_order_list_sample(
242 total_venues: u8,
243 total_instruments: u32,
244 orders_per_instrument: u32,
245) -> Vec<OrderAny> {
246 let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
249 for i in 0..total_venues {
250 let venue = Venue::from(format!("VENUE-{i}"));
251 order_generator.add_venue_and_total_instruments(venue, total_instruments);
252 }
253 order_generator.set_orders_per_instrument(orders_per_instrument);
254
255 order_generator.build()
256}