Skip to main content

binance/spot/ws/
incoming_message.rs

1use rust_decimal::Decimal;
2use serde::Deserialize;
3
4use crate::{
5    Timestamp,
6    spot::{
7        KlineInterval,
8        http::OrderLevel,
9        ws::{MessageID, StreamName},
10    },
11    ws::ReceivedMessage,
12};
13
14#[derive(PartialEq, Deserialize, Debug)]
15#[serde(untagged)]
16#[allow(clippy::large_enum_variant)]
17pub enum IncomingMessage {
18    CombinedStream(CombinedStreamMessage<StreamMessage>),
19    Stream(StreamMessage),
20    Error(ErrorMessage),
21    Response(ResponseMessage), // last.
22}
23
24impl ReceivedMessage for IncomingMessage {
25    fn server_shutdown_event_time(&self) -> Option<u64> {
26        match self {
27            IncomingMessage::Stream(StreamMessage::ServerShutdown(ServerShutdownMsg {
28                event_time,
29            })) => Some(*event_time),
30            IncomingMessage::CombinedStream(CombinedStreamMessage {
31                data: StreamMessage::ServerShutdown(ServerShutdownMsg { event_time }),
32                ..
33            }) => Some(*event_time),
34            _ => None,
35        }
36    }
37}
38
39#[derive(PartialEq, Deserialize, Debug)]
40pub struct ResponseMessage {
41    pub id: Option<MessageID>,
42    pub status: Option<i64>,
43    pub result: Option<serde_json::Value>,
44    pub rate_limits: Option<Vec<serde_json::Value>>,
45}
46
47#[derive(Debug, Deserialize, PartialEq)]
48pub struct CombinedStreamMessage<T> {
49    pub stream: StreamName,
50    pub data: T,
51}
52
53#[derive(PartialEq, Deserialize, Debug)]
54#[serde(tag = "e")]
55pub enum StreamMessage {
56    #[serde(rename = "aggTrade")]
57    AggTrade(AggTradeMsg),
58    #[serde(rename = "trade")]
59    Trade(TradeMsg),
60    #[serde(rename = "kline")]
61    Kline(KlineMsg),
62    #[serde(rename = "24hrMiniTicker")]
63    MiniTicker24(MiniTicker24Msg),
64    #[serde(rename = "depthUpdate")]
65    DepthUpdate(DepthUpdateMsg),
66    #[serde(rename = "serverShutdown")]
67    ServerShutdown(ServerShutdownMsg),
68}
69
70/// The Aggregate Trade Streams push trade information that is aggregated for a single taker order.
71#[derive(PartialEq, Deserialize, Debug)]
72pub struct AggTradeMsg {
73    /// Event time
74    #[serde(rename = "E")]
75    pub event_time: Timestamp,
76    /// Symbol
77    #[serde(rename = "s")]
78    pub symbol: String,
79    /// Aggregate trade ID
80    #[serde(rename = "a")]
81    pub trade_id: i64,
82    /// Price
83    #[serde(rename = "p")]
84    pub price: Decimal,
85    /// Quantity
86    #[serde(rename = "q")]
87    pub qty: Decimal,
88    /// First trade ID
89    #[serde(rename = "f")]
90    pub first_trade_id: i64,
91    /// Last trade ID
92    #[serde(rename = "l")]
93    pub last_trade_id: i64,
94    /// Trade time
95    #[serde(rename = "T")]
96    pub trade_time: Timestamp,
97    /// Is the buyer the market maker?
98    #[serde(rename = "m")]
99    pub is_buyer_maker: bool,
100}
101
102/// The Trade Streams push raw trade information; each trade has a unique buyer and seller.
103#[derive(PartialEq, Deserialize, Debug)]
104pub struct TradeMsg {
105    /// Event time
106    #[serde(rename = "E")]
107    pub event_time: Timestamp,
108    /// Symbol
109    #[serde(rename = "s")]
110    pub symbol: String,
111    /// Trade ID
112    #[serde(rename = "t")]
113    pub trade_id: i64,
114    /// Price
115    #[serde(rename = "p")]
116    pub price: Decimal,
117    /// Quantity
118    #[serde(rename = "q")]
119    pub qty: Decimal,
120    /// Trade time
121    #[serde(rename = "T")]
122    pub trade_time: Timestamp,
123    /// Is the buyer the market maker?
124    #[serde(rename = "m")]
125    pub is_buyer_maker: bool,
126}
127
128/// The Kline/Candlestick Stream push updates to the current klines/candlestick every second in UTC+0 timezone
129#[derive(PartialEq, Deserialize, Debug)]
130pub struct KlineMsg {
131    /// Event time
132    #[serde(rename = "E")]
133    pub event_time: Timestamp,
134    /// Symbol
135    #[serde(rename = "s")]
136    pub symbol: String,
137    #[serde(rename = "k")]
138    pub kline: Kline,
139}
140#[derive(PartialEq, Deserialize, Debug)]
141pub struct Kline {
142    /// Kline start time
143    #[serde(rename = "t")]
144    pub start_time: Timestamp,
145    /// Kline close time
146    #[serde(rename = "T")]
147    pub close_time: Timestamp,
148    /// Symbol
149    #[serde(rename = "s")]
150    pub symbol: String,
151    /// Interval
152    #[serde(rename = "i")]
153    pub interval: KlineInterval,
154    /// First trade ID
155    #[serde(rename = "f")]
156    pub first_trade_id: i64,
157    /// Last trade ID
158    #[serde(rename = "L")]
159    pub last_trade_id: i64,
160    /// Open price
161    #[serde(rename = "o")]
162    pub open_price: Decimal,
163    /// Close price
164    #[serde(rename = "c")]
165    pub close_price: Decimal,
166    /// High price
167    #[serde(rename = "h")]
168    pub high_price: Decimal,
169    /// Low price
170    #[serde(rename = "l")]
171    pub low_price: Decimal,
172    /// Base asset volume
173    #[serde(rename = "v")]
174    pub base_asset_volume: Decimal,
175    /// Number of trades
176    #[serde(rename = "n")]
177    pub trade_number: i64,
178    /// Is this kline closed?
179    #[serde(rename = "x")]
180    pub is_closed: bool,
181    /// Quote asset volume
182    #[serde(rename = "q")]
183    pub quote_asset_volume: Decimal,
184    /// Taker buy base asset volume
185    #[serde(rename = "V")]
186    pub taker_buy_base_asset_volume: Decimal,
187    /// Taker buy quote asset volume
188    #[serde(rename = "Q")]
189    pub taker_buy_quote_asset_volume: Decimal,
190}
191
192/// 24hr rolling window mini-ticker statistics. These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs.
193#[derive(PartialEq, Deserialize, Debug)]
194pub struct MiniTicker24Msg {
195    /// Event time
196    #[serde(rename = "E")]
197    pub event_time: Timestamp,
198    /// Symbol
199    #[serde(rename = "s")]
200    pub symbol: String,
201    /// Open price
202    #[serde(rename = "o")]
203    pub open_price: Decimal,
204    /// Close price
205    #[serde(rename = "c")]
206    pub close_price: Decimal,
207    /// High price
208    #[serde(rename = "h")]
209    pub high_price: Decimal,
210    /// Low price
211    #[serde(rename = "l")]
212    pub low_price: Decimal,
213    /// Total traded base asset volume
214    #[serde(rename = "v")]
215    pub total_base_asset_volume: Decimal,
216    /// Total traded quote asset volume
217    #[serde(rename = "q")]
218    pub total_quote_asset_volume: Decimal,
219}
220
221#[derive(PartialEq, Deserialize, Debug)]
222pub struct DepthUpdateMsg {
223    /// Event time
224    #[serde(rename = "E")]
225    pub event_time: Timestamp,
226    /// Symbol
227    #[serde(rename = "s")]
228    pub symbol: String,
229    /// First update ID in event
230    #[serde(rename = "U")]
231    pub first_update_id: i64,
232    /// Final update ID in event
233    #[serde(rename = "u")]
234    pub final_update_id: i64,
235    /// Bids to be updated (price + qty pairs).
236    #[serde(rename = "b")]
237    pub bids: Vec<OrderLevel>,
238    /// Asks to be updated (price + qty pairs).
239    #[serde(rename = "a")]
240    pub asks: Vec<OrderLevel>,
241}
242
243#[derive(PartialEq, Deserialize, Debug)]
244pub struct ServerShutdownMsg {
245    /// Event time
246    #[serde(rename = "E")]
247    pub event_time: Timestamp,
248}
249
250#[derive(PartialEq, Deserialize, Debug)]
251pub struct ErrorMessage {
252    pub error: ErrorValueMessage,
253    pub id: Option<MessageID>,
254}
255
256#[derive(PartialEq, Deserialize, Debug)]
257pub struct ErrorValueMessage {
258    pub code: i64,
259    pub msg: String,
260}
261
262#[cfg(test)]
263mod tests {
264    use rust_decimal::dec;
265
266    use crate::serde::deserialize_json;
267
268    use super::*;
269
270    #[test]
271    fn test_deserialize_combined_stream_event() {
272        let json = r#"{
273            "stream": "bnbbtc@trade",
274            "data": "DATA"
275        }"#;
276        let expected = CombinedStreamMessage {
277            stream: StreamName::Trade {
278                symbol: String::from("BNBBTC").to_lowercase(),
279            },
280            data: String::from("DATA"),
281        };
282
283        let current = deserialize_json(json).unwrap();
284
285        assert_eq!(expected, current);
286    }
287
288    #[test]
289    fn test_deserialize_stream_message_agg_trade() {
290        let json = r#"{
291            "e": "aggTrade",
292            "E": 1672515782136,
293            "s": "BNBBTC",
294            "a": 12345,
295            "p": "0.001",
296            "q": "100",
297            "f": 100,
298            "l": 105,
299            "T": 1672515782136,
300            "m": true,
301            "M": true
302        }"#;
303        let expected = AggTradeMsg {
304            event_time: 1672515782136,
305            symbol: String::from("BNBBTC"),
306            trade_id: 12345,
307            price: dec!(0.001),
308            qty: dec!(100),
309            first_trade_id: 100,
310            last_trade_id: 105,
311            trade_time: 1672515782136,
312            is_buyer_maker: true,
313        };
314
315        let current = deserialize_json(json).unwrap();
316
317        assert_eq!(expected, current);
318    }
319
320    #[test]
321    fn test_deserialize_stream_message_trade() {
322        let json = r#"{
323            "e": "trade",
324            "E": 1672515782136,
325            "s": "BNBBTC",
326            "t": 12345,
327            "p": "0.001",
328            "q": "100",
329            "T": 1672515782136,
330            "m": true,
331            "M": true
332        }"#;
333        let expected = TradeMsg {
334            event_time: 1672515782136,
335            symbol: String::from("BNBBTC"),
336            trade_id: 12345,
337            price: dec!(0.001),
338            qty: dec!(100),
339            trade_time: 1672515782136,
340            is_buyer_maker: true,
341        };
342
343        let current = deserialize_json(json).unwrap();
344
345        assert_eq!(expected, current);
346    }
347
348    #[test]
349    fn test_deserialize_stream_message_kline() {
350        let json = r#"{
351            "e": "kline",
352            "E": 1672515782136,
353            "s": "BNBBTC",
354            "k": {
355                "t": 1672515780000,
356                "T": 1672515839999,
357                "s": "BNBBTC",
358                "i": "1m",
359                "f": 100,
360                "L": 200,
361                "o": "0.0010",
362                "c": "0.0020",
363                "h": "0.0025",
364                "l": "0.0015",
365                "v": "1000",
366                "n": 100,
367                "x": false,
368                "q": "1.0000",
369                "V": "500",
370                "Q": "0.500",
371                "B": "123456"
372            }
373        }"#;
374        let symbol = String::from("BNBBTC");
375        let expected = KlineMsg {
376            event_time: 1672515782136,
377            symbol: symbol.clone(),
378            kline: Kline {
379                start_time: 1672515780000,
380                close_time: 1672515839999,
381                symbol,
382                interval: KlineInterval::Minute1,
383                first_trade_id: 100,
384                last_trade_id: 200,
385                open_price: dec!(0.0010),
386                close_price: dec!(0.0020),
387                high_price: dec!(0.0025),
388                low_price: dec!(0.0015),
389                base_asset_volume: dec!(1000),
390                trade_number: 100,
391                is_closed: false,
392                quote_asset_volume: dec!(1.0000),
393                taker_buy_base_asset_volume: dec!(500),
394                taker_buy_quote_asset_volume: dec!(0.500),
395            },
396        };
397
398        let current = deserialize_json(json).unwrap();
399
400        assert_eq!(expected, current);
401    }
402
403    #[test]
404    fn test_deserialize_stream_message_mini_ticker24() {
405        let json = r#"{
406            "e": "24hrMiniTicker",
407            "E": 1672515782136,
408            "s": "BNBBTC",
409            "c": "0.0025",
410            "o": "0.0010",
411            "h": "0.0025",
412            "l": "0.0010",
413            "v": "10000",
414            "q": "18"
415        }"#;
416        let expected = MiniTicker24Msg {
417            event_time: 1672515782136,
418            symbol: String::from("BNBBTC"),
419            open_price: dec!(0.0010),
420            close_price: dec!(0.0025),
421            high_price: dec!(0.0025),
422            low_price: dec!(0.0010),
423            total_base_asset_volume: dec!(10000),
424            total_quote_asset_volume: dec!(18),
425        };
426
427        let current = deserialize_json(json).unwrap();
428
429        assert_eq!(expected, current);
430    }
431
432    #[test]
433    fn test_deserialize_stream_message_response() {
434        let json = r#"{"result":null,"id":"message-id"}"#;
435        let parsed: IncomingMessage = deserialize_json(json).unwrap();
436        assert!(
437            matches!(parsed, IncomingMessage::Response(_)),
438            "expected Response variant, got {parsed:?}",
439        );
440    }
441}