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