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