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