matchcore/orderbook/analytics/
depth_statistics.rs1use crate::{Level2, LimitBook, Notional, Price, Quantity, Side};
2
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(Debug, Clone, PartialEq)]
6pub struct DepthStatistics {
7 n_analyzed_levels: usize,
9
10 total_value: Notional,
12
13 total_size: Quantity,
15
16 average_level_size: f64,
18
19 min_level_size: Quantity,
21
22 max_level_size: Quantity,
24
25 std_dev_level_size: f64,
27}
28
29impl DepthStatistics {
30 pub(super) fn compute(book: &LimitBook, side: Side, n_levels: usize) -> Self {
32 let mut stats = Self {
33 n_analyzed_levels: 0,
34 total_value: Notional(0),
35 total_size: Quantity(0),
36 average_level_size: 0.0,
37 min_level_size: Quantity(u64::MAX),
38 max_level_size: Quantity(0),
39 std_dev_level_size: 0.0,
40 };
41
42 let n_levels = if n_levels == 0 { usize::MAX } else { n_levels };
43 let mut sizes = Vec::new();
44
45 match side {
46 Side::Buy => {
47 for (price, level_id) in book.bids.iter().rev().take(n_levels) {
48 let level = &book.levels[*level_id];
49 stats.observe_level(*price, level.total_quantity());
50 sizes.push(level.total_quantity());
51 }
52 }
53 Side::Sell => {
54 for (price, level_id) in book.asks.iter().take(n_levels) {
55 let level = &book.levels[*level_id];
56 stats.observe_level(*price, level.total_quantity());
57 sizes.push(level.total_quantity());
58 }
59 }
60 }
61
62 if stats.is_empty() {
63 stats.min_level_size = Quantity(0);
64 return stats;
65 }
66
67 stats.average_level_size = stats.total_size.as_f64() / stats.n_analyzed_levels as f64;
68
69 let variance = sizes
70 .iter()
71 .map(|size| (size.as_f64() - stats.average_level_size).powi(2))
72 .sum::<f64>()
73 / stats.n_analyzed_levels as f64;
74 stats.std_dev_level_size = variance.sqrt();
75
76 stats
77 }
78
79 pub(crate) fn compute_from_level2(level2: &Level2, side: Side, n_levels: usize) -> Self {
82 let mut stats = Self {
83 n_analyzed_levels: 0,
84 total_value: Notional(0),
85 total_size: Quantity(0),
86 average_level_size: 0.0,
87 min_level_size: Quantity(u64::MAX),
88 max_level_size: Quantity(0),
89 std_dev_level_size: 0.0,
90 };
91
92 let n_levels = if n_levels == 0 { usize::MAX } else { n_levels };
93 let mut sizes = Vec::new();
94
95 match side {
96 Side::Buy => {
97 for (price, quantity) in level2.bid_levels().iter().take(n_levels) {
98 stats.observe_level(*price, *quantity);
99 sizes.push(*quantity);
100 }
101 }
102 Side::Sell => {
103 for (price, quantity) in level2.ask_levels().iter().take(n_levels) {
104 stats.observe_level(*price, *quantity);
105 sizes.push(*quantity);
106 }
107 }
108 }
109
110 if stats.is_empty() {
111 stats.min_level_size = Quantity(0);
112 return stats;
113 }
114
115 stats.average_level_size = stats.total_size.as_f64() / stats.n_analyzed_levels as f64;
116
117 let variance = sizes
118 .iter()
119 .map(|size| (size.as_f64() - stats.average_level_size).powi(2))
120 .sum::<f64>()
121 / stats.n_analyzed_levels as f64;
122 stats.std_dev_level_size = variance.sqrt();
123
124 stats
125 }
126
127 fn observe_level(&mut self, price: Price, quantity: Quantity) {
129 self.n_analyzed_levels += 1;
130 self.total_value = self.total_value.saturating_add(price * quantity);
131 self.total_size = self.total_size.saturating_add(quantity);
132 self.min_level_size = self.min_level_size.min(quantity);
133 self.max_level_size = self.max_level_size.max(quantity);
134 }
135
136 pub fn is_empty(&self) -> bool {
138 self.n_analyzed_levels == 0
139 }
140
141 pub fn n_analyzed_levels(&self) -> usize {
143 self.n_analyzed_levels
144 }
145
146 pub fn total_value(&self) -> Notional {
148 self.total_value
149 }
150
151 pub fn total_size(&self) -> Quantity {
153 self.total_size
154 }
155
156 pub fn average_level_size(&self) -> f64 {
158 self.average_level_size
159 }
160
161 pub fn min_level_size(&self) -> Quantity {
163 self.min_level_size
164 }
165
166 pub fn max_level_size(&self) -> Quantity {
168 self.max_level_size
169 }
170
171 pub fn std_dev_level_size(&self) -> f64 {
173 self.std_dev_level_size
174 }
175
176 pub fn vwap(&self) -> f64 {
178 self.total_value / self.total_size
179 }
180}