1#[derive(Debug, Clone, Copy, Default)]
9#[repr(C)]
10pub struct Level {
11 pub px_1e9: u64, pub sz_1e8: u64, }
14
15impl Level {
16 pub const EMPTY: Self = Self {
17 px_1e9: 0,
18 sz_1e8: 0,
19 };
20
21 #[inline(always)]
22 pub fn is_valid(&self) -> bool {
23 self.px_1e9 > 0
24 }
25}
26
27#[derive(Clone, Copy)]
34#[repr(C)]
35pub struct L2Book {
36 pub bids: [Level; 20], pub asks: [Level; 20], pub bid_ct: u8, pub ask_ct: u8, pub symbol_id: u16,
41 pub _pad: u32,
42 pub recv_ns: u64, }
44
45impl Default for L2Book {
46 fn default() -> Self {
47 Self {
48 bids: [Level::EMPTY; 20],
49 asks: [Level::EMPTY; 20],
50 bid_ct: 0,
51 ask_ct: 0,
52 symbol_id: 0,
53 _pad: 0,
54 recv_ns: 0,
55 }
56 }
57}
58
59impl L2Book {
60 #[inline(always)]
61 pub fn best_bid(&self) -> Option<&Level> {
62 if self.bid_ct > 0 && self.bids[0].px_1e9 > 0 {
63 Some(&self.bids[0])
64 } else {
65 None
66 }
67 }
68
69 #[inline(always)]
70 pub fn best_ask(&self) -> Option<&Level> {
71 if self.ask_ct > 0 && self.asks[0].px_1e9 > 0 {
72 Some(&self.asks[0])
73 } else {
74 None
75 }
76 }
77
78 #[inline(always)]
79 pub fn mid_px_1e9(&self) -> u64 {
80 if self.bid_ct == 0 || self.ask_ct == 0 {
81 return 0;
82 }
83 (self.bids[0].px_1e9 + self.asks[0].px_1e9) / 2
84 }
85
86 #[inline(always)]
90 pub fn spread_1e9(&self) -> u64 {
91 if self.bid_ct == 0 || self.ask_ct == 0 {
92 return u64::MAX;
93 }
94 self.asks[0].px_1e9.saturating_sub(self.bids[0].px_1e9)
95 }
96
97 #[inline(always)]
99 pub fn spread_signed_1e9(&self) -> i64 {
100 if self.bid_ct == 0 || self.ask_ct == 0 {
101 return i64::MAX;
102 }
103 self.asks[0].px_1e9 as i64 - self.bids[0].px_1e9 as i64
104 }
105
106 #[inline(always)]
110 pub fn spread_bps(&self) -> u32 {
111 let mid = self.mid_px_1e9();
112 if mid == 0 {
113 return u32::MAX;
114 }
115 ((self.spread_1e9() * 10_000) / mid) as u32
116 }
117
118 #[inline(always)]
122 pub fn spread_bps_x1000(&self) -> i32 {
123 let mid = self.mid_px_1e9();
124 if mid == 0 {
125 return i32::MAX;
126 }
127 let spread = self.asks[0].px_1e9 as i128 - self.bids[0].px_1e9 as i128;
128 ((spread * 10_000_000) / mid as i128) as i32
129 }
130
131 #[inline(always)]
134 pub fn is_crossed(&self) -> bool {
135 self.bid_ct > 0 && self.ask_ct > 0 && self.bids[0].px_1e9 > self.asks[0].px_1e9
136 }
137
138 #[inline(always)]
139 pub fn bid_depth_1e8(&self, levels: usize) -> u64 {
140 let n = levels.min(self.bid_ct as usize);
141 let mut sum = 0u64;
142 for i in 0..n {
143 sum += self.bids[i].sz_1e8;
144 }
145 sum
146 }
147
148 #[inline(always)]
149 pub fn ask_depth_1e8(&self, levels: usize) -> u64 {
150 let n = levels.min(self.ask_ct as usize);
151 let mut sum = 0u64;
152 for i in 0..n {
153 sum += self.asks[i].sz_1e8;
154 }
155 sum
156 }
157
158 #[inline(always)]
159 pub fn imbalance_bps(&self, levels: usize) -> i32 {
160 let bid_depth = self.bid_depth_1e8(levels);
161 let ask_depth = self.ask_depth_1e8(levels);
162 let total = bid_depth + ask_depth;
163 if total == 0 {
164 return 0;
165 }
166 (((bid_depth as i64 - ask_depth as i64) * 10_000) / total as i64) as i32
167 }
168}