1use std::{collections::HashMap, str::FromStr};
17
18use nautilus_core::{UUID4, UnixNanos};
19use rust_decimal_macros::dec;
20
21use super::{
22 any::OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder, market::MarketOrder,
23 market_if_touched::MarketIfTouchedOrder, market_to_limit::MarketToLimitOrder,
24 stop_limit::StopLimitOrder, stop_market::StopMarketOrder,
25 trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder,
26};
27use crate::{
28 enums::{LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
29 events::{OrderAccepted, OrderCanceled, OrderEventAny, OrderFilled, OrderSubmitted},
30 identifiers::{
31 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue,
32 VenueOrderId,
33 },
34 instruments::{Instrument, InstrumentAny},
35 orders::{Order, OrderTestBuilder},
36 stubs::TestDefault,
37 types::{Money, Price, Quantity},
38};
39
40impl TestDefault for LimitOrder {
41 fn test_default() -> Self {
43 Self::new(
44 TraderId::test_default(),
45 StrategyId::test_default(),
46 InstrumentId::test_default(),
47 ClientOrderId::test_default(),
48 OrderSide::Buy,
49 Quantity::from(100_000),
50 Price::from("1.00000"),
51 TimeInForce::Gtc,
52 None,
53 false,
54 false,
55 false,
56 None,
57 None,
58 None,
59 None,
60 None,
61 None,
62 None,
63 None,
64 None,
65 None,
66 None,
67 UUID4::default(),
68 UnixNanos::default(),
69 )
70 }
71}
72
73impl TestDefault for LimitIfTouchedOrder {
74 fn test_default() -> Self {
76 Self::new(
77 TraderId::test_default(),
78 StrategyId::test_default(),
79 InstrumentId::test_default(),
80 ClientOrderId::test_default(),
81 OrderSide::Buy,
82 Quantity::from(100_000),
83 Price::from("1.00000"),
84 Price::from("1.00000"),
85 TriggerType::BidAsk,
86 TimeInForce::Gtc,
87 None,
88 false,
89 false,
90 false,
91 None,
92 None,
93 None,
94 None,
95 None,
96 None,
97 None,
98 None,
99 None,
100 None,
101 None,
102 UUID4::default(),
103 UnixNanos::default(),
104 )
105 }
106}
107
108impl TestDefault for MarketOrder {
109 fn test_default() -> Self {
111 Self::new(
112 TraderId::test_default(),
113 StrategyId::test_default(),
114 InstrumentId::test_default(),
115 ClientOrderId::test_default(),
116 OrderSide::Buy,
117 Quantity::from(100_000),
118 TimeInForce::Day,
119 UUID4::default(),
120 UnixNanos::default(),
121 false,
122 false,
123 None,
124 None,
125 None,
126 None,
127 None,
128 None,
129 None,
130 None,
131 )
132 }
133}
134
135impl TestDefault for MarketIfTouchedOrder {
136 fn test_default() -> Self {
138 Self::new(
139 TraderId::test_default(),
140 StrategyId::test_default(),
141 InstrumentId::test_default(),
142 ClientOrderId::test_default(),
143 OrderSide::Buy,
144 Quantity::from(100_000),
145 Price::from("1.00000"),
146 TriggerType::BidAsk,
147 TimeInForce::Gtc,
148 None,
149 false,
150 false,
151 None,
152 None,
153 None,
154 None,
155 None,
156 None,
157 None,
158 None,
159 None,
160 None,
161 UUID4::default(),
162 UnixNanos::default(),
163 )
164 }
165}
166
167impl TestDefault for MarketToLimitOrder {
168 fn test_default() -> Self {
170 Self::new(
171 TraderId::test_default(),
172 StrategyId::test_default(),
173 InstrumentId::test_default(),
174 ClientOrderId::test_default(),
175 OrderSide::Buy,
176 Quantity::from(100_000),
177 TimeInForce::Gtc,
178 None,
179 false,
180 false,
181 false,
182 None,
183 None,
184 None,
185 None,
186 None,
187 None,
188 None,
189 None,
190 None,
191 UUID4::default(),
192 UnixNanos::default(),
193 )
194 }
195}
196
197impl TestDefault for StopLimitOrder {
198 fn test_default() -> Self {
200 Self::new(
201 TraderId::test_default(),
202 StrategyId::test_default(),
203 InstrumentId::test_default(),
204 ClientOrderId::test_default(),
205 OrderSide::Buy,
206 Quantity::from(100_000),
207 Price::from("1.00000"),
208 Price::from("1.00000"),
209 TriggerType::BidAsk,
210 TimeInForce::Gtc,
211 None,
212 false,
213 false,
214 false,
215 None,
216 None,
217 None,
218 None,
219 None,
220 None,
221 None,
222 None,
223 None,
224 None,
225 None,
226 UUID4::default(),
227 UnixNanos::default(),
228 )
229 }
230}
231
232impl TestDefault for StopMarketOrder {
233 fn test_default() -> Self {
235 Self::new(
236 TraderId::test_default(),
237 StrategyId::test_default(),
238 InstrumentId::test_default(),
239 ClientOrderId::test_default(),
240 OrderSide::Buy,
241 Quantity::from(100_000),
242 Price::from("1.00000"),
243 TriggerType::BidAsk,
244 TimeInForce::Gtc,
245 None,
246 false,
247 false,
248 None,
249 None,
250 None,
251 None,
252 None,
253 None,
254 None,
255 None,
256 None,
257 None,
258 None,
259 UUID4::default(),
260 UnixNanos::default(),
261 )
262 }
263}
264
265impl TestDefault for TrailingStopLimitOrder {
266 fn test_default() -> Self {
268 Self::new(
269 TraderId::test_default(),
270 StrategyId::test_default(),
271 InstrumentId::test_default(),
272 ClientOrderId::test_default(),
273 OrderSide::Buy,
274 Quantity::from(100_000),
275 Price::from("1.00000"),
276 Price::from("1.00000"),
277 TriggerType::BidAsk,
278 dec!(0.001),
279 dec!(0.001),
280 TrailingOffsetType::Price,
281 TimeInForce::Gtc,
282 None,
283 false,
284 false,
285 false,
286 None,
287 None,
288 None,
289 None,
290 None,
291 None,
292 None,
293 None,
294 None,
295 None,
296 None,
297 UUID4::default(),
298 UnixNanos::default(),
299 )
300 }
301}
302
303impl TestDefault for TrailingStopMarketOrder {
304 fn test_default() -> Self {
306 Self::new(
307 TraderId::test_default(),
308 StrategyId::test_default(),
309 InstrumentId::test_default(),
310 ClientOrderId::test_default(),
311 OrderSide::Buy,
312 Quantity::from(100_000),
313 Price::from("1.00000"),
314 TriggerType::BidAsk,
315 dec!(0.001),
316 TrailingOffsetType::Price,
317 TimeInForce::Gtc,
318 None,
319 false,
320 false,
321 None,
322 None,
323 None,
324 None,
325 None,
326 None,
327 None,
328 None,
329 None,
330 None,
331 None,
332 UUID4::default(),
333 UnixNanos::default(),
334 )
335 }
336}
337
338#[derive(Debug)]
339pub struct TestOrderEventStubs;
340
341impl TestOrderEventStubs {
342 pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
343 let event = OrderSubmitted::new(
344 order.trader_id(),
345 order.strategy_id(),
346 order.instrument_id(),
347 order.client_order_id(),
348 account_id,
349 UUID4::new(),
350 UnixNanos::default(),
351 UnixNanos::default(),
352 );
353 OrderEventAny::Submitted(event)
354 }
355
356 pub fn accepted(
357 order: &OrderAny,
358 account_id: AccountId,
359 venue_order_id: VenueOrderId,
360 ) -> OrderEventAny {
361 let event = OrderAccepted::new(
362 order.trader_id(),
363 order.strategy_id(),
364 order.instrument_id(),
365 order.client_order_id(),
366 venue_order_id,
367 account_id,
368 UUID4::new(),
369 UnixNanos::default(),
370 UnixNanos::default(),
371 false,
372 );
373 OrderEventAny::Accepted(event)
374 }
375
376 pub fn canceled(
377 order: &OrderAny,
378 account_id: AccountId,
379 venue_order_id: Option<VenueOrderId>,
380 ) -> OrderEventAny {
381 let event = OrderCanceled::new(
382 order.trader_id(),
383 order.strategy_id(),
384 order.instrument_id(),
385 order.client_order_id(),
386 UUID4::new(),
387 UnixNanos::default(),
388 UnixNanos::default(),
389 false, venue_order_id,
391 Some(account_id),
392 );
393 OrderEventAny::Canceled(event)
394 }
395
396 #[allow(clippy::too_many_arguments)]
400 pub fn filled(
401 order: &OrderAny,
402 instrument: &InstrumentAny,
403 trade_id: Option<TradeId>,
404 position_id: Option<PositionId>,
405 last_px: Option<Price>,
406 last_qty: Option<Quantity>,
407 liquidity_side: Option<LiquiditySide>,
408 commission: Option<Money>,
409 ts_filled_ns: Option<UnixNanos>,
410 account_id: Option<AccountId>,
411 ) -> OrderEventAny {
412 let venue_order_id = order
413 .venue_order_id()
414 .unwrap_or_else(VenueOrderId::test_default);
415 let account_id = account_id
416 .or(order.account_id())
417 .unwrap_or(AccountId::from("SIM-001"));
418 let trade_id = trade_id.unwrap_or(TradeId::new(
419 order.client_order_id().as_str().replace('O', "E").as_str(),
420 ));
421 let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
422 let event = UUID4::new();
423 let position_id = position_id
424 .or_else(|| order.position_id())
425 .unwrap_or(PositionId::new("1"));
426 let commission = commission.unwrap_or(Money::from("2 USD"));
427 let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
428 let last_qty = last_qty.unwrap_or(order.quantity());
429 let event = OrderFilled::new(
430 order.trader_id(),
431 order.strategy_id(),
432 instrument.id(),
433 order.client_order_id(),
434 venue_order_id,
435 account_id,
436 trade_id,
437 order.order_side(),
438 order.order_type(),
439 last_qty,
440 last_px,
441 instrument.quote_currency(),
442 liquidity_side,
443 event,
444 ts_filled_ns.unwrap_or_default(),
445 UnixNanos::default(),
446 false,
447 Some(position_id),
448 Some(commission),
449 );
450 OrderEventAny::Filled(event)
451 }
452}
453
454#[derive(Debug)]
455pub struct TestOrderStubs;
456
457impl TestOrderStubs {
458 pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
462 let mut new_order = order.clone();
463 let accepted_event = TestOrderEventStubs::accepted(
464 &new_order,
465 AccountId::from("SIM-001"),
466 VenueOrderId::from("V-001"),
467 );
468 new_order.apply(accepted_event).unwrap();
469 new_order
470 }
471
472 pub fn make_filled_order(
476 order: &OrderAny,
477 instrument: &InstrumentAny,
478 liquidity_side: LiquiditySide,
479 ) -> OrderAny {
480 let mut accepted_order = Self::make_accepted_order(order);
481 let fill = TestOrderEventStubs::filled(
482 &accepted_order,
483 instrument,
484 None,
485 None,
486 None,
487 None,
488 Some(liquidity_side),
489 None,
490 None,
491 None,
492 );
493 accepted_order.apply(fill).unwrap();
494 accepted_order
495 }
496}
497
498#[derive(Debug)]
499pub struct TestOrdersGenerator {
500 order_type: OrderType,
501 venue_instruments: HashMap<Venue, u32>,
502 orders_per_instrument: u32,
503}
504
505impl TestOrdersGenerator {
506 pub fn new(order_type: OrderType) -> Self {
507 Self {
508 order_type,
509 venue_instruments: HashMap::new(),
510 orders_per_instrument: 5,
511 }
512 }
513
514 pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
515 self.orders_per_instrument = total_orders;
516 }
517
518 pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
519 self.venue_instruments.insert(venue, total_instruments);
520 }
521
522 fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
523 let client_order_id =
524 ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
525 OrderTestBuilder::new(self.order_type)
526 .quantity(Quantity::from("1"))
527 .price(Price::from("1"))
528 .instrument_id(instrument_id)
529 .client_order_id(client_order_id)
530 .build()
531 }
532
533 pub fn build(&self) -> Vec<OrderAny> {
534 let mut orders = Vec::new();
535 for (venue, total_instruments) in &self.venue_instruments {
536 for i in 0..*total_instruments {
537 let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
538 for order_index in 0..self.orders_per_instrument {
539 let order = self.generate_order(instrument_id, order_index);
540 orders.push(order);
541 }
542 }
543 }
544 orders
545 }
546}
547
548pub fn create_order_list_sample(
549 total_venues: u8,
550 total_instruments: u32,
551 orders_per_instrument: u32,
552) -> Vec<OrderAny> {
553 let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
556 for i in 0..total_venues {
557 let venue = Venue::from(format!("VENUE-{i}"));
558 order_generator.add_venue_and_total_instruments(venue, total_instruments);
559 }
560 order_generator.set_orders_per_instrument(orders_per_instrument);
561
562 order_generator.build()
563}