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