1use std::{cell::RefCell, rc::Rc};
19
20use indexmap::IndexMap;
21use nautilus_core::{
22 UUID4, UnixNanos,
23 correctness::{check_equal, check_slice_not_empty},
24};
25use nautilus_model::{
26 enums::{ContingencyType, OrderSide, TimeInForce, TrailingOffsetType, TriggerType},
27 identifiers::{
28 ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
29 },
30 orders::{
31 LimitIfTouchedOrder, LimitOrder, MarketIfTouchedOrder, MarketOrder, Order, OrderAny,
32 OrderList, StopLimitOrder, StopMarketOrder, TrailingStopMarketOrder,
33 },
34 types::{Price, Quantity},
35};
36use rust_decimal::Decimal;
37use ustr::Ustr;
38
39use crate::{
40 clock::Clock,
41 generators::{client_order_id::ClientOrderIdGenerator, order_list_id::OrderListIdGenerator},
42};
43
44#[derive(Debug)]
45pub struct OrderFactory {
46 clock: Rc<RefCell<dyn Clock>>,
47 trader_id: TraderId,
48 strategy_id: StrategyId,
49 order_id_generator: ClientOrderIdGenerator,
50 order_list_id_generator: OrderListIdGenerator,
51}
52
53impl OrderFactory {
54 pub fn new(
56 trader_id: TraderId,
57 strategy_id: StrategyId,
58 init_order_id_count: Option<usize>,
59 init_order_list_id_count: Option<usize>,
60 clock: Rc<RefCell<dyn Clock>>,
61 use_uuids_for_client_order_ids: bool,
62 use_hyphens_in_client_order_ids: bool,
63 ) -> Self {
64 let order_id_generator = ClientOrderIdGenerator::new(
65 trader_id,
66 strategy_id,
67 init_order_id_count.unwrap_or(0),
68 clock.clone(),
69 use_uuids_for_client_order_ids,
70 use_hyphens_in_client_order_ids,
71 );
72
73 let order_list_id_generator = OrderListIdGenerator::new(
74 trader_id,
75 strategy_id,
76 init_order_list_id_count.unwrap_or(0),
77 clock.clone(),
78 );
79
80 Self {
81 clock,
82 trader_id,
83 strategy_id,
84 order_id_generator,
85 order_list_id_generator,
86 }
87 }
88
89 pub const fn set_client_order_id_count(&mut self, count: usize) {
91 self.order_id_generator.set_count(count);
92 }
93
94 pub const fn set_order_list_id_count(&mut self, count: usize) {
96 self.order_list_id_generator.set_count(count);
97 }
98
99 pub fn generate_client_order_id(&mut self) -> ClientOrderId {
101 self.order_id_generator.generate()
102 }
103
104 pub fn generate_order_list_id(&mut self) -> OrderListId {
106 self.order_list_id_generator.generate()
107 }
108
109 pub const fn reset_factory(&mut self) {
111 self.order_id_generator.reset();
112 self.order_list_id_generator.reset();
113 }
114
115 #[expect(clippy::too_many_arguments)]
117 pub fn market(
118 &mut self,
119 instrument_id: InstrumentId,
120 order_side: OrderSide,
121 quantity: Quantity,
122 time_in_force: Option<TimeInForce>,
123 reduce_only: Option<bool>,
124 quote_quantity: Option<bool>,
125 exec_algorithm_id: Option<ExecAlgorithmId>,
126 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
127 tags: Option<Vec<Ustr>>,
128 client_order_id: Option<ClientOrderId>,
129 ) -> OrderAny {
130 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
131 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
132 None
133 } else {
134 Some(client_order_id)
135 };
136 let order = MarketOrder::new(
137 self.trader_id,
138 self.strategy_id,
139 instrument_id,
140 client_order_id,
141 order_side,
142 quantity,
143 time_in_force.unwrap_or(TimeInForce::Gtc),
144 UUID4::new(),
145 self.clock.borrow().timestamp_ns(),
146 reduce_only.unwrap_or(false),
147 quote_quantity.unwrap_or(false),
148 Some(ContingencyType::NoContingency),
149 None,
150 None,
151 None,
152 exec_algorithm_id,
153 exec_algorithm_params,
154 exec_spawn_id,
155 tags,
156 );
157 OrderAny::Market(order)
158 }
159
160 #[expect(clippy::too_many_arguments)]
162 pub fn limit(
163 &mut self,
164 instrument_id: InstrumentId,
165 order_side: OrderSide,
166 quantity: Quantity,
167 price: Price,
168 time_in_force: Option<TimeInForce>,
169 expire_time: Option<nautilus_core::UnixNanos>,
170 post_only: Option<bool>,
171 reduce_only: Option<bool>,
172 quote_quantity: Option<bool>,
173 display_qty: Option<Quantity>,
174 emulation_trigger: Option<TriggerType>,
175 trigger_instrument_id: Option<InstrumentId>,
176 exec_algorithm_id: Option<ExecAlgorithmId>,
177 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
178 tags: Option<Vec<Ustr>>,
179 client_order_id: Option<ClientOrderId>,
180 ) -> OrderAny {
181 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
182 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
183 None
184 } else {
185 Some(client_order_id)
186 };
187 let order = LimitOrder::new(
188 self.trader_id,
189 self.strategy_id,
190 instrument_id,
191 client_order_id,
192 order_side,
193 quantity,
194 price,
195 time_in_force.unwrap_or(TimeInForce::Gtc),
196 expire_time,
197 post_only.unwrap_or(false),
198 reduce_only.unwrap_or(false),
199 quote_quantity.unwrap_or(false),
200 display_qty,
201 emulation_trigger,
202 trigger_instrument_id,
203 Some(ContingencyType::NoContingency),
204 None,
205 None,
206 None,
207 exec_algorithm_id,
208 exec_algorithm_params,
209 exec_spawn_id,
210 tags,
211 UUID4::new(),
212 self.clock.borrow().timestamp_ns(),
213 );
214 OrderAny::Limit(order)
215 }
216
217 #[expect(clippy::too_many_arguments)]
219 pub fn stop_market(
220 &mut self,
221 instrument_id: InstrumentId,
222 order_side: OrderSide,
223 quantity: Quantity,
224 trigger_price: Price,
225 trigger_type: Option<TriggerType>,
226 time_in_force: Option<TimeInForce>,
227 expire_time: Option<nautilus_core::UnixNanos>,
228 reduce_only: Option<bool>,
229 quote_quantity: Option<bool>,
230 display_qty: Option<Quantity>,
231 emulation_trigger: Option<TriggerType>,
232 trigger_instrument_id: Option<InstrumentId>,
233 exec_algorithm_id: Option<ExecAlgorithmId>,
234 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
235 tags: Option<Vec<Ustr>>,
236 client_order_id: Option<ClientOrderId>,
237 ) -> OrderAny {
238 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
239 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
240 None
241 } else {
242 Some(client_order_id)
243 };
244 let order = StopMarketOrder::new(
245 self.trader_id,
246 self.strategy_id,
247 instrument_id,
248 client_order_id,
249 order_side,
250 quantity,
251 trigger_price,
252 trigger_type.unwrap_or(TriggerType::Default),
253 time_in_force.unwrap_or(TimeInForce::Gtc),
254 expire_time,
255 reduce_only.unwrap_or(false),
256 quote_quantity.unwrap_or(false),
257 display_qty,
258 emulation_trigger,
259 trigger_instrument_id,
260 Some(ContingencyType::NoContingency),
261 None,
262 None,
263 None,
264 exec_algorithm_id,
265 exec_algorithm_params,
266 exec_spawn_id,
267 tags,
268 UUID4::new(),
269 self.clock.borrow().timestamp_ns(),
270 );
271 OrderAny::StopMarket(order)
272 }
273
274 #[expect(clippy::too_many_arguments)]
276 pub fn stop_limit(
277 &mut self,
278 instrument_id: InstrumentId,
279 order_side: OrderSide,
280 quantity: Quantity,
281 price: Price,
282 trigger_price: Price,
283 trigger_type: Option<TriggerType>,
284 time_in_force: Option<TimeInForce>,
285 expire_time: Option<nautilus_core::UnixNanos>,
286 post_only: Option<bool>,
287 reduce_only: Option<bool>,
288 quote_quantity: Option<bool>,
289 display_qty: Option<Quantity>,
290 emulation_trigger: Option<TriggerType>,
291 trigger_instrument_id: Option<InstrumentId>,
292 exec_algorithm_id: Option<ExecAlgorithmId>,
293 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
294 tags: Option<Vec<Ustr>>,
295 client_order_id: Option<ClientOrderId>,
296 ) -> OrderAny {
297 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
298 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
299 None
300 } else {
301 Some(client_order_id)
302 };
303 let order = StopLimitOrder::new(
304 self.trader_id,
305 self.strategy_id,
306 instrument_id,
307 client_order_id,
308 order_side,
309 quantity,
310 price,
311 trigger_price,
312 trigger_type.unwrap_or(TriggerType::Default),
313 time_in_force.unwrap_or(TimeInForce::Gtc),
314 expire_time,
315 post_only.unwrap_or(false),
316 reduce_only.unwrap_or(false),
317 quote_quantity.unwrap_or(false),
318 display_qty,
319 emulation_trigger,
320 trigger_instrument_id,
321 Some(ContingencyType::NoContingency),
322 None,
323 None,
324 None,
325 exec_algorithm_id,
326 exec_algorithm_params,
327 exec_spawn_id,
328 tags,
329 UUID4::new(),
330 self.clock.borrow().timestamp_ns(),
331 );
332 OrderAny::StopLimit(order)
333 }
334
335 #[expect(clippy::too_many_arguments)]
337 pub fn market_if_touched(
338 &mut self,
339 instrument_id: InstrumentId,
340 order_side: OrderSide,
341 quantity: Quantity,
342 trigger_price: Price,
343 trigger_type: Option<TriggerType>,
344 time_in_force: Option<TimeInForce>,
345 expire_time: Option<nautilus_core::UnixNanos>,
346 reduce_only: Option<bool>,
347 quote_quantity: Option<bool>,
348 emulation_trigger: Option<TriggerType>,
349 trigger_instrument_id: Option<InstrumentId>,
350 exec_algorithm_id: Option<ExecAlgorithmId>,
351 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
352 tags: Option<Vec<Ustr>>,
353 client_order_id: Option<ClientOrderId>,
354 ) -> OrderAny {
355 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
356 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
357 None
358 } else {
359 Some(client_order_id)
360 };
361 let order = MarketIfTouchedOrder::new(
362 self.trader_id,
363 self.strategy_id,
364 instrument_id,
365 client_order_id,
366 order_side,
367 quantity,
368 trigger_price,
369 trigger_type.unwrap_or(TriggerType::Default),
370 time_in_force.unwrap_or(TimeInForce::Gtc),
371 expire_time,
372 reduce_only.unwrap_or(false),
373 quote_quantity.unwrap_or(false),
374 emulation_trigger,
375 trigger_instrument_id,
376 Some(ContingencyType::NoContingency),
377 None,
378 None,
379 None,
380 exec_algorithm_id,
381 exec_algorithm_params,
382 exec_spawn_id,
383 tags,
384 UUID4::new(),
385 self.clock.borrow().timestamp_ns(),
386 );
387 OrderAny::MarketIfTouched(order)
388 }
389
390 #[expect(clippy::too_many_arguments)]
392 pub fn limit_if_touched(
393 &mut self,
394 instrument_id: InstrumentId,
395 order_side: OrderSide,
396 quantity: Quantity,
397 price: Price,
398 trigger_price: Price,
399 trigger_type: Option<TriggerType>,
400 time_in_force: Option<TimeInForce>,
401 expire_time: Option<nautilus_core::UnixNanos>,
402 post_only: Option<bool>,
403 reduce_only: Option<bool>,
404 quote_quantity: Option<bool>,
405 display_qty: Option<Quantity>,
406 emulation_trigger: Option<TriggerType>,
407 trigger_instrument_id: Option<InstrumentId>,
408 exec_algorithm_id: Option<ExecAlgorithmId>,
409 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
410 tags: Option<Vec<Ustr>>,
411 client_order_id: Option<ClientOrderId>,
412 ) -> OrderAny {
413 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
414 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
415 None
416 } else {
417 Some(client_order_id)
418 };
419 let order = LimitIfTouchedOrder::new(
420 self.trader_id,
421 self.strategy_id,
422 instrument_id,
423 client_order_id,
424 order_side,
425 quantity,
426 price,
427 trigger_price,
428 trigger_type.unwrap_or(TriggerType::Default),
429 time_in_force.unwrap_or(TimeInForce::Gtc),
430 expire_time,
431 post_only.unwrap_or(false),
432 reduce_only.unwrap_or(false),
433 quote_quantity.unwrap_or(false),
434 display_qty,
435 emulation_trigger,
436 trigger_instrument_id,
437 Some(ContingencyType::NoContingency),
438 None,
439 None,
440 None,
441 exec_algorithm_id,
442 exec_algorithm_params,
443 exec_spawn_id,
444 tags,
445 UUID4::new(),
446 self.clock.borrow().timestamp_ns(),
447 );
448 OrderAny::LimitIfTouched(order)
449 }
450
451 #[expect(clippy::too_many_arguments)]
457 pub fn trailing_stop_market(
458 &mut self,
459 instrument_id: InstrumentId,
460 order_side: OrderSide,
461 quantity: Quantity,
462 trailing_offset: Decimal,
463 trailing_offset_type: Option<TrailingOffsetType>,
464 activation_price: Option<Price>,
465 trigger_price: Option<Price>,
466 trigger_type: Option<TriggerType>,
467 time_in_force: Option<TimeInForce>,
468 expire_time: Option<nautilus_core::UnixNanos>,
469 reduce_only: Option<bool>,
470 quote_quantity: Option<bool>,
471 display_qty: Option<Quantity>,
472 emulation_trigger: Option<TriggerType>,
473 trigger_instrument_id: Option<InstrumentId>,
474 exec_algorithm_id: Option<ExecAlgorithmId>,
475 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
476 tags: Option<Vec<Ustr>>,
477 client_order_id: Option<ClientOrderId>,
478 ) -> OrderAny {
479 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
480 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
481 None
482 } else {
483 Some(client_order_id)
484 };
485
486 let trigger_price = trigger_price
489 .or(activation_price)
490 .expect("TrailingStopMarket requires either trigger_price or activation_price");
491
492 let order = TrailingStopMarketOrder::new(
493 self.trader_id,
494 self.strategy_id,
495 instrument_id,
496 client_order_id,
497 order_side,
498 quantity,
499 trigger_price,
500 trigger_type.unwrap_or(TriggerType::Default),
501 trailing_offset,
502 trailing_offset_type.unwrap_or(TrailingOffsetType::Price),
503 time_in_force.unwrap_or(TimeInForce::Gtc),
504 expire_time,
505 reduce_only.unwrap_or(false),
506 quote_quantity.unwrap_or(false),
507 display_qty,
508 emulation_trigger,
509 trigger_instrument_id,
510 Some(ContingencyType::NoContingency),
511 None,
512 None,
513 None,
514 exec_algorithm_id,
515 exec_algorithm_params,
516 exec_spawn_id,
517 tags,
518 UUID4::new(),
519 self.clock.borrow().timestamp_ns(),
520 );
521
522 let mut order = OrderAny::TrailingStopMarket(order);
523
524 if let (Some(activation_price), OrderAny::TrailingStopMarket(tsm)) =
525 (activation_price, &mut order)
526 {
527 tsm.activation_price = Some(activation_price);
528 }
529
530 order
531 }
532
533 pub fn create_list(&mut self, orders: &mut [OrderAny], ts_init: UnixNanos) -> OrderList {
543 check_slice_not_empty(orders, stringify!(orders)).unwrap();
544 let instrument_id = orders[0].instrument_id();
545 for order in orders.iter().skip(1) {
546 check_equal(
547 &order.instrument_id(),
548 &instrument_id,
549 "instrument_id",
550 "first order instrument_id",
551 )
552 .unwrap();
553 check_equal(
554 &order.strategy_id(),
555 &self.strategy_id,
556 "strategy_id",
557 "factory strategy_id",
558 )
559 .unwrap();
560 }
561 let order_list_id = self.generate_order_list_id();
562 let order_ids: Vec<ClientOrderId> = orders.iter().map(|o| o.client_order_id()).collect();
563
564 for order in orders.iter_mut() {
566 order.set_order_list_id(order_list_id);
567 }
568
569 OrderList::new(
570 order_list_id,
571 instrument_id,
572 self.strategy_id,
573 order_ids,
574 ts_init,
575 )
576 }
577
578 #[expect(clippy::too_many_arguments)]
580 pub fn bracket(
581 &mut self,
582 instrument_id: InstrumentId,
583 order_side: OrderSide,
584 quantity: Quantity,
585 entry_price: Option<Price>,
586 sl_trigger_price: Price,
587 sl_trigger_type: Option<TriggerType>,
588 tp_price: Price,
589 entry_trigger_price: Option<Price>,
590 time_in_force: Option<TimeInForce>,
591 expire_time: Option<nautilus_core::UnixNanos>,
592 sl_time_in_force: Option<TimeInForce>,
593 post_only: Option<bool>,
594 reduce_only: Option<bool>,
595 quote_quantity: Option<bool>,
596 emulation_trigger: Option<TriggerType>,
597 trigger_instrument_id: Option<InstrumentId>,
598 exec_algorithm_id: Option<ExecAlgorithmId>,
599 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
600 tags: Option<Vec<Ustr>>,
601 ) -> Vec<OrderAny> {
602 let order_list_id = self.generate_order_list_id();
603 let ts_init = self.clock.borrow().timestamp_ns();
604
605 let entry_client_order_id = self.generate_client_order_id();
606 let sl_client_order_id = self.generate_client_order_id();
607 let tp_client_order_id = self.generate_client_order_id();
608
609 let entry_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| entry_client_order_id);
611 let sl_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| sl_client_order_id);
612 let tp_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| tp_client_order_id);
613
614 let entry_contingency_type = Some(ContingencyType::Oto);
616 let entry_order_list_id = Some(order_list_id);
617 let entry_linked_order_ids = Some(vec![sl_client_order_id, tp_client_order_id]);
618 let entry_parent_order_id = None;
619
620 let entry_order = if let Some(trigger_price) = entry_trigger_price {
621 if let Some(price) = entry_price {
622 OrderAny::StopLimit(StopLimitOrder::new(
623 self.trader_id,
624 self.strategy_id,
625 instrument_id,
626 entry_client_order_id,
627 order_side,
628 quantity,
629 price,
630 trigger_price,
631 TriggerType::Default,
632 time_in_force.unwrap_or(TimeInForce::Gtc),
633 expire_time,
634 post_only.unwrap_or(false),
635 reduce_only.unwrap_or(false),
636 quote_quantity.unwrap_or(false),
637 None, emulation_trigger,
639 trigger_instrument_id,
640 entry_contingency_type,
641 entry_order_list_id,
642 entry_linked_order_ids,
643 entry_parent_order_id,
644 exec_algorithm_id,
645 exec_algorithm_params.clone(),
646 entry_exec_spawn_id,
647 tags.clone(),
648 UUID4::new(),
649 ts_init,
650 ))
651 } else {
652 OrderAny::StopMarket(StopMarketOrder::new(
653 self.trader_id,
654 self.strategy_id,
655 instrument_id,
656 entry_client_order_id,
657 order_side,
658 quantity,
659 trigger_price,
660 TriggerType::Default,
661 time_in_force.unwrap_or(TimeInForce::Gtc),
662 expire_time,
663 reduce_only.unwrap_or(false),
664 quote_quantity.unwrap_or(false),
665 None, emulation_trigger,
667 trigger_instrument_id,
668 entry_contingency_type,
669 entry_order_list_id,
670 entry_linked_order_ids,
671 entry_parent_order_id,
672 exec_algorithm_id,
673 exec_algorithm_params.clone(),
674 entry_exec_spawn_id,
675 tags.clone(),
676 UUID4::new(),
677 ts_init,
678 ))
679 }
680 } else if let Some(price) = entry_price {
681 OrderAny::Limit(LimitOrder::new(
682 self.trader_id,
683 self.strategy_id,
684 instrument_id,
685 entry_client_order_id,
686 order_side,
687 quantity,
688 price,
689 time_in_force.unwrap_or(TimeInForce::Gtc),
690 expire_time,
691 post_only.unwrap_or(false),
692 reduce_only.unwrap_or(false),
693 quote_quantity.unwrap_or(false),
694 None, emulation_trigger,
696 trigger_instrument_id,
697 entry_contingency_type,
698 entry_order_list_id,
699 entry_linked_order_ids,
700 entry_parent_order_id,
701 exec_algorithm_id,
702 exec_algorithm_params.clone(),
703 entry_exec_spawn_id,
704 tags.clone(),
705 UUID4::new(),
706 ts_init,
707 ))
708 } else {
709 OrderAny::Market(MarketOrder::new(
710 self.trader_id,
711 self.strategy_id,
712 instrument_id,
713 entry_client_order_id,
714 order_side,
715 quantity,
716 time_in_force.unwrap_or(TimeInForce::Gtc),
717 UUID4::new(),
718 ts_init,
719 reduce_only.unwrap_or(false),
720 quote_quantity.unwrap_or(false),
721 entry_contingency_type,
722 entry_order_list_id,
723 entry_linked_order_ids,
724 entry_parent_order_id,
725 exec_algorithm_id,
726 exec_algorithm_params.clone(),
727 entry_exec_spawn_id,
728 tags.clone(),
729 ))
730 };
731
732 let sl_tp_side = match order_side {
733 OrderSide::Buy => OrderSide::Sell,
734 OrderSide::Sell => OrderSide::Buy,
735 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
736 };
737
738 let sl_contingency_type = Some(ContingencyType::Oco);
740 let sl_order_list_id = Some(order_list_id);
741 let sl_linked_order_ids = Some(vec![tp_client_order_id]);
742 let sl_parent_order_id = Some(entry_client_order_id);
743
744 let sl_order = OrderAny::StopMarket(StopMarketOrder::new(
745 self.trader_id,
746 self.strategy_id,
747 instrument_id,
748 sl_client_order_id,
749 sl_tp_side,
750 quantity,
751 sl_trigger_price,
752 sl_trigger_type.unwrap_or(TriggerType::Default),
753 sl_time_in_force.unwrap_or(TimeInForce::Gtc),
754 None, true, quote_quantity.unwrap_or(false),
757 None, emulation_trigger,
759 trigger_instrument_id,
760 sl_contingency_type,
761 sl_order_list_id,
762 sl_linked_order_ids,
763 sl_parent_order_id,
764 exec_algorithm_id,
765 exec_algorithm_params.clone(),
766 sl_exec_spawn_id,
767 tags.clone(),
768 UUID4::new(),
769 ts_init,
770 ));
771
772 let tp_contingency_type = Some(ContingencyType::Oco);
774 let tp_order_list_id = Some(order_list_id);
775 let tp_linked_order_ids = Some(vec![sl_client_order_id]);
776 let tp_parent_order_id = Some(entry_client_order_id);
777
778 let tp_order = OrderAny::Limit(LimitOrder::new(
779 self.trader_id,
780 self.strategy_id,
781 instrument_id,
782 tp_client_order_id,
783 sl_tp_side,
784 quantity,
785 tp_price,
786 time_in_force.unwrap_or(TimeInForce::Gtc),
787 expire_time,
788 post_only.unwrap_or(false),
789 true, quote_quantity.unwrap_or(false),
791 None, emulation_trigger,
793 trigger_instrument_id,
794 tp_contingency_type,
795 tp_order_list_id,
796 tp_linked_order_ids,
797 tp_parent_order_id,
798 exec_algorithm_id,
799 exec_algorithm_params,
800 tp_exec_spawn_id,
801 tags,
802 UUID4::new(),
803 ts_init,
804 ));
805
806 vec![entry_order, sl_order, tp_order]
807 }
808}
809
810#[cfg(test)]
811pub mod tests {
812 use std::{cell::RefCell, rc::Rc};
813
814 use nautilus_core::UnixNanos;
815 use nautilus_model::{
816 enums::{ContingencyType, OrderSide, TimeInForce, TriggerType},
817 identifiers::{
818 ClientOrderId, InstrumentId, OrderListId,
819 stubs::{strategy_id_ema_cross, trader_id},
820 },
821 orders::Order,
822 types::Price,
823 };
824 use rstest::{fixture, rstest};
825
826 use crate::{clock::TestClock, factories::OrderFactory};
827
828 #[fixture]
829 pub fn order_factory() -> OrderFactory {
830 let trader_id = trader_id();
831 let strategy_id = strategy_id_ema_cross();
832 let clock = Rc::new(RefCell::new(TestClock::new()));
833 OrderFactory::new(
834 trader_id,
835 strategy_id,
836 None,
837 None,
838 clock,
839 false, true, )
842 }
843
844 #[rstest]
845 fn test_generate_client_order_id(mut order_factory: OrderFactory) {
846 let client_order_id = order_factory.generate_client_order_id();
847 assert_eq!(
848 client_order_id,
849 ClientOrderId::new("O-19700101-000000-001-001-1")
850 );
851 }
852
853 #[rstest]
854 fn test_generate_order_list_id(mut order_factory: OrderFactory) {
855 let order_list_id = order_factory.generate_order_list_id();
856 assert_eq!(
857 order_list_id,
858 OrderListId::new("OL-19700101-000000-001-001-1")
859 );
860 }
861
862 #[rstest]
863 fn test_set_client_order_id_count(mut order_factory: OrderFactory) {
864 order_factory.set_client_order_id_count(10);
865 let client_order_id = order_factory.generate_client_order_id();
866 assert_eq!(
867 client_order_id,
868 ClientOrderId::new("O-19700101-000000-001-001-11")
869 );
870 }
871
872 #[rstest]
873 fn test_set_order_list_id_count(mut order_factory: OrderFactory) {
874 order_factory.set_order_list_id_count(10);
875 let order_list_id = order_factory.generate_order_list_id();
876 assert_eq!(
877 order_list_id,
878 OrderListId::new("OL-19700101-000000-001-001-11")
879 );
880 }
881
882 #[rstest]
883 fn test_reset_factory(mut order_factory: OrderFactory) {
884 order_factory.generate_order_list_id();
885 order_factory.generate_client_order_id();
886 order_factory.reset_factory();
887 let client_order_id = order_factory.generate_client_order_id();
888 let order_list_id = order_factory.generate_order_list_id();
889 assert_eq!(
890 client_order_id,
891 ClientOrderId::new("O-19700101-000000-001-001-1")
892 );
893 assert_eq!(
894 order_list_id,
895 OrderListId::new("OL-19700101-000000-001-001-1")
896 );
897 }
898
899 #[fixture]
900 pub fn order_factory_with_uuids() -> OrderFactory {
901 let trader_id = trader_id();
902 let strategy_id = strategy_id_ema_cross();
903 let clock = Rc::new(RefCell::new(TestClock::new()));
904 OrderFactory::new(
905 trader_id,
906 strategy_id,
907 None,
908 None,
909 clock,
910 true, true, )
913 }
914
915 #[fixture]
916 pub fn order_factory_with_hyphens_removed() -> OrderFactory {
917 let trader_id = trader_id();
918 let strategy_id = strategy_id_ema_cross();
919 let clock = Rc::new(RefCell::new(TestClock::new()));
920 OrderFactory::new(
921 trader_id,
922 strategy_id,
923 None,
924 None,
925 clock,
926 false, false, )
929 }
930
931 #[fixture]
932 pub fn order_factory_with_uuids_and_hyphens_removed() -> OrderFactory {
933 let trader_id = trader_id();
934 let strategy_id = strategy_id_ema_cross();
935 let clock = Rc::new(RefCell::new(TestClock::new()));
936 OrderFactory::new(
937 trader_id,
938 strategy_id,
939 None,
940 None,
941 clock,
942 true, false, )
945 }
946
947 #[rstest]
948 fn test_generate_client_order_id_with_uuids(mut order_factory_with_uuids: OrderFactory) {
949 let client_order_id = order_factory_with_uuids.generate_client_order_id();
950
951 assert_eq!(client_order_id.as_str().len(), 36);
953 assert!(client_order_id.as_str().contains('-'));
954 }
955
956 #[rstest]
957 fn test_generate_client_order_id_with_hyphens_removed(
958 mut order_factory_with_hyphens_removed: OrderFactory,
959 ) {
960 let client_order_id = order_factory_with_hyphens_removed.generate_client_order_id();
961
962 assert_eq!(
963 client_order_id,
964 ClientOrderId::new("O197001010000000010011")
965 );
966 assert!(!client_order_id.as_str().contains('-'));
967 }
968
969 #[rstest]
970 fn test_generate_client_order_id_with_uuids_and_hyphens_removed(
971 mut order_factory_with_uuids_and_hyphens_removed: OrderFactory,
972 ) {
973 let client_order_id =
974 order_factory_with_uuids_and_hyphens_removed.generate_client_order_id();
975
976 assert_eq!(client_order_id.as_str().len(), 32);
978 assert!(!client_order_id.as_str().contains('-'));
979 }
980
981 #[rstest]
982 fn test_market_order(mut order_factory: OrderFactory) {
983 let market_order = order_factory.market(
984 InstrumentId::from("BTCUSDT.BINANCE"),
985 OrderSide::Buy,
986 100.into(),
987 Some(TimeInForce::Gtc),
988 Some(false),
989 Some(false),
990 None,
991 None,
992 None,
993 None,
994 );
995 assert_eq!(market_order.instrument_id(), "BTCUSDT.BINANCE".into());
997 assert_eq!(market_order.order_side(), OrderSide::Buy);
998 assert_eq!(market_order.quantity(), 100.into());
999 assert_eq!(market_order.exec_algorithm_id(), None);
1003 assert_eq!(
1007 market_order.client_order_id(),
1008 ClientOrderId::new("O-19700101-000000-001-001-1")
1009 );
1010 }
1012
1013 #[rstest]
1014 fn test_limit_order(mut order_factory: OrderFactory) {
1015 let limit_order = order_factory.limit(
1016 InstrumentId::from("BTCUSDT.BINANCE"),
1017 OrderSide::Buy,
1018 100.into(),
1019 Price::from("50000.00"),
1020 Some(TimeInForce::Gtc),
1021 None,
1022 Some(false),
1023 Some(false),
1024 Some(false),
1025 None,
1026 None,
1027 None,
1028 None,
1029 None,
1030 None,
1031 None,
1032 );
1033
1034 assert_eq!(limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1035 assert_eq!(limit_order.order_side(), OrderSide::Buy);
1036 assert_eq!(limit_order.quantity(), 100.into());
1037 assert_eq!(limit_order.price(), Some(Price::from("50000.00")));
1038 assert_eq!(
1039 limit_order.client_order_id(),
1040 ClientOrderId::new("O-19700101-000000-001-001-1")
1041 );
1042 }
1043
1044 #[rstest]
1045 fn test_limit_order_with_post_only(mut order_factory: OrderFactory) {
1046 let limit_order = order_factory.limit(
1047 InstrumentId::from("BTCUSDT.BINANCE"),
1048 OrderSide::Buy,
1049 100.into(),
1050 Price::from("50000.00"),
1051 Some(TimeInForce::Gtc),
1052 None,
1053 Some(true), Some(false),
1055 Some(false),
1056 None,
1057 None,
1058 None,
1059 None,
1060 None,
1061 None,
1062 None,
1063 );
1064
1065 assert!(limit_order.is_post_only());
1066 }
1067
1068 #[rstest]
1069 fn test_limit_order_with_display_qty(mut order_factory: OrderFactory) {
1070 let limit_order = order_factory.limit(
1071 InstrumentId::from("BTCUSDT.BINANCE"),
1072 OrderSide::Buy,
1073 100.into(),
1074 Price::from("50000.00"),
1075 Some(TimeInForce::Gtc),
1076 None,
1077 Some(false), Some(false), Some(false), Some(50.into()), None,
1082 None,
1083 None,
1084 None,
1085 None,
1086 None,
1087 );
1088
1089 assert_eq!(limit_order.display_qty(), Some(50.into()));
1090 }
1091
1092 #[rstest]
1093 fn test_stop_market_order(mut order_factory: OrderFactory) {
1094 let stop_order = order_factory.stop_market(
1095 InstrumentId::from("BTCUSDT.BINANCE"),
1096 OrderSide::Sell,
1097 100.into(),
1098 Price::from("45000.00"),
1099 Some(TriggerType::LastPrice),
1100 Some(TimeInForce::Gtc),
1101 None,
1102 Some(false),
1103 Some(false),
1104 None,
1105 None,
1106 None,
1107 None,
1108 None,
1109 None,
1110 None,
1111 );
1112
1113 assert_eq!(stop_order.instrument_id(), "BTCUSDT.BINANCE".into());
1114 assert_eq!(stop_order.order_side(), OrderSide::Sell);
1115 assert_eq!(stop_order.quantity(), 100.into());
1116 assert_eq!(stop_order.trigger_price(), Some(Price::from("45000.00")));
1117 assert_eq!(stop_order.trigger_type(), Some(TriggerType::LastPrice));
1118 }
1119
1120 #[rstest]
1121 fn test_stop_limit_order(mut order_factory: OrderFactory) {
1122 let stop_limit_order = order_factory.stop_limit(
1123 InstrumentId::from("BTCUSDT.BINANCE"),
1124 OrderSide::Sell,
1125 100.into(),
1126 Price::from("45100.00"), Price::from("45000.00"), Some(TriggerType::LastPrice),
1129 Some(TimeInForce::Gtc),
1130 None,
1131 Some(false),
1132 Some(false),
1133 Some(false),
1134 None,
1135 None,
1136 None,
1137 None,
1138 None,
1139 None,
1140 None,
1141 );
1142
1143 assert_eq!(stop_limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1144 assert_eq!(stop_limit_order.order_side(), OrderSide::Sell);
1145 assert_eq!(stop_limit_order.quantity(), 100.into());
1146 assert_eq!(stop_limit_order.price(), Some(Price::from("45100.00")));
1147 assert_eq!(
1148 stop_limit_order.trigger_price(),
1149 Some(Price::from("45000.00"))
1150 );
1151 assert_eq!(
1152 stop_limit_order.trigger_type(),
1153 Some(TriggerType::LastPrice)
1154 );
1155 }
1156
1157 #[rstest]
1158 fn test_market_if_touched_order(mut order_factory: OrderFactory) {
1159 let mit_order = order_factory.market_if_touched(
1160 InstrumentId::from("BTCUSDT.BINANCE"),
1161 OrderSide::Buy,
1162 100.into(),
1163 Price::from("48000.00"),
1164 Some(TriggerType::LastPrice),
1165 Some(TimeInForce::Gtc),
1166 None,
1167 Some(false),
1168 Some(false),
1169 None,
1170 None,
1171 None,
1172 None,
1173 None,
1174 None,
1175 );
1176
1177 assert_eq!(mit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1178 assert_eq!(mit_order.order_side(), OrderSide::Buy);
1179 assert_eq!(mit_order.quantity(), 100.into());
1180 assert_eq!(mit_order.trigger_price(), Some(Price::from("48000.00")));
1181 assert_eq!(mit_order.trigger_type(), Some(TriggerType::LastPrice));
1182 }
1183
1184 #[rstest]
1185 fn test_limit_if_touched_order(mut order_factory: OrderFactory) {
1186 let lit_order = order_factory.limit_if_touched(
1187 InstrumentId::from("BTCUSDT.BINANCE"),
1188 OrderSide::Buy,
1189 100.into(),
1190 Price::from("48100.00"), Price::from("48000.00"), Some(TriggerType::LastPrice),
1193 Some(TimeInForce::Gtc),
1194 None,
1195 Some(false),
1196 Some(false),
1197 Some(false),
1198 None,
1199 None,
1200 None,
1201 None,
1202 None,
1203 None,
1204 None,
1205 );
1206
1207 assert_eq!(lit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1208 assert_eq!(lit_order.order_side(), OrderSide::Buy);
1209 assert_eq!(lit_order.quantity(), 100.into());
1210 assert_eq!(lit_order.price(), Some(Price::from("48100.00")));
1211 assert_eq!(lit_order.trigger_price(), Some(Price::from("48000.00")));
1212 assert_eq!(lit_order.trigger_type(), Some(TriggerType::LastPrice));
1213 }
1214
1215 #[rstest]
1216 fn test_bracket_order_with_market_entry(mut order_factory: OrderFactory) {
1217 let orders = order_factory.bracket(
1218 InstrumentId::from("BTCUSDT.BINANCE"),
1219 OrderSide::Buy,
1220 100.into(),
1221 None, Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1227 None,
1228 None, Some(false),
1230 Some(false),
1231 Some(false),
1232 None,
1233 None,
1234 None,
1235 None,
1236 None,
1237 );
1238
1239 assert_eq!(orders.len(), 3);
1240 assert_eq!(orders[0].instrument_id(), "BTCUSDT.BINANCE".into());
1241
1242 assert_eq!(orders[0].order_side(), OrderSide::Buy);
1244
1245 assert_eq!(orders[1].order_side(), OrderSide::Sell);
1247 assert_eq!(orders[1].trigger_price(), Some(Price::from("45000.00")));
1248
1249 assert_eq!(orders[2].order_side(), OrderSide::Sell);
1251 assert_eq!(orders[2].price(), Some(Price::from("55000.00")));
1252 }
1253
1254 #[rstest]
1255 fn test_bracket_order_with_limit_entry(mut order_factory: OrderFactory) {
1256 let orders = order_factory.bracket(
1257 InstrumentId::from("BTCUSDT.BINANCE"),
1258 OrderSide::Buy,
1259 100.into(),
1260 Some(Price::from("49000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1266 None,
1267 None, Some(false),
1269 Some(false),
1270 Some(false),
1271 None,
1272 None,
1273 None,
1274 None,
1275 None,
1276 );
1277
1278 assert_eq!(orders.len(), 3);
1279
1280 assert_eq!(orders[0].price(), Some(Price::from("49000.00")));
1282 }
1283
1284 #[rstest]
1285 fn test_bracket_order_with_stop_entry(mut order_factory: OrderFactory) {
1286 let orders = order_factory.bracket(
1287 InstrumentId::from("BTCUSDT.BINANCE"),
1288 OrderSide::Buy,
1289 100.into(),
1290 None, Price::from("45000.00"), None, Price::from("55000.00"), Some(Price::from("51000.00")), Some(TimeInForce::Gtc),
1296 None,
1297 None, Some(false),
1299 Some(false),
1300 Some(false),
1301 None,
1302 None,
1303 None,
1304 None,
1305 None,
1306 );
1307
1308 assert_eq!(orders.len(), 3);
1309
1310 assert_eq!(orders[0].trigger_price(), Some(Price::from("51000.00")));
1312 }
1313
1314 #[rstest]
1315 fn test_bracket_order_sell_side(mut order_factory: OrderFactory) {
1316 let orders = order_factory.bracket(
1317 InstrumentId::from("BTCUSDT.BINANCE"),
1318 OrderSide::Sell,
1319 100.into(),
1320 Some(Price::from("51000.00")), Price::from("55000.00"), None, Price::from("45000.00"), None,
1325 Some(TimeInForce::Gtc),
1326 None,
1327 None, Some(false),
1329 Some(false),
1330 Some(false),
1331 None,
1332 None,
1333 None,
1334 None,
1335 None,
1336 );
1337
1338 assert_eq!(orders.len(), 3);
1339
1340 assert_eq!(orders[0].order_side(), OrderSide::Sell);
1342
1343 assert_eq!(orders[1].order_side(), OrderSide::Buy);
1345
1346 assert_eq!(orders[2].order_side(), OrderSide::Buy);
1348 }
1349
1350 #[rstest]
1351 fn test_bracket_order_sets_contingencies(mut order_factory: OrderFactory) {
1352 let orders = order_factory.bracket(
1353 InstrumentId::from("BTCUSDT.BINANCE"),
1354 OrderSide::Buy,
1355 100.into(),
1356 Some(Price::from("50000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1362 None,
1363 None, Some(false),
1365 Some(false),
1366 Some(false),
1367 None,
1368 None,
1369 None,
1370 None,
1371 None,
1372 );
1373
1374 let entry = &orders[0];
1375 let stop = &orders[1];
1376 let take = &orders[2];
1377
1378 let order_list_id = entry
1379 .order_list_id()
1380 .expect("Entry should have order_list_id");
1381 assert_eq!(entry.contingency_type(), Some(ContingencyType::Oto));
1382 assert_eq!(
1383 entry.linked_order_ids().unwrap(),
1384 &[stop.client_order_id(), take.client_order_id()]
1385 );
1386
1387 assert_eq!(stop.order_list_id(), Some(order_list_id));
1388 assert_eq!(stop.contingency_type(), Some(ContingencyType::Oco));
1389 assert_eq!(stop.parent_order_id(), Some(entry.client_order_id()));
1390 assert_eq!(stop.linked_order_ids().unwrap(), &[take.client_order_id()]);
1391
1392 assert_eq!(take.order_list_id(), Some(order_list_id));
1393 assert_eq!(take.contingency_type(), Some(ContingencyType::Oco));
1394 assert_eq!(take.parent_order_id(), Some(entry.client_order_id()));
1395 assert_eq!(take.linked_order_ids().unwrap(), &[stop.client_order_id()]);
1396 }
1397
1398 #[rstest]
1399 fn test_create_list_from_plain_orders(mut order_factory: OrderFactory) {
1400 let entry = order_factory.limit(
1401 InstrumentId::from("BTCUSDT.BINANCE"),
1402 OrderSide::Buy,
1403 100.into(),
1404 Price::from("50000.00"),
1405 None,
1406 None,
1407 None,
1408 None,
1409 None,
1410 None,
1411 None,
1412 None,
1413 None,
1414 None,
1415 None,
1416 None,
1417 );
1418 let sl = order_factory.stop_market(
1419 InstrumentId::from("BTCUSDT.BINANCE"),
1420 OrderSide::Sell,
1421 100.into(),
1422 Price::from("45000.00"),
1423 None,
1424 None,
1425 None,
1426 None,
1427 None,
1428 None,
1429 None,
1430 None,
1431 None,
1432 None,
1433 None,
1434 None,
1435 );
1436
1437 let mut orders = vec![entry.clone(), sl.clone()];
1438 let order_list = order_factory.create_list(&mut orders, UnixNanos::default());
1439
1440 assert_eq!(order_list.len(), 2);
1441 assert_eq!(
1442 order_list.instrument_id,
1443 InstrumentId::from("BTCUSDT.BINANCE")
1444 );
1445 assert_eq!(order_list.client_order_ids[0], entry.client_order_id());
1446 assert_eq!(order_list.client_order_ids[1], sl.client_order_id());
1447 assert_eq!(
1448 order_list.id,
1449 OrderListId::new("OL-19700101-000000-001-001-1"),
1450 );
1451 assert_eq!(orders[0].order_list_id(), Some(order_list.id));
1452 assert_eq!(orders[1].order_list_id(), Some(order_list.id));
1453 }
1454}