Skip to main content

nautilus_common/factories/
order.rs

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