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