1use rust_decimal::Decimal;
2use serde::{Deserialize, Deserializer, Serialize};
3
4use crate::Blockchain;
5
6#[derive(Debug, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Asset {
11 pub coingecko_id: Option<String>,
13 pub display_name: String,
15 pub symbol: String,
17 pub tokens: Vec<Token>,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct Ticker {
24 pub symbol: String,
25 pub first_price: Decimal,
26 pub last_price: Decimal,
27 pub price_change: Decimal,
28 pub price_change_percent: Decimal,
29 pub high: Decimal,
30 pub low: Decimal,
31 pub volume: Decimal,
32 pub trades: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct TickerUpdate {
39 #[serde(rename = "e")]
41 pub event_type: String,
42
43 #[serde(rename = "E")]
45 pub event_time: i64,
46
47 #[serde(rename = "s")]
49 pub symbol: String,
50
51 #[serde(rename = "a")]
52 pub ask_price: Decimal,
53
54 #[serde(rename = "A")]
55 pub ask_quantity: Decimal,
56
57 #[serde(rename = "b")]
58 pub bid_price: Decimal,
59
60 #[serde(rename = "B")]
61 pub bid_quantity: Decimal,
62
63 #[serde(rename = "u")]
65 pub update_id: u64,
66
67 #[serde(rename = "T")]
69 pub timestamp: u64,
70}
71
72#[derive(Debug, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct Market {
77 pub symbol: String,
79 pub base_symbol: String,
81 pub quote_symbol: String,
83 pub market_type: String,
86 pub filters: MarketFilters,
88}
89
90impl Market {
91 pub const fn price_decimal_places(&self) -> u32 {
95 self.filters.price.tick_size.scale()
96 }
97
98 pub const fn quantity_decimal_places(&self) -> u32 {
102 self.filters.quantity.step_size.scale()
103 }
104}
105
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct MarketFilters {
109 pub price: PriceFilter,
110 pub quantity: QuantityFilter,
111 pub leverage: Option<LeverageFilter>,
112}
113
114#[derive(Debug, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct PriceFilter {
117 pub min_price: Decimal,
119 pub max_price: Option<Decimal>,
121 pub tick_size: Decimal,
123 pub max_multiplier: Option<Decimal>,
125 pub min_multiplier: Option<Decimal>,
127 pub max_impact_multiplier: Option<Decimal>,
130 pub min_impact_multiplier: Option<Decimal>,
133 pub mean_mark_price_band: Option<PriceBandMarkPrice>,
136 pub mean_premium_band: Option<PriceBandPremium>,
139 pub borrow_entry_fee_max_multiplier: Option<Decimal>,
142 pub borrow_entry_fee_min_multiplier: Option<Decimal>,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct PriceBandMarkPrice {
150 pub max_multiplier: Decimal,
152 pub min_multiplier: Decimal,
154}
155
156#[derive(Debug, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct PriceBandPremium {
159 pub index_price: Option<Decimal>,
161 pub max_premium_pct: Option<Decimal>,
164 pub min_premium_pct: Option<Decimal>,
167 pub tolerance_pct: Decimal,
172}
173
174#[derive(Debug, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct QuantityFilter {
177 pub min_quantity: Decimal,
178 pub max_quantity: Option<Decimal>,
179 pub step_size: Decimal,
180}
181
182#[derive(Debug, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct LeverageFilter {
185 pub min_leverage: Decimal,
186 pub max_leverage: Decimal,
187 pub step_size: Decimal,
188}
189
190#[derive(Debug, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct Token {
193 pub blockchain: Blockchain,
194 pub contract_address: String,
195 pub deposit_enabled: bool,
196 pub display_name: String,
197 pub minimum_deposit: Decimal,
198 pub withdraw_enabled: bool,
199 pub minimum_withdrawal: Decimal,
200 pub maximum_withdrawal: Option<Decimal>,
201 pub withdrawal_fee: Decimal,
202}
203
204#[derive(Debug, Serialize, Deserialize, strum::AsRefStr)]
205pub enum OrderBookDepthLimit {
206 #[serde(rename = "5")]
207 #[strum(serialize = "5")]
208 Five,
209 #[serde(rename = "10")]
210 #[strum(serialize = "10")]
211 Ten,
212 #[serde(rename = "20")]
213 #[strum(serialize = "20")]
214 Twenty,
215 #[serde(rename = "50")]
216 #[strum(serialize = "50")]
217 Fifty,
218 #[serde(rename = "100")]
219 #[strum(serialize = "100")]
220 OneHundred,
221 #[serde(rename = "500")]
222 #[strum(serialize = "500")]
223 FiveHundred,
224 #[serde(rename = "1000")]
225 #[strum(serialize = "1000")]
226 OneThousand,
227}
228
229#[derive(Debug, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct OrderBookDepth {
232 pub asks: Vec<(Decimal, Decimal)>,
234 pub bids: Vec<(Decimal, Decimal)>,
236 #[serde(deserialize_with = "deserialize_str_or_i64")]
242 pub last_update_id: i64,
243 pub timestamp: i64,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct OrderBookDepthUpdate {
250 #[serde(rename = "e")]
252 pub event_type: String,
253
254 #[serde(rename = "E")]
256 pub event_time: i64,
257
258 #[serde(rename = "s")]
260 pub symbol: String,
261
262 #[serde(rename = "T")]
264 pub timestamp: i64,
265
266 #[serde(rename = "U")]
268 pub first_update_id: i64,
269
270 #[serde(rename = "u")]
272 pub last_update_id: i64,
273
274 #[serde(rename = "a")]
276 pub asks: Vec<(Decimal, Decimal)>,
277
278 #[serde(rename = "b")]
280 pub bids: Vec<(Decimal, Decimal)>,
281}
282
283#[derive(Debug, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct Kline {
286 pub start: String,
287 pub open: Option<Decimal>,
288 pub high: Option<Decimal>,
289 pub low: Option<Decimal>,
290 pub close: Option<Decimal>,
291 pub end: Option<String>,
292 pub volume: Decimal,
293 pub trades: String,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct KlineUpdate {
298 #[serde(rename = "e")]
300 pub event_type: String,
301
302 #[serde(rename = "E")]
304 pub event_time: i64,
305
306 #[serde(rename = "s")]
308 pub symbol: String,
309
310 #[serde(rename = "t")]
312 pub start_time: i64,
313
314 #[serde(rename = "T")]
316 pub end_time: i64,
317
318 #[serde(rename = "o")]
320 pub open_price: Decimal,
321
322 #[serde(rename = "c")]
324 pub close_price: Decimal,
325
326 #[serde(rename = "h")]
328 pub high_price: Decimal,
329
330 #[serde(rename = "l")]
332 pub low_price: Decimal,
333
334 #[serde(rename = "v")]
336 pub volume: Decimal,
337
338 #[serde(rename = "n")]
340 pub trades: u64,
341
342 #[serde(rename = "X")]
344 pub is_closed: bool,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct FundingRate {
350 pub symbol: String,
351 pub interval_end_timestamp: String,
352 pub funding_rate: Decimal,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct MarkPrice {
358 pub symbol: String,
359 pub funding_rate: Decimal,
360 pub index_price: Decimal,
361 pub mark_price: Decimal,
362 pub next_funding_timestamp: u64,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct MarkPriceUpdate {
368 #[serde(rename = "e")]
370 pub event_type: String,
371
372 #[serde(rename = "E")]
374 pub event_time: i64,
375
376 #[serde(rename = "s")]
378 pub symbol: String,
379
380 #[serde(rename = "p")]
382 pub mark_price: Decimal,
383
384 #[serde(rename = "f")]
386 pub funding_rate: Decimal,
387
388 #[serde(rename = "i")]
390 pub index_price: Decimal,
391
392 #[serde(rename = "n")]
394 pub funding_timestamp: u64,
395
396 #[serde(rename = "T")]
398 pub engine_timestamp: i64,
399}
400
401impl TryFrom<u32> for OrderBookDepthLimit {
402 type Error = &'static str;
403
404 fn try_from(value: u32) -> Result<Self, Self::Error> {
405 match value {
406 5 => Ok(OrderBookDepthLimit::Five),
407 10 => Ok(OrderBookDepthLimit::Ten),
408 20 => Ok(OrderBookDepthLimit::Twenty),
409 50 => Ok(OrderBookDepthLimit::Fifty),
410 100 => Ok(OrderBookDepthLimit::OneHundred),
411 500 => Ok(OrderBookDepthLimit::FiveHundred),
412 1000 => Ok(OrderBookDepthLimit::OneThousand),
413 _ => Err("Invalid OrderBookDepthLimit value"),
414 }
415 }
416}
417
418fn deserialize_str_or_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
420where
421 D: Deserializer<'de>,
422{
423 use serde::de::Visitor;
424 use std::fmt;
425
426 struct StringOrI64Visitor;
427
428 impl<'de> Visitor<'de> for StringOrI64Visitor {
429 type Value = i64;
430
431 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
432 formatter.write_str("a string or an integer")
433 }
434
435 fn visit_str<E>(self, value: &str) -> Result<i64, E>
436 where
437 E: serde::de::Error,
438 {
439 value.parse().map_err(serde::de::Error::custom)
440 }
441
442 fn visit_i64<E>(self, value: i64) -> Result<i64, E>
443 where
444 E: serde::de::Error,
445 {
446 Ok(value)
447 }
448
449 fn visit_u64<E>(self, value: u64) -> Result<i64, E>
450 where
451 E: serde::de::Error,
452 {
453 i64::try_from(value).map_err(|_| serde::de::Error::custom("value too large"))
454 }
455 }
456
457 deserializer.deserialize_any(StringOrI64Visitor)
458}
459
460#[cfg(test)]
461mod test {
462 use super::*;
463 use rust_decimal_macros::dec;
464
465 fn get_test_market() -> Market {
466 Market {
467 symbol: "TEST_MARKET".to_string(),
468 base_symbol: "TEST".to_string(),
469 quote_symbol: "MARKET".to_string(),
470 market_type: "SPOT".to_string(),
471 filters: super::MarketFilters {
472 price: PriceFilter {
473 min_price: dec!(0.0001),
474 max_price: None,
475 tick_size: dec!(0.0001),
476 min_multiplier: Some(dec!(1.25)),
477 max_multiplier: Some(dec!(0.75)),
478 max_impact_multiplier: Some(dec!(1.05)),
479 min_impact_multiplier: Some(dec!(0.95)),
480 mean_mark_price_band: None,
481 mean_premium_band: None,
482 borrow_entry_fee_max_multiplier: None,
483 borrow_entry_fee_min_multiplier: None,
484 },
485 quantity: QuantityFilter {
486 min_quantity: dec!(0.01),
487 max_quantity: None,
488 step_size: dec!(0.01),
489 },
490 leverage: None,
491 },
492 }
493 }
494
495 #[test]
496 fn test_decimal_places_on_price_filters_4() {
497 let market = get_test_market();
498 assert_eq!(market.price_decimal_places(), 4);
499 }
500
501 #[test]
502 fn test_decimal_places_on_quantity_filters() {
503 let market = get_test_market();
504 assert_eq!(market.quantity_decimal_places(), 2);
505 }
506
507 #[test]
508 fn test_mark_price_update_parse() {
509 let data = r#"
510{
511 "E": 1747291031914525,
512 "T": 1747291031910025,
513 "e": "markPrice",
514 "f": "-0.0000039641039274236048482914",
515 "i": "173.44031179",
516 "n": 1747296000000,
517 "p": "173.35998175",
518 "s": "SOL_USDC_PERP"
519}
520 "#;
521
522 let mark_price_update: MarkPriceUpdate = serde_json::from_str(data).unwrap();
523 assert_eq!(mark_price_update.symbol, "SOL_USDC_PERP".to_string());
524 assert_eq!(
525 mark_price_update.funding_rate,
526 dec!(-0.0000039641039274236048482914)
527 );
528 assert_eq!(mark_price_update.mark_price, dec!(173.35998175));
529 }
530
531 #[test]
532 fn test_kline_update_parse() {
533 let data = r#"
534{
535 "e": "kline",
536 "E": 1694687692980000,
537 "s": "SOL_USD",
538 "t": 123400000,
539 "T": 123460000,
540 "o": "18.75",
541 "c": "19.25",
542 "h": "19.80",
543 "l": "18.50",
544 "v": "32123",
545 "n": 93828,
546 "X": false
547}
548 "#;
549
550 let kline_update: KlineUpdate = serde_json::from_str(data).unwrap();
551 assert_eq!(kline_update.symbol, "SOL_USD".to_string());
552 assert_eq!(kline_update.start_time, 123400000);
553 assert_eq!(kline_update.open_price, dec!(18.75));
554 }
555
556 #[test]
557 fn test_order_book_depth_last_update_id_as_string() {
558 let data = r#"
559{
560 "asks": [["18.70", "0.000"]],
561 "bids": [["18.67", "0.832"]],
562 "lastUpdateId": "94978271",
563 "timestamp": 1694687965941000
564}
565 "#;
566
567 let depth: OrderBookDepth = serde_json::from_str(data).unwrap();
568 assert_eq!(depth.last_update_id, 94978271);
569 }
570
571 #[test]
572 fn test_order_book_depth_last_update_id_as_i64() {
573 let data = r#"
574{
575 "asks": [["18.70", "0.000"]],
576 "bids": [["18.67", "0.832"]],
577 "lastUpdateId": 94978271,
578 "timestamp": 1694687965941000
579}
580 "#;
581
582 let depth: OrderBookDepth = serde_json::from_str(data).unwrap();
583 assert_eq!(depth.last_update_id, 94978271);
584 }
585}