rustrade_data/exchange/binance/book/
l1.rs1use crate::{
2 Identifier,
3 books::Level,
4 event::{MarketEvent, MarketIter},
5 exchange::{binance::channel::BinanceChannel, subscription::ExchangeSub},
6 subscription::book::OrderBookL1,
7};
8use chrono::{DateTime, Utc};
9use rust_decimal::Decimal;
10use rustrade_instrument::exchange::ExchangeId;
11use rustrade_integration::subscription::SubscriptionId;
12use serde::Deserialize;
13
14#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize)]
46pub struct BinanceOrderBookL1 {
47 #[serde(alias = "s", deserialize_with = "de_ob_l1_subscription_id")]
48 pub subscription_id: SubscriptionId,
49 #[serde(
50 alias = "T",
51 deserialize_with = "rustrade_integration::serde::de::de_u64_epoch_ms_as_datetime_utc",
52 default = "Utc::now"
53 )]
54 pub time: DateTime<Utc>,
55 #[serde(alias = "b", with = "rust_decimal::serde::str")]
56 pub best_bid_price: Decimal,
57 #[serde(alias = "B", with = "rust_decimal::serde::str")]
58 pub best_bid_amount: Decimal,
59 #[serde(alias = "a", with = "rust_decimal::serde::str")]
60 pub best_ask_price: Decimal,
61 #[serde(alias = "A", with = "rust_decimal::serde::str")]
62 pub best_ask_amount: Decimal,
63}
64
65impl Identifier<Option<SubscriptionId>> for BinanceOrderBookL1 {
66 fn id(&self) -> Option<SubscriptionId> {
67 Some(self.subscription_id.clone())
68 }
69}
70
71impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceOrderBookL1)>
72 for MarketIter<InstrumentKey, OrderBookL1>
73{
74 fn from(
75 (exchange_id, instrument, book): (ExchangeId, InstrumentKey, BinanceOrderBookL1),
76 ) -> Self {
77 let best_ask = if book.best_ask_price.is_zero() {
78 None
79 } else {
80 Some(Level::new(book.best_ask_price, book.best_ask_amount))
81 };
82
83 let best_bid = if book.best_bid_price.is_zero() {
84 None
85 } else {
86 Some(Level::new(book.best_bid_price, book.best_bid_amount))
87 };
88
89 Self(vec![Ok(MarketEvent {
90 time_exchange: book.time,
91 time_received: Utc::now(),
92 exchange: exchange_id,
93 instrument,
94 kind: OrderBookL1 {
95 last_update_time: book.time,
96 best_bid,
97 best_ask,
98 },
99 })])
100 }
101}
102
103pub fn de_ob_l1_subscription_id<'de, D>(deserializer: D) -> Result<SubscriptionId, D::Error>
107where
108 D: serde::de::Deserializer<'de>,
109{
110 <&str as Deserialize>::deserialize(deserializer)
111 .map(|market| ExchangeSub::from((BinanceChannel::ORDER_BOOK_L1, market)).id())
112}
113
114#[cfg(test)]
115#[allow(clippy::unwrap_used)] mod tests {
117 use super::*;
118
119 mod de {
120 use super::*;
121 use rust_decimal_macros::dec;
122
123 #[test]
124 fn test_binance_order_book_l1() {
125 struct TestCase {
126 input: &'static str,
127 expected: BinanceOrderBookL1,
128 }
129
130 let time = Utc::now();
131
132 let tests = vec![
133 TestCase {
134 input: r#"
136 {
137 "u":22606535573,
138 "s":"ETHUSDT",
139 "b":"1215.27000000",
140 "B":"32.49110000",
141 "a":"1215.28000000",
142 "A":"13.93900000"
143 }
144 "#,
145 expected: BinanceOrderBookL1 {
146 subscription_id: SubscriptionId::from("@bookTicker|ETHUSDT"),
147 time,
148 best_bid_price: dec!(1215.27000000),
149 best_bid_amount: dec!(32.49110000),
150 best_ask_price: dec!(1215.28000000),
151 best_ask_amount: dec!(13.93900000),
152 },
153 },
154 TestCase {
155 input: r#"
157 {
158 "e":"bookTicker",
159 "u":2286618712950,
160 "s":"BTCUSDT",
161 "b":"16858.90",
162 "B":"13.692",
163 "a":"16859.00",
164 "A":"30.219",
165 "T":1671621244670,
166 "E":1671621244673
167 }"#,
168 expected: BinanceOrderBookL1 {
169 subscription_id: SubscriptionId::from("@bookTicker|BTCUSDT"),
170 time,
171 best_bid_price: dec!(16858.90),
172 best_bid_amount: dec!(13.692),
173 best_ask_price: dec!(16859.00),
174 best_ask_amount: dec!(30.219),
175 },
176 },
177 ];
178
179 for (index, test) in tests.into_iter().enumerate() {
180 let actual = serde_json::from_str::<BinanceOrderBookL1>(test.input).unwrap();
181 let actual = BinanceOrderBookL1 { time, ..actual };
182 assert_eq!(actual, test.expected, "TC{} failed", index);
183 }
184 }
185 }
186}