1use std::{fmt, str::FromStr};
10
11use rust_decimal::Decimal;
12use serde::{
13 de::{self, DeserializeOwned, IgnoredAny, SeqAccess, Visitor},
14 ser::SerializeSeq,
15 Deserialize, Deserializer, Serialize, Serializer,
16};
17use serde_json::Value;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub struct Symbol(String);
33
34#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
36#[error("{0:?} is not a Binance symbol: 1 to 32 letters, digits or underscores")]
37pub struct InvalidSymbol(pub String);
38
39impl Symbol {
40 pub const MAX_LEN: usize = 32;
42
43 pub fn new(symbol: impl Into<String>) -> Result<Self, InvalidSymbol> {
45 let symbol = symbol.into();
46 let chars = symbol.chars().count();
47 let valid = (1..=Self::MAX_LEN).contains(&chars)
48 && symbol.chars().all(|c| c.is_alphanumeric() || c == '_');
49 if valid {
50 Ok(Self(symbol.to_ascii_uppercase()))
51 } else {
52 Err(InvalidSymbol(symbol))
53 }
54 }
55
56 pub fn as_str(&self) -> &str {
58 &self.0
59 }
60}
61
62impl fmt::Display for Symbol {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(&self.0)
65 }
66}
67
68impl FromStr for Symbol {
69 type Err = InvalidSymbol;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 Self::new(s)
73 }
74}
75
76impl AsRef<str> for Symbol {
77 fn as_ref(&self) -> &str {
78 &self.0
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
84#[error("{value:?} is not a valid {type_name}")]
85pub struct UnknownVariant {
86 pub type_name: &'static str,
88 pub value: String,
90}
91
92macro_rules! wire_enum {
95 ($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident => $wire:literal),+ $(,)? }) => {
96 $(#[$meta])*
97 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
98 pub enum $name {
99 $( $(#[$vmeta])* #[serde(rename = $wire)] $variant, )+
100 }
101
102 impl $name {
103 pub const ALL: &'static [$name] = &[$( $name::$variant, )+];
105
106 pub fn as_str(self) -> &'static str {
108 match self { $( $name::$variant => $wire, )+ }
109 }
110 }
111
112 impl fmt::Display for $name {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str(self.as_str())
115 }
116 }
117
118 impl FromStr for $name {
119 type Err = UnknownVariant;
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 match s {
122 $( $wire => Ok($name::$variant), )+
123 _ => Err(UnknownVariant { type_name: stringify!($name), value: s.to_owned() }),
124 }
125 }
126 }
127 };
128}
129
130macro_rules! open_enum {
133 ($(#[$meta:meta])* $name:ident { $($(#[$vmeta:meta])* $variant:ident => $wire:literal),+ $(,)? }) => {
134 $(#[$meta])*
135 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
136 #[non_exhaustive]
137 pub enum $name {
138 $( $(#[$vmeta])* $variant, )+
139 Other(String),
141 }
142
143 impl $name {
144 pub const ALL: &'static [Self] = &[$( Self::$variant ),+];
146
147 pub fn as_str(&self) -> &str {
149 match self {
150 $( Self::$variant => $wire, )+
151 Self::Other(raw) => raw,
152 }
153 }
154 }
155
156 impl fmt::Display for $name {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160 }
161
162 impl FromStr for $name {
163 type Err = std::convert::Infallible;
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
165 Ok(match s {
166 $( $wire => Self::$variant, )+
167 other => Self::Other(other.to_owned()),
168 })
169 }
170 }
171
172 impl Serialize for $name {
173 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
174 serializer.serialize_str(self.as_str())
175 }
176 }
177
178 impl<'de> Deserialize<'de> for $name {
179 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
180 let raw = String::deserialize(deserializer)?;
181 let Ok(value) = raw.parse();
182 Ok(value)
183 }
184 }
185 };
186}
187
188wire_enum! {
189 Interval {
192 M1 => "1m", M3 => "3m", M5 => "5m", M15 => "15m", M30 => "30m",
193 H1 => "1h", H2 => "2h", H4 => "4h", H6 => "6h", H8 => "8h", H12 => "12h",
194 D1 => "1d", D3 => "3d", W1 => "1w",
195 Mo1 => "1M",
197 }
198}
199
200wire_enum! {
201 DepthLimit {
204 Five => "5", Ten => "10", Twenty => "20", Fifty => "50",
205 Hundred => "100", FiveHundred => "500", Thousand => "1000",
206 }
207}
208
209open_enum! {
210 ContractType {
212 Perpetual => "PERPETUAL",
213 TradifiPerpetual => "TRADIFI_PERPETUAL",
215 CurrentMonth => "CURRENT_MONTH",
216 NextMonth => "NEXT_MONTH",
217 CurrentQuarter => "CURRENT_QUARTER",
218 NextQuarter => "NEXT_QUARTER",
219 PerpetualDelivering => "PERPETUAL_DELIVERING",
220 }
221}
222
223open_enum! {
224 SymbolStatus {
228 PendingTrading => "PENDING_TRADING",
229 Trading => "TRADING",
230 PreDelivering => "PRE_DELIVERING",
231 Delivering => "DELIVERING",
232 Delivered => "DELIVERED",
233 PreSettle => "PRE_SETTLE",
234 Settling => "SETTLING",
236 Close => "CLOSE",
237 TradingHalt => "TRADING_HALT",
238 TradingCancelOnly => "TRADING_CANCEL_ONLY",
239 }
240}
241
242open_enum! {
243 UnderlyingType {
246 Coin => "COIN",
247 Index => "INDEX",
248 Premarket => "PREMARKET",
249 Commodity => "COMMODITY",
250 Equity => "EQUITY",
251 CnEquity => "CN_EQUITY",
252 HkEquity => "HK_EQUITY",
253 KrEquity => "KR_EQUITY",
254 Fx => "FX",
255 }
256}
257
258mod decimal_or_empty {
263 use super::*;
264
265 pub fn serialize<S: Serializer>(
266 value: &Option<Decimal>,
267 serializer: S,
268 ) -> Result<S::Ok, S::Error> {
269 match value {
270 Some(value) => serializer.collect_str(value),
271 None => serializer.serialize_str(""),
272 }
273 }
274
275 pub fn deserialize<'de, D: Deserializer<'de>>(
276 deserializer: D,
277 ) -> Result<Option<Decimal>, D::Error> {
278 let raw = String::deserialize(deserializer)?;
279 if raw.is_empty() {
280 return Ok(None);
281 }
282 raw.parse().map(Some).map_err(de::Error::custom)
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289#[non_exhaustive]
290pub struct ServerTime {
291 pub server_time: u64,
293}
294
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase")]
298#[non_exhaustive]
299pub struct ExchangeInfo {
300 pub timezone: String,
302 pub server_time: u64,
304 pub futures_type: String,
306 pub rate_limits: Vec<RateLimit>,
308 pub exchange_filters: Vec<Filter>,
310 pub assets: Vec<AssetInfo>,
312 pub symbols: Vec<SymbolInfo>,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319#[non_exhaustive]
320pub struct RateLimit {
321 pub rate_limit_type: String,
323 pub interval: String,
325 pub interval_num: u32,
327 pub limit: u32,
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334#[non_exhaustive]
335pub struct AssetInfo {
336 pub asset: String,
338 pub margin_available: bool,
340 #[serde(with = "rust_decimal::serde::str")]
342 pub auto_asset_exchange: Decimal,
343}
344
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347#[serde(rename_all = "camelCase")]
348#[non_exhaustive]
349pub struct SymbolInfo {
350 pub symbol: String,
352 pub pair: String,
354 pub contract_type: ContractType,
356 pub delivery_date: u64,
358 pub onboard_date: u64,
360 pub status: SymbolStatus,
362 #[serde(with = "rust_decimal::serde::str")]
364 pub maint_margin_percent: Decimal,
365 #[serde(with = "rust_decimal::serde::str")]
367 pub required_margin_percent: Decimal,
368 pub base_asset: String,
370 pub quote_asset: String,
372 pub margin_asset: String,
374 pub price_precision: u32,
376 pub quantity_precision: u32,
378 pub base_asset_precision: u32,
380 pub quote_precision: u32,
382 pub underlying_type: UnderlyingType,
384 pub underlying_sub_type: Vec<String>,
386 #[serde(with = "rust_decimal::serde::str")]
388 pub trigger_protect: Decimal,
389 #[serde(with = "rust_decimal::serde::str")]
391 pub liquidation_fee: Decimal,
392 #[serde(with = "rust_decimal::serde::str")]
394 pub market_take_bound: Decimal,
395 pub max_move_order_limit: u32,
397 pub filters: Vec<Filter>,
399 pub order_types: Vec<String>,
401 pub time_in_force: Vec<String>,
403 pub permission_sets: Vec<String>,
405}
406
407#[derive(Debug, Clone, PartialEq)]
414#[non_exhaustive]
415pub enum Filter {
416 #[non_exhaustive]
418 PriceFilter {
419 min_price: Decimal,
421 max_price: Decimal,
423 tick_size: Decimal,
425 },
426 #[non_exhaustive]
428 LotSize {
429 min_qty: Decimal,
431 max_qty: Decimal,
433 step_size: Decimal,
435 },
436 #[non_exhaustive]
438 MarketLotSize {
439 min_qty: Decimal,
441 max_qty: Decimal,
443 step_size: Decimal,
445 },
446 #[non_exhaustive]
448 MaxNumOrders {
449 limit: u32,
451 },
452 #[non_exhaustive]
454 MinNotional {
455 notional: Decimal,
457 },
458 #[non_exhaustive]
460 PercentPrice {
461 multiplier_up: Decimal,
463 multiplier_down: Decimal,
465 multiplier_decimal: Decimal,
467 },
468 #[non_exhaustive]
470 PositionRiskControl {
471 position_control_side: String,
473 },
474 Other {
476 filter_type: String,
478 raw: Value,
480 },
481}
482
483mod filter_wire {
485 use super::*;
486
487 #[derive(Serialize, Deserialize)]
488 #[serde(rename_all = "camelCase")]
489 pub struct Price {
490 #[serde(with = "rust_decimal::serde::str")]
491 pub min_price: Decimal,
492 #[serde(with = "rust_decimal::serde::str")]
493 pub max_price: Decimal,
494 #[serde(with = "rust_decimal::serde::str")]
495 pub tick_size: Decimal,
496 }
497
498 #[derive(Serialize, Deserialize)]
499 #[serde(rename_all = "camelCase")]
500 pub struct Lot {
501 #[serde(with = "rust_decimal::serde::str")]
502 pub min_qty: Decimal,
503 #[serde(with = "rust_decimal::serde::str")]
504 pub max_qty: Decimal,
505 #[serde(with = "rust_decimal::serde::str")]
506 pub step_size: Decimal,
507 }
508
509 #[derive(Serialize, Deserialize)]
510 pub struct MaxNumOrders {
511 pub limit: u32,
512 }
513
514 #[derive(Serialize, Deserialize)]
515 pub struct MinNotional {
516 #[serde(with = "rust_decimal::serde::str")]
517 pub notional: Decimal,
518 }
519
520 #[derive(Serialize, Deserialize)]
521 #[serde(rename_all = "camelCase")]
522 pub struct PercentPrice {
523 #[serde(with = "rust_decimal::serde::str")]
524 pub multiplier_up: Decimal,
525 #[serde(with = "rust_decimal::serde::str")]
526 pub multiplier_down: Decimal,
527 #[serde(with = "rust_decimal::serde::str")]
528 pub multiplier_decimal: Decimal,
529 }
530
531 #[derive(Serialize, Deserialize)]
532 #[serde(rename_all = "camelCase")]
533 pub struct PositionRiskControl {
534 pub position_control_side: String,
535 }
536}
537
538impl<'de> Deserialize<'de> for Filter {
539 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
540 use filter_wire as wire;
541
542 fn parse<T: DeserializeOwned, E: de::Error>(raw: Value) -> Result<T, E> {
543 serde_json::from_value(raw).map_err(E::custom)
544 }
545
546 let raw = Value::deserialize(deserializer)?;
547 let filter_type = raw
548 .get("filterType")
549 .and_then(Value::as_str)
550 .ok_or_else(|| de::Error::custom("a filter without a string filterType"))?
551 .to_owned();
552 Ok(match filter_type.as_str() {
553 "PRICE_FILTER" => {
554 let f: wire::Price = parse(raw)?;
555 Self::PriceFilter {
556 min_price: f.min_price,
557 max_price: f.max_price,
558 tick_size: f.tick_size,
559 }
560 }
561 "LOT_SIZE" => {
562 let f: wire::Lot = parse(raw)?;
563 Self::LotSize {
564 min_qty: f.min_qty,
565 max_qty: f.max_qty,
566 step_size: f.step_size,
567 }
568 }
569 "MARKET_LOT_SIZE" => {
570 let f: wire::Lot = parse(raw)?;
571 Self::MarketLotSize {
572 min_qty: f.min_qty,
573 max_qty: f.max_qty,
574 step_size: f.step_size,
575 }
576 }
577 "MAX_NUM_ORDERS" => {
578 let f: wire::MaxNumOrders = parse(raw)?;
579 Self::MaxNumOrders { limit: f.limit }
580 }
581 "MIN_NOTIONAL" => {
582 let f: wire::MinNotional = parse(raw)?;
583 Self::MinNotional {
584 notional: f.notional,
585 }
586 }
587 "PERCENT_PRICE" => {
588 let f: wire::PercentPrice = parse(raw)?;
589 Self::PercentPrice {
590 multiplier_up: f.multiplier_up,
591 multiplier_down: f.multiplier_down,
592 multiplier_decimal: f.multiplier_decimal,
593 }
594 }
595 "POSITION_RISK_CONTROL" => {
596 let f: wire::PositionRiskControl = parse(raw)?;
597 Self::PositionRiskControl {
598 position_control_side: f.position_control_side,
599 }
600 }
601 _ => Self::Other { filter_type, raw },
602 })
603 }
604}
605
606impl Serialize for Filter {
607 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
608 use filter_wire as wire;
609
610 fn tagged<T: Serialize, E: serde::ser::Error>(
611 filter_type: &str,
612 body: &T,
613 ) -> Result<Value, E> {
614 let mut value = serde_json::to_value(body).map_err(E::custom)?;
615 if let Value::Object(map) = &mut value {
616 map.insert(
617 "filterType".to_owned(),
618 Value::String(filter_type.to_owned()),
619 );
620 }
621 Ok(value)
622 }
623
624 let value = match self {
625 Self::PriceFilter {
626 min_price,
627 max_price,
628 tick_size,
629 } => tagged(
630 "PRICE_FILTER",
631 &wire::Price {
632 min_price: *min_price,
633 max_price: *max_price,
634 tick_size: *tick_size,
635 },
636 )?,
637 Self::LotSize {
638 min_qty,
639 max_qty,
640 step_size,
641 } => tagged(
642 "LOT_SIZE",
643 &wire::Lot {
644 min_qty: *min_qty,
645 max_qty: *max_qty,
646 step_size: *step_size,
647 },
648 )?,
649 Self::MarketLotSize {
650 min_qty,
651 max_qty,
652 step_size,
653 } => tagged(
654 "MARKET_LOT_SIZE",
655 &wire::Lot {
656 min_qty: *min_qty,
657 max_qty: *max_qty,
658 step_size: *step_size,
659 },
660 )?,
661 Self::MaxNumOrders { limit } => {
662 tagged("MAX_NUM_ORDERS", &wire::MaxNumOrders { limit: *limit })?
663 }
664 Self::MinNotional { notional } => tagged(
665 "MIN_NOTIONAL",
666 &wire::MinNotional {
667 notional: *notional,
668 },
669 )?,
670 Self::PercentPrice {
671 multiplier_up,
672 multiplier_down,
673 multiplier_decimal,
674 } => tagged(
675 "PERCENT_PRICE",
676 &wire::PercentPrice {
677 multiplier_up: *multiplier_up,
678 multiplier_down: *multiplier_down,
679 multiplier_decimal: *multiplier_decimal,
680 },
681 )?,
682 Self::PositionRiskControl {
683 position_control_side,
684 } => tagged(
685 "POSITION_RISK_CONTROL",
686 &wire::PositionRiskControl {
687 position_control_side: position_control_side.clone(),
688 },
689 )?,
690 Self::Other { raw, .. } => raw.clone(),
691 };
692 value.serialize(serializer)
693 }
694}
695
696#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700#[non_exhaustive]
701pub struct FundingInfo {
702 pub symbol: String,
705 #[serde(with = "rust_decimal::serde::str")]
707 pub adjusted_funding_rate_cap: Decimal,
708 #[serde(with = "rust_decimal::serde::str")]
710 pub adjusted_funding_rate_floor: Decimal,
711 pub funding_interval_hours: u32,
713 pub disclaimer: bool,
715 pub update_time: Option<u64>,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
722#[serde(rename_all = "camelCase")]
723#[non_exhaustive]
724pub struct Ticker24h {
725 pub symbol: String,
727 #[serde(with = "rust_decimal::serde::str")]
729 pub price_change: Decimal,
730 #[serde(with = "rust_decimal::serde::str")]
732 pub price_change_percent: Decimal,
733 #[serde(with = "rust_decimal::serde::str")]
735 pub weighted_avg_price: Decimal,
736 #[serde(with = "rust_decimal::serde::str")]
738 pub last_price: Decimal,
739 #[serde(with = "rust_decimal::serde::str")]
741 pub last_qty: Decimal,
742 #[serde(with = "rust_decimal::serde::str")]
744 pub open_price: Decimal,
745 #[serde(with = "rust_decimal::serde::str")]
747 pub high_price: Decimal,
748 #[serde(with = "rust_decimal::serde::str")]
750 pub low_price: Decimal,
751 #[serde(with = "rust_decimal::serde::str")]
753 pub volume: Decimal,
754 #[serde(with = "rust_decimal::serde::str")]
756 pub quote_volume: Decimal,
757 pub open_time: u64,
759 pub close_time: u64,
761 pub first_id: i64,
763 pub last_id: i64,
765 pub count: u64,
767}
768
769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
771#[serde(rename_all = "camelCase")]
772#[non_exhaustive]
773pub struct PremiumIndex {
774 pub symbol: String,
776 #[serde(with = "rust_decimal::serde::str")]
778 pub mark_price: Decimal,
779 #[serde(with = "rust_decimal::serde::str")]
781 pub index_price: Decimal,
782 #[serde(with = "rust_decimal::serde::str")]
784 pub estimated_settle_price: Decimal,
785 #[serde(with = "rust_decimal::serde::str")]
787 pub last_funding_rate: Decimal,
788 #[serde(with = "rust_decimal::serde::str")]
790 pub interest_rate: Decimal,
791 pub next_funding_time: u64,
793 pub time: u64,
795}
796
797#[derive(Debug, Clone, PartialEq, Eq)]
802#[non_exhaustive]
803pub struct Kline {
804 pub open_time: u64,
806 pub open: Decimal,
808 pub high: Decimal,
810 pub low: Decimal,
812 pub close: Decimal,
814 pub volume: Decimal,
816 pub close_time: u64,
818 pub quote_volume: Decimal,
820 pub trade_count: u64,
822 pub taker_buy_base_volume: Decimal,
824 pub taker_buy_quote_volume: Decimal,
826}
827
828struct DecimalStr(Decimal);
830
831impl<'de> Deserialize<'de> for DecimalStr {
832 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
833 rust_decimal::serde::str::deserialize(deserializer).map(Self)
834 }
835}
836
837fn next<'de, T: Deserialize<'de>, A: SeqAccess<'de>>(
838 seq: &mut A,
839 index: usize,
840 what: &str,
841) -> Result<T, A::Error> {
842 seq.next_element()?
843 .ok_or_else(|| de::Error::invalid_length(index, &what))
844}
845
846fn drain<'de, A: SeqAccess<'de>>(seq: &mut A) -> Result<(), A::Error> {
847 while seq.next_element::<IgnoredAny>()?.is_some() {}
848 Ok(())
849}
850
851impl<'de> Deserialize<'de> for Kline {
852 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
853 struct KlineVisitor;
854
855 impl<'de> Visitor<'de> for KlineVisitor {
856 type Value = Kline;
857
858 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
859 f.write_str("a kline array of at least 11 elements")
860 }
861
862 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Kline, A::Error> {
863 const WHAT: &str = "a kline array of at least 11 elements";
864 let kline = Kline {
865 open_time: next(&mut seq, 0, WHAT)?,
866 open: next::<DecimalStr, _>(&mut seq, 1, WHAT)?.0,
867 high: next::<DecimalStr, _>(&mut seq, 2, WHAT)?.0,
868 low: next::<DecimalStr, _>(&mut seq, 3, WHAT)?.0,
869 close: next::<DecimalStr, _>(&mut seq, 4, WHAT)?.0,
870 volume: next::<DecimalStr, _>(&mut seq, 5, WHAT)?.0,
871 close_time: next(&mut seq, 6, WHAT)?,
872 quote_volume: next::<DecimalStr, _>(&mut seq, 7, WHAT)?.0,
873 trade_count: next(&mut seq, 8, WHAT)?,
874 taker_buy_base_volume: next::<DecimalStr, _>(&mut seq, 9, WHAT)?.0,
875 taker_buy_quote_volume: next::<DecimalStr, _>(&mut seq, 10, WHAT)?.0,
876 };
877 drain(&mut seq)?;
878 Ok(kline)
879 }
880 }
881
882 deserializer.deserialize_seq(KlineVisitor)
883 }
884}
885
886impl Serialize for Kline {
887 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
888 let mut seq = serializer.serialize_seq(Some(11))?;
889 seq.serialize_element(&self.open_time)?;
890 for value in [&self.open, &self.high, &self.low, &self.close, &self.volume] {
891 seq.serialize_element(&value.to_string())?;
892 }
893 seq.serialize_element(&self.close_time)?;
894 seq.serialize_element(&self.quote_volume.to_string())?;
895 seq.serialize_element(&self.trade_count)?;
896 seq.serialize_element(&self.taker_buy_base_volume.to_string())?;
897 seq.serialize_element(&self.taker_buy_quote_volume.to_string())?;
898 seq.end()
899 }
900}
901
902#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
904#[serde(rename_all = "camelCase")]
905#[non_exhaustive]
906pub struct FundingRate {
907 pub symbol: String,
909 pub funding_time: u64,
911 #[serde(with = "rust_decimal::serde::str")]
913 pub funding_rate: Decimal,
914 #[serde(with = "decimal_or_empty")]
917 pub mark_price: Option<Decimal>,
918 pub rate_type: String,
920}
921
922#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
924#[serde(rename_all = "camelCase")]
925#[non_exhaustive]
926pub struct OpenInterest {
927 pub symbol: String,
929 #[serde(with = "rust_decimal::serde::str")]
931 pub open_interest: Decimal,
932 pub time: u64,
934}
935
936#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
939#[non_exhaustive]
940pub struct AggTrade {
941 #[serde(rename = "a")]
943 pub id: u64,
944 #[serde(rename = "p", with = "rust_decimal::serde::str")]
946 pub price: Decimal,
947 #[serde(rename = "q", with = "rust_decimal::serde::str")]
949 pub quantity: Decimal,
950 #[serde(rename = "nq", with = "rust_decimal::serde::str")]
953 pub normal_quantity: Decimal,
954 #[serde(rename = "f")]
956 pub first_trade_id: u64,
957 #[serde(rename = "l")]
959 pub last_trade_id: u64,
960 #[serde(rename = "T")]
962 pub time: u64,
963 #[serde(rename = "m")]
965 pub is_buyer_maker: bool,
966}
967
968#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
970#[non_exhaustive]
971pub struct Depth {
972 #[serde(rename = "lastUpdateId")]
974 pub last_update_id: u64,
975 #[serde(rename = "E")]
977 pub event_time: u64,
978 #[serde(rename = "T")]
980 pub transaction_time: u64,
981 pub bids: Vec<Level>,
983 pub asks: Vec<Level>,
985}
986
987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989#[non_exhaustive]
990pub struct Level {
991 pub price: Decimal,
993 pub quantity: Decimal,
995}
996
997impl Level {
998 pub fn new(price: Decimal, quantity: Decimal) -> Self {
1000 Self { price, quantity }
1001 }
1002}
1003
1004impl<'de> Deserialize<'de> for Level {
1005 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1006 struct LevelVisitor;
1007
1008 impl<'de> Visitor<'de> for LevelVisitor {
1009 type Value = Level;
1010
1011 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 f.write_str("a [price, quantity] array")
1013 }
1014
1015 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Level, A::Error> {
1016 const WHAT: &str = "a [price, quantity] array";
1017 let level = Level {
1018 price: next::<DecimalStr, _>(&mut seq, 0, WHAT)?.0,
1019 quantity: next::<DecimalStr, _>(&mut seq, 1, WHAT)?.0,
1020 };
1021 drain(&mut seq)?;
1022 Ok(level)
1023 }
1024 }
1025
1026 deserializer.deserialize_seq(LevelVisitor)
1027 }
1028}
1029
1030impl Serialize for Level {
1031 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1032 let mut seq = serializer.serialize_seq(Some(2))?;
1033 seq.serialize_element(&self.price.to_string())?;
1034 seq.serialize_element(&self.quantity.to_string())?;
1035 seq.end()
1036 }
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041 use super::*;
1042
1043 #[test]
1044 fn symbols_take_letters_digits_and_underscores_in_any_script() {
1045 for good in ["BTCUSDT", "BTCUSDT_261225", "币安人生USDT", "1000PEPEUSDT"] {
1046 assert_eq!(Symbol::new(good).unwrap().as_str(), good);
1047 }
1048 for bad in [
1049 "",
1050 "BTC USDT",
1051 "BTC@USDT",
1052 "BTC/USDT",
1053 "BTC-USDT",
1054 &"A".repeat(33),
1055 ] {
1056 assert_eq!(
1057 Symbol::new(bad),
1058 Err(InvalidSymbol(bad.to_owned())),
1059 "{bad:?}"
1060 );
1061 }
1062 assert!(Symbol::new("A".repeat(32)).is_ok());
1063 }
1064
1065 #[test]
1066 fn ascii_letters_are_uppercased_and_nothing_else_changes() {
1067 assert_eq!(
1068 Symbol::new("btcusdt").unwrap(),
1069 Symbol::new("BTCUSDT").unwrap()
1070 );
1071 assert_eq!(
1072 Symbol::new("btcusdt_261225").unwrap().as_str(),
1073 "BTCUSDT_261225"
1074 );
1075 assert_eq!(
1076 Symbol::new("币安人生usdt").unwrap().as_str(),
1077 "币安人生USDT"
1078 );
1079 }
1080
1081 #[test]
1082 fn symbols_are_measured_in_characters_and_only_ascii_changes_case() {
1083 assert_eq!(Symbol::new("straße").unwrap().as_str(), "STRAßE");
1085 assert!(Symbol::new("币".repeat(32)).is_ok());
1086 assert!(Symbol::new("币".repeat(33)).is_err());
1087 }
1088
1089 #[test]
1090 fn wire_spellings_are_binance_s() {
1091 let intervals: Vec<&str> = Interval::ALL.iter().map(|i| i.as_str()).collect();
1094 assert_eq!(
1095 intervals,
1096 [
1097 "1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d",
1098 "1w", "1M"
1099 ]
1100 );
1101 let limits: Vec<&str> = DepthLimit::ALL.iter().map(|l| l.as_str()).collect();
1102 assert_eq!(limits, ["5", "10", "20", "50", "100", "500", "1000"]);
1103 }
1104
1105 #[test]
1106 fn every_interval_round_trips_its_wire_spelling() {
1107 assert_eq!(Interval::ALL.len(), 15);
1108 for interval in Interval::ALL {
1109 assert_eq!(interval.as_str().parse::<Interval>(), Ok(*interval));
1110 }
1111 assert!("1s".parse::<Interval>().is_err(), "this host refuses 1s");
1112 }
1113
1114 #[test]
1115 fn an_unknown_contract_type_is_kept_verbatim() {
1116 let parsed: ContractType = serde_json::from_str(r#""NEXT_DECADE""#).unwrap();
1117 assert_eq!(parsed, ContractType::Other("NEXT_DECADE".to_owned()));
1118 assert_eq!(serde_json::to_string(&parsed).unwrap(), r#""NEXT_DECADE""#);
1119 let known: ContractType = serde_json::from_str(r#""TRADIFI_PERPETUAL""#).unwrap();
1120 assert_eq!(known, ContractType::TradifiPerpetual);
1121 }
1122
1123 #[test]
1124 fn an_unseen_filter_type_is_kept_and_the_known_ones_parse() {
1125 let filters: Vec<Filter> = serde_json::from_str(
1126 r#"[{"filterType":"PRICE_FILTER","minPrice":"261.10","maxPrice":"809484","tickSize":"0.10"},
1127 {"filterType":"MAX_NUM_ICEBERG_ORDERS","maxNumIcebergOrders":5}]"#,
1128 )
1129 .unwrap();
1130 assert!(
1131 matches!(&filters[0], Filter::PriceFilter { tick_size, .. } if *tick_size == Decimal::new(10, 2))
1132 );
1133 let Filter::Other { filter_type, raw } = &filters[1] else {
1134 panic!("an unseen filter type must not fail the response");
1135 };
1136 assert_eq!(filter_type, "MAX_NUM_ICEBERG_ORDERS");
1137 assert_eq!(raw["maxNumIcebergOrders"], 5);
1138 let again: Vec<Value> =
1139 serde_json::from_value(serde_json::to_value(&filters).unwrap()).unwrap();
1140 assert_eq!(again[0]["filterType"], "PRICE_FILTER");
1141 assert_eq!(again[1]["maxNumIcebergOrders"], 5);
1142 }
1143
1144 #[test]
1145 fn a_known_filter_with_a_missing_field_fails_loudly() {
1146 let err = serde_json::from_str::<Filter>(r#"{"filterType":"LOT_SIZE","minQty":"0.001"}"#)
1147 .unwrap_err();
1148 assert!(err.to_string().contains("maxQty"), "{err}");
1149 }
1150
1151 #[test]
1152 fn a_kline_drops_the_ignored_element_and_tolerates_more() {
1153 let wire = r#"[1791354000000,"84125.70","84125.70","84099.90","84106.90","214.561",1791354059999,"18046628.85200",2912,"38.078","3202701.86390","0","extra"]"#;
1154 let kline: Kline = serde_json::from_str(wire).unwrap();
1155 assert_eq!(kline.close, "84106.90".parse::<Decimal>().unwrap());
1156 assert_eq!(kline.trade_count, 2912);
1157 assert_eq!(
1158 serde_json::to_string(&kline).unwrap(),
1159 r#"[1791354000000,"84125.70","84125.70","84099.90","84106.90","214.561",1791354059999,"18046628.85200",2912,"38.078","3202701.86390"]"#
1160 );
1161 assert!(serde_json::from_str::<Kline>("[1791354000000]").is_err());
1162 }
1163
1164 #[test]
1165 fn a_level_keeps_every_digit() {
1166 let level: Level = serde_json::from_str(r#"["84141.123456789012345","14.663"]"#).unwrap();
1168 assert_eq!(level.price.to_string(), "84141.123456789012345");
1169 assert_eq!(
1170 serde_json::to_string(&level).unwrap(),
1171 r#"["84141.123456789012345","14.663"]"#
1172 );
1173 }
1174
1175 #[test]
1176 fn an_old_funding_rate_has_no_mark_price() {
1177 let old: FundingRate = serde_json::from_str(
1179 r#"{"symbol":"BTCUSDT","fundingTime":1568102400000,"fundingRate":"0.00010000","markPrice":"","rateType":"Regular"}"#,
1180 )
1181 .unwrap();
1182 assert_eq!(old.mark_price, None);
1183 assert_eq!(serde_json::to_value(&old).unwrap()["markPrice"], "");
1184
1185 let new: FundingRate = serde_json::from_str(
1186 r#"{"symbol":"BTCUSDT","fundingTime":1791273600001,"fundingRate":"-0.00001592","markPrice":"85514.00007496","rateType":"Regular"}"#,
1187 )
1188 .unwrap();
1189 assert_eq!(new.mark_price, Some("85514.00007496".parse().unwrap()));
1190 }
1191}