Skip to main content

rustrade_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 chrono::{DateTime, Utc};
7use derive_more::Constructor;
8use rustrade_instrument::exchange::ExchangeId;
9use rustrade_integration::subscription::SubscriptionId;
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)]
108#[allow(clippy::unwrap_used)] // Test code: panics on bad input are acceptable
109mod tests {
110    use super::*;
111
112    mod de {
113        use super::*;
114        use rust_decimal_macros::dec;
115
116        #[test]
117        fn test_binance_order_book_l2_snapshot() {
118            struct TestCase {
119                input: &'static str,
120                expected: BinanceOrderBookL2Snapshot,
121            }
122
123            let tests = vec![
124                TestCase {
125                    // TC0: valid Spot BinanceOrderBookL2Snapshot
126                    input: r#"
127                    {
128                        "lastUpdateId": 1027024,
129                        "bids": [
130                            [
131                                "4.00000000",
132                                "431.00000000"
133                            ]
134                        ],
135                        "asks": [
136                            [
137                                "4.00000200",
138                                "12.00000000"
139                            ]
140                        ]
141                    }
142                    "#,
143                    expected: BinanceOrderBookL2Snapshot {
144                        last_update_id: 1027024,
145                        time_exchange: Default::default(),
146                        time_engine: Default::default(),
147                        bids: vec![BinanceLevel {
148                            price: dec!(4.00000000),
149                            amount: dec!(431.00000000),
150                        }],
151                        asks: vec![BinanceLevel {
152                            price: dec!(4.00000200),
153                            amount: dec!(12.00000000),
154                        }],
155                    },
156                },
157                TestCase {
158                    // TC1: valid FuturePerpetual BinanceOrderBookL2Snapshot
159                    input: r#"
160                    {
161                        "lastUpdateId": 1027024,
162                        "E": 1589436922972,
163                        "T": 1589436922959,
164                        "bids": [
165                            [
166                                "4.00000000",
167                                "431.00000000"
168                            ]
169                        ],
170                        "asks": [
171                            [
172                                "4.00000200",
173                                "12.00000000"
174                            ]
175                        ]
176                    }
177                    "#,
178                    expected: BinanceOrderBookL2Snapshot {
179                        last_update_id: 1027024,
180                        time_exchange: Some(
181                            DateTime::from_timestamp_millis(1589436922972).unwrap(),
182                        ),
183                        time_engine: Some(DateTime::from_timestamp_millis(1589436922959).unwrap()),
184                        bids: vec![BinanceLevel {
185                            price: dec!(4.0),
186                            amount: dec!(431.0),
187                        }],
188                        asks: vec![BinanceLevel {
189                            price: dec!(4.00000200),
190                            amount: dec!(12.0),
191                        }],
192                    },
193                },
194            ];
195
196            for (index, test) in tests.into_iter().enumerate() {
197                assert_eq!(
198                    serde_json::from_str::<BinanceOrderBookL2Snapshot>(test.input).unwrap(),
199                    test.expected,
200                    "TC{} failed",
201                    index
202                );
203            }
204        }
205    }
206}