nautilus_common/generators/
order_list_id.rs1use std::{
17 cell::RefCell,
18 fmt::{Debug, Write},
19 rc::Rc,
20};
21
22use chrono::{DateTime, Datelike, Timelike};
23use itoa::Buffer;
24use nautilus_model::identifiers::{OrderListId, StrategyId, TraderId};
25
26use crate::clock::Clock;
27
28const DATETIME_TAG_LEN: usize = 15; const MAX_USIZE_DECIMAL_LEN: usize = 20; pub struct OrderListIdGenerator {
32 clock: Rc<RefCell<dyn Clock>>,
33 trader_id: TraderId,
34 strategy_id: StrategyId,
35 count: usize,
36 trader_tag: String,
37 strategy_tag: String,
38 buf: String,
39 fixed_prefix_len: usize,
40 epoch_second: u64,
41 count_buf: Buffer,
42}
43
44impl Debug for OrderListIdGenerator {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct(stringify!(OrderListIdGenerator))
47 .field("clock", &self.clock)
48 .field("trader_id", &self.trader_id)
49 .field("strategy_id", &self.strategy_id)
50 .field("count", &self.count)
51 .field("trader_tag", &self.trader_tag)
52 .field("strategy_tag", &self.strategy_tag)
53 .field("buf", &self.buf)
54 .field("fixed_prefix_len", &self.fixed_prefix_len)
55 .field("epoch_second", &self.epoch_second)
56 .finish_non_exhaustive()
57 }
58}
59
60impl OrderListIdGenerator {
61 #[must_use]
63 pub fn new(
64 trader_id: TraderId,
65 strategy_id: StrategyId,
66 initial_count: usize,
67 clock: Rc<RefCell<dyn Clock>>,
68 ) -> Self {
69 let trader_tag = trader_id.get_tag().to_string();
70 let strategy_tag = strategy_id.get_tag().to_string();
71 let buf = String::with_capacity(
72 fixed_prefix_capacity(&trader_tag, &strategy_tag) + MAX_USIZE_DECIMAL_LEN,
73 );
74
75 Self {
76 clock,
77 trader_id,
78 strategy_id,
79 count: initial_count,
80 trader_tag,
81 strategy_tag,
82 buf,
83 fixed_prefix_len: 0,
84 epoch_second: u64::MAX,
85 count_buf: Buffer::new(),
86 }
87 }
88
89 pub const fn set_count(&mut self, count: usize) {
90 self.count = count;
91 }
92
93 pub const fn reset(&mut self) {
94 self.count = 0;
95 }
96
97 #[must_use]
98 pub const fn count(&self) -> usize {
99 self.count
100 }
101
102 pub fn generate(&mut self) -> OrderListId {
103 let timestamp_ms = self.clock.borrow().timestamp_ms();
104 self.refresh_fixed_prefix(timestamp_ms);
105 self.count += 1;
106
107 self.buf.truncate(self.fixed_prefix_len);
108 self.buf.push_str(self.count_buf.format(self.count));
109
110 OrderListId::from(self.buf.as_str())
111 }
112
113 #[inline]
114 fn refresh_fixed_prefix(&mut self, timestamp_ms: u64) {
115 let epoch_second = timestamp_ms / 1_000;
116 if epoch_second == self.epoch_second {
117 return;
118 }
119
120 write_fixed_prefix(
121 &mut self.buf,
122 &self.trader_tag,
123 &self.strategy_tag,
124 epoch_second,
125 );
126 self.fixed_prefix_len = self.buf.len();
127 self.epoch_second = epoch_second;
128 }
129}
130
131#[inline]
132fn fixed_prefix_capacity(trader_tag: &str, strategy_tag: &str) -> usize {
133 "OL-".len()
134 + DATETIME_TAG_LEN
135 + "-".len()
136 + trader_tag.len()
137 + "-".len()
138 + strategy_tag.len()
139 + "-".len()
140}
141
142fn write_fixed_prefix(buf: &mut String, trader_tag: &str, strategy_tag: &str, epoch_second: u64) {
143 let now_utc = DateTime::from_timestamp_millis((epoch_second * 1_000) as i64)
144 .expect("Milliseconds timestamp should be within valid range");
145
146 buf.clear();
147
148 write!(
149 buf,
150 "OL-{:04}{:02}{:02}-{:02}{:02}{:02}-{trader_tag}-{strategy_tag}-",
151 now_utc.year(),
152 now_utc.month(),
153 now_utc.day(),
154 now_utc.hour(),
155 now_utc.minute(),
156 now_utc.second(),
157 )
158 .expect("writing to String should not fail");
159}
160
161#[cfg(test)]
162mod tests {
163 use std::{cell::RefCell, rc::Rc};
164
165 use nautilus_core::UnixNanos;
166 use nautilus_model::{
167 identifiers::{OrderListId, StrategyId, TraderId},
168 stubs::TestDefault,
169 };
170 use rstest::rstest;
171
172 use crate::{clock::TestClock, generators::order_list_id::OrderListIdGenerator};
173
174 fn get_order_list_id_generator(initial_count: Option<usize>) -> OrderListIdGenerator {
175 let clock = Rc::new(RefCell::new(TestClock::new()));
176 OrderListIdGenerator::new(
177 TraderId::test_default(),
178 StrategyId::test_default(),
179 initial_count.unwrap_or(0),
180 clock,
181 )
182 }
183
184 #[rstest]
185 fn test_init() {
186 let generator = get_order_list_id_generator(None);
187 assert_eq!(generator.count(), 0);
188 }
189
190 #[rstest]
191 fn test_init_with_initial_count() {
192 let generator = get_order_list_id_generator(Some(7));
193 assert_eq!(generator.count(), 7);
194 }
195
196 #[rstest]
197 fn test_generate_order_list_id_from_start() {
198 let mut generator = get_order_list_id_generator(None);
199 let result1 = generator.generate();
200 let result2 = generator.generate();
201 let result3 = generator.generate();
202
203 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
204 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-2"));
205 assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-3"));
206 }
207
208 #[rstest]
209 fn test_generate_order_list_id_from_initial() {
210 let mut generator = get_order_list_id_generator(Some(5));
211 let result1 = generator.generate();
212 let result2 = generator.generate();
213 let result3 = generator.generate();
214
215 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-6"));
216 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-7"));
217 assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-8"));
218 }
219
220 #[rstest]
221 fn test_generate_persists_fixed_prefix_in_buffer_within_same_second() {
222 let mut generator = get_order_list_id_generator(None);
223
224 let result1 = generator.generate();
225 let fixed_prefix = "OL-19700101-000000-001-001-";
226 let capacity_after_first = generator.buf.capacity();
227
228 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
229 assert_eq!(generator.fixed_prefix_len, fixed_prefix.len());
230 assert_eq!(&generator.buf[..generator.fixed_prefix_len], fixed_prefix);
231
232 let result2 = generator.generate();
233
234 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-2"));
235 assert_eq!(generator.fixed_prefix_len, fixed_prefix.len());
236 assert_eq!(&generator.buf[..generator.fixed_prefix_len], fixed_prefix);
237 assert_eq!(generator.buf.capacity(), capacity_after_first);
238 }
239
240 #[rstest]
241 fn test_generate_refreshes_persistent_fixed_prefix_when_second_changes() {
242 let clock = Rc::new(RefCell::new(TestClock::new()));
243 let mut generator = OrderListIdGenerator::new(
244 TraderId::test_default(),
245 StrategyId::test_default(),
246 0,
247 clock.clone(),
248 );
249
250 let result1 = generator.generate();
251 clock.borrow_mut().set_time(UnixNanos::from(1_000_000_000));
252 let result2 = generator.generate();
253
254 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
255 assert_eq!(result2, OrderListId::new("OL-19700101-000001-001-001-2"));
256 assert_eq!(generator.epoch_second, 1);
257 assert_eq!(
258 &generator.buf[..generator.fixed_prefix_len],
259 "OL-19700101-000001-001-001-"
260 );
261 }
262
263 #[rstest]
264 fn test_reset() {
265 let mut generator = get_order_list_id_generator(None);
266 generator.generate();
267 generator.generate();
268 generator.reset();
269 let result = generator.generate();
270
271 assert_eq!(result, OrderListId::new("OL-19700101-000000-001-001-1"));
272 }
273}