nautilus_common/generators/
order_list_id.rs1use core::fmt::NumBuffer;
17use std::{
18 cell::RefCell,
19 fmt::{Debug, Write},
20 rc::Rc,
21};
22
23use jiff::{Timestamp, tz::Offset};
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: NumBuffer<usize>,
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: NumBuffer::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
109 .push_str(self.count.format_into(&mut self.count_buf));
110
111 OrderListId::from(self.buf.as_str())
112 }
113
114 #[inline]
115 fn refresh_fixed_prefix(&mut self, timestamp_ms: u64) {
116 let epoch_second = timestamp_ms / 1_000;
117 if epoch_second == self.epoch_second {
118 return;
119 }
120
121 write_fixed_prefix(
122 &mut self.buf,
123 &self.trader_tag,
124 &self.strategy_tag,
125 epoch_second,
126 );
127 self.fixed_prefix_len = self.buf.len();
128 self.epoch_second = epoch_second;
129 }
130}
131
132#[inline]
133fn fixed_prefix_capacity(trader_tag: &str, strategy_tag: &str) -> usize {
134 "OL-".len()
135 + DATETIME_TAG_LEN
136 + "-".len()
137 + trader_tag.len()
138 + "-".len()
139 + strategy_tag.len()
140 + "-".len()
141}
142
143fn write_fixed_prefix(buf: &mut String, trader_tag: &str, strategy_tag: &str, epoch_second: u64) {
144 let now_utc = Offset::UTC.to_datetime(
145 Timestamp::from_second(
146 i64::try_from(epoch_second).expect("seconds timestamp should fit i64"),
147 )
148 .expect("seconds timestamp should be within valid range"),
149 );
150
151 buf.clear();
152
153 write!(
154 buf,
155 "OL-{:04}{:02}{:02}-{:02}{:02}{:02}-{trader_tag}-{strategy_tag}-",
156 now_utc.year(),
157 now_utc.month(),
158 now_utc.day(),
159 now_utc.hour(),
160 now_utc.minute(),
161 now_utc.second(),
162 )
163 .expect("writing to String should not fail");
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{cell::RefCell, rc::Rc};
169
170 use nautilus_core::UnixNanos;
171 use nautilus_model::{
172 identifiers::{OrderListId, StrategyId, TraderId},
173 stubs::TestDefault,
174 };
175 use rstest::rstest;
176
177 use crate::{clock::TestClock, generators::order_list_id::OrderListIdGenerator};
178
179 fn get_order_list_id_generator(initial_count: Option<usize>) -> OrderListIdGenerator {
180 let clock = Rc::new(RefCell::new(TestClock::new()));
181 OrderListIdGenerator::new(
182 TraderId::test_default(),
183 StrategyId::test_default(),
184 initial_count.unwrap_or(0),
185 clock,
186 )
187 }
188
189 #[rstest]
190 fn test_init() {
191 let generator = get_order_list_id_generator(None);
192 assert_eq!(generator.count(), 0);
193 }
194
195 #[rstest]
196 fn test_init_with_initial_count() {
197 let generator = get_order_list_id_generator(Some(7));
198 assert_eq!(generator.count(), 7);
199 }
200
201 #[rstest]
202 fn test_generate_order_list_id_from_start() {
203 let mut generator = get_order_list_id_generator(None);
204 let result1 = generator.generate();
205 let result2 = generator.generate();
206 let result3 = generator.generate();
207
208 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
209 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-2"));
210 assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-3"));
211 }
212
213 #[rstest]
214 fn test_generate_order_list_id_from_initial() {
215 let mut generator = get_order_list_id_generator(Some(5));
216 let result1 = generator.generate();
217 let result2 = generator.generate();
218 let result3 = generator.generate();
219
220 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-6"));
221 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-7"));
222 assert_eq!(result3, OrderListId::new("OL-19700101-000000-001-001-8"));
223 }
224
225 #[rstest]
226 fn test_generate_persists_fixed_prefix_in_buffer_within_same_second() {
227 let mut generator = get_order_list_id_generator(None);
228
229 let result1 = generator.generate();
230 let fixed_prefix = "OL-19700101-000000-001-001-";
231 let capacity_after_first = generator.buf.capacity();
232
233 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
234 assert_eq!(generator.fixed_prefix_len, fixed_prefix.len());
235 assert_eq!(&generator.buf[..generator.fixed_prefix_len], fixed_prefix);
236
237 let result2 = generator.generate();
238
239 assert_eq!(result2, OrderListId::new("OL-19700101-000000-001-001-2"));
240 assert_eq!(generator.fixed_prefix_len, fixed_prefix.len());
241 assert_eq!(&generator.buf[..generator.fixed_prefix_len], fixed_prefix);
242 assert_eq!(generator.buf.capacity(), capacity_after_first);
243 }
244
245 #[rstest]
246 fn test_generate_refreshes_persistent_fixed_prefix_when_second_changes() {
247 let clock = Rc::new(RefCell::new(TestClock::new()));
248 let mut generator = OrderListIdGenerator::new(
249 TraderId::test_default(),
250 StrategyId::test_default(),
251 0,
252 clock.clone(),
253 );
254
255 let result1 = generator.generate();
256 clock.borrow_mut().set_time(UnixNanos::from(1_000_000_000));
257 let result2 = generator.generate();
258
259 assert_eq!(result1, OrderListId::new("OL-19700101-000000-001-001-1"));
260 assert_eq!(result2, OrderListId::new("OL-19700101-000001-001-001-2"));
261 assert_eq!(generator.epoch_second, 1);
262 assert_eq!(
263 &generator.buf[..generator.fixed_prefix_len],
264 "OL-19700101-000001-001-001-"
265 );
266 }
267
268 #[rstest]
269 fn test_reset() {
270 let mut generator = get_order_list_id_generator(None);
271 generator.generate();
272 generator.generate();
273 generator.reset();
274 let result = generator.generate();
275
276 assert_eq!(result, OrderListId::new("OL-19700101-000000-001-001-1"));
277 }
278}