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 #[must_use]
343 pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
344 let event = OrderSubmitted::new(
345 order.trader_id(),
346 order.strategy_id(),
347 order.instrument_id(),
348 order.client_order_id(),
349 account_id,
350 UUID4::new(),
351 UnixNanos::default(),
352 UnixNanos::default(),
353 );
354 OrderEventAny::Submitted(event)
355 }
356
357 #[must_use]
358 pub fn accepted(
359 order: &OrderAny,
360 account_id: AccountId,
361 venue_order_id: VenueOrderId,
362 ) -> OrderEventAny {
363 let event = OrderAccepted::new(
364 order.trader_id(),
365 order.strategy_id(),
366 order.instrument_id(),
367 order.client_order_id(),
368 venue_order_id,
369 account_id,
370 UUID4::new(),
371 UnixNanos::default(),
372 UnixNanos::default(),
373 false,
374 );
375 OrderEventAny::Accepted(event)
376 }
377
378 #[must_use]
379 pub fn canceled(
380 order: &OrderAny,
381 account_id: AccountId,
382 venue_order_id: Option<VenueOrderId>,
383 ) -> OrderEventAny {
384 let event = OrderCanceled::new(
385 order.trader_id(),
386 order.strategy_id(),
387 order.instrument_id(),
388 order.client_order_id(),
389 UUID4::new(),
390 UnixNanos::default(),
391 UnixNanos::default(),
392 false, venue_order_id,
394 Some(account_id),
395 );
396 OrderEventAny::Canceled(event)
397 }
398
399 #[expect(clippy::too_many_arguments)]
403 pub fn filled(
404 order: &OrderAny,
405 instrument: &InstrumentAny,
406 trade_id: Option<TradeId>,
407 position_id: Option<PositionId>,
408 last_px: Option<Price>,
409 last_qty: Option<Quantity>,
410 liquidity_side: Option<LiquiditySide>,
411 commission: Option<Money>,
412 ts_filled_ns: Option<UnixNanos>,
413 account_id: Option<AccountId>,
414 ) -> OrderEventAny {
415 let venue_order_id = order
416 .venue_order_id()
417 .unwrap_or_else(VenueOrderId::test_default);
418 let account_id = account_id
419 .or(order.account_id())
420 .unwrap_or(AccountId::from("SIM-001"));
421 let trade_id = trade_id.unwrap_or(TradeId::new(
422 order.client_order_id().as_str().replace('O', "E").as_str(),
423 ));
424 let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
425 let event = UUID4::new();
426 let position_id = position_id
427 .or_else(|| order.position_id())
428 .unwrap_or(PositionId::new("1"));
429 let commission = commission.unwrap_or(Money::from("2 USD"));
430 let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
431 let last_qty = last_qty.unwrap_or(order.quantity());
432 let event = OrderFilled::new(
433 order.trader_id(),
434 order.strategy_id(),
435 instrument.id(),
436 order.client_order_id(),
437 venue_order_id,
438 account_id,
439 trade_id,
440 order.order_side(),
441 order.order_type(),
442 last_qty,
443 last_px,
444 instrument.quote_currency(),
445 liquidity_side,
446 event,
447 ts_filled_ns.unwrap_or_default(),
448 UnixNanos::default(),
449 false,
450 Some(position_id),
451 Some(commission),
452 );
453 OrderEventAny::Filled(event)
454 }
455}
456
457#[derive(Debug)]
458pub struct TestOrderStubs;
459
460impl TestOrderStubs {
461 #[must_use]
465 pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
466 let mut new_order = order.clone();
467 let accepted_event = TestOrderEventStubs::accepted(
468 &new_order,
469 AccountId::from("SIM-001"),
470 VenueOrderId::from("V-001"),
471 );
472 new_order.apply(accepted_event).unwrap();
473 new_order
474 }
475
476 #[must_use]
480 pub fn make_filled_order(
481 order: &OrderAny,
482 instrument: &InstrumentAny,
483 liquidity_side: LiquiditySide,
484 ) -> OrderAny {
485 let mut accepted_order = Self::make_accepted_order(order);
486 let fill = TestOrderEventStubs::filled(
487 &accepted_order,
488 instrument,
489 None,
490 None,
491 None,
492 None,
493 Some(liquidity_side),
494 None,
495 None,
496 None,
497 );
498 accepted_order.apply(fill).unwrap();
499 accepted_order
500 }
501}
502
503#[derive(Debug)]
504pub struct TestOrdersGenerator {
505 order_type: OrderType,
506 venue_instruments: HashMap<Venue, u32>,
507 orders_per_instrument: u32,
508}
509
510impl TestOrdersGenerator {
511 #[must_use]
512 pub fn new(order_type: OrderType) -> Self {
513 Self {
514 order_type,
515 venue_instruments: HashMap::new(),
516 orders_per_instrument: 5,
517 }
518 }
519
520 pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
521 self.orders_per_instrument = total_orders;
522 }
523
524 pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
525 self.venue_instruments.insert(venue, total_instruments);
526 }
527
528 fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
529 let client_order_id =
530 ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
531 OrderTestBuilder::new(self.order_type)
532 .quantity(Quantity::from("1"))
533 .price(Price::from("1"))
534 .instrument_id(instrument_id)
535 .client_order_id(client_order_id)
536 .build()
537 }
538
539 #[must_use]
540 pub fn build(&self) -> Vec<OrderAny> {
541 let mut orders = Vec::new();
542
543 for (venue, total_instruments) in &self.venue_instruments {
544 for i in 0..*total_instruments {
545 let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
546 for order_index in 0..self.orders_per_instrument {
547 let order = self.generate_order(instrument_id, order_index);
548 orders.push(order);
549 }
550 }
551 }
552 orders
553 }
554}
555
556#[must_use]
557pub fn create_order_list_sample(
558 total_venues: u8,
559 total_instruments: u32,
560 orders_per_instrument: u32,
561) -> Vec<OrderAny> {
562 let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
565
566 for i in 0..total_venues {
567 let venue = Venue::from(format!("VENUE-{i}"));
568 order_generator.add_venue_and_total_instruments(venue, total_instruments);
569 }
570 order_generator.set_orders_per_instrument(orders_per_instrument);
571
572 order_generator.build()
573}