Skip to main content

nautilus_execution/models/
latency.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::fmt::{Debug, Display};
17
18use nautilus_core::UnixNanos;
19
20/// Trait for latency models used in backtesting.
21///
22/// Latency models simulate network delays for order operations during backtesting.
23/// Implementations can provide static or dynamic (jittered) latency values.
24pub trait LatencyModel: Debug {
25    /// Returns the latency for order insertion operations.
26    fn get_insert_latency(&self) -> UnixNanos;
27
28    /// Returns the latency for order update/modify operations.
29    fn get_update_latency(&self) -> UnixNanos;
30
31    /// Returns the latency for order delete/cancel operations.
32    fn get_delete_latency(&self) -> UnixNanos;
33
34    /// Returns the base latency component.
35    fn get_base_latency(&self) -> UnixNanos;
36}
37
38#[derive(Debug, Clone)]
39pub enum LatencyModelAny {
40    Static(StaticLatencyModel),
41}
42
43impl LatencyModel for LatencyModelAny {
44    fn get_insert_latency(&self) -> UnixNanos {
45        match self {
46            Self::Static(model) => model.get_insert_latency(),
47        }
48    }
49
50    fn get_update_latency(&self) -> UnixNanos {
51        match self {
52            Self::Static(model) => model.get_update_latency(),
53        }
54    }
55
56    fn get_delete_latency(&self) -> UnixNanos {
57        match self {
58            Self::Static(model) => model.get_delete_latency(),
59        }
60    }
61
62    fn get_base_latency(&self) -> UnixNanos {
63        match self {
64            Self::Static(model) => model.get_base_latency(),
65        }
66    }
67}
68
69impl From<LatencyModelAny> for Box<dyn LatencyModel> {
70    fn from(value: LatencyModelAny) -> Self {
71        match value {
72            LatencyModelAny::Static(model) => Box::new(model),
73        }
74    }
75}
76
77/// Static latency model with fixed latency values.
78///
79/// Models the latency for different order operations including base network latency
80/// and specific operation latencies for insert, update, and delete operations.
81///
82/// The base latency is automatically added to each operation latency, matching
83/// Python's behavior. For example, if `base_latency_nanos = 100ms` and
84/// `insert_latency_nanos = 200ms`, the effective insert latency will be 300ms.
85#[derive(Debug, Clone)]
86#[cfg_attr(
87    feature = "python",
88    pyo3::pyclass(
89        module = "nautilus_trader.core.nautilus_pyo3.execution",
90        unsendable,
91        from_py_object
92    )
93)]
94#[cfg_attr(
95    feature = "python",
96    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.execution")
97)]
98#[allow(
99    clippy::struct_field_names,
100    reason = "latency_nanos suffix consistently identifies latency types"
101)]
102pub struct StaticLatencyModel {
103    base_latency_nanos: UnixNanos,
104    insert_latency_nanos: UnixNanos,
105    update_latency_nanos: UnixNanos,
106    delete_latency_nanos: UnixNanos,
107}
108
109impl StaticLatencyModel {
110    /// Creates a new [`StaticLatencyModel`] instance.
111    ///
112    /// The base latency is added to each operation latency to get the effective latency.
113    ///
114    /// # Arguments
115    ///
116    /// * `base_latency_nanos` - Base network latency added to all operations
117    /// * `insert_latency_nanos` - Additional latency for order insertion
118    /// * `update_latency_nanos` - Additional latency for order updates
119    /// * `delete_latency_nanos` - Additional latency for order cancellation
120    #[must_use]
121    pub fn new(
122        base_latency_nanos: UnixNanos,
123        insert_latency_nanos: UnixNanos,
124        update_latency_nanos: UnixNanos,
125        delete_latency_nanos: UnixNanos,
126    ) -> Self {
127        Self {
128            base_latency_nanos,
129            insert_latency_nanos: UnixNanos::from(
130                base_latency_nanos.as_u64() + insert_latency_nanos.as_u64(),
131            ),
132            update_latency_nanos: UnixNanos::from(
133                base_latency_nanos.as_u64() + update_latency_nanos.as_u64(),
134            ),
135            delete_latency_nanos: UnixNanos::from(
136                base_latency_nanos.as_u64() + delete_latency_nanos.as_u64(),
137            ),
138        }
139    }
140}
141
142impl LatencyModel for StaticLatencyModel {
143    fn get_insert_latency(&self) -> UnixNanos {
144        self.insert_latency_nanos
145    }
146
147    fn get_update_latency(&self) -> UnixNanos {
148        self.update_latency_nanos
149    }
150
151    fn get_delete_latency(&self) -> UnixNanos {
152        self.delete_latency_nanos
153    }
154
155    fn get_base_latency(&self) -> UnixNanos {
156        self.base_latency_nanos
157    }
158}
159
160impl Display for StaticLatencyModel {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        write!(f, "LatencyModel()")
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use rstest::rstest;
169
170    use super::*;
171
172    #[rstest]
173    fn test_static_latency_model() {
174        let model = StaticLatencyModel::new(
175            UnixNanos::from(1_000_000),
176            UnixNanos::from(2_000_000),
177            UnixNanos::from(3_000_000),
178            UnixNanos::from(4_000_000),
179        );
180
181        // Base is added to each operation latency
182        assert_eq!(model.get_insert_latency().as_u64(), 3_000_000);
183        assert_eq!(model.get_update_latency().as_u64(), 4_000_000);
184        assert_eq!(model.get_delete_latency().as_u64(), 5_000_000);
185        assert_eq!(model.get_base_latency().as_u64(), 1_000_000);
186    }
187}