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