1use indexmap::IndexMap;
17use nautilus_core::{UUID4, UnixNanos};
18use rust_decimal::Decimal;
19use ustr::Ustr;
20
21use crate::{
22 enums::{
23 ContingencyType, LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType,
24 TriggerType,
25 },
26 events::{OrderEventAny, OrderSubmitted},
27 identifiers::{
28 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TradeId,
29 TraderId,
30 },
31 orders::{
32 any::OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder,
33 market::MarketOrder, market_if_touched::MarketIfTouchedOrder,
34 market_to_limit::MarketToLimitOrder, stop_limit::StopLimitOrder,
35 stop_market::StopMarketOrder, trailing_stop_limit::TrailingStopLimitOrder,
36 trailing_stop_market::TrailingStopMarketOrder,
37 },
38 types::{Currency, Price, Quantity},
39};
40
41pub struct OrderTestBuilder {
42 kind: OrderType,
43 trader_id: Option<TraderId>,
44 strategy_id: Option<StrategyId>,
45 instrument_id: Option<InstrumentId>,
46 client_order_id: Option<ClientOrderId>,
47 trade_id: Option<TradeId>,
48 currency: Option<Currency>,
49 side: Option<OrderSide>,
50 quantity: Option<Quantity>,
51 price: Option<Price>,
52 trigger_price: Option<Price>,
53 trigger_type: Option<TriggerType>,
54 limit_offset: Option<Decimal>,
55 trailing_offset: Option<Decimal>,
56 trailing_offset_type: Option<TrailingOffsetType>,
57 time_in_force: Option<TimeInForce>,
58 expire_time: Option<UnixNanos>,
59 reduce_only: Option<bool>,
60 post_only: Option<bool>,
61 quote_quantity: Option<bool>,
62 reconciliation: Option<bool>,
63 display_qty: Option<Quantity>,
64 liquidity_side: Option<LiquiditySide>,
65 emulation_trigger: Option<TriggerType>,
66 trigger_instrument_id: Option<InstrumentId>,
67 order_list_id: Option<OrderListId>,
68 linked_order_ids: Option<Vec<ClientOrderId>>,
69 parent_order_id: Option<ClientOrderId>,
70 exec_algorithm_id: Option<ExecAlgorithmId>,
71 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
72 exec_spawn_id: Option<ClientOrderId>,
73 tags: Option<Vec<Ustr>>,
74 init_id: Option<UUID4>,
75 ts_init: Option<UnixNanos>,
76 contingency_type: Option<ContingencyType>,
77 submitted: bool,
78}
79
80impl OrderTestBuilder {
81 pub fn new(kind: OrderType) -> Self {
83 OrderTestBuilder {
84 kind,
85 trader_id: None,
86 strategy_id: None,
87 instrument_id: None,
88 client_order_id: None,
89 trade_id: None,
90 currency: None,
91 side: None,
92 quantity: None,
93 price: None,
94 trigger_price: None,
95 trigger_type: None,
96 limit_offset: None,
97 trailing_offset: None,
98 trailing_offset_type: None,
99 time_in_force: None,
100 contingency_type: None,
101 expire_time: None,
102 reduce_only: None,
103 post_only: None,
104 quote_quantity: None,
105 reconciliation: None,
106 display_qty: None,
107 liquidity_side: None,
108 emulation_trigger: None,
109 trigger_instrument_id: None,
110 linked_order_ids: None,
111 order_list_id: None,
112 parent_order_id: None,
113 exec_algorithm_id: None,
114 exec_algorithm_params: None,
115 exec_spawn_id: None,
116 init_id: None,
117 ts_init: None,
118 tags: None,
119 submitted: false,
120 }
121 }
122
123 pub fn submit(&mut self, submit: bool) -> &mut Self {
124 self.submitted = submit;
125 self
126 }
127
128 pub fn kind(&mut self, kind: OrderType) -> &mut Self {
129 self.kind = kind;
130 self
131 }
132
133 pub fn trader_id(&mut self, trader_id: TraderId) -> &mut Self {
135 self.trader_id = Some(trader_id);
136 self
137 }
138
139 fn get_trader_id(&self) -> TraderId {
140 self.trader_id.unwrap_or_default()
141 }
142
143 pub fn strategy_id(&mut self, strategy_id: StrategyId) -> &mut Self {
145 self.strategy_id = Some(strategy_id);
146 self
147 }
148
149 fn get_strategy_id(&self) -> StrategyId {
150 self.strategy_id.unwrap_or_default()
151 }
152
153 pub fn instrument_id(&mut self, instrument_id: InstrumentId) -> &mut Self {
155 self.instrument_id = Some(instrument_id);
156 self
157 }
158
159 fn get_instrument_id(&self) -> InstrumentId {
160 self.instrument_id.expect("Instrument ID not set")
161 }
162
163 pub fn client_order_id(&mut self, client_order_id: ClientOrderId) -> &mut Self {
165 self.client_order_id = Some(client_order_id);
166 self
167 }
168
169 fn get_client_order_id(&self) -> ClientOrderId {
170 self.client_order_id.unwrap_or_default()
171 }
172
173 pub fn trade_id(&mut self, trade_id: TradeId) -> &mut Self {
175 self.trade_id = Some(trade_id);
176 self
177 }
178
179 fn get_trade_id(&self) -> TradeId {
180 self.trade_id.unwrap_or_default()
181 }
182
183 pub fn currency(&mut self, currency: Currency) -> &mut Self {
185 self.currency = Some(currency);
186 self
187 }
188
189 fn get_currency(&self) -> Currency {
190 self.currency.unwrap_or(Currency::from("USDT"))
191 }
192
193 pub fn side(&mut self, side: OrderSide) -> &mut Self {
195 self.side = Some(side);
196 self
197 }
198
199 fn get_side(&self) -> OrderSide {
200 self.side.unwrap_or(OrderSide::Buy)
201 }
202
203 pub fn quantity(&mut self, quantity: Quantity) -> &mut Self {
205 self.quantity = Some(quantity);
206 self
207 }
208
209 fn get_quantity(&self) -> Quantity {
210 self.quantity.expect("Order quantity not set")
211 }
212
213 pub fn price(&mut self, price: Price) -> &mut Self {
215 self.price = Some(price);
216 self
217 }
218
219 fn get_price(&self) -> Price {
220 self.price.expect("Price not set")
221 }
222
223 pub fn trigger_price(&mut self, trigger_price: Price) -> &mut Self {
225 self.trigger_price = Some(trigger_price);
226 self
227 }
228
229 fn get_trigger_price(&self) -> Price {
230 self.trigger_price.expect("Trigger price not set")
231 }
232
233 pub fn trigger_type(&mut self, trigger_type: TriggerType) -> &mut Self {
235 self.trigger_type = Some(trigger_type);
236 self
237 }
238
239 fn get_trigger_type(&self) -> TriggerType {
240 self.trigger_type.unwrap_or(TriggerType::Default)
241 }
242
243 pub fn limit_offset(&mut self, limit_offset: Decimal) -> &mut Self {
245 self.limit_offset = Some(limit_offset);
246 self
247 }
248
249 fn get_limit_offset(&self) -> Decimal {
250 self.limit_offset.expect("Limit offset not set")
251 }
252
253 pub fn trailing_offset(&mut self, trailing_offset: Decimal) -> &mut Self {
255 self.trailing_offset = Some(trailing_offset);
256 self
257 }
258
259 fn get_trailing_offset(&self) -> Decimal {
260 self.trailing_offset.expect("Trailing offset not set")
261 }
262
263 pub fn trailing_offset_type(&mut self, trailing_offset_type: TrailingOffsetType) -> &mut Self {
265 self.trailing_offset_type = Some(trailing_offset_type);
266 self
267 }
268
269 fn get_trailing_offset_type(&self) -> TrailingOffsetType {
270 self.trailing_offset_type
271 .unwrap_or(TrailingOffsetType::NoTrailingOffset)
272 }
273
274 pub fn time_in_force(&mut self, time_in_force: TimeInForce) -> &mut Self {
276 self.time_in_force = Some(time_in_force);
277 self
278 }
279
280 fn get_time_in_force(&self) -> TimeInForce {
281 self.time_in_force.unwrap_or(TimeInForce::Gtc)
282 }
283
284 pub fn expire_time(&mut self, expire_time: UnixNanos) -> &mut Self {
286 self.expire_time = Some(expire_time);
287 self
288 }
289
290 fn get_expire_time(&self) -> Option<UnixNanos> {
291 self.expire_time
292 }
293
294 pub fn display_qty(&mut self, display_qty: Quantity) -> &mut Self {
296 self.display_qty = Some(display_qty);
297 self
298 }
299
300 fn get_display_qty(&self) -> Option<Quantity> {
301 self.display_qty
302 }
303
304 pub fn liquidity_side(&mut self, liquidity_side: LiquiditySide) -> &mut Self {
306 self.liquidity_side = Some(liquidity_side);
307 self
308 }
309
310 fn get_liquidity_side(&self) -> LiquiditySide {
311 self.liquidity_side.unwrap_or(LiquiditySide::Maker)
312 }
313
314 pub fn emulation_trigger(&mut self, emulation_trigger: TriggerType) -> &mut Self {
316 self.emulation_trigger = Some(emulation_trigger);
317 self
318 }
319
320 fn get_emulation_trigger(&self) -> Option<TriggerType> {
321 self.emulation_trigger
322 }
323
324 pub fn trigger_instrument_id(&mut self, trigger_instrument_id: InstrumentId) -> &mut Self {
326 self.trigger_instrument_id = Some(trigger_instrument_id);
327 self
328 }
329
330 fn get_trigger_instrument_id(&self) -> Option<InstrumentId> {
331 self.trigger_instrument_id
332 }
333
334 pub fn order_list_id(&mut self, order_list_id: OrderListId) -> &mut Self {
336 self.order_list_id = Some(order_list_id);
337 self
338 }
339
340 fn get_order_list_id(&self) -> Option<OrderListId> {
341 self.order_list_id
342 }
343
344 pub fn linked_order_ids(&mut self, linked_order_ids: Vec<ClientOrderId>) -> &mut Self {
346 self.linked_order_ids = Some(linked_order_ids);
347 self
348 }
349
350 fn get_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
351 self.linked_order_ids.clone()
352 }
353
354 pub fn parent_order_id(&mut self, parent_order_id: ClientOrderId) -> &mut Self {
356 self.parent_order_id = Some(parent_order_id);
357 self
358 }
359
360 fn get_parent_order_id(&self) -> Option<ClientOrderId> {
361 self.parent_order_id
362 }
363
364 pub fn exec_algorithm_id(&mut self, exec_algorithm_id: ExecAlgorithmId) -> &mut Self {
366 self.exec_algorithm_id = Some(exec_algorithm_id);
367 self
368 }
369
370 fn get_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
371 self.exec_algorithm_id
372 }
373
374 pub fn exec_algorithm_params(
376 &mut self,
377 exec_algorithm_params: IndexMap<Ustr, Ustr>,
378 ) -> &mut Self {
379 self.exec_algorithm_params = Some(exec_algorithm_params);
380 self
381 }
382
383 fn get_exec_algorithm_params(&self) -> Option<IndexMap<Ustr, Ustr>> {
384 self.exec_algorithm_params.clone()
385 }
386
387 pub fn exec_spawn_id(&mut self, exec_spawn_id: ClientOrderId) -> &mut Self {
389 self.exec_spawn_id = Some(exec_spawn_id);
390 self
391 }
392
393 fn get_exec_spawn_id(&self) -> Option<ClientOrderId> {
394 self.exec_spawn_id
395 }
396
397 pub fn tags(&mut self, tags: Vec<Ustr>) -> &mut Self {
399 self.tags = Some(tags);
400 self
401 }
402
403 fn get_tags(&self) -> Option<Vec<Ustr>> {
404 self.tags.clone()
405 }
406
407 pub fn init_id(&mut self, init_id: UUID4) -> &mut Self {
409 self.init_id = Some(init_id);
410 self
411 }
412
413 fn get_init_id(&self) -> UUID4 {
414 self.init_id.unwrap_or_default()
415 }
416
417 pub fn ts_init(&mut self, ts_init: UnixNanos) -> &mut Self {
419 self.ts_init = Some(ts_init);
420 self
421 }
422
423 fn get_ts_init(&self) -> UnixNanos {
424 self.ts_init.unwrap_or_default()
425 }
426
427 pub fn reduce_only(&mut self, reduce_only: bool) -> &mut Self {
429 self.reduce_only = Some(reduce_only);
430 self
431 }
432
433 fn get_reduce_only(&self) -> bool {
434 self.reduce_only.unwrap_or(false)
435 }
436
437 pub fn post_only(&mut self, post_only: bool) -> &mut Self {
439 self.post_only = Some(post_only);
440 self
441 }
442
443 fn get_post_only(&self) -> bool {
444 self.post_only.unwrap_or(false)
445 }
446
447 pub fn quote_quantity(&mut self, quote_quantity: bool) -> &mut Self {
449 self.quote_quantity = Some(quote_quantity);
450 self
451 }
452
453 fn get_quote_quantity(&self) -> bool {
454 self.quote_quantity.unwrap_or(false)
455 }
456
457 pub fn reconciliation(&mut self, reconciliation: bool) -> &mut Self {
459 self.reconciliation = Some(reconciliation);
460 self
461 }
462
463 fn get_reconciliation(&self) -> bool {
464 self.reconciliation.unwrap_or(false)
465 }
466
467 pub fn contingency_type(&mut self, contingency_type: ContingencyType) -> &mut Self {
469 self.contingency_type = Some(contingency_type);
470 self
471 }
472
473 fn get_contingency_type(&self) -> Option<ContingencyType> {
474 Some(
475 self.contingency_type
476 .unwrap_or(ContingencyType::NoContingency),
477 )
478 }
479
480 pub fn build(&self) -> OrderAny {
481 let mut order = match self.kind {
482 OrderType::Market => OrderAny::Market(MarketOrder::new(
483 self.get_trader_id(),
484 self.get_strategy_id(),
485 self.get_instrument_id(),
486 self.get_client_order_id(),
487 self.get_side(),
488 self.get_quantity(),
489 self.get_time_in_force(),
490 self.get_init_id(),
491 self.get_ts_init(),
492 self.get_reduce_only(),
493 self.get_quote_quantity(),
494 self.get_contingency_type(),
495 self.get_order_list_id(),
496 self.get_linked_order_ids(),
497 self.get_parent_order_id(),
498 self.get_exec_algorithm_id(),
499 self.get_exec_algorithm_params(),
500 self.get_exec_spawn_id(),
501 self.get_tags(),
502 )),
503 OrderType::Limit => OrderAny::Limit(
504 LimitOrder::new(
505 self.get_trader_id(),
506 self.get_strategy_id(),
507 self.get_instrument_id(),
508 self.get_client_order_id(),
509 self.get_side(),
510 self.get_quantity(),
511 self.get_price(),
512 self.get_time_in_force(),
513 self.get_expire_time(),
514 self.get_post_only(),
515 self.get_reduce_only(),
516 self.get_quote_quantity(),
517 self.get_display_qty(),
518 self.get_emulation_trigger(),
519 self.get_trigger_instrument_id(),
520 self.get_contingency_type(),
521 self.get_order_list_id(),
522 self.get_linked_order_ids(),
523 self.get_parent_order_id(),
524 self.get_exec_algorithm_id(),
525 self.get_exec_algorithm_params(),
526 self.get_exec_spawn_id(),
527 self.get_tags(),
528 self.get_init_id(),
529 self.get_ts_init(),
530 )
531 .unwrap(),
532 ),
533 OrderType::StopMarket => OrderAny::StopMarket(StopMarketOrder::new(
534 self.get_trader_id(),
535 self.get_strategy_id(),
536 self.get_instrument_id(),
537 self.get_client_order_id(),
538 self.get_side(),
539 self.get_quantity(),
540 self.get_trigger_price(),
541 self.get_trigger_type(),
542 self.get_time_in_force(),
543 self.get_expire_time(),
544 self.get_reduce_only(),
545 self.get_quote_quantity(),
546 self.get_display_qty(),
547 self.get_emulation_trigger(),
548 self.get_trigger_instrument_id(),
549 self.get_contingency_type(),
550 self.get_order_list_id(),
551 self.get_linked_order_ids(),
552 self.get_parent_order_id(),
553 self.get_exec_algorithm_id(),
554 self.get_exec_algorithm_params(),
555 self.get_exec_spawn_id(),
556 self.get_tags(),
557 self.get_init_id(),
558 self.get_ts_init(),
559 )),
560 OrderType::StopLimit => OrderAny::StopLimit(StopLimitOrder::new(
561 self.get_trader_id(),
562 self.get_strategy_id(),
563 self.get_instrument_id(),
564 self.get_client_order_id(),
565 self.get_side(),
566 self.get_quantity(),
567 self.get_price(),
568 self.get_trigger_price(),
569 self.get_trigger_type(),
570 self.get_time_in_force(),
571 self.get_expire_time(),
572 self.get_post_only(),
573 self.get_reduce_only(),
574 self.get_quote_quantity(),
575 self.get_display_qty(),
576 self.get_emulation_trigger(),
577 self.get_trigger_instrument_id(),
578 self.get_contingency_type(),
579 self.get_order_list_id(),
580 self.get_linked_order_ids(),
581 self.get_parent_order_id(),
582 self.get_exec_algorithm_id(),
583 self.get_exec_algorithm_params(),
584 self.get_exec_spawn_id(),
585 self.get_tags(),
586 self.get_init_id(),
587 self.get_ts_init(),
588 )),
589 OrderType::MarketToLimit => OrderAny::MarketToLimit(MarketToLimitOrder::new(
590 self.get_trader_id(),
591 self.get_strategy_id(),
592 self.get_instrument_id(),
593 self.get_client_order_id(),
594 self.get_side(),
595 self.get_quantity(),
596 self.get_time_in_force(),
597 self.get_expire_time(),
598 self.get_post_only(),
599 self.get_reduce_only(),
600 self.get_quote_quantity(),
601 self.get_display_qty(),
602 self.get_contingency_type(),
603 self.get_order_list_id(),
604 self.get_linked_order_ids(),
605 self.get_parent_order_id(),
606 self.get_exec_algorithm_id(),
607 self.get_exec_algorithm_params(),
608 self.get_exec_spawn_id(),
609 self.get_tags(),
610 self.get_init_id(),
611 self.get_ts_init(),
612 )),
613 OrderType::MarketIfTouched => OrderAny::MarketIfTouched(MarketIfTouchedOrder::new(
614 self.get_trader_id(),
615 self.get_strategy_id(),
616 self.get_instrument_id(),
617 self.get_client_order_id(),
618 self.get_side(),
619 self.get_quantity(),
620 self.get_trigger_price(),
621 self.get_trigger_type(),
622 self.get_time_in_force(),
623 self.get_expire_time(),
624 self.get_reduce_only(),
625 self.get_quote_quantity(),
626 self.get_display_qty(),
627 self.get_emulation_trigger(),
628 self.get_trigger_instrument_id(),
629 self.get_contingency_type(),
630 self.get_order_list_id(),
631 self.get_linked_order_ids(),
632 self.get_parent_order_id(),
633 self.get_exec_algorithm_id(),
634 self.get_exec_algorithm_params(),
635 self.get_exec_spawn_id(),
636 self.get_tags(),
637 self.get_init_id(),
638 self.get_ts_init(),
639 )),
640 OrderType::LimitIfTouched => OrderAny::LimitIfTouched(LimitIfTouchedOrder::new(
641 self.get_trader_id(),
642 self.get_strategy_id(),
643 self.get_instrument_id(),
644 self.get_client_order_id(),
645 self.get_side(),
646 self.get_quantity(),
647 self.get_price(),
648 self.get_trigger_price(),
649 self.get_trigger_type(),
650 self.get_time_in_force(),
651 self.get_expire_time(),
652 self.get_post_only(),
653 self.get_reduce_only(),
654 self.get_quote_quantity(),
655 self.get_display_qty(),
656 self.get_emulation_trigger(),
657 self.get_trigger_instrument_id(),
658 self.get_contingency_type(),
659 self.get_order_list_id(),
660 self.get_linked_order_ids(),
661 self.get_parent_order_id(),
662 self.get_exec_algorithm_id(),
663 self.get_exec_algorithm_params(),
664 self.get_exec_spawn_id(),
665 self.get_tags(),
666 self.get_init_id(),
667 self.get_ts_init(),
668 )),
669 OrderType::TrailingStopMarket => {
670 OrderAny::TrailingStopMarket(TrailingStopMarketOrder::new(
671 self.get_trader_id(),
672 self.get_strategy_id(),
673 self.get_instrument_id(),
674 self.get_client_order_id(),
675 self.get_side(),
676 self.get_quantity(),
677 self.get_trigger_price(),
678 self.get_trigger_type(),
679 self.get_trailing_offset(),
680 self.get_trailing_offset_type(),
681 self.get_time_in_force(),
682 self.get_expire_time(),
683 self.get_reduce_only(),
684 self.get_quote_quantity(),
685 self.get_display_qty(),
686 self.get_emulation_trigger(),
687 self.get_trigger_instrument_id(),
688 self.get_contingency_type(),
689 self.get_order_list_id(),
690 self.get_linked_order_ids(),
691 self.get_parent_order_id(),
692 self.get_exec_algorithm_id(),
693 self.get_exec_algorithm_params(),
694 self.get_exec_spawn_id(),
695 self.get_tags(),
696 self.get_init_id(),
697 self.get_ts_init(),
698 ))
699 }
700 OrderType::TrailingStopLimit => {
701 OrderAny::TrailingStopLimit(TrailingStopLimitOrder::new(
702 self.get_trader_id(),
703 self.get_strategy_id(),
704 self.get_instrument_id(),
705 self.get_client_order_id(),
706 self.get_side(),
707 self.get_quantity(),
708 self.get_price(),
709 self.get_trigger_price(),
710 self.get_trigger_type(),
711 self.get_limit_offset(),
712 self.get_trailing_offset(),
713 self.get_trailing_offset_type(),
714 self.get_time_in_force(),
715 self.get_expire_time(),
716 self.get_post_only(),
717 self.get_reduce_only(),
718 self.get_quote_quantity(),
719 self.get_display_qty(),
720 self.get_emulation_trigger(),
721 self.get_trigger_instrument_id(),
722 self.get_contingency_type(),
723 self.get_order_list_id(),
724 self.get_linked_order_ids(),
725 self.get_parent_order_id(),
726 self.get_exec_algorithm_id(),
727 self.get_exec_algorithm_params(),
728 self.get_exec_spawn_id(),
729 self.get_tags(),
730 self.get_init_id(),
731 self.get_ts_init(),
732 ))
733 }
734 };
735
736 if self.submitted {
737 let submit_event = OrderSubmitted::new(
738 order.trader_id(),
739 order.strategy_id(),
740 order.instrument_id(),
741 order.client_order_id(),
742 AccountId::from("ACCOUNT-001"),
743 UUID4::new(),
744 UnixNanos::default(),
745 UnixNanos::default(),
746 );
747 order.apply(OrderEventAny::Submitted(submit_event)).unwrap();
748 }
749
750 order
751 }
752}