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)]
98pub struct StaticLatencyModel {
99    base_latency_nanos: UnixNanos,
100    insert_latency_nanos: UnixNanos,
101    update_latency_nanos: UnixNanos,
102    delete_latency_nanos: UnixNanos,
103}
104
105impl StaticLatencyModel {
106    /// Creates a new [`StaticLatencyModel`] instance.
107    ///
108    /// The base latency is added to each operation latency to get the effective latency.
109    ///
110    /// # Arguments
111    ///
112    /// * `base_latency_nanos` - Base network latency added to all operations
113    /// * `insert_latency_nanos` - Additional latency for order insertion
114    /// * `update_latency_nanos` - Additional latency for order updates
115    /// * `delete_latency_nanos` - Additional latency for order cancellation
116    #[must_use]
117    pub fn new(
118        base_latency_nanos: UnixNanos,
119        insert_latency_nanos: UnixNanos,
120        update_latency_nanos: UnixNanos,
121        delete_latency_nanos: UnixNanos,
122    ) -> Self {
123        Self {
124            base_latency_nanos,
125            insert_latency_nanos: UnixNanos::from(
126                base_latency_nanos.as_u64() + insert_latency_nanos.as_u64(),
127            ),
128            update_latency_nanos: UnixNanos::from(
129                base_latency_nanos.as_u64() + update_latency_nanos.as_u64(),
130            ),
131            delete_latency_nanos: UnixNanos::from(
132                base_latency_nanos.as_u64() + delete_latency_nanos.as_u64(),
133            ),
134        }
135    }
136}
137
138impl LatencyModel for StaticLatencyModel {
139    fn get_insert_latency(&self) -> UnixNanos {
140        self.insert_latency_nanos
141    }
142
143    fn get_update_latency(&self) -> UnixNanos {
144        self.update_latency_nanos
145    }
146
147    fn get_delete_latency(&self) -> UnixNanos {
148        self.delete_latency_nanos
149    }
150
151    fn get_base_latency(&self) -> UnixNanos {
152        self.base_latency_nanos
153    }
154}
155
156impl Display for StaticLatencyModel {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(f, "LatencyModel()")
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use rstest::rstest;
165
166    use super::*;
167
168    #[rstest]
169    fn test_static_latency_model() {
170        let model = StaticLatencyModel::new(
171            UnixNanos::from(1_000_000),
172            UnixNanos::from(2_000_000),
173            UnixNanos::from(3_000_000),
174            UnixNanos::from(4_000_000),
175        );
176
177        // Base is added to each operation latency
178        assert_eq!(model.get_insert_latency().as_u64(), 3_000_000);
179        assert_eq!(model.get_update_latency().as_u64(), 4_000_000);
180        assert_eq!(model.get_delete_latency().as_u64(), 5_000_000);
181        assert_eq!(model.get_base_latency().as_u64(), 1_000_000);
182    }
183}