1use serde::Deserialize;
6
7use super::ExtraFields;
8use crate::model::NumberString;
9
10pub(super) fn array_value(values: &[NumberString], index: usize) -> NumberString {
12 values.get(index).cloned().unwrap_or_default()
13}
14
15#[derive(Debug, Clone, Default, Deserialize)]
19#[serde(rename_all = "camelCase")]
20#[non_exhaustive]
21pub struct TickerUpdate {
22 #[serde(default)]
24 pub inst_type: String,
25 #[serde(default)]
27 pub inst_id: String,
28 #[serde(default)]
30 pub last: NumberString,
31 #[serde(default)]
33 pub last_sz: NumberString,
34 #[serde(default)]
36 pub ask_px: NumberString,
37 #[serde(default)]
39 pub ask_sz: NumberString,
40 #[serde(default)]
42 pub bid_px: NumberString,
43 #[serde(default)]
45 pub bid_sz: NumberString,
46 #[serde(default)]
48 pub open24h: NumberString,
49 #[serde(default)]
51 pub high24h: NumberString,
52 #[serde(default)]
54 pub low24h: NumberString,
55 #[serde(default)]
57 pub vol_ccy24h: NumberString,
58 #[serde(default)]
60 pub vol24h: NumberString,
61 #[serde(default)]
63 pub sod_utc0: NumberString,
64 #[serde(default)]
66 pub sod_utc8: NumberString,
67 #[serde(default)]
69 pub ts: NumberString,
70 #[serde(flatten, default)]
72 pub extra: ExtraFields,
73}
74
75#[derive(Debug, Clone, Default, Deserialize)]
79#[serde(from = "Vec<NumberString>", rename_all = "camelCase")]
80#[non_exhaustive]
81pub struct CandleUpdate {
82 pub ts: NumberString,
84 pub o: NumberString,
86 pub h: NumberString,
88 pub l: NumberString,
90 pub c: NumberString,
92 pub vol: NumberString,
94 pub vol_ccy: NumberString,
96 pub vol_ccy_quote: NumberString,
99 pub confirm: NumberString,
101}
102
103impl From<Vec<NumberString>> for CandleUpdate {
104 fn from(values: Vec<NumberString>) -> Self {
105 Self {
106 ts: array_value(&values, 0),
107 o: array_value(&values, 1),
108 h: array_value(&values, 2),
109 l: array_value(&values, 3),
110 c: array_value(&values, 4),
111 vol: array_value(&values, 5),
112 vol_ccy: array_value(&values, 6),
113 vol_ccy_quote: array_value(&values, 7),
114 confirm: array_value(&values, 8),
115 }
116 }
117}
118
119#[derive(Debug, Clone, Default, Deserialize)]
123#[serde(rename_all = "camelCase")]
124#[non_exhaustive]
125pub struct TradeUpdate {
126 #[serde(default)]
128 pub inst_id: String,
129 #[serde(default)]
131 pub trade_id: String,
132 #[serde(default)]
134 pub px: NumberString,
135 #[serde(default)]
137 pub sz: NumberString,
138 #[serde(default)]
140 pub side: String,
141 #[serde(default)]
145 pub source: String,
146 #[serde(default)]
148 pub count: NumberString,
149 #[serde(default)]
151 pub ts: NumberString,
152 #[serde(flatten, default)]
154 pub extra: ExtraFields,
155}
156
157#[derive(Debug, Clone, Default, Deserialize)]
161#[serde(rename_all = "camelCase")]
162#[non_exhaustive]
163pub struct AllTradeUpdate {
164 #[serde(default)]
166 pub inst_id: String,
167 #[serde(default)]
169 pub trade_id: String,
170 #[serde(default)]
172 pub px: NumberString,
173 #[serde(default)]
177 pub sz: NumberString,
178 #[serde(default)]
180 pub side: String,
181 #[serde(default)]
183 pub source: String,
184 #[serde(default)]
186 pub ts: NumberString,
187 #[serde(flatten, default)]
189 pub extra: ExtraFields,
190}
191
192#[derive(Debug, Clone, Default, Deserialize)]
196#[serde(rename_all = "camelCase")]
197#[non_exhaustive]
198pub struct OptionTradeUpdate {
199 #[serde(default)]
201 pub inst_id: String,
202 #[serde(default)]
204 pub inst_family: String,
205 #[serde(default)]
207 pub trade_id: String,
208 #[serde(default)]
210 pub px: NumberString,
211 #[serde(default)]
213 pub sz: NumberString,
214 #[serde(default)]
216 pub side: String,
217 #[serde(default)]
219 pub opt_type: String,
220 #[serde(default)]
222 pub fill_vol: NumberString,
223 #[serde(default)]
225 pub fwd_px: NumberString,
226 #[serde(default)]
228 pub idx_px: NumberString,
229 #[serde(default)]
231 pub mark_px: NumberString,
232 #[serde(default)]
234 pub ts: NumberString,
235 #[serde(flatten, default)]
237 pub extra: ExtraFields,
238}
239
240#[derive(Debug, Clone, Default, Deserialize)]
244#[serde(rename_all = "camelCase")]
245#[non_exhaustive]
246pub struct CallAuctionDetailsUpdate {
247 #[serde(default)]
249 pub inst_id: String,
250 #[serde(default)]
252 pub eq_px: NumberString,
253 #[serde(default)]
255 pub matched_sz: NumberString,
256 #[serde(default)]
258 pub unmatched_sz: NumberString,
259 #[serde(default)]
261 pub auction_end_time: NumberString,
262 #[serde(default)]
267 pub state: String,
268 #[serde(default)]
270 pub ts: NumberString,
271 #[serde(flatten, default)]
273 pub extra: ExtraFields,
274}
275
276#[derive(Debug, Clone, Default, Deserialize)]
280#[serde(rename_all = "camelCase")]
281#[non_exhaustive]
282pub struct OrderBookUpdate {
283 #[serde(default)]
285 pub asks: Vec<BookLevel>,
286 #[serde(default)]
288 pub bids: Vec<BookLevel>,
289 #[serde(default)]
291 pub checksum: i64,
292 #[serde(default)]
296 pub prev_seq_id: i64,
297 #[serde(default)]
299 pub seq_id: i64,
300 #[serde(default)]
302 pub ts: NumberString,
303 #[serde(flatten, default)]
305 pub extra: ExtraFields,
306}
307
308#[derive(Debug, Clone, Default, Deserialize)]
310#[serde(from = "Vec<NumberString>", rename_all = "camelCase")]
311#[non_exhaustive]
312pub struct BookLevel {
313 pub price: NumberString,
315
316 pub size: NumberString,
318
319 pub liquidated_order_count: NumberString,
321
322 pub order_count: NumberString,
324}
325
326impl From<Vec<NumberString>> for BookLevel {
327 fn from(values: Vec<NumberString>) -> Self {
328 Self {
329 price: array_value(&values, 0),
330 size: array_value(&values, 1),
331 liquidated_order_count: array_value(&values, 2),
332 order_count: array_value(&values, 3),
333 }
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn parses_ticker_and_retains_new_fields() {
343 let row: TickerUpdate = serde_json::from_str(
344 r#"{"instType":"SPOT","instId":"BTC-USDT","last":"1","ts":"2","futureField":"ok"}"#,
345 )
346 .unwrap();
347 assert_eq!(row.inst_id, "BTC-USDT");
348 assert_eq!(row.extra["futureField"], "ok");
349 }
350
351 #[test]
352 fn parses_market_candle_array() {
353 let row: CandleUpdate =
354 serde_json::from_str(r#"["1","2","3","4","5","6","7","8","1"]"#).unwrap();
355 assert_eq!(row.ts.as_str(), "1");
356 assert_eq!(row.vol.as_str(), "6");
357 assert_eq!(row.confirm.as_str(), "1");
358 }
359
360 #[test]
361 fn parses_all_trade_update() {
362 let row: AllTradeUpdate = serde_json::from_str(
363 r#"{"instId":"BTC-USDT","tradeId":"130639474","px":"42219.9","sz":"0.12060306","side":"buy","source":"0","ts":"1630048897897"}"#,
364 )
365 .unwrap();
366 assert_eq!(row.inst_id, "BTC-USDT");
367 assert_eq!(row.trade_id, "130639474");
368 assert_eq!(row.source, "0");
369 assert_eq!(row.ts.as_str(), "1630048897897");
370 }
371
372 #[test]
373 fn parses_option_trade_update() {
374 let row: OptionTradeUpdate = serde_json::from_str(
375 r#"{"fillVol":"0.5066007836914062","fwdPx":"16469.69928595038","idxPx":"16537.2","instFamily":"BTC-USD","instId":"BTC-USD-230224-18000-C","markPx":"0.04690107010619562","optType":"C","px":"0.045","side":"sell","sz":"2","tradeId":"38","ts":"1672286551080"}"#,
376 )
377 .unwrap();
378 assert_eq!(row.inst_id, "BTC-USD-230224-18000-C");
379 assert_eq!(row.inst_family, "BTC-USD");
380 assert_eq!(row.opt_type, "C");
381 assert_eq!(row.fill_vol.as_str(), "0.5066007836914062");
382 }
383}