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