wickra_core/indicators/
quoted_spread.rs1use crate::microstructure::OrderBook;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
37pub struct QuotedSpread {
38 has_emitted: bool,
39}
40
41impl QuotedSpread {
42 pub const fn new() -> Self {
44 Self { has_emitted: false }
45 }
46}
47
48impl Indicator for QuotedSpread {
49 type Input = OrderBook;
50 type Output = f64;
51
52 fn update(&mut self, book: OrderBook) -> Option<f64> {
53 self.has_emitted = true;
54 let (Some(bid), Some(ask)) = (book.best_bid(), book.best_ask()) else {
55 return Some(0.0);
56 };
57 let mid = f64::midpoint(bid.price, ask.price);
58 Some((ask.price - bid.price) / mid * 10_000.0)
59 }
60
61 fn reset(&mut self) {
62 self.has_emitted = false;
63 }
64
65 fn warmup_period(&self) -> usize {
66 1
67 }
68
69 fn is_ready(&self) -> bool {
70 self.has_emitted
71 }
72
73 fn name(&self) -> &'static str {
74 "QuotedSpread"
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::microstructure::Level;
82 use crate::traits::BatchExt;
83
84 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
85 let to_levels = |xs: &[(f64, f64)]| {
86 xs.iter()
87 .map(|&(p, s)| Level::new(p, s).unwrap())
88 .collect::<Vec<_>>()
89 };
90 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
91 }
92
93 #[test]
94 fn accessors_and_metadata() {
95 let qs = QuotedSpread::new();
96 assert_eq!(qs.name(), "QuotedSpread");
97 assert_eq!(qs.warmup_period(), 1);
98 assert!(!qs.is_ready());
99 }
100
101 #[test]
102 fn known_value_in_bps() {
103 let mut qs = QuotedSpread::new();
104 let bps = qs.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)])).unwrap();
106 assert!((bps - 99.502_487_56).abs() < 1e-6);
107 assert!(qs.is_ready());
108 }
109
110 #[test]
111 fn tight_book_is_small() {
112 let mut qs = QuotedSpread::new();
113 let bps = qs.update(book(&[(100.0, 1.0)], &[(100.01, 1.0)])).unwrap();
114 assert!(bps > 0.0 && bps < 2.0);
115 }
116
117 #[test]
118 fn empty_book_is_zero() {
119 let mut qs = QuotedSpread::new();
120 assert_eq!(
121 qs.update(OrderBook::new_unchecked(vec![], vec![])),
122 Some(0.0)
123 );
124 }
125
126 #[test]
127 fn batch_equals_streaming() {
128 let books: Vec<OrderBook> = (0..20)
129 .map(|i| {
130 let ask = 100.5 + f64::from(i % 4) * 0.1;
131 book(&[(100.0, 1.0)], &[(ask, 1.0)])
132 })
133 .collect();
134 let mut a = QuotedSpread::new();
135 let mut b = QuotedSpread::new();
136 assert_eq!(
137 a.batch(&books),
138 books
139 .iter()
140 .map(|x| b.update(x.clone()))
141 .collect::<Vec<_>>()
142 );
143 }
144
145 #[test]
146 fn reset_clears_state() {
147 let mut qs = QuotedSpread::new();
148 qs.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)]));
149 assert!(qs.is_ready());
150 qs.reset();
151 assert!(!qs.is_ready());
152 }
153}