use std::{
fmt::{Debug, Display},
rc::Rc,
};
use nautilus_core::UnixNanos;
pub trait LatencyModel: Debug {
fn get_insert_latency(&self) -> UnixNanos;
fn get_update_latency(&self) -> UnixNanos;
fn get_delete_latency(&self) -> UnixNanos;
fn get_base_latency(&self) -> UnixNanos;
}
#[derive(Clone)]
pub struct LatencyModelHandle(Rc<dyn LatencyModel>);
impl LatencyModelHandle {
#[must_use]
pub fn new<T>(model: T) -> Self
where
T: LatencyModel + 'static,
{
Self(Rc::new(model))
}
#[must_use]
pub fn from_rc(model: Rc<dyn LatencyModel>) -> Self {
Self(model)
}
}
impl Debug for LatencyModelHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple(stringify!(LatencyModelHandle))
.field(&"<dyn LatencyModel>")
.finish()
}
}
impl LatencyModel for LatencyModelHandle {
fn get_insert_latency(&self) -> UnixNanos {
self.0.get_insert_latency()
}
fn get_update_latency(&self) -> UnixNanos {
self.0.get_update_latency()
}
fn get_delete_latency(&self) -> UnixNanos {
self.0.get_delete_latency()
}
fn get_base_latency(&self) -> UnixNanos {
self.0.get_base_latency()
}
}
#[derive(Debug, Clone)]
pub enum LatencyModelAny {
Static(StaticLatencyModel),
}
impl LatencyModel for LatencyModelAny {
fn get_insert_latency(&self) -> UnixNanos {
match self {
Self::Static(model) => model.get_insert_latency(),
}
}
fn get_update_latency(&self) -> UnixNanos {
match self {
Self::Static(model) => model.get_update_latency(),
}
}
fn get_delete_latency(&self) -> UnixNanos {
match self {
Self::Static(model) => model.get_delete_latency(),
}
}
fn get_base_latency(&self) -> UnixNanos {
match self {
Self::Static(model) => model.get_base_latency(),
}
}
}
impl From<LatencyModelAny> for LatencyModelHandle {
fn from(model: LatencyModelAny) -> Self {
Self::new(model)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.execution", unsendable, from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.execution")
)]
#[allow(
clippy::struct_field_names,
reason = "latency_nanos suffix consistently identifies latency types"
)]
pub struct StaticLatencyModel {
base_latency_nanos: UnixNanos,
insert_latency_nanos: UnixNanos,
update_latency_nanos: UnixNanos,
delete_latency_nanos: UnixNanos,
}
impl StaticLatencyModel {
#[must_use]
pub fn new(
base_latency_nanos: UnixNanos,
insert_latency_nanos: UnixNanos,
update_latency_nanos: UnixNanos,
delete_latency_nanos: UnixNanos,
) -> Self {
Self {
base_latency_nanos,
insert_latency_nanos: UnixNanos::from(
base_latency_nanos.as_u64() + insert_latency_nanos.as_u64(),
),
update_latency_nanos: UnixNanos::from(
base_latency_nanos.as_u64() + update_latency_nanos.as_u64(),
),
delete_latency_nanos: UnixNanos::from(
base_latency_nanos.as_u64() + delete_latency_nanos.as_u64(),
),
}
}
}
impl LatencyModel for StaticLatencyModel {
fn get_insert_latency(&self) -> UnixNanos {
self.insert_latency_nanos
}
fn get_update_latency(&self) -> UnixNanos {
self.update_latency_nanos
}
fn get_delete_latency(&self) -> UnixNanos {
self.delete_latency_nanos
}
fn get_base_latency(&self) -> UnixNanos {
self.base_latency_nanos
}
}
impl Display for StaticLatencyModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "LatencyModel()")
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[derive(Debug)]
struct CustomLatencyModel;
impl LatencyModel for CustomLatencyModel {
fn get_insert_latency(&self) -> UnixNanos {
UnixNanos::from(11)
}
fn get_update_latency(&self) -> UnixNanos {
UnixNanos::from(22)
}
fn get_delete_latency(&self) -> UnixNanos {
UnixNanos::from(33)
}
fn get_base_latency(&self) -> UnixNanos {
UnixNanos::from(44)
}
}
#[rstest]
fn test_latency_model_handle_calls_custom_model() {
let model: Rc<dyn LatencyModel> = Rc::new(CustomLatencyModel);
let handle = LatencyModelHandle::from_rc(model);
let cloned_handle = handle.clone();
drop(handle);
assert_eq!(cloned_handle.get_insert_latency(), UnixNanos::from(11));
assert_eq!(cloned_handle.get_update_latency(), UnixNanos::from(22));
assert_eq!(cloned_handle.get_delete_latency(), UnixNanos::from(33));
assert_eq!(cloned_handle.get_base_latency(), UnixNanos::from(44));
}
#[rstest]
fn test_latency_model_handle_from_any_preserves_model() {
let model = StaticLatencyModel::new(
UnixNanos::from(1),
UnixNanos::from(10),
UnixNanos::from(20),
UnixNanos::from(30),
);
let handle: LatencyModelHandle = LatencyModelAny::Static(model).into();
assert_eq!(handle.get_insert_latency(), UnixNanos::from(11));
assert_eq!(handle.get_update_latency(), UnixNanos::from(21));
assert_eq!(handle.get_delete_latency(), UnixNanos::from(31));
assert_eq!(handle.get_base_latency(), UnixNanos::from(1));
}
#[rstest]
fn test_static_latency_model() {
let model = StaticLatencyModel::new(
UnixNanos::from(1_000_000),
UnixNanos::from(2_000_000),
UnixNanos::from(3_000_000),
UnixNanos::from(4_000_000),
);
assert_eq!(model.get_insert_latency().as_u64(), 3_000_000);
assert_eq!(model.get_update_latency().as_u64(), 4_000_000);
assert_eq!(model.get_delete_latency().as_u64(), 5_000_000);
assert_eq!(model.get_base_latency().as_u64(), 1_000_000);
}
}