Skip to main content

nautilus_common/generators/
client_order_id.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
16use std::{cell::RefCell, rc::Rc};
17
18use nautilus_core::uuid::UUID4;
19use nautilus_model::identifiers::{ClientOrderId, StrategyId, TraderId};
20
21use super::get_datetime_tag;
22use crate::clock::Clock;
23
24#[derive(Debug)]
25pub struct ClientOrderIdGenerator {
26    clock: Rc<RefCell<dyn Clock>>,
27    trader_id: TraderId,
28    strategy_id: StrategyId,
29    count: usize,
30    use_uuids: bool,
31    use_hyphens: bool,
32}
33
34impl ClientOrderIdGenerator {
35    /// Creates a new [`ClientOrderIdGenerator`] instance.
36    #[must_use]
37    pub fn new(
38        trader_id: TraderId,
39        strategy_id: StrategyId,
40        initial_count: usize,
41        clock: Rc<RefCell<dyn Clock>>,
42        use_uuids: bool,
43        use_hyphens: bool,
44    ) -> Self {
45        Self {
46            trader_id,
47            strategy_id,
48            count: initial_count,
49            clock,
50            use_uuids,
51            use_hyphens,
52        }
53    }
54
55    pub const fn set_count(&mut self, count: usize) {
56        self.count = count;
57    }
58
59    pub const fn reset(&mut self) {
60        self.count = 0;
61    }
62
63    #[must_use]
64    pub const fn count(&self) -> usize {
65        self.count
66    }
67
68    pub fn generate(&mut self) -> ClientOrderId {
69        let value = if self.use_uuids {
70            let mut uuid_value = UUID4::new().to_string();
71            if !self.use_hyphens {
72                uuid_value = uuid_value.replace('-', "");
73            }
74            uuid_value
75        } else {
76            let datetime_tag = get_datetime_tag(self.clock.borrow().timestamp_ms());
77            let trader_tag = self.trader_id.get_tag();
78            let strategy_tag = self.strategy_id.get_tag();
79            self.count += 1;
80
81            if self.use_hyphens {
82                format!(
83                    "O-{}-{}-{}-{}",
84                    datetime_tag, trader_tag, strategy_tag, self.count
85                )
86            } else {
87                let datetime_no_hyphens = datetime_tag.replace('-', "");
88                format!(
89                    "O{}{}{}{}",
90                    datetime_no_hyphens, trader_tag, strategy_tag, self.count
91                )
92            }
93        };
94
95        ClientOrderId::from(value)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use std::{cell::RefCell, rc::Rc};
102
103    use nautilus_model::{
104        identifiers::{ClientOrderId, StrategyId, TraderId},
105        stubs::TestDefault,
106    };
107    use rstest::rstest;
108
109    use crate::{clock::TestClock, generators::client_order_id::ClientOrderIdGenerator};
110
111    fn get_client_order_id_generator(
112        initial_count: Option<usize>,
113        use_uuids: bool,
114        use_hyphens: bool,
115    ) -> ClientOrderIdGenerator {
116        let clock = Rc::new(RefCell::new(TestClock::new()));
117        ClientOrderIdGenerator::new(
118            TraderId::test_default(),
119            StrategyId::test_default(),
120            initial_count.unwrap_or(0),
121            clock,
122            use_uuids,
123            use_hyphens,
124        )
125    }
126
127    #[rstest]
128    fn test_init() {
129        let generator = get_client_order_id_generator(None, false, true);
130        assert_eq!(generator.count(), 0);
131    }
132
133    #[rstest]
134    fn test_init_with_initial_count() {
135        let generator = get_client_order_id_generator(Some(7), false, true);
136        assert_eq!(generator.count(), 7);
137    }
138
139    #[rstest]
140    fn test_generate_client_order_id_from_start() {
141        let mut generator = get_client_order_id_generator(None, false, true);
142        let result1 = generator.generate();
143        let result2 = generator.generate();
144        let result3 = generator.generate();
145
146        assert_eq!(result1, ClientOrderId::new("O-19700101-000000-001-001-1"));
147        assert_eq!(result2, ClientOrderId::new("O-19700101-000000-001-001-2"));
148        assert_eq!(result3, ClientOrderId::new("O-19700101-000000-001-001-3"));
149    }
150
151    #[rstest]
152    fn test_generate_client_order_id_from_initial() {
153        let mut generator = get_client_order_id_generator(Some(5), false, true);
154        let result1 = generator.generate();
155        let result2 = generator.generate();
156        let result3 = generator.generate();
157
158        assert_eq!(result1, ClientOrderId::new("O-19700101-000000-001-001-6"));
159        assert_eq!(result2, ClientOrderId::new("O-19700101-000000-001-001-7"));
160        assert_eq!(result3, ClientOrderId::new("O-19700101-000000-001-001-8"));
161    }
162
163    #[rstest]
164    fn test_generate_client_order_id_with_hyphens_removed() {
165        let mut generator = get_client_order_id_generator(None, false, false);
166        let result = generator.generate();
167
168        assert_eq!(result, ClientOrderId::new("O197001010000000010011"));
169    }
170
171    #[rstest]
172    fn test_generate_uuid_client_order_id() {
173        let mut generator = get_client_order_id_generator(None, true, true);
174        let result = generator.generate();
175
176        // UUID should be 36 characters with hyphens
177        assert_eq!(result.as_str().len(), 36);
178        assert!(result.as_str().contains('-'));
179    }
180
181    #[rstest]
182    fn test_generate_uuid_client_order_id_with_hyphens_removed() {
183        let mut generator = get_client_order_id_generator(None, true, false);
184        let result = generator.generate();
185
186        // UUID without hyphens should be 32 characters
187        assert_eq!(result.as_str().len(), 32);
188        assert!(!result.as_str().contains('-'));
189    }
190
191    #[rstest]
192    fn test_reset() {
193        let mut generator = get_client_order_id_generator(None, false, true);
194        generator.generate();
195        generator.generate();
196        generator.reset();
197        let result = generator.generate();
198
199        assert_eq!(result, ClientOrderId::new("O-19700101-000000-001-001-1"));
200    }
201}