1use serde::{Deserialize, Deserializer, Serialize};
7use crate::common::side::Side;
8use crate::transaction::ActionMeta;
9
10#[derive(Clone, Debug, Deserialize, Serialize)]
17pub struct Matrix {
18 pub index: Vec<String>,
20 pub matrix: Vec<Vec<f64>>,
21
22 #[serde(skip)]
23 pub meta: ActionMeta,
24}
25
26#[derive(Debug, Clone, Deserialize)]
34#[serde(rename_all = "camelCase")]
35#[allow(unused)]
36pub struct Ticker {
37 pub symbol: String,
38 #[serde(deserialize_with = "f64_or_nan")]
39 pub last_price: f64,
40 #[serde(deserialize_with = "f64_or_nan")]
41 pub mark_price: f64,
42 #[serde(deserialize_with = "f64_or_nan")]
43 pub oracle_price: f64,
44 #[serde(deserialize_with = "f64_or_nan")]
45 pub price_change: f64,
46 #[serde(deserialize_with = "f64_or_nan")]
47 pub price_change_percent: f64,
48 #[serde(deserialize_with = "f64_or_nan")]
49 pub high_price: f64,
50 #[serde(deserialize_with = "f64_or_nan")]
51 pub low_price: f64,
52 #[serde(deserialize_with = "f64_or_nan")]
53 pub volume: f64,
54 #[serde(deserialize_with = "f64_or_nan")]
55 pub quote_volume: f64,
56 #[serde(deserialize_with = "f64_or_nan")]
57 pub open_interest: f64,
58 #[serde(deserialize_with = "f64_or_nan")]
59 pub funding_rate: f64,
60}
61
62#[derive(Debug, Clone,Deserialize)]
73#[allow(unused)]
74pub struct Candle {
75 #[serde(rename="t")]
76 pub open_time: u64,
77 #[serde(rename = "T")]
78 pub close_time: u64,
79 #[serde(skip)]
80 pub symbol: String,
81 #[serde(skip)]
82 pub interval: String,
83 #[serde(rename = "o")]
84 pub open: f64,
85 #[serde(rename = "h")]
86 pub high: f64,
87 #[serde(rename = "l")]
88 pub low: f64,
89 #[serde(rename = "c")]
90 pub close: f64,
91 #[serde(rename = "v")]
92 pub volume: f64,
93 #[serde(rename = "n")]
94 pub num_trades: u64,
95}
96
97
98#[derive(Debug, Clone,Deserialize)]
106#[allow(unused)]
107pub struct Trade {
108 #[serde(rename="time")]
109 pub timestamp: u64,
110 #[serde(rename="s")]
111 pub symbol: String,
112 #[serde(rename="b")]
113 pub side: Side,
114 #[serde(rename="sz")]
115 pub size: f64,
116 #[serde(rename="px")]
117 pub price: f64,
118 pub maker: String,
119 pub taker: String,
120}
121
122#[derive(Debug, Clone, PartialEq, Deserialize)]
128#[allow(unused)]
129pub struct OrderBookLevel {
130 #[serde(rename="px")]
132 pub price: f64,
133 #[serde(rename="sz")]
135 pub size: f64,
136 #[serde(rename="n")]
138 pub num_orders: u32,
139}
140
141impl std::fmt::Display for OrderBookLevel {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 write!(f, "{} @ {}", self.size, self.price)
144 }
145}
146
147
148#[derive(Debug, Clone, Deserialize)]
152#[allow(unused)]
153pub struct L2Snapshot {
154 pub timestamp: u64,
155 pub symbol: String,
156 pub levels: (Vec<OrderBookLevel>, Vec<OrderBookLevel>),
158}
159
160
161pub fn f64_or_nan<'de, D>(deserializer: D) -> Result<f64, D::Error>
167where
168 D: Deserializer<'de>,
169{
170 Ok(Option::<f64>::deserialize(deserializer)?.unwrap_or(f64::NAN))
171}
172
173
174#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_ticker_deserialize_with_nulls() {
245 let json = r#"{
246 "symbol": "BTC-USD",
247 "priceChange": 0.0,
248 "priceChangePercent": 0.0,
249 "lastPrice": 100000.1824189499,
250 "highPrice": 100000.52095557356,
251 "lowPrice": 99999.62809703613,
252 "volume": 0.0,
253 "quoteVolume": 0.0,
254 "markPrice": null,
255 "oraclePrice": null,
256 "openInterest": 0.0,
257 "fundingRate": 0.0
258 }"#;
259
260 let ticker: Ticker = serde_json::from_str(json).unwrap();
261
262 assert_eq!(ticker.symbol, "BTC-USD");
263 assert!((ticker.last_price - 100000.1824189499).abs() < 1e-6);
264 assert!((ticker.high_price - 100000.52095557356).abs() < 1e-6);
265 assert!((ticker.low_price - 99999.62809703613).abs() < 1e-6);
266 assert_eq!(ticker.price_change, 0.0);
267 assert_eq!(ticker.price_change_percent, 0.0);
268 assert_eq!(ticker.volume, 0.0);
269 assert_eq!(ticker.quote_volume, 0.0);
270 assert_eq!(ticker.open_interest, 0.0);
271 assert_eq!(ticker.funding_rate, 0.0);
272
273 assert!(ticker.mark_price.is_nan());
275 assert!(ticker.oracle_price.is_nan());
276 }
277
278 #[test]
279 fn test_ticker_deserialize_with_values() {
280 let json = r#"{
281 "symbol": "ETH-USD",
282 "priceChange": 10.5,
283 "priceChangePercent": 0.33,
284 "lastPrice": 3200.0,
285 "highPrice": 3250.0,
286 "lowPrice": 3150.0,
287 "volume": 1234.56,
288 "quoteVolume": 3950000.0,
289 "markPrice": 3201.5,
290 "oraclePrice": 3200.8,
291 "openInterest": 50000.0,
292 "fundingRate": 0.0001
293 }"#;
294
295 let ticker: Ticker = serde_json::from_str(json).unwrap();
296
297 assert_eq!(ticker.symbol, "ETH-USD");
298 assert!((ticker.mark_price - 3201.5).abs() < 1e-6);
299 assert!((ticker.oracle_price - 3200.8).abs() < 1e-6);
300 assert!(!ticker.mark_price.is_nan());
301 assert!(!ticker.oracle_price.is_nan());
302 }
303
304 #[test]
305 fn test_l2_snapshot_deserialize() {
306 let json = serde_json::json!({
307 "timestamp": 1770906894450242133u64,
308 "symbol": "ETH-USD",
309 "updateType": "snapshot",
310 "levels": [
311 [
312 {"px": 1988.36, "sz": 50.0000001, "n": 5},
313 {"px": 1988.35, "sz": 71.3240001, "n": 5},
314 {"px": 1988.34, "sz": 40.00000006, "n": 4},
315 {"px": 1988.32, "sz": 102.8540001, "n": 5},
316 {"px": 1988.31, "sz": 30.00000003, "n": 3},
317 {"px": 1988.30, "sz": 30.00000003, "n": 3},
318 {"px": 1988.29, "sz": 50.8950001, "n": 5},
319 {"px": 1988.28, "sz": 64.4280001, "n": 5},
320 {"px": 1988.27, "sz": 53.7410001, "n": 5},
321 {"px": 1988.25, "sz": 121.9040001, "n": 5}
322 ],
323 [
324 {"px": 1988.47, "sz": 10.0, "n": 1},
325 {"px": 1988.75, "sz": 70.0, "n": 7},
326 {"px": 1989.00, "sz": 230.00000006, "n": 23},
327 {"px": 1989.25, "sz": 140.0, "n": 14},
328 {"px": 1989.50, "sz": 440.25680003, "n": 44},
329 {"px": 1989.75, "sz": 230.00000007, "n": 23},
330 {"px": 1990.00, "sz": 200.0, "n": 20},
331 {"px": 1990.25, "sz": 230.00000009, "n": 23},
332 {"px": 1990.50, "sz": 340.0, "n": 34},
333 {"px": 1990.75, "sz": 250.0, "n": 25}
334 ]
335 ]
336 });
337
338 let snap: L2Snapshot = serde_json::from_value(json).expect("deserialize L2Snapshot");
339
340 assert_eq!(snap.symbol, "ETH-USD");
341 assert_eq!(snap.timestamp, 1770906894450242133);
342
343 let (bids, asks) = &snap.levels;
344 assert_eq!(bids.len(), 10);
345 assert_eq!(asks.len(), 10);
346
347 assert_eq!(bids[0].price, 1988.36);
349 assert_eq!(bids[0].size, 50.0000001);
350 assert_eq!(bids[0].num_orders, 5);
351
352 assert_eq!(asks[0].price, 1988.47);
354 assert_eq!(asks[0].size, 10.0);
355 assert_eq!(asks[0].num_orders, 1);
356
357 assert_eq!(bids[9].price, 1988.25);
359 assert_eq!(bids[9].size, 121.9040001);
360 assert_eq!(bids[9].num_orders, 5);
361
362 assert_eq!(asks[9].price, 1990.75);
364 assert_eq!(asks[9].size, 250.0);
365 assert_eq!(asks[9].num_orders, 25);
366 }
367}
368
369