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 {
68 self.event_success(self.prob_fill_on_limit)
69 }
70
71 pub fn is_stop_filled(&mut self) -> bool {
73 self.event_success(self.prob_fill_on_stop)
74 }
75
76 pub fn is_slipped(&mut self) -> bool {
78 self.event_success(self.prob_slippage)
79 }
80
81 fn event_success(&mut self, probability: f64) -> bool {
82 match probability {
83 0.0 => false,
84 1.0 => true,
85 _ => self.rng.random_bool(probability),
86 }
87 }
88}
89
90impl Display for FillModel {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(
93 f,
94 "FillModel(prob_fill_on_limit: {}, prob_fill_on_stop: {}, prob_slippage: {})",
95 self.prob_fill_on_limit, self.prob_fill_on_stop, self.prob_slippage
96 )
97 }
98}
99
100impl Default for FillModel {
101 fn default() -> Self {
103 Self::new(0.5, 0.5, 0.1, None).unwrap()
104 }
105}
106
107#[cfg(test)]
111mod tests {
112 use rstest::{fixture, rstest};
113
114 use super::*;
115
116 #[fixture]
117 fn fill_model() -> FillModel {
118 let seed = 42;
119 FillModel::new(0.5, 0.5, 0.1, Some(seed)).unwrap()
120 }
121
122 #[rstest]
123 #[should_panic(
124 expected = "Condition failed: invalid f64 for 'prob_fill_on_limit' not in range [0, 1], was 1.1"
125 )]
126 fn test_fill_model_param_prob_fill_on_limit_error() {
127 let _ = super::FillModel::new(1.1, 0.5, 0.1, None).unwrap();
128 }
129
130 #[rstest]
131 #[should_panic(
132 expected = "Condition failed: invalid f64 for 'prob_fill_on_stop' not in range [0, 1], was 1.1"
133 )]
134 fn test_fill_model_param_prob_fill_on_stop_error() {
135 let _ = super::FillModel::new(0.5, 1.1, 0.1, None).unwrap();
136 }
137
138 #[rstest]
139 #[should_panic(
140 expected = "Condition failed: invalid f64 for 'prob_slippage' not in range [0, 1], was 1.1"
141 )]
142 fn test_fill_model_param_prob_slippage_error() {
143 let _ = super::FillModel::new(0.5, 0.5, 1.1, None).unwrap();
144 }
145
146 #[rstest]
147 fn test_fill_model_is_limit_filled(mut fill_model: FillModel) {
148 let result = fill_model.is_limit_filled();
150 assert!(!result);
151 }
152
153 #[rstest]
154 fn test_fill_model_is_stop_filled(mut fill_model: FillModel) {
155 let result = fill_model.is_stop_filled();
157 assert!(!result);
158 }
159
160 #[rstest]
161 fn test_fill_model_is_slipped(mut fill_model: FillModel) {
162 let result = fill_model.is_slipped();
164 assert!(!result);
165 }
166}