1use crate::microstructure::{Level, OrderBook};
4use crate::traits::Indicator;
5
6fn cumulative_slope(levels: &[Level], mid: f64, signed_distance: f64) -> f64 {
16 let count = levels.len() as f64;
17 let mut cumulative = 0.0;
18 let mut sum_x = 0.0;
19 let mut sum_y = 0.0;
20 let mut sum_xy = 0.0;
21 let mut sum_xx = 0.0;
22 for level in levels {
23 let x = signed_distance * (level.price - mid);
24 cumulative += level.size;
25 sum_x += x;
26 sum_y += cumulative;
27 sum_xy += x * cumulative;
28 sum_xx += x * x;
29 }
30 let denom = count * sum_xx - sum_x * sum_x;
31 if denom == 0.0 {
32 return 0.0;
33 }
34 (count * sum_xy - sum_x * sum_y) / denom
35}
36
37#[derive(Debug, Clone, Default)]
79pub struct DepthSlope {
80 has_emitted: bool,
81}
82
83impl DepthSlope {
84 pub const fn new() -> Self {
86 Self { has_emitted: false }
87 }
88}
89
90impl Indicator for DepthSlope {
91 type Input = OrderBook;
92 type Output = f64;
93
94 fn update(&mut self, book: OrderBook) -> Option<f64> {
95 self.has_emitted = true;
96 let Some(mid) = book.mid() else {
97 return Some(0.0);
98 };
99 if book.bids.len() < 2 || book.asks.len() < 2 {
100 return Some(0.0);
101 }
102 let bid_slope = cumulative_slope(&book.bids, mid, -1.0);
103 let ask_slope = cumulative_slope(&book.asks, mid, 1.0);
104 Some(f64::midpoint(bid_slope, ask_slope))
105 }
106
107 fn reset(&mut self) {
108 self.has_emitted = false;
109 }
110
111 fn warmup_period(&self) -> usize {
112 1
113 }
114
115 fn is_ready(&self) -> bool {
116 self.has_emitted
117 }
118
119 fn name(&self) -> &'static str {
120 "DepthSlope"
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::traits::BatchExt;
128
129 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
130 let to_levels = |xs: &[(f64, f64)]| {
131 xs.iter()
132 .map(|&(p, s)| Level::new(p, s).unwrap())
133 .collect::<Vec<_>>()
134 };
135 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
136 }
137
138 #[test]
139 fn accessors_and_metadata() {
140 let ds = DepthSlope::new();
141 assert_eq!(ds.name(), "DepthSlope");
142 assert_eq!(ds.warmup_period(), 1);
143 assert!(!ds.is_ready());
144 }
145
146 #[test]
147 fn thickening_book_has_positive_slope() {
148 let mut ds = DepthSlope::new();
149 let out = ds
150 .update(book(
151 &[(99.0, 1.0), (98.0, 2.0), (97.0, 3.0)],
152 &[(101.0, 1.0), (102.0, 2.0), (103.0, 3.0)],
153 ))
154 .unwrap();
155 assert!(out > 0.0);
156 assert!(ds.is_ready());
157 }
158
159 #[test]
160 fn front_loaded_book_has_smaller_slope_than_back_loaded() {
161 let mut back = DepthSlope::new();
165 let back_slope = back
166 .update(book(
167 &[(99.0, 1.0), (98.0, 2.0), (97.0, 3.0)],
168 &[(101.0, 1.0), (102.0, 2.0), (103.0, 3.0)],
169 ))
170 .unwrap();
171 let mut front = DepthSlope::new();
172 let front_slope = front
173 .update(book(
174 &[(99.0, 3.0), (98.0, 2.0), (97.0, 1.0)],
175 &[(101.0, 3.0), (102.0, 2.0), (103.0, 1.0)],
176 ))
177 .unwrap();
178 assert!(front_slope >= 0.0);
179 assert!(back_slope > front_slope);
180 }
181
182 #[test]
183 fn known_slope_value() {
184 let mut ds = DepthSlope::new();
187 let out = ds
188 .update(book(
189 &[(99.0, 1.0), (98.0, 2.0)],
190 &[(101.0, 1.0), (102.0, 2.0)],
191 ))
192 .unwrap();
193 assert!((out - 2.0).abs() < 1e-9);
194 }
195
196 #[test]
197 fn single_level_side_is_zero() {
198 let mut ds = DepthSlope::new();
199 assert_eq!(
201 ds.update(book(&[(100.0, 1.0)], &[(101.0, 1.0), (102.0, 1.0)])),
202 Some(0.0)
203 );
204 }
205
206 #[test]
207 fn empty_book_is_zero() {
208 let mut ds = DepthSlope::new();
209 assert_eq!(
210 ds.update(OrderBook::new_unchecked(vec![], vec![])),
211 Some(0.0)
212 );
213 }
214
215 #[test]
216 fn degenerate_distance_slope_is_zero() {
217 let levels = [
219 Level::new_unchecked(100.0, 1.0),
220 Level::new_unchecked(100.0, 2.0),
221 ];
222 assert_eq!(cumulative_slope(&levels, 100.0, 1.0), 0.0);
223 }
224
225 #[test]
226 fn batch_equals_streaming() {
227 let books: Vec<OrderBook> = (0..20)
228 .map(|i| {
229 let extra = f64::from(i % 4);
230 book(
231 &[(99.0, 1.0 + extra), (98.0, 2.0)],
232 &[(101.0, 1.0), (102.0, 2.0 + extra)],
233 )
234 })
235 .collect();
236 let mut a = DepthSlope::new();
237 let mut b = DepthSlope::new();
238 assert_eq!(
239 a.batch(&books),
240 books
241 .iter()
242 .map(|x| b.update(x.clone()))
243 .collect::<Vec<_>>()
244 );
245 }
246
247 #[test]
248 fn reset_clears_state() {
249 let mut ds = DepthSlope::new();
250 ds.update(book(
251 &[(99.0, 1.0), (98.0, 2.0)],
252 &[(101.0, 1.0), (102.0, 2.0)],
253 ));
254 assert!(ds.is_ready());
255 ds.reset();
256 assert!(!ds.is_ready());
257 }
258}