barter_data/exchange/binance/book/
l2.rs

1use super::{super::channel::BinanceChannel, BinanceLevel};
2use crate::{
3    Identifier, books::OrderBook, event::MarketEvent, exchange::subscription::ExchangeSub,
4    subscription::book::OrderBookEvent,
5};
6use barter_instrument::exchange::ExchangeId;
7use barter_integration::subscription::SubscriptionId;
8use chrono::{DateTime, Utc};
9use derive_more::Constructor;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Constructor)]
13pub struct BinanceOrderBookL2Meta<InstrumentKey, Sequencer> {
14    pub key: InstrumentKey,
15    pub sequencer: Sequencer,
16}
17
18/// [`Binance`](super::super::Binance) OrderBook Level2 snapshot HTTP message.
19///
20/// Used as the starting [`OrderBook`] before OrderBook Level2 delta WebSocket updates are
21/// applied.
22///
23/// ### Payload Examples
24/// See docs: <https://binance-docs.github.io/apidocs/spot/en/#order-book>
25/// #### BinanceSpot OrderBookL2Snapshot
26/// ```json
27/// {
28///     "lastUpdateId": 1027024,
29///     "bids": [
30///         ["4.00000000", "431.00000000"]
31///     ],
32///     "asks": [
33///         ["4.00000200", "12.00000000"]
34///     ]
35/// }
36/// ```
37///
38/// #### BinanceFuturesUsd OrderBookL2Snapshot
39/// See docs: <https://binance-docs.github.io/apidocs/futures/en/#order-book>
40/// ```json
41/// {
42///     "lastUpdateId": 1027024,
43///     "E": 1589436922972,
44///     "T": 1589436922959,
45///     "bids": [
46///         ["4.00000000", "431.00000000"]
47///     ],
48///     "asks": [
49///         ["4.00000200", "12.00000000"]
50///     ]
51/// }
52/// ```
53#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
54pub struct BinanceOrderBookL2Snapshot {
55    #[serde(rename = "lastUpdateId")]
56    pub last_update_id: u64,
57    #[serde(default, rename = "E", with = "chrono::serde::ts_milliseconds_option")]
58    pub time_exchange: Option<DateTime<Utc>>,
59    #[serde(default, rename = "T", with = "chrono::serde::ts_milliseconds_option")]
60    pub time_engine: Option<DateTime<Utc>>,
61    pub bids: Vec<BinanceLevel>,
62    pub asks: Vec<BinanceLevel>,
63}
64
65impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceOrderBookL2Snapshot)>
66    for MarketEvent<InstrumentKey, OrderBookEvent>
67{
68    fn from(
69        (exchange, instrument, snapshot): (ExchangeId, InstrumentKey, BinanceOrderBookL2Snapshot),
70    ) -> Self {
71        let time_received = Utc::now();
72        Self {
73            time_exchange: snapshot.time_exchange.unwrap_or(time_received),
74            time_received,
75            exchange,
76            instrument,
77            kind: OrderBookEvent::from(snapshot),
78        }
79    }
80}
81
82impl From<BinanceOrderBookL2Snapshot> for OrderBookEvent {
83    fn from(snapshot: BinanceOrderBookL2Snapshot) -> Self {
84        Self::Snapshot(OrderBook::new(
85            snapshot.last_update_id,
86            snapshot.time_engine,
87            snapshot.bids,
88            snapshot.asks,
89        ))
90    }
91}
92
93/// Deserialize a
94/// [`BinanceSpotOrderBookL2Update`](super::super::spot::l2::BinanceSpotOrderBookL2Update) or
95/// [`BinanceFuturesOrderBookL2Update`](super::super::futures::l2::BinanceFuturesOrderBookL2Update)
96/// "s" field (eg/ "BTCUSDT") as the associated [`SubscriptionId`]
97///
98/// eg/ "@depth@100ms|BTCUSDT"
99pub fn de_ob_l2_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
100where
101    D: serde::de::Deserializer<'de>,
102{
103    <&str as Deserialize>::deserialize(deserializer)
104        .map(|market| ExchangeSub::from((BinanceChannel::ORDER_BOOK_L2, market)).id())
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    mod de {
112        use super::*;
113        use rust_decimal_macros::dec;
114
115        #[test]
116        fn test_binance_order_book_l2_snapshot() {
117            struct TestCase {
118                input: &'static str,
119                expected: BinanceOrderBookL2Snapshot,
120            }
121
122            let tests = vec![
123                TestCase {
124                    // TC0: valid Spot BinanceOrderBookL2Snapshot
125                    input: r#"
126                    {
127                        "lastUpdateId": 1027024,
128                        "bids": [
129                            [
130                                "4.00000000",
131                                "431.00000000"
132                            ]
133                        ],
134                        "asks": [
135                            [
136                                "4.00000200",
137                                "12.00000000"
138                            ]
139                        ]
140                    }
141                    "#,
142                    expected: BinanceOrderBookL2Snapshot {
143                        last_update_id: 1027024,
144                        time_exchange: Default::default(),
145                        time_engine: Default::default(),
146                        bids: vec![BinanceLevel {
147                            price: dec!(4.00000000),
148                            amount: dec!(431.00000000),
149                        }],
150                        asks: vec![BinanceLevel {
151                            price: dec!(4.00000200),
152                            amount: dec!(12.00000000),
153                        }],
154                    },
155                },
156                TestCase {
157                    // TC1: valid FuturePerpetual BinanceOrderBookL2Snapshot
158                    input: r#"
159                    {
160                        "lastUpdateId": 1027024,
161                        "E": 1589436922972,
162                        "T": 1589436922959,
163                        "bids": [
164                            [
165                                "4.00000000",
166                                "431.00000000"
167                            ]
168                        ],
169                        "asks": [
170                            [
171                                "4.00000200",
172                                "12.00000000"
173                            ]
174                        ]
175                    }
176                    "#,
177                    expected: BinanceOrderBookL2Snapshot {
178                        last_update_id: 1027024,
179                        time_exchange: Some(
180                            DateTime::from_timestamp_millis(1589436922972).unwrap(),
181                        ),
182                        time_engine: Some(DateTime::from_timestamp_millis(1589436922959).unwrap()),
183                        bids: vec![BinanceLevel {
184                            price: dec!(4.0),
185                            amount: dec!(431.0),
186                        }],
187                        asks: vec![BinanceLevel {
188                            price: dec!(4.00000200),
189                            amount: dec!(12.0),
190                        }],
191                    },
192                },
193            ];
194
195            for (index, test) in tests.into_iter().enumerate() {
196                assert_eq!(
197                    serde_json::from_str::<BinanceOrderBookL2Snapshot>(test.input).unwrap(),
198                    test.expected,
199                    "TC{} failed",
200                    index
201                );
202            }
203        }
204    }
205}