barter_data/exchange/binance/book/
l1.rs

1use crate::{
2    Identifier,
3    books::Level,
4    event::{MarketEvent, MarketIter},
5    exchange::{binance::channel::BinanceChannel, subscription::ExchangeSub},
6    subscription::book::OrderBookL1,
7};
8use barter_instrument::exchange::ExchangeId;
9use barter_integration::subscription::SubscriptionId;
10use chrono::{DateTime, Utc};
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13
14/// [`Binance`](super::super::Binance) real-time OrderBook Level1 (top of books) message.
15///
16/// ### Raw Payload Examples
17/// #### BinanceSpot OrderBookL1
18/// See docs: <https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-book-ticker-streams>
19/// ```json
20/// {
21///     "u":22606535573,
22///     "s":"ETHUSDT",
23///     "b":"1215.27000000",
24///     "B":"32.49110000",
25///     "a":"1215.28000000",
26///     "A":"13.93900000"
27/// }
28/// ```
29///
30/// #### BinanceFuturesUsd OrderBookL1
31/// See docs: <https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-book-ticker-streams>
32/// ```json
33/// {
34///     "u":22606535573,
35///     "s":"ETHUSDT",
36///     "b":"1215.27000000",
37///     "B":"32.49110000",
38///     "a":"1215.28000000",
39///     "A":"13.93900000"
40/// }
41/// ```
42#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
43pub struct BinanceOrderBookL1 {
44    #[serde(alias = "s", deserialize_with = "de_ob_l1_subscription_id")]
45    pub subscription_id: SubscriptionId,
46    #[serde(
47        alias = "T",
48        deserialize_with = "barter_integration::de::de_u64_epoch_ms_as_datetime_utc",
49        default = "Utc::now"
50    )]
51    pub time: DateTime<Utc>,
52    #[serde(alias = "b", with = "rust_decimal::serde::str")]
53    pub best_bid_price: Decimal,
54    #[serde(alias = "B", with = "rust_decimal::serde::str")]
55    pub best_bid_amount: Decimal,
56    #[serde(alias = "a", with = "rust_decimal::serde::str")]
57    pub best_ask_price: Decimal,
58    #[serde(alias = "A", with = "rust_decimal::serde::str")]
59    pub best_ask_amount: Decimal,
60}
61
62impl Identifier<Option<SubscriptionId>> for BinanceOrderBookL1 {
63    fn id(&self) -> Option<SubscriptionId> {
64        Some(self.subscription_id.clone())
65    }
66}
67
68impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceOrderBookL1)>
69    for MarketIter<InstrumentKey, OrderBookL1>
70{
71    fn from(
72        (exchange_id, instrument, book): (ExchangeId, InstrumentKey, BinanceOrderBookL1),
73    ) -> Self {
74        let best_ask = if book.best_ask_price.is_zero() {
75            None
76        } else {
77            Some(Level::new(book.best_ask_price, book.best_ask_amount))
78        };
79
80        let best_bid = if book.best_bid_price.is_zero() {
81            None
82        } else {
83            Some(Level::new(book.best_bid_price, book.best_bid_amount))
84        };
85
86        Self(vec![Ok(MarketEvent {
87            time_exchange: book.time,
88            time_received: Utc::now(),
89            exchange: exchange_id,
90            instrument,
91            kind: OrderBookL1 {
92                last_update_time: book.time,
93                best_bid,
94                best_ask,
95            },
96        })])
97    }
98}
99
100/// Deserialize a [`BinanceOrderBookL1`] "s" (eg/ "BTCUSDT") as the associated [`SubscriptionId`].
101///
102/// eg/ "@bookTicker|BTCUSDT"
103pub fn de_ob_l1_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
104where
105    D: serde::de::Deserializer<'de>,
106{
107    <&str as Deserialize>::deserialize(deserializer)
108        .map(|market| ExchangeSub::from((BinanceChannel::ORDER_BOOK_L1, market)).id())
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    mod de {
116        use super::*;
117        use rust_decimal_macros::dec;
118
119        #[test]
120        fn test_binance_order_book_l1() {
121            struct TestCase {
122                input: &'static str,
123                expected: BinanceOrderBookL1,
124            }
125
126            let time = Utc::now();
127
128            let tests = vec![
129                TestCase {
130                    // TC0: valid Spot BinanceOrderBookL1
131                    input: r#"
132                    {
133                        "u":22606535573,
134                        "s":"ETHUSDT",
135                        "b":"1215.27000000",
136                        "B":"32.49110000",
137                        "a":"1215.28000000",
138                        "A":"13.93900000"
139                    }
140                "#,
141                    expected: BinanceOrderBookL1 {
142                        subscription_id: SubscriptionId::from("@bookTicker|ETHUSDT"),
143                        time,
144                        best_bid_price: dec!(1215.27000000),
145                        best_bid_amount: dec!(32.49110000),
146                        best_ask_price: dec!(1215.28000000),
147                        best_ask_amount: dec!(13.93900000),
148                    },
149                },
150                TestCase {
151                    // TC1: valid FuturePerpetual BinanceOrderBookL1
152                    input: r#"
153                    {
154                        "e":"bookTicker",
155                        "u":2286618712950,
156                        "s":"BTCUSDT",
157                        "b":"16858.90",
158                        "B":"13.692",
159                        "a":"16859.00",
160                        "A":"30.219",
161                        "T":1671621244670,
162                        "E":1671621244673
163                    }"#,
164                    expected: BinanceOrderBookL1 {
165                        subscription_id: SubscriptionId::from("@bookTicker|BTCUSDT"),
166                        time,
167                        best_bid_price: dec!(16858.90),
168                        best_bid_amount: dec!(13.692),
169                        best_ask_price: dec!(16859.00),
170                        best_ask_amount: dec!(30.219),
171                    },
172                },
173            ];
174
175            for (index, test) in tests.into_iter().enumerate() {
176                let actual = serde_json::from_str::<BinanceOrderBookL1>(test.input).unwrap();
177                let actual = BinanceOrderBookL1 { time, ..actual };
178                assert_eq!(actual, test.expected, "TC{} failed", index);
179            }
180        }
181    }
182}