nautilus_execution/models/
fill.rs1use std::fmt::Display;
17
18use nautilus_core::correctness::{FAILED, check_in_range_inclusive_f64};
19use rand::{Rng, SeedableRng, rngs::StdRng};
20
21#[derive(Debug, Clone)]
22pub struct FillModel {
23 prob_fill_on_limit: f64,
25 prob_fill_on_stop: f64,
27 prob_slippage: f64,
29 rng: StdRng,
31}
32
33impl FillModel {
34 pub fn new(
44 prob_fill_on_limit: f64,
45 prob_fill_on_stop: f64,
46 prob_slippage: f64,
47 random_seed: Option<u64>,
48 ) -> anyhow::Result<Self> {
49 check_in_range_inclusive_f64(prob_fill_on_limit, 0.0, 1.0, "prob_fill_on_limit")
50 .expect(FAILED);
51 check_in_range_inclusive_f64(prob_fill_on_stop, 0.0, 1.0, "prob_fill_on_stop")
52 .expect(FAILED);
53 check_in_range_inclusive_f64(prob_slippage, 0.0, 1.0, "prob_slippage").expect(FAILED);
54 let rng = match random_seed {
55 Some(seed) => StdRng::seed_from_u64(seed),
56 None => StdRng::from_os_rng(),
57 };
58 Ok(Self {
59 prob_fill_on_limit,
60 prob_fill_on_stop,
61 prob_slippage,
62 rng,
63 })
64 }
65
66 pub fn is_limit_filled(&mut self) -> bool {
67 self.event_success(self.prob_fill_on_limit)
68 }
69
70 pub fn is_stop_filled(&mut self) -> bool {
71 self.event_success(self.prob_fill_on_stop)
72 }
73
74 pub fn is_slipped(&mut self) -> bool {
75 self.event_success(self.prob_slippage)
76 }
77
78 fn event_success(&mut self, probability: f64) -> bool {
79 match probability {
80 0.0 => false,
81 1.0 => true,
82 _ => self.rng.random_bool(probability),
83 }
84 }
85}
86
87impl Display for FillModel {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(
90 f,
91 "FillModel(prob_fill_on_limit: {}, prob_fill_on_stop: {}, prob_slippage: {})",
92 self.prob_fill_on_limit, self.prob_fill_on_stop, self.prob_slippage
93 )
94 }
95}
96
97impl Default for FillModel {
98 fn default() -> Self {
100 Self::new(0.5, 0.5, 0.1, None).unwrap()
101 }
102}
103
104#[cfg(test)]
108mod tests {
109 use rstest::{fixture, rstest};
110
111 use super::*;
112
113 #[fixture]
114 fn fill_model() -> FillModel {
115 let seed = 42;
116 FillModel::new(0.5, 0.5, 0.1, Some(seed)).unwrap()
117 }
118
119 #[rstest]
120 #[should_panic(
121 expected = "Condition failed: invalid f64 for 'prob_fill_on_limit' not in range [0, 1], was 1.1"
122 )]
123 fn test_fill_model_param_prob_fill_on_limit_error() {
124 let _ = super::FillModel::new(1.1, 0.5, 0.1, None).unwrap();
125 }
126
127 #[rstest]
128 #[should_panic(
129 expected = "Condition failed: invalid f64 for 'prob_fill_on_stop' not in range [0, 1], was 1.1"
130 )]
131 fn test_fill_model_param_prob_fill_on_stop_error() {
132 let _ = super::FillModel::new(0.5, 1.1, 0.1, None).unwrap();
133 }
134
135 #[rstest]
136 #[should_panic(
137 expected = "Condition failed: invalid f64 for 'prob_slippage' not in range [0, 1], was 1.1"
138 )]
139 fn test_fill_model_param_prob_slippage_error() {
140 let _ = super::FillModel::new(0.5, 0.5, 1.1, None).unwrap();
141 }
142
143 #[rstest]
144 fn test_fill_model_is_limit_filled(mut fill_model: FillModel) {
145 let result = fill_model.is_limit_filled();
147 assert!(!result);
148 }
149
150 #[rstest]
151 fn test_fill_model_is_stop_filled(mut fill_model: FillModel) {
152 let result = fill_model.is_stop_filled();
154 assert!(!result);
155 }
156
157 #[rstest]
158 fn test_fill_model_is_slipped(mut fill_model: FillModel) {
159 let result = fill_model.is_slipped();
161 assert!(!result);
162 }
163}