1use serde::{Deserialize, Serialize};
6
7use crate::types::{OrderType, RateLimitInterval, RateLimitType, SymbolPermission, SymbolStatus};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct ServerTime {
13 pub server_time: u64,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ExchangeInfo {
21 pub timezone: String,
23 pub server_time: u64,
25 pub rate_limits: Vec<RateLimit>,
27 pub symbols: Vec<Symbol>,
29 #[serde(default)]
31 pub exchange_filters: Vec<SymbolFilter>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct RateLimit {
38 pub rate_limit_type: RateLimitType,
40 pub interval: RateLimitInterval,
42 pub interval_num: i32,
44 pub limit: i32,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct Symbol {
52 pub symbol: String,
54 pub status: SymbolStatus,
56 pub base_asset: String,
58 pub base_asset_precision: u8,
60 pub quote_asset: String,
62 pub quote_precision: u8,
64 pub quote_asset_precision: u8,
66 #[serde(default)]
68 pub base_commission_precision: u8,
69 #[serde(default)]
71 pub quote_commission_precision: u8,
72 pub order_types: Vec<OrderType>,
74 pub iceberg_allowed: bool,
76 pub oco_allowed: bool,
78 #[serde(default)]
80 pub quote_order_qty_market_allowed: bool,
81 #[serde(default = "default_true")]
83 pub is_spot_trading_allowed: bool,
84 #[serde(default)]
86 pub is_margin_trading_allowed: bool,
87 pub filters: Vec<SymbolFilter>,
89 #[serde(default)]
91 pub permissions: Vec<SymbolPermission>,
92}
93
94fn default_true() -> bool {
95 true
96}
97
98impl Symbol {
99 pub fn lot_size(&self) -> Option<&SymbolFilter> {
101 self.filters
102 .iter()
103 .find(|f| matches!(f, SymbolFilter::LotSize { .. }))
104 }
105
106 pub fn price_filter(&self) -> Option<&SymbolFilter> {
108 self.filters
109 .iter()
110 .find(|f| matches!(f, SymbolFilter::PriceFilter { .. }))
111 }
112
113 pub fn min_notional(&self) -> Option<&SymbolFilter> {
115 self.filters
116 .iter()
117 .find(|f| matches!(f, SymbolFilter::MinNotional { .. }))
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
123#[serde(tag = "filterType")]
124pub enum SymbolFilter {
125 #[serde(rename = "PRICE_FILTER")]
127 #[serde(rename_all = "camelCase")]
128 PriceFilter {
129 #[serde(with = "string_or_float")]
131 min_price: f64,
132 #[serde(with = "string_or_float")]
134 max_price: f64,
135 #[serde(with = "string_or_float")]
137 tick_size: f64,
138 },
139 #[serde(rename = "PERCENT_PRICE")]
141 #[serde(rename_all = "camelCase")]
142 PercentPrice {
143 #[serde(with = "string_or_float")]
145 multiplier_up: f64,
146 #[serde(with = "string_or_float")]
148 multiplier_down: f64,
149 avg_price_mins: u64,
151 },
152 #[serde(rename = "LOT_SIZE")]
154 #[serde(rename_all = "camelCase")]
155 LotSize {
156 #[serde(with = "string_or_float")]
158 min_qty: f64,
159 #[serde(with = "string_or_float")]
161 max_qty: f64,
162 #[serde(with = "string_or_float")]
164 step_size: f64,
165 },
166 #[serde(rename = "MARKET_LOT_SIZE")]
168 #[serde(rename_all = "camelCase")]
169 MarketLotSize {
170 #[serde(with = "string_or_float")]
172 min_qty: f64,
173 #[serde(with = "string_or_float")]
175 max_qty: f64,
176 #[serde(with = "string_or_float")]
178 step_size: f64,
179 },
180 #[serde(rename = "MIN_NOTIONAL")]
182 #[serde(rename_all = "camelCase")]
183 MinNotional {
184 #[serde(with = "string_or_float")]
186 min_notional: f64,
187 apply_to_market: bool,
189 avg_price_mins: u64,
191 },
192 #[serde(rename = "NOTIONAL")]
194 #[serde(rename_all = "camelCase")]
195 Notional {
196 #[serde(with = "string_or_float")]
198 min_notional: f64,
199 apply_min_to_market: bool,
201 #[serde(with = "string_or_float")]
203 max_notional: f64,
204 apply_max_to_market: bool,
206 avg_price_mins: u64,
208 },
209 #[serde(rename = "ICEBERG_PARTS")]
211 #[serde(rename_all = "camelCase")]
212 IcebergParts {
213 limit: u16,
215 },
216 #[serde(rename = "MAX_NUM_ORDERS")]
218 #[serde(rename_all = "camelCase")]
219 MaxNumOrders {
220 max_num_orders: u16,
222 },
223 #[serde(rename = "MAX_NUM_ALGO_ORDERS")]
225 #[serde(rename_all = "camelCase")]
226 MaxNumAlgoOrders {
227 max_num_algo_orders: u16,
229 },
230 #[serde(rename = "MAX_NUM_ICEBERG_ORDERS")]
232 #[serde(rename_all = "camelCase")]
233 MaxNumIcebergOrders {
234 max_num_iceberg_orders: u16,
236 },
237 #[serde(rename = "MAX_POSITION")]
239 #[serde(rename_all = "camelCase")]
240 MaxPosition {
241 #[serde(with = "string_or_float")]
243 max_position: f64,
244 },
245 #[serde(rename = "EXCHANGE_MAX_NUM_ORDERS")]
247 #[serde(rename_all = "camelCase")]
248 ExchangeMaxNumOrders {
249 max_num_orders: u16,
251 },
252 #[serde(rename = "EXCHANGE_MAX_NUM_ALGO_ORDERS")]
254 #[serde(rename_all = "camelCase")]
255 ExchangeMaxNumAlgoOrders {
256 max_num_algo_orders: u16,
258 },
259 #[serde(rename = "TRAILING_DELTA")]
261 #[serde(rename_all = "camelCase")]
262 TrailingDelta {
263 min_trailing_above_delta: u32,
265 max_trailing_above_delta: u32,
267 min_trailing_below_delta: u32,
269 max_trailing_below_delta: u32,
271 },
272 #[serde(other)]
274 Other,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct OrderBook {
281 pub last_update_id: u64,
283 pub bids: Vec<OrderBookEntry>,
285 pub asks: Vec<OrderBookEntry>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct OrderBookEntry {
292 #[serde(with = "string_or_float")]
294 pub price: f64,
295 #[serde(with = "string_or_float")]
297 pub quantity: f64,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct Trade {
304 pub id: u64,
306 #[serde(with = "string_or_float")]
308 pub price: f64,
309 #[serde(rename = "qty", with = "string_or_float")]
311 pub quantity: f64,
312 #[serde(rename = "quoteQty", with = "string_or_float")]
314 pub quote_quantity: f64,
315 pub time: u64,
317 pub is_buyer_maker: bool,
319 pub is_best_match: bool,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct AggTrade {
326 #[serde(rename = "a")]
328 pub agg_trade_id: u64,
329 #[serde(rename = "p", with = "string_or_float")]
331 pub price: f64,
332 #[serde(rename = "q", with = "string_or_float")]
334 pub quantity: f64,
335 #[serde(rename = "f")]
337 pub first_trade_id: u64,
338 #[serde(rename = "l")]
340 pub last_trade_id: u64,
341 #[serde(rename = "T")]
343 pub timestamp: u64,
344 #[serde(rename = "m")]
346 pub is_buyer_maker: bool,
347 #[serde(rename = "M")]
349 pub is_best_match: bool,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
354pub struct Kline {
355 pub open_time: i64,
357 pub open: f64,
359 pub high: f64,
361 pub low: f64,
363 pub close: f64,
365 pub volume: f64,
367 pub close_time: i64,
369 pub quote_asset_volume: f64,
371 pub number_of_trades: i64,
373 pub taker_buy_base_asset_volume: f64,
375 pub taker_buy_quote_asset_volume: f64,
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub struct Ticker24h {
383 pub symbol: String,
385 #[serde(with = "string_or_float")]
387 pub price_change: f64,
388 #[serde(with = "string_or_float")]
390 pub price_change_percent: f64,
391 #[serde(with = "string_or_float")]
393 pub weighted_avg_price: f64,
394 #[serde(with = "string_or_float")]
396 pub prev_close_price: f64,
397 #[serde(with = "string_or_float")]
399 pub last_price: f64,
400 #[serde(with = "string_or_float")]
402 pub last_qty: f64,
403 #[serde(with = "string_or_float")]
405 pub bid_price: f64,
406 #[serde(with = "string_or_float")]
408 pub bid_qty: f64,
409 #[serde(with = "string_or_float")]
411 pub ask_price: f64,
412 #[serde(with = "string_or_float")]
414 pub ask_qty: f64,
415 #[serde(with = "string_or_float")]
417 pub open_price: f64,
418 #[serde(with = "string_or_float")]
420 pub high_price: f64,
421 #[serde(with = "string_or_float")]
423 pub low_price: f64,
424 #[serde(with = "string_or_float")]
426 pub volume: f64,
427 #[serde(with = "string_or_float")]
429 pub quote_volume: f64,
430 pub open_time: u64,
432 pub close_time: u64,
434 pub first_id: i64,
436 pub last_id: i64,
438 pub count: u64,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct TradingDayTicker {
446 pub symbol: String,
448 #[serde(with = "string_or_float")]
450 pub price_change: f64,
451 #[serde(with = "string_or_float")]
453 pub price_change_percent: f64,
454 #[serde(with = "string_or_float")]
456 pub weighted_avg_price: f64,
457 #[serde(with = "string_or_float")]
459 pub open_price: f64,
460 #[serde(with = "string_or_float")]
462 pub high_price: f64,
463 #[serde(with = "string_or_float")]
465 pub low_price: f64,
466 #[serde(with = "string_or_float")]
468 pub last_price: f64,
469 #[serde(with = "string_or_float")]
471 pub volume: f64,
472 #[serde(with = "string_or_float")]
474 pub quote_volume: f64,
475 pub open_time: u64,
477 pub close_time: u64,
479 pub first_id: i64,
481 pub last_id: i64,
483 pub count: u64,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "camelCase")]
490pub struct TradingDayTickerMini {
491 pub symbol: String,
493 #[serde(with = "string_or_float")]
495 pub open_price: f64,
496 #[serde(with = "string_or_float")]
498 pub high_price: f64,
499 #[serde(with = "string_or_float")]
501 pub low_price: f64,
502 #[serde(with = "string_or_float")]
504 pub last_price: f64,
505 #[serde(with = "string_or_float")]
507 pub volume: f64,
508 #[serde(with = "string_or_float")]
510 pub quote_volume: f64,
511 pub open_time: u64,
513 pub close_time: u64,
515 pub first_id: i64,
517 pub last_id: i64,
519 pub count: u64,
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct RollingWindowTicker {
527 pub symbol: String,
529 #[serde(with = "string_or_float")]
531 pub price_change: f64,
532 #[serde(with = "string_or_float")]
534 pub price_change_percent: f64,
535 #[serde(with = "string_or_float")]
537 pub weighted_avg_price: f64,
538 #[serde(with = "string_or_float")]
540 pub open_price: f64,
541 #[serde(with = "string_or_float")]
543 pub high_price: f64,
544 #[serde(with = "string_or_float")]
546 pub low_price: f64,
547 #[serde(with = "string_or_float")]
549 pub last_price: f64,
550 #[serde(with = "string_or_float")]
552 pub volume: f64,
553 #[serde(with = "string_or_float")]
555 pub quote_volume: f64,
556 pub open_time: u64,
558 pub close_time: u64,
560 pub first_id: i64,
562 pub last_id: i64,
564 pub count: u64,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct RollingWindowTickerMini {
572 pub symbol: String,
574 #[serde(with = "string_or_float")]
576 pub open_price: f64,
577 #[serde(with = "string_or_float")]
579 pub high_price: f64,
580 #[serde(with = "string_or_float")]
582 pub low_price: f64,
583 #[serde(with = "string_or_float")]
585 pub last_price: f64,
586 #[serde(with = "string_or_float")]
588 pub volume: f64,
589 #[serde(with = "string_or_float")]
591 pub quote_volume: f64,
592 pub open_time: u64,
594 pub close_time: u64,
596 pub first_id: i64,
598 pub last_id: i64,
600 pub count: u64,
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
606pub struct TickerPrice {
607 pub symbol: String,
609 #[serde(with = "string_or_float")]
611 pub price: f64,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
616#[serde(rename_all = "camelCase")]
617pub struct BookTicker {
618 pub symbol: String,
620 #[serde(with = "string_or_float")]
622 pub bid_price: f64,
623 #[serde(with = "string_or_float")]
625 pub bid_qty: f64,
626 #[serde(with = "string_or_float")]
628 pub ask_price: f64,
629 #[serde(with = "string_or_float")]
631 pub ask_qty: f64,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct AveragePrice {
637 pub mins: u64,
639 #[serde(with = "string_or_float")]
641 pub price: f64,
642}
643
644pub mod string_or_float {
648 use serde::{Deserialize, Deserializer, Serializer, de};
649 use std::fmt;
650
651 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
652 where
653 T: fmt::Display,
654 S: Serializer,
655 {
656 serializer.collect_str(value)
657 }
658
659 pub fn deserialize<'de, D>(deserializer: D) -> Result<f64, D::Error>
660 where
661 D: Deserializer<'de>,
662 {
663 #[derive(Deserialize)]
664 #[serde(untagged)]
665 enum StringOrFloat {
666 String(String),
667 Float(f64),
668 }
669
670 match StringOrFloat::deserialize(deserializer)? {
671 StringOrFloat::String(s) => s.parse().map_err(de::Error::custom),
672 StringOrFloat::Float(f) => Ok(f),
673 }
674 }
675}
676
677pub mod string_or_float_opt {
679 use serde::{Deserialize, Deserializer, Serializer};
680 use std::fmt;
681
682 pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
683 where
684 T: fmt::Display,
685 S: Serializer,
686 {
687 match value {
688 Some(v) => super::string_or_float::serialize(v, serializer),
689 None => serializer.serialize_none(),
690 }
691 }
692
693 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
694 where
695 D: Deserializer<'de>,
696 {
697 #[derive(Deserialize)]
698 #[serde(untagged)]
699 enum StringOrFloat {
700 String(String),
701 Float(f64),
702 Null,
703 }
704
705 match StringOrFloat::deserialize(deserializer)? {
706 StringOrFloat::String(s) if s.is_empty() => Ok(None),
707 StringOrFloat::String(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
708 StringOrFloat::Float(f) => Ok(Some(f)),
709 StringOrFloat::Null => Ok(None),
710 }
711 }
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717
718 #[test]
719 fn test_server_time_deserialize() {
720 let json = r#"{"serverTime": 1234567890123}"#;
721 let time: ServerTime = serde_json::from_str(json).unwrap();
722 assert_eq!(time.server_time, 1234567890123);
723 }
724
725 #[test]
726 fn test_ticker_price_deserialize() {
727 let json = r#"{"symbol": "BTCUSDT", "price": "50000.00"}"#;
728 let ticker: TickerPrice = serde_json::from_str(json).unwrap();
729 assert_eq!(ticker.symbol, "BTCUSDT");
730 assert_eq!(ticker.price, 50000.0);
731 }
732
733 #[test]
734 fn test_book_ticker_deserialize() {
735 let json = r#"{
736 "symbol": "BTCUSDT",
737 "bidPrice": "50000.00",
738 "bidQty": "1.5",
739 "askPrice": "50001.00",
740 "askQty": "2.0"
741 }"#;
742 let ticker: BookTicker = serde_json::from_str(json).unwrap();
743 assert_eq!(ticker.symbol, "BTCUSDT");
744 assert_eq!(ticker.bid_price, 50000.0);
745 assert_eq!(ticker.bid_qty, 1.5);
746 assert_eq!(ticker.ask_price, 50001.0);
747 assert_eq!(ticker.ask_qty, 2.0);
748 }
749
750 #[test]
751 fn test_order_book_entry_deserialize() {
752 let json = r#"["50000.00", "1.5"]"#;
754 let entry: OrderBookEntry = serde_json::from_str(json).unwrap();
755 assert_eq!(entry.price, 50000.0);
756 assert_eq!(entry.quantity, 1.5);
757 }
758
759 #[test]
760 fn test_agg_trade_deserialize() {
761 let json = r#"{
762 "a": 12345,
763 "p": "50000.00",
764 "q": "1.5",
765 "f": 100,
766 "l": 105,
767 "T": 1234567890123,
768 "m": true,
769 "M": true
770 }"#;
771 let trade: AggTrade = serde_json::from_str(json).unwrap();
772 assert_eq!(trade.agg_trade_id, 12345);
773 assert_eq!(trade.price, 50000.0);
774 assert_eq!(trade.quantity, 1.5);
775 assert_eq!(trade.first_trade_id, 100);
776 assert_eq!(trade.last_trade_id, 105);
777 assert_eq!(trade.timestamp, 1234567890123);
778 assert!(trade.is_buyer_maker);
779 assert!(trade.is_best_match);
780 }
781
782 #[test]
783 fn test_average_price_deserialize() {
784 let json = r#"{"mins": 5, "price": "50000.00"}"#;
785 let avg: AveragePrice = serde_json::from_str(json).unwrap();
786 assert_eq!(avg.mins, 5);
787 assert_eq!(avg.price, 50000.0);
788 }
789
790 #[test]
791 fn test_symbol_filter_price_filter() {
792 let json = r#"{
793 "filterType": "PRICE_FILTER",
794 "minPrice": "0.00001000",
795 "maxPrice": "1000000.00000000",
796 "tickSize": "0.00001000"
797 }"#;
798 let filter: SymbolFilter = serde_json::from_str(json).unwrap();
799 match filter {
800 SymbolFilter::PriceFilter {
801 min_price,
802 max_price,
803 tick_size,
804 } => {
805 assert_eq!(min_price, 0.00001);
806 assert_eq!(max_price, 1000000.0);
807 assert_eq!(tick_size, 0.00001);
808 }
809 _ => panic!("Expected PriceFilter"),
810 }
811 }
812
813 #[test]
814 fn test_symbol_filter_lot_size() {
815 let json = r#"{
816 "filterType": "LOT_SIZE",
817 "minQty": "0.00100000",
818 "maxQty": "100000.00000000",
819 "stepSize": "0.00100000"
820 }"#;
821 let filter: SymbolFilter = serde_json::from_str(json).unwrap();
822 match filter {
823 SymbolFilter::LotSize {
824 min_qty,
825 max_qty,
826 step_size,
827 } => {
828 assert_eq!(min_qty, 0.001);
829 assert_eq!(max_qty, 100000.0);
830 assert_eq!(step_size, 0.001);
831 }
832 _ => panic!("Expected LotSize"),
833 }
834 }
835
836 #[test]
837 fn test_unknown_filter_type() {
838 let json = r#"{"filterType": "UNKNOWN_FILTER_TYPE"}"#;
839 let filter: SymbolFilter = serde_json::from_str(json).unwrap();
840 assert_eq!(filter, SymbolFilter::Other);
841 }
842}