nautilus_common/
factories.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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//! Factories for constructing domain objects such as orders.
17
18use indexmap::IndexMap;
19use nautilus_core::{AtomicTime, UUID4};
20use nautilus_model::{
21    enums::{ContingencyType, OrderSide, TimeInForce},
22    identifiers::{
23        ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
24    },
25    orders::{MarketOrder, OrderAny},
26    types::Quantity,
27};
28use ustr::Ustr;
29
30use crate::generators::{
31    client_order_id::ClientOrderIdGenerator, order_list_id::OrderListIdGenerator,
32};
33
34#[repr(C)]
35#[derive(Debug)]
36pub struct OrderFactory {
37    clock: &'static AtomicTime,
38    trader_id: TraderId,
39    strategy_id: StrategyId,
40    order_id_generator: ClientOrderIdGenerator,
41    order_list_id_generator: OrderListIdGenerator,
42}
43
44impl OrderFactory {
45    /// Creates a new [`OrderFactory`] instance.
46    pub fn new(
47        trader_id: TraderId,
48        strategy_id: StrategyId,
49        init_order_id_count: Option<usize>,
50        init_order_list_id_count: Option<usize>,
51        clock: &'static AtomicTime,
52        use_uuids_for_client_order_ids: bool,
53        use_hyphens_in_client_order_ids: bool,
54    ) -> Self {
55        let order_id_generator = ClientOrderIdGenerator::new(
56            trader_id,
57            strategy_id,
58            init_order_id_count.unwrap_or(0),
59            clock,
60            use_uuids_for_client_order_ids,
61            use_hyphens_in_client_order_ids,
62        );
63        let order_list_id_generator = OrderListIdGenerator::new(
64            trader_id,
65            strategy_id,
66            init_order_list_id_count.unwrap_or(0),
67            clock,
68        );
69        Self {
70            clock,
71            trader_id,
72            strategy_id,
73            order_id_generator,
74            order_list_id_generator,
75        }
76    }
77
78    /// Sets the client order ID generator count.
79    pub const fn set_client_order_id_count(&mut self, count: usize) {
80        self.order_id_generator.set_count(count);
81    }
82
83    /// Sets the order list ID generator count.
84    pub const fn set_order_list_id_count(&mut self, count: usize) {
85        self.order_list_id_generator.set_count(count);
86    }
87
88    /// Generates a new client order ID.
89    pub fn generate_client_order_id(&mut self) -> ClientOrderId {
90        self.order_id_generator.generate()
91    }
92
93    /// Generates a new order list ID.
94    pub fn generate_order_list_id(&mut self) -> OrderListId {
95        self.order_list_id_generator.generate()
96    }
97
98    /// Resets the factory by resetting all ID generators.
99    pub const fn reset_factory(&mut self) {
100        self.order_id_generator.reset();
101        self.order_list_id_generator.reset();
102    }
103
104    /// Creates a new market order.
105    #[allow(clippy::too_many_arguments)]
106    pub fn market(
107        &mut self,
108        instrument_id: InstrumentId,
109        order_side: OrderSide,
110        quantity: Quantity,
111        time_in_force: Option<TimeInForce>,
112        reduce_only: Option<bool>,
113        quote_quantity: Option<bool>,
114        exec_algorithm_id: Option<ExecAlgorithmId>,
115        exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
116        tags: Option<Vec<Ustr>>,
117        client_order_id: Option<ClientOrderId>,
118    ) -> OrderAny {
119        let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
120        let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
121            None
122        } else {
123            Some(client_order_id)
124        };
125        let order = MarketOrder::new(
126            self.trader_id,
127            self.strategy_id,
128            instrument_id,
129            client_order_id,
130            order_side,
131            quantity,
132            time_in_force.unwrap_or(TimeInForce::Gtc),
133            UUID4::new(),
134            self.clock.get_time_ns(),
135            reduce_only.unwrap_or(false),
136            quote_quantity.unwrap_or(false),
137            Some(ContingencyType::NoContingency),
138            None,
139            None,
140            None,
141            exec_algorithm_id,
142            exec_algorithm_params,
143            exec_spawn_id,
144            tags,
145        );
146        OrderAny::Market(order)
147    }
148}
149
150////////////////////////////////////////////////////////////////////////////////
151// Tests
152////////////////////////////////////////////////////////////////////////////////
153#[cfg(test)]
154pub mod tests {
155    use nautilus_core::time::get_atomic_clock_static;
156    use nautilus_model::{
157        enums::{OrderSide, TimeInForce},
158        identifiers::{
159            ClientOrderId, InstrumentId, OrderListId,
160            stubs::{strategy_id_ema_cross, trader_id},
161        },
162        orders::Order,
163    };
164    use rstest::{fixture, rstest};
165
166    use crate::factories::OrderFactory;
167
168    #[fixture]
169    pub fn order_factory() -> OrderFactory {
170        let trader_id = trader_id();
171        let strategy_id = strategy_id_ema_cross();
172        OrderFactory::new(
173            trader_id,
174            strategy_id,
175            None,
176            None,
177            get_atomic_clock_static(),
178            false, // use_uuids_for_client_order_ids
179            true,  // use_hyphens_in_client_order_ids
180        )
181    }
182
183    #[rstest]
184    fn test_generate_client_order_id(mut order_factory: OrderFactory) {
185        let client_order_id = order_factory.generate_client_order_id();
186        assert_eq!(
187            client_order_id,
188            ClientOrderId::new("O-19700101-000000-001-001-1")
189        );
190    }
191
192    #[rstest]
193    fn test_generate_order_list_id(mut order_factory: OrderFactory) {
194        let order_list_id = order_factory.generate_order_list_id();
195        assert_eq!(
196            order_list_id,
197            OrderListId::new("OL-19700101-000000-001-001-1")
198        );
199    }
200
201    #[rstest]
202    fn test_set_client_order_id_count(mut order_factory: OrderFactory) {
203        order_factory.set_client_order_id_count(10);
204        let client_order_id = order_factory.generate_client_order_id();
205        assert_eq!(
206            client_order_id,
207            ClientOrderId::new("O-19700101-000000-001-001-11")
208        );
209    }
210
211    #[rstest]
212    fn test_set_order_list_id_count(mut order_factory: OrderFactory) {
213        order_factory.set_order_list_id_count(10);
214        let order_list_id = order_factory.generate_order_list_id();
215        assert_eq!(
216            order_list_id,
217            OrderListId::new("OL-19700101-000000-001-001-11")
218        );
219    }
220
221    #[rstest]
222    fn test_reset_factory(mut order_factory: OrderFactory) {
223        order_factory.generate_order_list_id();
224        order_factory.generate_client_order_id();
225        order_factory.reset_factory();
226        let client_order_id = order_factory.generate_client_order_id();
227        let order_list_id = order_factory.generate_order_list_id();
228        assert_eq!(
229            client_order_id,
230            ClientOrderId::new("O-19700101-000000-001-001-1")
231        );
232        assert_eq!(
233            order_list_id,
234            OrderListId::new("OL-19700101-000000-001-001-1")
235        );
236    }
237
238    #[fixture]
239    pub fn order_factory_with_uuids() -> OrderFactory {
240        let trader_id = trader_id();
241        let strategy_id = strategy_id_ema_cross();
242        OrderFactory::new(
243            trader_id,
244            strategy_id,
245            None,
246            None,
247            get_atomic_clock_static(),
248            true, // use_uuids_for_client_order_ids
249            true, // use_hyphens_in_client_order_ids
250        )
251    }
252
253    #[fixture]
254    pub fn order_factory_with_hyphens_removed() -> OrderFactory {
255        let trader_id = trader_id();
256        let strategy_id = strategy_id_ema_cross();
257        OrderFactory::new(
258            trader_id,
259            strategy_id,
260            None,
261            None,
262            get_atomic_clock_static(),
263            false, // use_uuids_for_client_order_ids
264            false, // use_hyphens_in_client_order_ids
265        )
266    }
267
268    #[fixture]
269    pub fn order_factory_with_uuids_and_hyphens_removed() -> OrderFactory {
270        let trader_id = trader_id();
271        let strategy_id = strategy_id_ema_cross();
272        OrderFactory::new(
273            trader_id,
274            strategy_id,
275            None,
276            None,
277            get_atomic_clock_static(),
278            true,  // use_uuids_for_client_order_ids
279            false, // use_hyphens_in_client_order_ids
280        )
281    }
282
283    #[rstest]
284    fn test_generate_client_order_id_with_uuids(mut order_factory_with_uuids: OrderFactory) {
285        let client_order_id = order_factory_with_uuids.generate_client_order_id();
286
287        // UUID should be 36 characters with hyphens
288        assert_eq!(client_order_id.as_str().len(), 36);
289        assert!(client_order_id.as_str().contains('-'));
290    }
291
292    #[rstest]
293    fn test_generate_client_order_id_with_hyphens_removed(
294        mut order_factory_with_hyphens_removed: OrderFactory,
295    ) {
296        let client_order_id = order_factory_with_hyphens_removed.generate_client_order_id();
297
298        assert_eq!(
299            client_order_id,
300            ClientOrderId::new("O197001010000000010011")
301        );
302        assert!(!client_order_id.as_str().contains('-'));
303    }
304
305    #[rstest]
306    fn test_generate_client_order_id_with_uuids_and_hyphens_removed(
307        mut order_factory_with_uuids_and_hyphens_removed: OrderFactory,
308    ) {
309        let client_order_id =
310            order_factory_with_uuids_and_hyphens_removed.generate_client_order_id();
311
312        // UUID without hyphens should be 32 characters
313        assert_eq!(client_order_id.as_str().len(), 32);
314        assert!(!client_order_id.as_str().contains('-'));
315    }
316
317    #[rstest]
318    fn test_market_order(mut order_factory: OrderFactory) {
319        let market_order = order_factory.market(
320            InstrumentId::from("BTCUSDT.BINANCE"),
321            OrderSide::Buy,
322            100.into(),
323            Some(TimeInForce::Gtc),
324            Some(false),
325            Some(false),
326            None,
327            None,
328            None,
329            None,
330        );
331        // TODO: Add additional polymorphic getters
332        assert_eq!(market_order.instrument_id(), "BTCUSDT.BINANCE".into());
333        assert_eq!(market_order.order_side(), OrderSide::Buy);
334        assert_eq!(market_order.quantity(), 100.into());
335        // assert_eq!(market_order.time_in_force(), TimeInForce::Gtc);
336        // assert!(!market_order.is_reduce_only);
337        // assert!(!market_order.is_quote_quantity);
338        assert_eq!(market_order.exec_algorithm_id(), None);
339        // assert_eq!(market_order.exec_algorithm_params(), None);
340        // assert_eq!(market_order.exec_spawn_id, None);
341        // assert_eq!(market_order.tags, None);
342        assert_eq!(
343            market_order.client_order_id(),
344            ClientOrderId::new("O-19700101-000000-001-001-1")
345        );
346        // assert_eq!(market_order.order_list_id(), None);
347    }
348}