rustrade_data/exchange/binance/book/
l2.rs1use super::{super::channel::BinanceChannel, BinanceLevel};
2use crate::{
3 Identifier,
4 books::{OrderBook, OrderBookTimes},
5 event::MarketEvent,
6 exchange::subscription::ExchangeSub,
7 subscription::book::OrderBookEvent,
8};
9use chrono::{DateTime, Utc};
10use derive_more::Constructor;
11use rustrade_instrument::exchange::ExchangeId;
12use rustrade_integration::subscription::SubscriptionId;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Constructor)]
16pub struct BinanceOrderBookL2Meta<InstrumentKey, Sequencer> {
17 pub key: InstrumentKey,
18 pub sequencer: Sequencer,
19}
20
21#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
57pub struct BinanceOrderBookL2Snapshot {
58 #[serde(rename = "lastUpdateId")]
59 pub last_update_id: u64,
60 #[serde(default, rename = "E", with = "chrono::serde::ts_milliseconds_option")]
61 pub time_exchange: Option<DateTime<Utc>>,
62 #[serde(default, rename = "T", with = "chrono::serde::ts_milliseconds_option")]
63 pub time_engine: Option<DateTime<Utc>>,
64 pub bids: Vec<BinanceLevel>,
65 pub asks: Vec<BinanceLevel>,
66}
67
68impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceOrderBookL2Snapshot)>
69 for MarketEvent<InstrumentKey, OrderBookEvent>
70{
71 fn from(
72 (exchange, instrument, snapshot): (ExchangeId, InstrumentKey, BinanceOrderBookL2Snapshot),
73 ) -> Self {
74 let time_received = Utc::now();
86 Self {
87 time_exchange: snapshot.time_exchange.unwrap_or(time_received),
88 time_received,
89 exchange,
90 instrument,
91 kind: OrderBookEvent::Snapshot(OrderBook::new(
92 snapshot.last_update_id,
93 OrderBookTimes {
94 time_engine: snapshot.time_engine,
95 time_exchange: snapshot.time_exchange,
96 time_received,
97 },
98 snapshot.bids,
99 snapshot.asks,
100 )),
101 }
102 }
103}
104
105pub fn de_ob_l2_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
112where
113 D: serde::de::Deserializer<'de>,
114{
115 <&str as Deserialize>::deserialize(deserializer)
116 .map(|market| ExchangeSub::from((BinanceChannel::ORDER_BOOK_L2, market)).id())
117}
118
119#[cfg(test)]
120#[allow(clippy::unwrap_used)] mod tests {
122 use super::*;
123
124 mod de {
125 use super::*;
126 use rust_decimal_macros::dec;
127
128 #[test]
129 fn test_binance_order_book_l2_snapshot() {
130 struct TestCase {
131 input: &'static str,
132 expected: BinanceOrderBookL2Snapshot,
133 }
134
135 let tests = vec![
136 TestCase {
137 input: r#"
139 {
140 "lastUpdateId": 1027024,
141 "bids": [
142 [
143 "4.00000000",
144 "431.00000000"
145 ]
146 ],
147 "asks": [
148 [
149 "4.00000200",
150 "12.00000000"
151 ]
152 ]
153 }
154 "#,
155 expected: BinanceOrderBookL2Snapshot {
156 last_update_id: 1027024,
157 time_exchange: Default::default(),
158 time_engine: Default::default(),
159 bids: vec![BinanceLevel {
160 price: dec!(4.00000000),
161 amount: dec!(431.00000000),
162 }],
163 asks: vec![BinanceLevel {
164 price: dec!(4.00000200),
165 amount: dec!(12.00000000),
166 }],
167 },
168 },
169 TestCase {
170 input: r#"
172 {
173 "lastUpdateId": 1027024,
174 "E": 1589436922972,
175 "T": 1589436922959,
176 "bids": [
177 [
178 "4.00000000",
179 "431.00000000"
180 ]
181 ],
182 "asks": [
183 [
184 "4.00000200",
185 "12.00000000"
186 ]
187 ]
188 }
189 "#,
190 expected: BinanceOrderBookL2Snapshot {
191 last_update_id: 1027024,
192 time_exchange: Some(
193 DateTime::from_timestamp_millis(1589436922972).unwrap(),
194 ),
195 time_engine: Some(DateTime::from_timestamp_millis(1589436922959).unwrap()),
196 bids: vec![BinanceLevel {
197 price: dec!(4.0),
198 amount: dec!(431.0),
199 }],
200 asks: vec![BinanceLevel {
201 price: dec!(4.00000200),
202 amount: dec!(12.0),
203 }],
204 },
205 },
206 ];
207
208 for (index, test) in tests.into_iter().enumerate() {
209 assert_eq!(
210 serde_json::from_str::<BinanceOrderBookL2Snapshot>(test.input).unwrap(),
211 test.expected,
212 "TC{} failed",
213 index
214 );
215 }
216 }
217 }
218
219 mod seed {
220 use super::*;
221 use rust_decimal_macros::dec;
222
223 fn snapshot(
224 time_exchange: Option<DateTime<Utc>>,
225 time_engine: Option<DateTime<Utc>>,
226 ) -> BinanceOrderBookL2Snapshot {
227 BinanceOrderBookL2Snapshot {
228 last_update_id: 1,
229 time_exchange,
230 time_engine,
231 bids: vec![BinanceLevel {
232 price: dec!(100),
233 amount: dec!(1),
234 }],
235 asks: vec![],
236 }
237 }
238
239 #[test]
240 fn futures_seed_carries_distinct_times_not_transposed() {
241 let e = DateTime::from_timestamp_millis(1589436922972).unwrap(); let t = DateTime::from_timestamp_millis(1589436922959).unwrap(); assert_ne!(e, t);
246
247 let event: MarketEvent<&str, OrderBookEvent> = (
248 ExchangeId::BinanceFuturesUsd,
249 "inst",
250 snapshot(Some(e), Some(t)),
251 )
252 .into();
253 let OrderBookEvent::Snapshot(book) = event.kind else {
254 panic!("expected Snapshot");
255 };
256 assert_eq!(book.time_exchange(), Some(e), "time_exchange must be \"E\"");
257 assert_eq!(book.time_engine(), Some(t), "time_engine must be \"T\"");
258 assert_eq!(book.time_received(), event.time_received);
260 }
261
262 #[test]
263 fn spot_seed_has_no_venue_times_only_time_received() {
264 let event: MarketEvent<&str, OrderBookEvent> =
267 (ExchangeId::BinanceSpot, "inst", snapshot(None, None)).into();
268 let OrderBookEvent::Snapshot(book) = event.kind else {
269 panic!("expected Snapshot");
270 };
271 assert_eq!(book.time_exchange(), None);
272 assert_eq!(book.time_engine(), None);
273 assert_eq!(book.time_received(), event.time_received);
274 }
275 }
276}