1use indexmap::IndexMap;
19use nautilus_core::{AtomicTime, UUID4};
20use nautilus_model::{
21 enums::{ContingencyType, OrderSide, TimeInForce, TriggerType},
22 identifiers::{
23 ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
24 },
25 orders::{
26 LimitIfTouchedOrder, LimitOrder, MarketIfTouchedOrder, MarketOrder, OrderAny, OrderList,
27 StopLimitOrder, StopMarketOrder,
28 },
29 types::{Price, Quantity},
30};
31use ustr::Ustr;
32
33use crate::generators::{
34 client_order_id::ClientOrderIdGenerator, order_list_id::OrderListIdGenerator,
35};
36
37#[repr(C)]
38#[derive(Debug)]
39pub struct OrderFactory {
40 clock: &'static AtomicTime,
41 trader_id: TraderId,
42 strategy_id: StrategyId,
43 order_id_generator: ClientOrderIdGenerator,
44 order_list_id_generator: OrderListIdGenerator,
45}
46
47impl OrderFactory {
48 pub fn new(
50 trader_id: TraderId,
51 strategy_id: StrategyId,
52 init_order_id_count: Option<usize>,
53 init_order_list_id_count: Option<usize>,
54 clock: &'static AtomicTime,
55 use_uuids_for_client_order_ids: bool,
56 use_hyphens_in_client_order_ids: bool,
57 ) -> Self {
58 let order_id_generator = ClientOrderIdGenerator::new(
59 trader_id,
60 strategy_id,
61 init_order_id_count.unwrap_or(0),
62 clock,
63 use_uuids_for_client_order_ids,
64 use_hyphens_in_client_order_ids,
65 );
66
67 let order_list_id_generator = OrderListIdGenerator::new(
68 trader_id,
69 strategy_id,
70 init_order_list_id_count.unwrap_or(0),
71 clock,
72 );
73
74 Self {
75 clock,
76 trader_id,
77 strategy_id,
78 order_id_generator,
79 order_list_id_generator,
80 }
81 }
82
83 pub const fn set_client_order_id_count(&mut self, count: usize) {
85 self.order_id_generator.set_count(count);
86 }
87
88 pub const fn set_order_list_id_count(&mut self, count: usize) {
90 self.order_list_id_generator.set_count(count);
91 }
92
93 pub fn generate_client_order_id(&mut self) -> ClientOrderId {
95 self.order_id_generator.generate()
96 }
97
98 pub fn generate_order_list_id(&mut self) -> OrderListId {
100 self.order_list_id_generator.generate()
101 }
102
103 pub const fn reset_factory(&mut self) {
105 self.order_id_generator.reset();
106 self.order_list_id_generator.reset();
107 }
108
109 #[allow(clippy::too_many_arguments)]
111 pub fn market(
112 &mut self,
113 instrument_id: InstrumentId,
114 order_side: OrderSide,
115 quantity: Quantity,
116 time_in_force: Option<TimeInForce>,
117 reduce_only: Option<bool>,
118 quote_quantity: Option<bool>,
119 exec_algorithm_id: Option<ExecAlgorithmId>,
120 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
121 tags: Option<Vec<Ustr>>,
122 client_order_id: Option<ClientOrderId>,
123 ) -> OrderAny {
124 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
125 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
126 None
127 } else {
128 Some(client_order_id)
129 };
130 let order = MarketOrder::new(
131 self.trader_id,
132 self.strategy_id,
133 instrument_id,
134 client_order_id,
135 order_side,
136 quantity,
137 time_in_force.unwrap_or(TimeInForce::Gtc),
138 UUID4::new(),
139 self.clock.get_time_ns(),
140 reduce_only.unwrap_or(false),
141 quote_quantity.unwrap_or(false),
142 Some(ContingencyType::NoContingency),
143 None,
144 None,
145 None,
146 exec_algorithm_id,
147 exec_algorithm_params,
148 exec_spawn_id,
149 tags,
150 );
151 OrderAny::Market(order)
152 }
153
154 #[allow(clippy::too_many_arguments)]
156 pub fn limit(
157 &mut self,
158 instrument_id: InstrumentId,
159 order_side: OrderSide,
160 quantity: Quantity,
161 price: Price,
162 time_in_force: Option<TimeInForce>,
163 expire_time: Option<nautilus_core::UnixNanos>,
164 post_only: Option<bool>,
165 reduce_only: Option<bool>,
166 quote_quantity: Option<bool>,
167 display_qty: Option<Quantity>,
168 emulation_trigger: Option<TriggerType>,
169 trigger_instrument_id: Option<InstrumentId>,
170 exec_algorithm_id: Option<ExecAlgorithmId>,
171 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
172 tags: Option<Vec<Ustr>>,
173 client_order_id: Option<ClientOrderId>,
174 ) -> OrderAny {
175 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
176 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
177 None
178 } else {
179 Some(client_order_id)
180 };
181 let order = LimitOrder::new(
182 self.trader_id,
183 self.strategy_id,
184 instrument_id,
185 client_order_id,
186 order_side,
187 quantity,
188 price,
189 time_in_force.unwrap_or(TimeInForce::Gtc),
190 expire_time,
191 post_only.unwrap_or(false),
192 reduce_only.unwrap_or(false),
193 quote_quantity.unwrap_or(false),
194 display_qty,
195 emulation_trigger,
196 trigger_instrument_id,
197 Some(ContingencyType::NoContingency),
198 None,
199 None,
200 None,
201 exec_algorithm_id,
202 exec_algorithm_params,
203 exec_spawn_id,
204 tags,
205 UUID4::new(),
206 self.clock.get_time_ns(),
207 );
208 OrderAny::Limit(order)
209 }
210
211 #[allow(clippy::too_many_arguments)]
213 pub fn stop_market(
214 &mut self,
215 instrument_id: InstrumentId,
216 order_side: OrderSide,
217 quantity: Quantity,
218 trigger_price: Price,
219 trigger_type: Option<TriggerType>,
220 time_in_force: Option<TimeInForce>,
221 expire_time: Option<nautilus_core::UnixNanos>,
222 reduce_only: Option<bool>,
223 quote_quantity: Option<bool>,
224 display_qty: Option<Quantity>,
225 emulation_trigger: Option<TriggerType>,
226 trigger_instrument_id: Option<InstrumentId>,
227 exec_algorithm_id: Option<ExecAlgorithmId>,
228 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
229 tags: Option<Vec<Ustr>>,
230 client_order_id: Option<ClientOrderId>,
231 ) -> OrderAny {
232 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
233 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
234 None
235 } else {
236 Some(client_order_id)
237 };
238 let order = StopMarketOrder::new(
239 self.trader_id,
240 self.strategy_id,
241 instrument_id,
242 client_order_id,
243 order_side,
244 quantity,
245 trigger_price,
246 trigger_type.unwrap_or(TriggerType::Default),
247 time_in_force.unwrap_or(TimeInForce::Gtc),
248 expire_time,
249 reduce_only.unwrap_or(false),
250 quote_quantity.unwrap_or(false),
251 display_qty,
252 emulation_trigger,
253 trigger_instrument_id,
254 Some(ContingencyType::NoContingency),
255 None,
256 None,
257 None,
258 exec_algorithm_id,
259 exec_algorithm_params,
260 exec_spawn_id,
261 tags,
262 UUID4::new(),
263 self.clock.get_time_ns(),
264 );
265 OrderAny::StopMarket(order)
266 }
267
268 #[allow(clippy::too_many_arguments)]
270 pub fn stop_limit(
271 &mut self,
272 instrument_id: InstrumentId,
273 order_side: OrderSide,
274 quantity: Quantity,
275 price: Price,
276 trigger_price: Price,
277 trigger_type: Option<TriggerType>,
278 time_in_force: Option<TimeInForce>,
279 expire_time: Option<nautilus_core::UnixNanos>,
280 post_only: Option<bool>,
281 reduce_only: Option<bool>,
282 quote_quantity: Option<bool>,
283 display_qty: Option<Quantity>,
284 emulation_trigger: Option<TriggerType>,
285 trigger_instrument_id: Option<InstrumentId>,
286 exec_algorithm_id: Option<ExecAlgorithmId>,
287 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
288 tags: Option<Vec<Ustr>>,
289 client_order_id: Option<ClientOrderId>,
290 ) -> OrderAny {
291 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
292 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
293 None
294 } else {
295 Some(client_order_id)
296 };
297 let order = StopLimitOrder::new(
298 self.trader_id,
299 self.strategy_id,
300 instrument_id,
301 client_order_id,
302 order_side,
303 quantity,
304 price,
305 trigger_price,
306 trigger_type.unwrap_or(TriggerType::Default),
307 time_in_force.unwrap_or(TimeInForce::Gtc),
308 expire_time,
309 post_only.unwrap_or(false),
310 reduce_only.unwrap_or(false),
311 quote_quantity.unwrap_or(false),
312 display_qty,
313 emulation_trigger,
314 trigger_instrument_id,
315 Some(ContingencyType::NoContingency),
316 None,
317 None,
318 None,
319 exec_algorithm_id,
320 exec_algorithm_params,
321 exec_spawn_id,
322 tags,
323 UUID4::new(),
324 self.clock.get_time_ns(),
325 );
326 OrderAny::StopLimit(order)
327 }
328
329 #[allow(clippy::too_many_arguments)]
331 pub fn market_if_touched(
332 &mut self,
333 instrument_id: InstrumentId,
334 order_side: OrderSide,
335 quantity: Quantity,
336 trigger_price: Price,
337 trigger_type: Option<TriggerType>,
338 time_in_force: Option<TimeInForce>,
339 expire_time: Option<nautilus_core::UnixNanos>,
340 reduce_only: Option<bool>,
341 quote_quantity: Option<bool>,
342 emulation_trigger: Option<TriggerType>,
343 trigger_instrument_id: Option<InstrumentId>,
344 exec_algorithm_id: Option<ExecAlgorithmId>,
345 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
346 tags: Option<Vec<Ustr>>,
347 client_order_id: Option<ClientOrderId>,
348 ) -> OrderAny {
349 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
350 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
351 None
352 } else {
353 Some(client_order_id)
354 };
355 let order = MarketIfTouchedOrder::new(
356 self.trader_id,
357 self.strategy_id,
358 instrument_id,
359 client_order_id,
360 order_side,
361 quantity,
362 trigger_price,
363 trigger_type.unwrap_or(TriggerType::Default),
364 time_in_force.unwrap_or(TimeInForce::Gtc),
365 expire_time,
366 reduce_only.unwrap_or(false),
367 quote_quantity.unwrap_or(false),
368 emulation_trigger,
369 trigger_instrument_id,
370 Some(ContingencyType::NoContingency),
371 None,
372 None,
373 None,
374 exec_algorithm_id,
375 exec_algorithm_params,
376 exec_spawn_id,
377 tags,
378 UUID4::new(),
379 self.clock.get_time_ns(),
380 );
381 OrderAny::MarketIfTouched(order)
382 }
383
384 #[allow(clippy::too_many_arguments)]
386 pub fn limit_if_touched(
387 &mut self,
388 instrument_id: InstrumentId,
389 order_side: OrderSide,
390 quantity: Quantity,
391 price: Price,
392 trigger_price: Price,
393 trigger_type: Option<TriggerType>,
394 time_in_force: Option<TimeInForce>,
395 expire_time: Option<nautilus_core::UnixNanos>,
396 post_only: Option<bool>,
397 reduce_only: Option<bool>,
398 quote_quantity: Option<bool>,
399 display_qty: Option<Quantity>,
400 emulation_trigger: Option<TriggerType>,
401 trigger_instrument_id: Option<InstrumentId>,
402 exec_algorithm_id: Option<ExecAlgorithmId>,
403 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
404 tags: Option<Vec<Ustr>>,
405 client_order_id: Option<ClientOrderId>,
406 ) -> OrderAny {
407 let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
408 let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
409 None
410 } else {
411 Some(client_order_id)
412 };
413 let order = LimitIfTouchedOrder::new(
414 self.trader_id,
415 self.strategy_id,
416 instrument_id,
417 client_order_id,
418 order_side,
419 quantity,
420 price,
421 trigger_price,
422 trigger_type.unwrap_or(TriggerType::Default),
423 time_in_force.unwrap_or(TimeInForce::Gtc),
424 expire_time,
425 post_only.unwrap_or(false),
426 reduce_only.unwrap_or(false),
427 quote_quantity.unwrap_or(false),
428 display_qty,
429 emulation_trigger,
430 trigger_instrument_id,
431 Some(ContingencyType::NoContingency),
432 None,
433 None,
434 None,
435 exec_algorithm_id,
436 exec_algorithm_params,
437 exec_spawn_id,
438 tags,
439 UUID4::new(),
440 self.clock.get_time_ns(),
441 );
442 OrderAny::LimitIfTouched(order)
443 }
444
445 #[allow(clippy::too_many_arguments)]
447 pub fn bracket(
448 &mut self,
449 instrument_id: InstrumentId,
450 order_side: OrderSide,
451 quantity: Quantity,
452 entry_price: Option<Price>,
453 sl_trigger_price: Price,
454 tp_price: Price,
455 entry_trigger_price: Option<Price>,
456 time_in_force: Option<TimeInForce>,
457 expire_time: Option<nautilus_core::UnixNanos>,
458 post_only: Option<bool>,
459 reduce_only: Option<bool>,
460 quote_quantity: Option<bool>,
461 emulation_trigger: Option<TriggerType>,
462 trigger_instrument_id: Option<InstrumentId>,
463 exec_algorithm_id: Option<ExecAlgorithmId>,
464 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
465 tags: Option<Vec<Ustr>>,
466 ) -> OrderList {
467 let order_list_id = self.generate_order_list_id();
468 let ts_init = self.clock.get_time_ns();
469
470 let entry_client_order_id = self.generate_client_order_id();
471 let sl_client_order_id = self.generate_client_order_id();
472 let tp_client_order_id = self.generate_client_order_id();
473
474 let entry_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| entry_client_order_id);
476 let sl_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| sl_client_order_id);
477 let tp_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| tp_client_order_id);
478
479 let entry_contingency_type = Some(ContingencyType::Oto);
481 let entry_order_list_id = Some(order_list_id);
482 let entry_linked_order_ids = Some(vec![sl_client_order_id, tp_client_order_id]);
483 let entry_parent_order_id = None;
484
485 let entry_order = if let Some(trigger_price) = entry_trigger_price {
486 if let Some(price) = entry_price {
487 OrderAny::StopLimit(StopLimitOrder::new(
488 self.trader_id,
489 self.strategy_id,
490 instrument_id,
491 entry_client_order_id,
492 order_side,
493 quantity,
494 price,
495 trigger_price,
496 TriggerType::Default,
497 time_in_force.unwrap_or(TimeInForce::Gtc),
498 expire_time,
499 post_only.unwrap_or(false),
500 reduce_only.unwrap_or(false),
501 quote_quantity.unwrap_or(false),
502 None, emulation_trigger,
504 trigger_instrument_id,
505 entry_contingency_type,
506 entry_order_list_id,
507 entry_linked_order_ids,
508 entry_parent_order_id,
509 exec_algorithm_id,
510 exec_algorithm_params.clone(),
511 entry_exec_spawn_id,
512 tags.clone(),
513 UUID4::new(),
514 ts_init,
515 ))
516 } else {
517 OrderAny::StopMarket(StopMarketOrder::new(
518 self.trader_id,
519 self.strategy_id,
520 instrument_id,
521 entry_client_order_id,
522 order_side,
523 quantity,
524 trigger_price,
525 TriggerType::Default,
526 time_in_force.unwrap_or(TimeInForce::Gtc),
527 expire_time,
528 reduce_only.unwrap_or(false),
529 quote_quantity.unwrap_or(false),
530 None, emulation_trigger,
532 trigger_instrument_id,
533 entry_contingency_type,
534 entry_order_list_id,
535 entry_linked_order_ids,
536 entry_parent_order_id,
537 exec_algorithm_id,
538 exec_algorithm_params.clone(),
539 entry_exec_spawn_id,
540 tags.clone(),
541 UUID4::new(),
542 ts_init,
543 ))
544 }
545 } else if let Some(price) = entry_price {
546 OrderAny::Limit(LimitOrder::new(
547 self.trader_id,
548 self.strategy_id,
549 instrument_id,
550 entry_client_order_id,
551 order_side,
552 quantity,
553 price,
554 time_in_force.unwrap_or(TimeInForce::Gtc),
555 expire_time,
556 post_only.unwrap_or(false),
557 reduce_only.unwrap_or(false),
558 quote_quantity.unwrap_or(false),
559 None, emulation_trigger,
561 trigger_instrument_id,
562 entry_contingency_type,
563 entry_order_list_id,
564 entry_linked_order_ids,
565 entry_parent_order_id,
566 exec_algorithm_id,
567 exec_algorithm_params.clone(),
568 entry_exec_spawn_id,
569 tags.clone(),
570 UUID4::new(),
571 ts_init,
572 ))
573 } else {
574 OrderAny::Market(MarketOrder::new(
575 self.trader_id,
576 self.strategy_id,
577 instrument_id,
578 entry_client_order_id,
579 order_side,
580 quantity,
581 time_in_force.unwrap_or(TimeInForce::Gtc),
582 UUID4::new(),
583 ts_init,
584 reduce_only.unwrap_or(false),
585 quote_quantity.unwrap_or(false),
586 entry_contingency_type,
587 entry_order_list_id,
588 entry_linked_order_ids,
589 entry_parent_order_id,
590 exec_algorithm_id,
591 exec_algorithm_params.clone(),
592 entry_exec_spawn_id,
593 tags.clone(),
594 ))
595 };
596
597 let sl_tp_side = match order_side {
598 OrderSide::Buy => OrderSide::Sell,
599 OrderSide::Sell => OrderSide::Buy,
600 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
601 };
602
603 let sl_contingency_type = Some(ContingencyType::Oco);
605 let sl_order_list_id = Some(order_list_id);
606 let sl_linked_order_ids = Some(vec![tp_client_order_id]);
607 let sl_parent_order_id = Some(entry_client_order_id);
608
609 let sl_order = OrderAny::StopMarket(StopMarketOrder::new(
610 self.trader_id,
611 self.strategy_id,
612 instrument_id,
613 sl_client_order_id,
614 sl_tp_side,
615 quantity,
616 sl_trigger_price,
617 TriggerType::Default,
618 time_in_force.unwrap_or(TimeInForce::Gtc),
619 expire_time,
620 true, quote_quantity.unwrap_or(false),
622 None, emulation_trigger,
624 trigger_instrument_id,
625 sl_contingency_type,
626 sl_order_list_id,
627 sl_linked_order_ids,
628 sl_parent_order_id,
629 exec_algorithm_id,
630 exec_algorithm_params.clone(),
631 sl_exec_spawn_id,
632 tags.clone(),
633 UUID4::new(),
634 ts_init,
635 ));
636
637 let tp_contingency_type = Some(ContingencyType::Oco);
639 let tp_order_list_id = Some(order_list_id);
640 let tp_linked_order_ids = Some(vec![sl_client_order_id]);
641 let tp_parent_order_id = Some(entry_client_order_id);
642
643 let tp_order = OrderAny::Limit(LimitOrder::new(
644 self.trader_id,
645 self.strategy_id,
646 instrument_id,
647 tp_client_order_id,
648 sl_tp_side,
649 quantity,
650 tp_price,
651 time_in_force.unwrap_or(TimeInForce::Gtc),
652 expire_time,
653 post_only.unwrap_or(false),
654 true, quote_quantity.unwrap_or(false),
656 None, emulation_trigger,
658 trigger_instrument_id,
659 tp_contingency_type,
660 tp_order_list_id,
661 tp_linked_order_ids,
662 tp_parent_order_id,
663 exec_algorithm_id,
664 exec_algorithm_params,
665 tp_exec_spawn_id,
666 tags,
667 UUID4::new(),
668 ts_init,
669 ));
670
671 OrderList::new(
672 order_list_id,
673 instrument_id,
674 self.strategy_id,
675 vec![entry_order, sl_order, tp_order],
676 ts_init,
677 )
678 }
679}
680
681#[cfg(test)]
682pub mod tests {
683 use nautilus_core::time::get_atomic_clock_static;
684 use nautilus_model::{
685 enums::{ContingencyType, OrderSide, TimeInForce, TriggerType},
686 identifiers::{
687 ClientOrderId, InstrumentId, OrderListId,
688 stubs::{strategy_id_ema_cross, trader_id},
689 },
690 orders::Order,
691 types::Price,
692 };
693 use rstest::{fixture, rstest};
694
695 use crate::factories::OrderFactory;
696
697 #[fixture]
698 pub fn order_factory() -> OrderFactory {
699 let trader_id = trader_id();
700 let strategy_id = strategy_id_ema_cross();
701 OrderFactory::new(
702 trader_id,
703 strategy_id,
704 None,
705 None,
706 get_atomic_clock_static(),
707 false, true, )
710 }
711
712 #[rstest]
713 fn test_generate_client_order_id(mut order_factory: OrderFactory) {
714 let client_order_id = order_factory.generate_client_order_id();
715 assert_eq!(
716 client_order_id,
717 ClientOrderId::new("O-19700101-000000-001-001-1")
718 );
719 }
720
721 #[rstest]
722 fn test_generate_order_list_id(mut order_factory: OrderFactory) {
723 let order_list_id = order_factory.generate_order_list_id();
724 assert_eq!(
725 order_list_id,
726 OrderListId::new("OL-19700101-000000-001-001-1")
727 );
728 }
729
730 #[rstest]
731 fn test_set_client_order_id_count(mut order_factory: OrderFactory) {
732 order_factory.set_client_order_id_count(10);
733 let client_order_id = order_factory.generate_client_order_id();
734 assert_eq!(
735 client_order_id,
736 ClientOrderId::new("O-19700101-000000-001-001-11")
737 );
738 }
739
740 #[rstest]
741 fn test_set_order_list_id_count(mut order_factory: OrderFactory) {
742 order_factory.set_order_list_id_count(10);
743 let order_list_id = order_factory.generate_order_list_id();
744 assert_eq!(
745 order_list_id,
746 OrderListId::new("OL-19700101-000000-001-001-11")
747 );
748 }
749
750 #[rstest]
751 fn test_reset_factory(mut order_factory: OrderFactory) {
752 order_factory.generate_order_list_id();
753 order_factory.generate_client_order_id();
754 order_factory.reset_factory();
755 let client_order_id = order_factory.generate_client_order_id();
756 let order_list_id = order_factory.generate_order_list_id();
757 assert_eq!(
758 client_order_id,
759 ClientOrderId::new("O-19700101-000000-001-001-1")
760 );
761 assert_eq!(
762 order_list_id,
763 OrderListId::new("OL-19700101-000000-001-001-1")
764 );
765 }
766
767 #[fixture]
768 pub fn order_factory_with_uuids() -> OrderFactory {
769 let trader_id = trader_id();
770 let strategy_id = strategy_id_ema_cross();
771 OrderFactory::new(
772 trader_id,
773 strategy_id,
774 None,
775 None,
776 get_atomic_clock_static(),
777 true, true, )
780 }
781
782 #[fixture]
783 pub fn order_factory_with_hyphens_removed() -> OrderFactory {
784 let trader_id = trader_id();
785 let strategy_id = strategy_id_ema_cross();
786 OrderFactory::new(
787 trader_id,
788 strategy_id,
789 None,
790 None,
791 get_atomic_clock_static(),
792 false, false, )
795 }
796
797 #[fixture]
798 pub fn order_factory_with_uuids_and_hyphens_removed() -> OrderFactory {
799 let trader_id = trader_id();
800 let strategy_id = strategy_id_ema_cross();
801 OrderFactory::new(
802 trader_id,
803 strategy_id,
804 None,
805 None,
806 get_atomic_clock_static(),
807 true, false, )
810 }
811
812 #[rstest]
813 fn test_generate_client_order_id_with_uuids(mut order_factory_with_uuids: OrderFactory) {
814 let client_order_id = order_factory_with_uuids.generate_client_order_id();
815
816 assert_eq!(client_order_id.as_str().len(), 36);
818 assert!(client_order_id.as_str().contains('-'));
819 }
820
821 #[rstest]
822 fn test_generate_client_order_id_with_hyphens_removed(
823 mut order_factory_with_hyphens_removed: OrderFactory,
824 ) {
825 let client_order_id = order_factory_with_hyphens_removed.generate_client_order_id();
826
827 assert_eq!(
828 client_order_id,
829 ClientOrderId::new("O197001010000000010011")
830 );
831 assert!(!client_order_id.as_str().contains('-'));
832 }
833
834 #[rstest]
835 fn test_generate_client_order_id_with_uuids_and_hyphens_removed(
836 mut order_factory_with_uuids_and_hyphens_removed: OrderFactory,
837 ) {
838 let client_order_id =
839 order_factory_with_uuids_and_hyphens_removed.generate_client_order_id();
840
841 assert_eq!(client_order_id.as_str().len(), 32);
843 assert!(!client_order_id.as_str().contains('-'));
844 }
845
846 #[rstest]
847 fn test_market_order(mut order_factory: OrderFactory) {
848 let market_order = order_factory.market(
849 InstrumentId::from("BTCUSDT.BINANCE"),
850 OrderSide::Buy,
851 100.into(),
852 Some(TimeInForce::Gtc),
853 Some(false),
854 Some(false),
855 None,
856 None,
857 None,
858 None,
859 );
860 assert_eq!(market_order.instrument_id(), "BTCUSDT.BINANCE".into());
862 assert_eq!(market_order.order_side(), OrderSide::Buy);
863 assert_eq!(market_order.quantity(), 100.into());
864 assert_eq!(market_order.exec_algorithm_id(), None);
868 assert_eq!(
872 market_order.client_order_id(),
873 ClientOrderId::new("O-19700101-000000-001-001-1")
874 );
875 }
877
878 #[rstest]
879 fn test_limit_order(mut order_factory: OrderFactory) {
880 let limit_order = order_factory.limit(
881 InstrumentId::from("BTCUSDT.BINANCE"),
882 OrderSide::Buy,
883 100.into(),
884 Price::from("50000.00"),
885 Some(TimeInForce::Gtc),
886 None,
887 Some(false),
888 Some(false),
889 Some(false),
890 None,
891 None,
892 None,
893 None,
894 None,
895 None,
896 None,
897 );
898
899 assert_eq!(limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
900 assert_eq!(limit_order.order_side(), OrderSide::Buy);
901 assert_eq!(limit_order.quantity(), 100.into());
902 assert_eq!(limit_order.price(), Some(Price::from("50000.00")));
903 assert_eq!(
904 limit_order.client_order_id(),
905 ClientOrderId::new("O-19700101-000000-001-001-1")
906 );
907 }
908
909 #[rstest]
910 fn test_limit_order_with_post_only(mut order_factory: OrderFactory) {
911 let limit_order = order_factory.limit(
912 InstrumentId::from("BTCUSDT.BINANCE"),
913 OrderSide::Buy,
914 100.into(),
915 Price::from("50000.00"),
916 Some(TimeInForce::Gtc),
917 None,
918 Some(true), Some(false),
920 Some(false),
921 None,
922 None,
923 None,
924 None,
925 None,
926 None,
927 None,
928 );
929
930 assert!(limit_order.is_post_only());
931 }
932
933 #[rstest]
934 fn test_limit_order_with_display_qty(mut order_factory: OrderFactory) {
935 let limit_order = order_factory.limit(
936 InstrumentId::from("BTCUSDT.BINANCE"),
937 OrderSide::Buy,
938 100.into(),
939 Price::from("50000.00"),
940 Some(TimeInForce::Gtc),
941 None,
942 Some(false), Some(false), Some(false), Some(50.into()), None,
947 None,
948 None,
949 None,
950 None,
951 None,
952 );
953
954 assert_eq!(limit_order.display_qty(), Some(50.into()));
955 }
956
957 #[rstest]
958 fn test_stop_market_order(mut order_factory: OrderFactory) {
959 let stop_order = order_factory.stop_market(
960 InstrumentId::from("BTCUSDT.BINANCE"),
961 OrderSide::Sell,
962 100.into(),
963 Price::from("45000.00"),
964 Some(TriggerType::LastPrice),
965 Some(TimeInForce::Gtc),
966 None,
967 Some(false),
968 Some(false),
969 None,
970 None,
971 None,
972 None,
973 None,
974 None,
975 None,
976 );
977
978 assert_eq!(stop_order.instrument_id(), "BTCUSDT.BINANCE".into());
979 assert_eq!(stop_order.order_side(), OrderSide::Sell);
980 assert_eq!(stop_order.quantity(), 100.into());
981 assert_eq!(stop_order.trigger_price(), Some(Price::from("45000.00")));
982 assert_eq!(stop_order.trigger_type(), Some(TriggerType::LastPrice));
983 }
984
985 #[rstest]
986 fn test_stop_limit_order(mut order_factory: OrderFactory) {
987 let stop_limit_order = order_factory.stop_limit(
988 InstrumentId::from("BTCUSDT.BINANCE"),
989 OrderSide::Sell,
990 100.into(),
991 Price::from("45100.00"), Price::from("45000.00"), Some(TriggerType::LastPrice),
994 Some(TimeInForce::Gtc),
995 None,
996 Some(false),
997 Some(false),
998 Some(false),
999 None,
1000 None,
1001 None,
1002 None,
1003 None,
1004 None,
1005 None,
1006 );
1007
1008 assert_eq!(stop_limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1009 assert_eq!(stop_limit_order.order_side(), OrderSide::Sell);
1010 assert_eq!(stop_limit_order.quantity(), 100.into());
1011 assert_eq!(stop_limit_order.price(), Some(Price::from("45100.00")));
1012 assert_eq!(
1013 stop_limit_order.trigger_price(),
1014 Some(Price::from("45000.00"))
1015 );
1016 assert_eq!(
1017 stop_limit_order.trigger_type(),
1018 Some(TriggerType::LastPrice)
1019 );
1020 }
1021
1022 #[rstest]
1023 fn test_market_if_touched_order(mut order_factory: OrderFactory) {
1024 let mit_order = order_factory.market_if_touched(
1025 InstrumentId::from("BTCUSDT.BINANCE"),
1026 OrderSide::Buy,
1027 100.into(),
1028 Price::from("48000.00"),
1029 Some(TriggerType::LastPrice),
1030 Some(TimeInForce::Gtc),
1031 None,
1032 Some(false),
1033 Some(false),
1034 None,
1035 None,
1036 None,
1037 None,
1038 None,
1039 None,
1040 );
1041
1042 assert_eq!(mit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1043 assert_eq!(mit_order.order_side(), OrderSide::Buy);
1044 assert_eq!(mit_order.quantity(), 100.into());
1045 assert_eq!(mit_order.trigger_price(), Some(Price::from("48000.00")));
1046 assert_eq!(mit_order.trigger_type(), Some(TriggerType::LastPrice));
1047 }
1048
1049 #[rstest]
1050 fn test_limit_if_touched_order(mut order_factory: OrderFactory) {
1051 let lit_order = order_factory.limit_if_touched(
1052 InstrumentId::from("BTCUSDT.BINANCE"),
1053 OrderSide::Buy,
1054 100.into(),
1055 Price::from("48100.00"), Price::from("48000.00"), Some(TriggerType::LastPrice),
1058 Some(TimeInForce::Gtc),
1059 None,
1060 Some(false),
1061 Some(false),
1062 Some(false),
1063 None,
1064 None,
1065 None,
1066 None,
1067 None,
1068 None,
1069 None,
1070 );
1071
1072 assert_eq!(lit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1073 assert_eq!(lit_order.order_side(), OrderSide::Buy);
1074 assert_eq!(lit_order.quantity(), 100.into());
1075 assert_eq!(lit_order.price(), Some(Price::from("48100.00")));
1076 assert_eq!(lit_order.trigger_price(), Some(Price::from("48000.00")));
1077 assert_eq!(lit_order.trigger_type(), Some(TriggerType::LastPrice));
1078 }
1079
1080 #[rstest]
1081 fn test_bracket_order_with_market_entry(mut order_factory: OrderFactory) {
1082 let bracket = order_factory.bracket(
1083 InstrumentId::from("BTCUSDT.BINANCE"),
1084 OrderSide::Buy,
1085 100.into(),
1086 None, Price::from("45000.00"), Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1091 None,
1092 Some(false),
1093 Some(false),
1094 Some(false),
1095 None,
1096 None,
1097 None,
1098 None,
1099 None,
1100 );
1101
1102 assert_eq!(bracket.orders.len(), 3);
1103 assert_eq!(bracket.instrument_id, "BTCUSDT.BINANCE".into());
1104
1105 assert_eq!(bracket.orders[0].order_side(), OrderSide::Buy);
1107
1108 assert_eq!(bracket.orders[1].order_side(), OrderSide::Sell);
1110 assert_eq!(
1111 bracket.orders[1].trigger_price(),
1112 Some(Price::from("45000.00"))
1113 );
1114
1115 assert_eq!(bracket.orders[2].order_side(), OrderSide::Sell);
1117 assert_eq!(bracket.orders[2].price(), Some(Price::from("55000.00")));
1118 }
1119
1120 #[rstest]
1121 fn test_bracket_order_with_limit_entry(mut order_factory: OrderFactory) {
1122 let bracket = order_factory.bracket(
1123 InstrumentId::from("BTCUSDT.BINANCE"),
1124 OrderSide::Buy,
1125 100.into(),
1126 Some(Price::from("49000.00")), Price::from("45000.00"), Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1131 None,
1132 Some(false),
1133 Some(false),
1134 Some(false),
1135 None,
1136 None,
1137 None,
1138 None,
1139 None,
1140 );
1141
1142 assert_eq!(bracket.orders.len(), 3);
1143
1144 assert_eq!(bracket.orders[0].price(), Some(Price::from("49000.00")));
1146 }
1147
1148 #[rstest]
1149 fn test_bracket_order_with_stop_entry(mut order_factory: OrderFactory) {
1150 let bracket = order_factory.bracket(
1151 InstrumentId::from("BTCUSDT.BINANCE"),
1152 OrderSide::Buy,
1153 100.into(),
1154 None, Price::from("45000.00"), Price::from("55000.00"), Some(Price::from("51000.00")), Some(TimeInForce::Gtc),
1159 None,
1160 Some(false),
1161 Some(false),
1162 Some(false),
1163 None,
1164 None,
1165 None,
1166 None,
1167 None,
1168 );
1169
1170 assert_eq!(bracket.orders.len(), 3);
1171
1172 assert_eq!(
1174 bracket.orders[0].trigger_price(),
1175 Some(Price::from("51000.00"))
1176 );
1177 }
1178
1179 #[rstest]
1180 fn test_bracket_order_sell_side(mut order_factory: OrderFactory) {
1181 let bracket = order_factory.bracket(
1182 InstrumentId::from("BTCUSDT.BINANCE"),
1183 OrderSide::Sell,
1184 100.into(),
1185 Some(Price::from("51000.00")), Price::from("55000.00"), Price::from("45000.00"), None,
1189 Some(TimeInForce::Gtc),
1190 None,
1191 Some(false),
1192 Some(false),
1193 Some(false),
1194 None,
1195 None,
1196 None,
1197 None,
1198 None,
1199 );
1200
1201 assert_eq!(bracket.orders.len(), 3);
1202
1203 assert_eq!(bracket.orders[0].order_side(), OrderSide::Sell);
1205
1206 assert_eq!(bracket.orders[1].order_side(), OrderSide::Buy);
1208
1209 assert_eq!(bracket.orders[2].order_side(), OrderSide::Buy);
1211 }
1212
1213 #[rstest]
1214 fn test_bracket_order_sets_contingencies(mut order_factory: OrderFactory) {
1215 let bracket = order_factory.bracket(
1216 InstrumentId::from("BTCUSDT.BINANCE"),
1217 OrderSide::Buy,
1218 100.into(),
1219 Some(Price::from("50000.00")),
1220 Price::from("45000.00"),
1221 Price::from("55000.00"),
1222 None,
1223 Some(TimeInForce::Gtc),
1224 None,
1225 Some(false),
1226 Some(false),
1227 Some(false),
1228 None,
1229 None,
1230 None,
1231 None,
1232 None,
1233 );
1234
1235 let entry = &bracket.orders[0];
1236 let stop = &bracket.orders[1];
1237 let take = &bracket.orders[2];
1238
1239 assert_eq!(entry.order_list_id(), Some(bracket.id));
1240 assert_eq!(entry.contingency_type(), Some(ContingencyType::Oto));
1241 assert_eq!(
1242 entry.linked_order_ids().unwrap(),
1243 &[stop.client_order_id(), take.client_order_id()]
1244 );
1245
1246 assert_eq!(stop.order_list_id(), Some(bracket.id));
1247 assert_eq!(stop.contingency_type(), Some(ContingencyType::Oco));
1248 assert_eq!(stop.parent_order_id(), Some(entry.client_order_id()));
1249 assert_eq!(stop.linked_order_ids().unwrap(), &[take.client_order_id()]);
1250
1251 assert_eq!(take.order_list_id(), Some(bracket.id));
1252 assert_eq!(take.contingency_type(), Some(ContingencyType::Oco));
1253 assert_eq!(take.parent_order_id(), Some(entry.client_order_id()));
1254 assert_eq!(take.linked_order_ids().unwrap(), &[stop.client_order_id()]);
1255 }
1256}