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 #[inline]
95 fn update(&mut self, book: OrderBook) -> Option<f64> {
96 self.has_emitted = true;
97 let Some(mid) = book.mid() else {
98 return Some(0.0);
99 };
100 if book.bids.len() < 2 || book.asks.len() < 2 {
101 return Some(0.0);
102 }
103 let bid_slope = cumulative_slope(&book.bids, mid, -1.0);
104 let ask_slope = cumulative_slope(&book.asks, mid, 1.0);
105 Some(f64::midpoint(bid_slope, ask_slope))
106 }
107
108 fn reset(&mut self) {
109 self.has_emitted = false;
110 }
111
112 #[inline]
113 fn warmup_period(&self) -> usize {
114 1
115 }
116
117 #[inline]
118 fn is_ready(&self) -> bool {
119 self.has_emitted
120 }
121
122 #[inline]
123 fn name(&self) -> &'static str {
124 "DepthSlope"
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::traits::BatchExt;
132
133 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
134 let to_levels = |xs: &[(f64, f64)]| {
135 xs.iter()
136 .map(|&(p, s)| Level::new(p, s).unwrap())
137 .collect::<Vec<_>>()
138 };
139 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
140 }
141
142 #[test]
143 fn accessors_and_metadata() {
144 let ds = DepthSlope::new();
145 assert_eq!(ds.name(), "DepthSlope");
146 assert_eq!(ds.warmup_period(), 1);
147 assert!(!ds.is_ready());
148 }
149
150 #[test]
151 fn thickening_book_has_positive_slope() {
152 let mut ds = DepthSlope::new();
153 let out = ds
154 .update(book(
155 &[(99.0, 1.0), (98.0, 2.0), (97.0, 3.0)],
156 &[(101.0, 1.0), (102.0, 2.0), (103.0, 3.0)],
157 ))
158 .unwrap();
159 assert!(out > 0.0);
160 assert!(ds.is_ready());
161 }
162
163 #[test]
164 fn front_loaded_book_has_smaller_slope_than_back_loaded() {
165 let mut back = DepthSlope::new();
169 let back_slope = back
170 .update(book(
171 &[(99.0, 1.0), (98.0, 2.0), (97.0, 3.0)],
172 &[(101.0, 1.0), (102.0, 2.0), (103.0, 3.0)],
173 ))
174 .unwrap();
175 let mut front = DepthSlope::new();
176 let front_slope = front
177 .update(book(
178 &[(99.0, 3.0), (98.0, 2.0), (97.0, 1.0)],
179 &[(101.0, 3.0), (102.0, 2.0), (103.0, 1.0)],
180 ))
181 .unwrap();
182 assert!(front_slope >= 0.0);
183 assert!(back_slope > front_slope);
184 }
185
186 #[test]
187 fn known_slope_value() {
188 let mut ds = DepthSlope::new();
191 let out = ds
192 .update(book(
193 &[(99.0, 1.0), (98.0, 2.0)],
194 &[(101.0, 1.0), (102.0, 2.0)],
195 ))
196 .unwrap();
197 assert!((out - 2.0).abs() < 1e-9);
198 }
199
200 #[test]
201 fn single_level_side_is_zero() {
202 let mut ds = DepthSlope::new();
203 assert_eq!(
205 ds.update(book(&[(100.0, 1.0)], &[(101.0, 1.0), (102.0, 1.0)])),
206 Some(0.0)
207 );
208 }
209
210 #[test]
211 fn empty_book_is_zero() {
212 let mut ds = DepthSlope::new();
213 assert_eq!(
214 ds.update(OrderBook::new_unchecked(vec![], vec![])),
215 Some(0.0)
216 );
217 }
218
219 #[test]
220 fn degenerate_distance_slope_is_zero() {
221 let levels = [
223 Level::new_unchecked(100.0, 1.0),
224 Level::new_unchecked(100.0, 2.0),
225 ];
226 assert_eq!(cumulative_slope(&levels, 100.0, 1.0), 0.0);
227 }
228
229 #[test]
230 fn batch_equals_streaming() {
231 let books: Vec<OrderBook> = (0..20)
232 .map(|i| {
233 let extra = f64::from(i % 4);
234 book(
235 &[(99.0, 1.0 + extra), (98.0, 2.0)],
236 &[(101.0, 1.0), (102.0, 2.0 + extra)],
237 )
238 })
239 .collect();
240 let mut a = DepthSlope::new();
241 let mut b = DepthSlope::new();
242 assert_eq!(
243 a.batch(&books),
244 books
245 .iter()
246 .map(|x| b.update(x.clone()))
247 .collect::<Vec<_>>()
248 );
249 }
250
251 #[test]
252 fn reset_clears_state() {
253 let mut ds = DepthSlope::new();
254 ds.update(book(
255 &[(99.0, 1.0), (98.0, 2.0)],
256 &[(101.0, 1.0), (102.0, 2.0)],
257 ));
258 assert!(ds.is_ready());
259 ds.reset();
260 assert!(!ds.is_ready());
261 }
262}