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 pub fn get_orderbook_for_fill_simulation(
91 &self,
92 _instrument: &dyn std::any::Any, _order: &dyn std::any::Any, _best_bid: f64,
95 _best_ask: f64,
96 ) -> Option<Box<dyn std::any::Any>> {
97 None }
99
100 fn event_success(&mut self, probability: f64) -> bool {
101 match probability {
102 0.0 => false,
103 1.0 => true,
104 _ => self.rng.random_bool(probability),
105 }
106 }
107}
108
109impl Display for FillModel {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(
112 f,
113 "FillModel(prob_fill_on_limit: {}, prob_fill_on_stop: {}, prob_slippage: {})",
114 self.prob_fill_on_limit, self.prob_fill_on_stop, self.prob_slippage
115 )
116 }
117}
118
119impl Default for FillModel {
120 fn default() -> Self {
122 Self::new(0.5, 0.5, 0.1, None).unwrap()
123 }
124}
125
126#[cfg(test)]
130mod tests {
131 use rstest::{fixture, rstest};
132
133 use super::*;
134
135 #[fixture]
136 fn fill_model() -> FillModel {
137 let seed = 42;
138 FillModel::new(0.5, 0.5, 0.1, Some(seed)).unwrap()
139 }
140
141 #[rstest]
142 #[should_panic(
143 expected = "Condition failed: invalid f64 for 'prob_fill_on_limit' not in range [0, 1], was 1.1"
144 )]
145 fn test_fill_model_param_prob_fill_on_limit_error() {
146 let _ = super::FillModel::new(1.1, 0.5, 0.1, None).unwrap();
147 }
148
149 #[rstest]
150 #[should_panic(
151 expected = "Condition failed: invalid f64 for 'prob_fill_on_stop' not in range [0, 1], was 1.1"
152 )]
153 fn test_fill_model_param_prob_fill_on_stop_error() {
154 let _ = super::FillModel::new(0.5, 1.1, 0.1, None).unwrap();
155 }
156
157 #[rstest]
158 #[should_panic(
159 expected = "Condition failed: invalid f64 for 'prob_slippage' not in range [0, 1], was 1.1"
160 )]
161 fn test_fill_model_param_prob_slippage_error() {
162 let _ = super::FillModel::new(0.5, 0.5, 1.1, None).unwrap();
163 }
164
165 #[rstest]
166 fn test_fill_model_is_limit_filled(mut fill_model: FillModel) {
167 let result = fill_model.is_limit_filled();
169 assert!(!result);
170 }
171
172 #[rstest]
173 fn test_fill_model_is_stop_filled(mut fill_model: FillModel) {
174 let result = fill_model.is_stop_filled();
176 assert!(!result);
177 }
178
179 #[rstest]
180 fn test_fill_model_is_slipped(mut fill_model: FillModel) {
181 let result = fill_model.is_slipped();
183 assert!(!result);
184 }
185}