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 sl_trigger_type: Option<TriggerType>,
455 tp_price: Price,
456 entry_trigger_price: Option<Price>,
457 time_in_force: Option<TimeInForce>,
458 expire_time: Option<nautilus_core::UnixNanos>,
459 post_only: Option<bool>,
460 reduce_only: Option<bool>,
461 quote_quantity: Option<bool>,
462 emulation_trigger: Option<TriggerType>,
463 trigger_instrument_id: Option<InstrumentId>,
464 exec_algorithm_id: Option<ExecAlgorithmId>,
465 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
466 tags: Option<Vec<Ustr>>,
467 ) -> OrderList {
468 let order_list_id = self.generate_order_list_id();
469 let ts_init = self.clock.get_time_ns();
470
471 let entry_client_order_id = self.generate_client_order_id();
472 let sl_client_order_id = self.generate_client_order_id();
473 let tp_client_order_id = self.generate_client_order_id();
474
475 let entry_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| entry_client_order_id);
477 let sl_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| sl_client_order_id);
478 let tp_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| tp_client_order_id);
479
480 let entry_contingency_type = Some(ContingencyType::Oto);
482 let entry_order_list_id = Some(order_list_id);
483 let entry_linked_order_ids = Some(vec![sl_client_order_id, tp_client_order_id]);
484 let entry_parent_order_id = None;
485
486 let entry_order = if let Some(trigger_price) = entry_trigger_price {
487 if let Some(price) = entry_price {
488 OrderAny::StopLimit(StopLimitOrder::new(
489 self.trader_id,
490 self.strategy_id,
491 instrument_id,
492 entry_client_order_id,
493 order_side,
494 quantity,
495 price,
496 trigger_price,
497 TriggerType::Default,
498 time_in_force.unwrap_or(TimeInForce::Gtc),
499 expire_time,
500 post_only.unwrap_or(false),
501 reduce_only.unwrap_or(false),
502 quote_quantity.unwrap_or(false),
503 None, emulation_trigger,
505 trigger_instrument_id,
506 entry_contingency_type,
507 entry_order_list_id,
508 entry_linked_order_ids,
509 entry_parent_order_id,
510 exec_algorithm_id,
511 exec_algorithm_params.clone(),
512 entry_exec_spawn_id,
513 tags.clone(),
514 UUID4::new(),
515 ts_init,
516 ))
517 } else {
518 OrderAny::StopMarket(StopMarketOrder::new(
519 self.trader_id,
520 self.strategy_id,
521 instrument_id,
522 entry_client_order_id,
523 order_side,
524 quantity,
525 trigger_price,
526 TriggerType::Default,
527 time_in_force.unwrap_or(TimeInForce::Gtc),
528 expire_time,
529 reduce_only.unwrap_or(false),
530 quote_quantity.unwrap_or(false),
531 None, emulation_trigger,
533 trigger_instrument_id,
534 entry_contingency_type,
535 entry_order_list_id,
536 entry_linked_order_ids,
537 entry_parent_order_id,
538 exec_algorithm_id,
539 exec_algorithm_params.clone(),
540 entry_exec_spawn_id,
541 tags.clone(),
542 UUID4::new(),
543 ts_init,
544 ))
545 }
546 } else if let Some(price) = entry_price {
547 OrderAny::Limit(LimitOrder::new(
548 self.trader_id,
549 self.strategy_id,
550 instrument_id,
551 entry_client_order_id,
552 order_side,
553 quantity,
554 price,
555 time_in_force.unwrap_or(TimeInForce::Gtc),
556 expire_time,
557 post_only.unwrap_or(false),
558 reduce_only.unwrap_or(false),
559 quote_quantity.unwrap_or(false),
560 None, emulation_trigger,
562 trigger_instrument_id,
563 entry_contingency_type,
564 entry_order_list_id,
565 entry_linked_order_ids,
566 entry_parent_order_id,
567 exec_algorithm_id,
568 exec_algorithm_params.clone(),
569 entry_exec_spawn_id,
570 tags.clone(),
571 UUID4::new(),
572 ts_init,
573 ))
574 } else {
575 OrderAny::Market(MarketOrder::new(
576 self.trader_id,
577 self.strategy_id,
578 instrument_id,
579 entry_client_order_id,
580 order_side,
581 quantity,
582 time_in_force.unwrap_or(TimeInForce::Gtc),
583 UUID4::new(),
584 ts_init,
585 reduce_only.unwrap_or(false),
586 quote_quantity.unwrap_or(false),
587 entry_contingency_type,
588 entry_order_list_id,
589 entry_linked_order_ids,
590 entry_parent_order_id,
591 exec_algorithm_id,
592 exec_algorithm_params.clone(),
593 entry_exec_spawn_id,
594 tags.clone(),
595 ))
596 };
597
598 let sl_tp_side = match order_side {
599 OrderSide::Buy => OrderSide::Sell,
600 OrderSide::Sell => OrderSide::Buy,
601 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
602 };
603
604 let sl_contingency_type = Some(ContingencyType::Oco);
606 let sl_order_list_id = Some(order_list_id);
607 let sl_linked_order_ids = Some(vec![tp_client_order_id]);
608 let sl_parent_order_id = Some(entry_client_order_id);
609
610 let sl_order = OrderAny::StopMarket(StopMarketOrder::new(
611 self.trader_id,
612 self.strategy_id,
613 instrument_id,
614 sl_client_order_id,
615 sl_tp_side,
616 quantity,
617 sl_trigger_price,
618 sl_trigger_type.unwrap_or(TriggerType::Default),
619 time_in_force.unwrap_or(TimeInForce::Gtc),
620 expire_time,
621 true, quote_quantity.unwrap_or(false),
623 None, emulation_trigger,
625 trigger_instrument_id,
626 sl_contingency_type,
627 sl_order_list_id,
628 sl_linked_order_ids,
629 sl_parent_order_id,
630 exec_algorithm_id,
631 exec_algorithm_params.clone(),
632 sl_exec_spawn_id,
633 tags.clone(),
634 UUID4::new(),
635 ts_init,
636 ));
637
638 let tp_contingency_type = Some(ContingencyType::Oco);
640 let tp_order_list_id = Some(order_list_id);
641 let tp_linked_order_ids = Some(vec![sl_client_order_id]);
642 let tp_parent_order_id = Some(entry_client_order_id);
643
644 let tp_order = OrderAny::Limit(LimitOrder::new(
645 self.trader_id,
646 self.strategy_id,
647 instrument_id,
648 tp_client_order_id,
649 sl_tp_side,
650 quantity,
651 tp_price,
652 time_in_force.unwrap_or(TimeInForce::Gtc),
653 expire_time,
654 post_only.unwrap_or(false),
655 true, quote_quantity.unwrap_or(false),
657 None, emulation_trigger,
659 trigger_instrument_id,
660 tp_contingency_type,
661 tp_order_list_id,
662 tp_linked_order_ids,
663 tp_parent_order_id,
664 exec_algorithm_id,
665 exec_algorithm_params,
666 tp_exec_spawn_id,
667 tags,
668 UUID4::new(),
669 ts_init,
670 ));
671
672 OrderList::new(
673 order_list_id,
674 instrument_id,
675 self.strategy_id,
676 vec![entry_order, sl_order, tp_order],
677 ts_init,
678 )
679 }
680}
681
682#[cfg(test)]
683pub mod tests {
684 use nautilus_core::time::get_atomic_clock_static;
685 use nautilus_model::{
686 enums::{ContingencyType, OrderSide, TimeInForce, TriggerType},
687 identifiers::{
688 ClientOrderId, InstrumentId, OrderListId,
689 stubs::{strategy_id_ema_cross, trader_id},
690 },
691 orders::Order,
692 types::Price,
693 };
694 use rstest::{fixture, rstest};
695
696 use crate::factories::OrderFactory;
697
698 #[fixture]
699 pub fn order_factory() -> OrderFactory {
700 let trader_id = trader_id();
701 let strategy_id = strategy_id_ema_cross();
702 OrderFactory::new(
703 trader_id,
704 strategy_id,
705 None,
706 None,
707 get_atomic_clock_static(),
708 false, true, )
711 }
712
713 #[rstest]
714 fn test_generate_client_order_id(mut order_factory: OrderFactory) {
715 let client_order_id = order_factory.generate_client_order_id();
716 assert_eq!(
717 client_order_id,
718 ClientOrderId::new("O-19700101-000000-001-001-1")
719 );
720 }
721
722 #[rstest]
723 fn test_generate_order_list_id(mut order_factory: OrderFactory) {
724 let order_list_id = order_factory.generate_order_list_id();
725 assert_eq!(
726 order_list_id,
727 OrderListId::new("OL-19700101-000000-001-001-1")
728 );
729 }
730
731 #[rstest]
732 fn test_set_client_order_id_count(mut order_factory: OrderFactory) {
733 order_factory.set_client_order_id_count(10);
734 let client_order_id = order_factory.generate_client_order_id();
735 assert_eq!(
736 client_order_id,
737 ClientOrderId::new("O-19700101-000000-001-001-11")
738 );
739 }
740
741 #[rstest]
742 fn test_set_order_list_id_count(mut order_factory: OrderFactory) {
743 order_factory.set_order_list_id_count(10);
744 let order_list_id = order_factory.generate_order_list_id();
745 assert_eq!(
746 order_list_id,
747 OrderListId::new("OL-19700101-000000-001-001-11")
748 );
749 }
750
751 #[rstest]
752 fn test_reset_factory(mut order_factory: OrderFactory) {
753 order_factory.generate_order_list_id();
754 order_factory.generate_client_order_id();
755 order_factory.reset_factory();
756 let client_order_id = order_factory.generate_client_order_id();
757 let order_list_id = order_factory.generate_order_list_id();
758 assert_eq!(
759 client_order_id,
760 ClientOrderId::new("O-19700101-000000-001-001-1")
761 );
762 assert_eq!(
763 order_list_id,
764 OrderListId::new("OL-19700101-000000-001-001-1")
765 );
766 }
767
768 #[fixture]
769 pub fn order_factory_with_uuids() -> OrderFactory {
770 let trader_id = trader_id();
771 let strategy_id = strategy_id_ema_cross();
772 OrderFactory::new(
773 trader_id,
774 strategy_id,
775 None,
776 None,
777 get_atomic_clock_static(),
778 true, true, )
781 }
782
783 #[fixture]
784 pub fn order_factory_with_hyphens_removed() -> OrderFactory {
785 let trader_id = trader_id();
786 let strategy_id = strategy_id_ema_cross();
787 OrderFactory::new(
788 trader_id,
789 strategy_id,
790 None,
791 None,
792 get_atomic_clock_static(),
793 false, false, )
796 }
797
798 #[fixture]
799 pub fn order_factory_with_uuids_and_hyphens_removed() -> OrderFactory {
800 let trader_id = trader_id();
801 let strategy_id = strategy_id_ema_cross();
802 OrderFactory::new(
803 trader_id,
804 strategy_id,
805 None,
806 None,
807 get_atomic_clock_static(),
808 true, false, )
811 }
812
813 #[rstest]
814 fn test_generate_client_order_id_with_uuids(mut order_factory_with_uuids: OrderFactory) {
815 let client_order_id = order_factory_with_uuids.generate_client_order_id();
816
817 assert_eq!(client_order_id.as_str().len(), 36);
819 assert!(client_order_id.as_str().contains('-'));
820 }
821
822 #[rstest]
823 fn test_generate_client_order_id_with_hyphens_removed(
824 mut order_factory_with_hyphens_removed: OrderFactory,
825 ) {
826 let client_order_id = order_factory_with_hyphens_removed.generate_client_order_id();
827
828 assert_eq!(
829 client_order_id,
830 ClientOrderId::new("O197001010000000010011")
831 );
832 assert!(!client_order_id.as_str().contains('-'));
833 }
834
835 #[rstest]
836 fn test_generate_client_order_id_with_uuids_and_hyphens_removed(
837 mut order_factory_with_uuids_and_hyphens_removed: OrderFactory,
838 ) {
839 let client_order_id =
840 order_factory_with_uuids_and_hyphens_removed.generate_client_order_id();
841
842 assert_eq!(client_order_id.as_str().len(), 32);
844 assert!(!client_order_id.as_str().contains('-'));
845 }
846
847 #[rstest]
848 fn test_market_order(mut order_factory: OrderFactory) {
849 let market_order = order_factory.market(
850 InstrumentId::from("BTCUSDT.BINANCE"),
851 OrderSide::Buy,
852 100.into(),
853 Some(TimeInForce::Gtc),
854 Some(false),
855 Some(false),
856 None,
857 None,
858 None,
859 None,
860 );
861 assert_eq!(market_order.instrument_id(), "BTCUSDT.BINANCE".into());
863 assert_eq!(market_order.order_side(), OrderSide::Buy);
864 assert_eq!(market_order.quantity(), 100.into());
865 assert_eq!(market_order.exec_algorithm_id(), None);
869 assert_eq!(
873 market_order.client_order_id(),
874 ClientOrderId::new("O-19700101-000000-001-001-1")
875 );
876 }
878
879 #[rstest]
880 fn test_limit_order(mut order_factory: OrderFactory) {
881 let limit_order = order_factory.limit(
882 InstrumentId::from("BTCUSDT.BINANCE"),
883 OrderSide::Buy,
884 100.into(),
885 Price::from("50000.00"),
886 Some(TimeInForce::Gtc),
887 None,
888 Some(false),
889 Some(false),
890 Some(false),
891 None,
892 None,
893 None,
894 None,
895 None,
896 None,
897 None,
898 );
899
900 assert_eq!(limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
901 assert_eq!(limit_order.order_side(), OrderSide::Buy);
902 assert_eq!(limit_order.quantity(), 100.into());
903 assert_eq!(limit_order.price(), Some(Price::from("50000.00")));
904 assert_eq!(
905 limit_order.client_order_id(),
906 ClientOrderId::new("O-19700101-000000-001-001-1")
907 );
908 }
909
910 #[rstest]
911 fn test_limit_order_with_post_only(mut order_factory: OrderFactory) {
912 let limit_order = order_factory.limit(
913 InstrumentId::from("BTCUSDT.BINANCE"),
914 OrderSide::Buy,
915 100.into(),
916 Price::from("50000.00"),
917 Some(TimeInForce::Gtc),
918 None,
919 Some(true), Some(false),
921 Some(false),
922 None,
923 None,
924 None,
925 None,
926 None,
927 None,
928 None,
929 );
930
931 assert!(limit_order.is_post_only());
932 }
933
934 #[rstest]
935 fn test_limit_order_with_display_qty(mut order_factory: OrderFactory) {
936 let limit_order = order_factory.limit(
937 InstrumentId::from("BTCUSDT.BINANCE"),
938 OrderSide::Buy,
939 100.into(),
940 Price::from("50000.00"),
941 Some(TimeInForce::Gtc),
942 None,
943 Some(false), Some(false), Some(false), Some(50.into()), None,
948 None,
949 None,
950 None,
951 None,
952 None,
953 );
954
955 assert_eq!(limit_order.display_qty(), Some(50.into()));
956 }
957
958 #[rstest]
959 fn test_stop_market_order(mut order_factory: OrderFactory) {
960 let stop_order = order_factory.stop_market(
961 InstrumentId::from("BTCUSDT.BINANCE"),
962 OrderSide::Sell,
963 100.into(),
964 Price::from("45000.00"),
965 Some(TriggerType::LastPrice),
966 Some(TimeInForce::Gtc),
967 None,
968 Some(false),
969 Some(false),
970 None,
971 None,
972 None,
973 None,
974 None,
975 None,
976 None,
977 );
978
979 assert_eq!(stop_order.instrument_id(), "BTCUSDT.BINANCE".into());
980 assert_eq!(stop_order.order_side(), OrderSide::Sell);
981 assert_eq!(stop_order.quantity(), 100.into());
982 assert_eq!(stop_order.trigger_price(), Some(Price::from("45000.00")));
983 assert_eq!(stop_order.trigger_type(), Some(TriggerType::LastPrice));
984 }
985
986 #[rstest]
987 fn test_stop_limit_order(mut order_factory: OrderFactory) {
988 let stop_limit_order = order_factory.stop_limit(
989 InstrumentId::from("BTCUSDT.BINANCE"),
990 OrderSide::Sell,
991 100.into(),
992 Price::from("45100.00"), Price::from("45000.00"), Some(TriggerType::LastPrice),
995 Some(TimeInForce::Gtc),
996 None,
997 Some(false),
998 Some(false),
999 Some(false),
1000 None,
1001 None,
1002 None,
1003 None,
1004 None,
1005 None,
1006 None,
1007 );
1008
1009 assert_eq!(stop_limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1010 assert_eq!(stop_limit_order.order_side(), OrderSide::Sell);
1011 assert_eq!(stop_limit_order.quantity(), 100.into());
1012 assert_eq!(stop_limit_order.price(), Some(Price::from("45100.00")));
1013 assert_eq!(
1014 stop_limit_order.trigger_price(),
1015 Some(Price::from("45000.00"))
1016 );
1017 assert_eq!(
1018 stop_limit_order.trigger_type(),
1019 Some(TriggerType::LastPrice)
1020 );
1021 }
1022
1023 #[rstest]
1024 fn test_market_if_touched_order(mut order_factory: OrderFactory) {
1025 let mit_order = order_factory.market_if_touched(
1026 InstrumentId::from("BTCUSDT.BINANCE"),
1027 OrderSide::Buy,
1028 100.into(),
1029 Price::from("48000.00"),
1030 Some(TriggerType::LastPrice),
1031 Some(TimeInForce::Gtc),
1032 None,
1033 Some(false),
1034 Some(false),
1035 None,
1036 None,
1037 None,
1038 None,
1039 None,
1040 None,
1041 );
1042
1043 assert_eq!(mit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1044 assert_eq!(mit_order.order_side(), OrderSide::Buy);
1045 assert_eq!(mit_order.quantity(), 100.into());
1046 assert_eq!(mit_order.trigger_price(), Some(Price::from("48000.00")));
1047 assert_eq!(mit_order.trigger_type(), Some(TriggerType::LastPrice));
1048 }
1049
1050 #[rstest]
1051 fn test_limit_if_touched_order(mut order_factory: OrderFactory) {
1052 let lit_order = order_factory.limit_if_touched(
1053 InstrumentId::from("BTCUSDT.BINANCE"),
1054 OrderSide::Buy,
1055 100.into(),
1056 Price::from("48100.00"), Price::from("48000.00"), Some(TriggerType::LastPrice),
1059 Some(TimeInForce::Gtc),
1060 None,
1061 Some(false),
1062 Some(false),
1063 Some(false),
1064 None,
1065 None,
1066 None,
1067 None,
1068 None,
1069 None,
1070 None,
1071 );
1072
1073 assert_eq!(lit_order.instrument_id(), "BTCUSDT.BINANCE".into());
1074 assert_eq!(lit_order.order_side(), OrderSide::Buy);
1075 assert_eq!(lit_order.quantity(), 100.into());
1076 assert_eq!(lit_order.price(), Some(Price::from("48100.00")));
1077 assert_eq!(lit_order.trigger_price(), Some(Price::from("48000.00")));
1078 assert_eq!(lit_order.trigger_type(), Some(TriggerType::LastPrice));
1079 }
1080
1081 #[rstest]
1082 fn test_bracket_order_with_market_entry(mut order_factory: OrderFactory) {
1083 let bracket = order_factory.bracket(
1084 InstrumentId::from("BTCUSDT.BINANCE"),
1085 OrderSide::Buy,
1086 100.into(),
1087 None, Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1093 None,
1094 Some(false),
1095 Some(false),
1096 Some(false),
1097 None,
1098 None,
1099 None,
1100 None,
1101 None,
1102 );
1103
1104 assert_eq!(bracket.orders.len(), 3);
1105 assert_eq!(bracket.instrument_id, "BTCUSDT.BINANCE".into());
1106
1107 assert_eq!(bracket.orders[0].order_side(), OrderSide::Buy);
1109
1110 assert_eq!(bracket.orders[1].order_side(), OrderSide::Sell);
1112 assert_eq!(
1113 bracket.orders[1].trigger_price(),
1114 Some(Price::from("45000.00"))
1115 );
1116
1117 assert_eq!(bracket.orders[2].order_side(), OrderSide::Sell);
1119 assert_eq!(bracket.orders[2].price(), Some(Price::from("55000.00")));
1120 }
1121
1122 #[rstest]
1123 fn test_bracket_order_with_limit_entry(mut order_factory: OrderFactory) {
1124 let bracket = order_factory.bracket(
1125 InstrumentId::from("BTCUSDT.BINANCE"),
1126 OrderSide::Buy,
1127 100.into(),
1128 Some(Price::from("49000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1134 None,
1135 Some(false),
1136 Some(false),
1137 Some(false),
1138 None,
1139 None,
1140 None,
1141 None,
1142 None,
1143 );
1144
1145 assert_eq!(bracket.orders.len(), 3);
1146
1147 assert_eq!(bracket.orders[0].price(), Some(Price::from("49000.00")));
1149 }
1150
1151 #[rstest]
1152 fn test_bracket_order_with_stop_entry(mut order_factory: OrderFactory) {
1153 let bracket = order_factory.bracket(
1154 InstrumentId::from("BTCUSDT.BINANCE"),
1155 OrderSide::Buy,
1156 100.into(),
1157 None, Price::from("45000.00"), None, Price::from("55000.00"), Some(Price::from("51000.00")), Some(TimeInForce::Gtc),
1163 None,
1164 Some(false),
1165 Some(false),
1166 Some(false),
1167 None,
1168 None,
1169 None,
1170 None,
1171 None,
1172 );
1173
1174 assert_eq!(bracket.orders.len(), 3);
1175
1176 assert_eq!(
1178 bracket.orders[0].trigger_price(),
1179 Some(Price::from("51000.00"))
1180 );
1181 }
1182
1183 #[rstest]
1184 fn test_bracket_order_sell_side(mut order_factory: OrderFactory) {
1185 let bracket = order_factory.bracket(
1186 InstrumentId::from("BTCUSDT.BINANCE"),
1187 OrderSide::Sell,
1188 100.into(),
1189 Some(Price::from("51000.00")), Price::from("55000.00"), None, Price::from("45000.00"), None,
1194 Some(TimeInForce::Gtc),
1195 None,
1196 Some(false),
1197 Some(false),
1198 Some(false),
1199 None,
1200 None,
1201 None,
1202 None,
1203 None,
1204 );
1205
1206 assert_eq!(bracket.orders.len(), 3);
1207
1208 assert_eq!(bracket.orders[0].order_side(), OrderSide::Sell);
1210
1211 assert_eq!(bracket.orders[1].order_side(), OrderSide::Buy);
1213
1214 assert_eq!(bracket.orders[2].order_side(), OrderSide::Buy);
1216 }
1217
1218 #[rstest]
1219 fn test_bracket_order_sets_contingencies(mut order_factory: OrderFactory) {
1220 let bracket = order_factory.bracket(
1221 InstrumentId::from("BTCUSDT.BINANCE"),
1222 OrderSide::Buy,
1223 100.into(),
1224 Some(Price::from("50000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
1230 None,
1231 Some(false),
1232 Some(false),
1233 Some(false),
1234 None,
1235 None,
1236 None,
1237 None,
1238 None,
1239 );
1240
1241 let entry = &bracket.orders[0];
1242 let stop = &bracket.orders[1];
1243 let take = &bracket.orders[2];
1244
1245 assert_eq!(entry.order_list_id(), Some(bracket.id));
1246 assert_eq!(entry.contingency_type(), Some(ContingencyType::Oto));
1247 assert_eq!(
1248 entry.linked_order_ids().unwrap(),
1249 &[stop.client_order_id(), take.client_order_id()]
1250 );
1251
1252 assert_eq!(stop.order_list_id(), Some(bracket.id));
1253 assert_eq!(stop.contingency_type(), Some(ContingencyType::Oco));
1254 assert_eq!(stop.parent_order_id(), Some(entry.client_order_id()));
1255 assert_eq!(stop.linked_order_ids().unwrap(), &[take.client_order_id()]);
1256
1257 assert_eq!(take.order_list_id(), Some(bracket.id));
1258 assert_eq!(take.contingency_type(), Some(ContingencyType::Oco));
1259 assert_eq!(take.parent_order_id(), Some(entry.client_order_id()));
1260 assert_eq!(take.linked_order_ids().unwrap(), &[stop.client_order_id()]);
1261 }
1262}