1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9
10#[skip_serializing_none]
12#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
13pub struct BookSummary {
14 pub instrument_name: String,
16 pub base_currency: String,
18 pub quote_currency: String,
20 pub volume: f64,
22 pub volume_usd: f64,
24 pub open_interest: f64,
26 pub price_change: Option<f64>,
28 pub mark_price: f64,
30 pub mark_iv: Option<f64>,
32 pub bid_price: Option<f64>,
34 pub ask_price: Option<f64>,
36 pub mid_price: Option<f64>,
38 pub last: Option<f64>,
40 pub high: Option<f64>,
42 pub low: Option<f64>,
44 pub estimated_delivery_price: Option<f64>,
46 pub current_funding: Option<f64>,
48 pub funding_8h: Option<f64>,
50 pub creation_timestamp: i64,
52 pub underlying_index: Option<String>,
54 pub underlying_price: Option<f64>,
56 pub interest_rate: Option<f64>,
58}
59
60impl BookSummary {
61 pub fn new(
63 instrument_name: String,
64 base_currency: String,
65 quote_currency: String,
66 mark_price: f64,
67 creation_timestamp: i64,
68 ) -> Self {
69 Self {
70 instrument_name,
71 base_currency,
72 quote_currency,
73 volume: 0.0,
74 volume_usd: 0.0,
75 open_interest: 0.0,
76 price_change: None,
77 mark_price,
78 mark_iv: None,
79 bid_price: None,
80 ask_price: None,
81 mid_price: None,
82 last: None,
83 high: None,
84 low: None,
85 estimated_delivery_price: None,
86 current_funding: None,
87 funding_8h: None,
88 creation_timestamp,
89 underlying_index: None,
91 underlying_price: None,
92 interest_rate: None,
93 }
94 }
95
96 pub fn with_volume(mut self, volume: f64, volume_usd: f64) -> Self {
98 self.volume = volume;
99 self.volume_usd = volume_usd;
100 self
101 }
102
103 pub fn with_prices(
105 mut self,
106 bid: Option<f64>,
107 ask: Option<f64>,
108 last: Option<f64>,
109 high: Option<f64>,
110 low: Option<f64>,
111 ) -> Self {
112 self.bid_price = bid;
113 self.ask_price = ask;
114 self.last = last;
115 self.high = high;
116 self.low = low;
117
118 if let (Some(bid), Some(ask)) = (bid, ask) {
120 self.mid_price = Some((bid + ask) / 2.0);
121 }
122
123 self
124 }
125
126 pub fn with_open_interest(mut self, open_interest: f64) -> Self {
128 self.open_interest = open_interest;
129 self
130 }
131
132 pub fn with_price_change(mut self, price_change: f64) -> Self {
134 self.price_change = Some(price_change);
135 self
136 }
137
138 pub fn with_iv(mut self, mark_iv: f64) -> Self {
140 self.mark_iv = Some(mark_iv);
141 self
142 }
143
144 pub fn with_funding(mut self, current: f64, funding_8h: f64) -> Self {
146 self.current_funding = Some(current);
147 self.funding_8h = Some(funding_8h);
148 self
149 }
150
151 pub fn with_delivery_price(mut self, price: f64) -> Self {
153 self.estimated_delivery_price = Some(price);
154 self
155 }
156
157 pub fn spread(&self) -> Option<f64> {
159 match (self.bid_price, self.ask_price) {
160 (Some(bid), Some(ask)) => Some(ask - bid),
161 _ => None,
162 }
163 }
164
165 pub fn spread_percentage(&self) -> Option<f64> {
167 match (self.spread(), self.mid_price) {
168 (Some(spread), Some(mid)) if mid > 0.0 => Some((spread / mid) * 100.0),
169 _ => None,
170 }
171 }
172
173 pub fn is_perpetual(&self) -> bool {
175 self.instrument_name.contains("PERPETUAL")
176 }
177
178 pub fn is_option(&self) -> bool {
180 !self.is_perpetual()
182 && (self.instrument_name.ends_with("-C") || self.instrument_name.ends_with("-P"))
183 }
184
185 pub fn is_future(&self) -> bool {
187 !self.is_perpetual() && !self.is_option()
188 }
189
190 pub fn price_change_absolute(&self) -> Option<f64> {
192 self.price_change.map(|change| {
193 if let Some(last) = self.last {
194 last * (change / 100.0)
195 } else {
196 self.mark_price * (change / 100.0)
197 }
198 })
199 }
200}
201
202#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
204pub struct BookSummaries {
205 pub summaries: Vec<BookSummary>,
207}
208
209impl BookSummaries {
210 pub fn new() -> Self {
212 Self {
213 summaries: Vec::new(),
214 }
215 }
216
217 pub fn add(&mut self, summary: BookSummary) {
219 self.summaries.push(summary);
220 }
221
222 pub fn by_currency(&self, currency: String) -> Vec<&BookSummary> {
224 self.summaries
225 .iter()
226 .filter(|s| s.base_currency == currency)
227 .collect()
228 }
229
230 pub fn perpetuals(&self) -> Vec<&BookSummary> {
232 self.summaries.iter().filter(|s| s.is_perpetual()).collect()
233 }
234
235 pub fn options(&self) -> Vec<&BookSummary> {
237 self.summaries.iter().filter(|s| s.is_option()).collect()
238 }
239
240 pub fn futures(&self) -> Vec<&BookSummary> {
242 self.summaries.iter().filter(|s| s.is_future()).collect()
243 }
244
245 pub fn sort_by_volume(&mut self) {
247 self.summaries
248 .sort_by(|a, b| b.volume_usd.partial_cmp(&a.volume_usd).unwrap());
249 }
250
251 pub fn sort_by_open_interest(&mut self) {
253 self.summaries
254 .sort_by(|a, b| b.open_interest.partial_cmp(&a.open_interest).unwrap());
255 }
256}
257
258impl Default for BookSummaries {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
266pub struct OrderBookEntry {
267 pub price: f64,
269 pub amount: f64,
271}
272
273impl OrderBookEntry {
274 pub fn new(price: f64, amount: f64) -> Self {
276 Self { price, amount }
277 }
278
279 pub fn notional(&self) -> f64 {
281 self.price * self.amount
282 }
283}
284
285#[skip_serializing_none]
287#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
288pub struct OrderBook {
289 pub instrument_name: String,
291 pub timestamp: i64,
293 pub bids: Vec<OrderBookEntry>,
295 pub asks: Vec<OrderBookEntry>,
297 pub change_id: u64,
299 pub prev_change_id: Option<u64>,
301}
302
303impl OrderBook {
304 pub fn new(instrument_name: String, timestamp: i64, change_id: u64) -> Self {
306 Self {
307 instrument_name,
308 timestamp,
309 bids: Vec::new(),
310 asks: Vec::new(),
311 change_id,
312 prev_change_id: None,
313 }
314 }
315
316 pub fn best_bid(&self) -> Option<f64> {
318 self.bids.first().map(|entry| entry.price)
319 }
320
321 pub fn best_ask(&self) -> Option<f64> {
323 self.asks.first().map(|entry| entry.price)
324 }
325
326 pub fn spread(&self) -> Option<f64> {
328 match (self.best_ask(), self.best_bid()) {
329 (Some(ask), Some(bid)) => Some(ask - bid),
330 _ => None,
331 }
332 }
333
334 pub fn mid_price(&self) -> Option<f64> {
336 match (self.best_ask(), self.best_bid()) {
337 (Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
338 _ => None,
339 }
340 }
341
342 pub fn total_bid_volume(&self) -> f64 {
344 self.bids.iter().map(|entry| entry.amount).sum()
345 }
346
347 pub fn total_ask_volume(&self) -> f64 {
349 self.asks.iter().map(|entry| entry.amount).sum()
350 }
351
352 pub fn volume_at_price(&self, price: f64, is_bid: bool) -> f64 {
354 let levels = if is_bid { &self.bids } else { &self.asks };
355 levels
356 .iter()
357 .find(|entry| (entry.price - price).abs() < f64::EPSILON)
358 .map(|entry| entry.amount)
359 .unwrap_or(0.0)
360 }
361}