nautilus_execution/models/
latency.rs1use std::{
17 fmt::{Debug, Display},
18 rc::Rc,
19};
20
21use nautilus_core::DurationNanos;
22
23pub trait LatencyModel: Debug {
28 fn get_insert_latency(&self) -> DurationNanos;
30
31 fn get_update_latency(&self) -> DurationNanos;
33
34 fn get_delete_latency(&self) -> DurationNanos;
36
37 fn get_base_latency(&self) -> DurationNanos;
39}
40
41#[derive(Clone)]
43pub struct LatencyModelHandle(Rc<dyn LatencyModel>);
44
45impl LatencyModelHandle {
46 #[must_use]
48 pub fn new<T>(model: T) -> Self
49 where
50 T: LatencyModel + 'static,
51 {
52 Self(Rc::new(model))
53 }
54
55 #[must_use]
57 pub fn from_rc(model: Rc<dyn LatencyModel>) -> Self {
58 Self(model)
59 }
60}
61
62impl Debug for LatencyModelHandle {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_tuple(stringify!(LatencyModelHandle))
65 .field(&"<dyn LatencyModel>")
66 .finish()
67 }
68}
69
70impl LatencyModel for LatencyModelHandle {
71 fn get_insert_latency(&self) -> DurationNanos {
72 self.0.get_insert_latency()
73 }
74
75 fn get_update_latency(&self) -> DurationNanos {
76 self.0.get_update_latency()
77 }
78
79 fn get_delete_latency(&self) -> DurationNanos {
80 self.0.get_delete_latency()
81 }
82
83 fn get_base_latency(&self) -> DurationNanos {
84 self.0.get_base_latency()
85 }
86}
87
88#[derive(Debug, Clone)]
89pub enum LatencyModelAny {
90 Static(StaticLatencyModel),
91}
92
93impl LatencyModel for LatencyModelAny {
94 fn get_insert_latency(&self) -> DurationNanos {
95 match self {
96 Self::Static(model) => model.get_insert_latency(),
97 }
98 }
99
100 fn get_update_latency(&self) -> DurationNanos {
101 match self {
102 Self::Static(model) => model.get_update_latency(),
103 }
104 }
105
106 fn get_delete_latency(&self) -> DurationNanos {
107 match self {
108 Self::Static(model) => model.get_delete_latency(),
109 }
110 }
111
112 fn get_base_latency(&self) -> DurationNanos {
113 match self {
114 Self::Static(model) => model.get_base_latency(),
115 }
116 }
117}
118
119impl From<LatencyModelAny> for LatencyModelHandle {
120 fn from(model: LatencyModelAny) -> Self {
121 Self::new(model)
122 }
123}
124
125#[derive(Debug, Clone)]
134#[cfg_attr(
135 feature = "python",
136 pyo3::pyclass(module = "nautilus_trader.execution", unsendable, from_py_object)
137)]
138#[cfg_attr(
139 feature = "python",
140 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.execution")
141)]
142#[allow(
143 clippy::struct_field_names,
144 reason = "latency_nanos suffix consistently identifies latency types"
145)]
146pub struct StaticLatencyModel {
147 base_latency_nanos: DurationNanos,
148 insert_latency_nanos: DurationNanos,
149 update_latency_nanos: DurationNanos,
150 delete_latency_nanos: DurationNanos,
151}
152
153impl StaticLatencyModel {
154 #[must_use]
165 pub fn new(
166 base_latency_nanos: DurationNanos,
167 insert_latency_nanos: DurationNanos,
168 update_latency_nanos: DurationNanos,
169 delete_latency_nanos: DurationNanos,
170 ) -> Self {
171 Self {
172 base_latency_nanos,
173 insert_latency_nanos: base_latency_nanos + insert_latency_nanos,
174 update_latency_nanos: base_latency_nanos + update_latency_nanos,
175 delete_latency_nanos: base_latency_nanos + delete_latency_nanos,
176 }
177 }
178}
179
180impl LatencyModel for StaticLatencyModel {
181 fn get_insert_latency(&self) -> DurationNanos {
182 self.insert_latency_nanos
183 }
184
185 fn get_update_latency(&self) -> DurationNanos {
186 self.update_latency_nanos
187 }
188
189 fn get_delete_latency(&self) -> DurationNanos {
190 self.delete_latency_nanos
191 }
192
193 fn get_base_latency(&self) -> DurationNanos {
194 self.base_latency_nanos
195 }
196}
197
198impl Display for StaticLatencyModel {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(f, "LatencyModel()")
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use rstest::rstest;
207
208 use super::*;
209
210 #[derive(Debug)]
211 struct CustomLatencyModel;
212
213 impl LatencyModel for CustomLatencyModel {
214 fn get_insert_latency(&self) -> DurationNanos {
215 DurationNanos::new(11)
216 }
217
218 fn get_update_latency(&self) -> DurationNanos {
219 DurationNanos::new(22)
220 }
221
222 fn get_delete_latency(&self) -> DurationNanos {
223 DurationNanos::new(33)
224 }
225
226 fn get_base_latency(&self) -> DurationNanos {
227 DurationNanos::new(44)
228 }
229 }
230
231 #[rstest]
232 fn test_latency_model_handle_calls_custom_model() {
233 let model: Rc<dyn LatencyModel> = Rc::new(CustomLatencyModel);
234 let handle = LatencyModelHandle::from_rc(model);
235 let cloned_handle = handle.clone();
236 drop(handle);
237
238 assert_eq!(cloned_handle.get_insert_latency(), DurationNanos::new(11));
239 assert_eq!(cloned_handle.get_update_latency(), DurationNanos::new(22));
240 assert_eq!(cloned_handle.get_delete_latency(), DurationNanos::new(33));
241 assert_eq!(cloned_handle.get_base_latency(), DurationNanos::new(44));
242 }
243
244 #[rstest]
245 fn test_latency_model_handle_from_any_preserves_model() {
246 let model = StaticLatencyModel::new(
247 DurationNanos::new(1),
248 DurationNanos::new(10),
249 DurationNanos::new(20),
250 DurationNanos::new(30),
251 );
252 let handle: LatencyModelHandle = LatencyModelAny::Static(model).into();
253
254 assert_eq!(handle.get_insert_latency(), DurationNanos::new(11));
255 assert_eq!(handle.get_update_latency(), DurationNanos::new(21));
256 assert_eq!(handle.get_delete_latency(), DurationNanos::new(31));
257 assert_eq!(handle.get_base_latency(), DurationNanos::new(1));
258 }
259
260 #[rstest]
261 fn test_static_latency_model() {
262 let model = StaticLatencyModel::new(
263 DurationNanos::from_millis(1),
264 DurationNanos::from_millis(2),
265 DurationNanos::from_millis(3),
266 DurationNanos::from_millis(4),
267 );
268
269 assert_eq!(model.get_insert_latency().as_u64(), 3_000_000);
271 assert_eq!(model.get_update_latency().as_u64(), 4_000_000);
272 assert_eq!(model.get_delete_latency().as_u64(), 5_000_000);
273 assert_eq!(model.get_base_latency().as_u64(), 1_000_000);
274 }
275}