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