barter_data/exchange/bitmex/
trade.rs

1use crate::{
2    event::{MarketEvent, MarketIter},
3    exchange::bitmex::message::BitmexMessage,
4    subscription::trade::PublicTrade,
5};
6use barter_instrument::{Side, exchange::ExchangeId};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10/// Terse type alias for an [`BitmexTrade`](BitmexTradeInner) real-time trades WebSocket message.
11pub type BitmexTrade = BitmexMessage<BitmexTradeInner>;
12
13/// ### Raw Payload Examples
14/// See docs: <https://www.bitmex.com/app/wsAPI#Response-Format>
15/// #### Trade payload
16/// ```json
17/// {
18///     "table": "trade",
19///     "action": "insert",
20///     "data": [
21///         {
22///             "timestamp": "2023-02-18T09:27:59.701Z",
23///             "symbol": "XBTUSD",
24///             "side": "Sell",
25///             "size": 200,
26///             "price": 24564.5,
27///             "tickDirection": "MinusTick",
28///             "trdMatchID": "31e50cb7-e005-a44e-f354-86e88dff52eb",
29///             "grossValue": 814184,
30///             "homeNotional": 0.00814184,
31///             "foreignNotional": 200,
32///             "trdType": "Regular"
33///         }
34///     ]
35/// }
36///```
37#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
38pub struct BitmexTradeInner {
39    pub timestamp: DateTime<Utc>,
40    pub symbol: String,
41    pub side: Side,
42    #[serde(rename = "size")]
43    pub amount: f64,
44    pub price: f64,
45
46    #[serde(rename = "trdMatchID")]
47    pub id: String,
48}
49
50impl<InstrumentKey: Clone> From<(ExchangeId, InstrumentKey, BitmexTrade)>
51    for MarketIter<InstrumentKey, PublicTrade>
52{
53    fn from((exchange, instrument, trades): (ExchangeId, InstrumentKey, BitmexTrade)) -> Self {
54        Self(
55            trades
56                .data
57                .into_iter()
58                .map(|trade| {
59                    Ok(MarketEvent {
60                        time_exchange: trade.timestamp,
61                        time_received: Utc::now(),
62                        exchange,
63                        instrument: instrument.clone(),
64                        kind: PublicTrade {
65                            id: trade.id,
66                            price: trade.price,
67                            amount: trade.amount,
68                            side: trade.side,
69                        },
70                    })
71                })
72                .collect(),
73        )
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    mod de {
82        use super::*;
83        use barter_integration::error::SocketError;
84        use chrono::{Duration, TimeZone};
85
86        #[test]
87        fn test_bitmex_trade() {
88            struct TestCase {
89                input: &'static str,
90                expected: Result<BitmexTradeInner, SocketError>,
91            }
92
93            let tests = vec![
94                // TC0: input BitmexTrade is deserialised
95                TestCase {
96                    input: r#"
97                    {
98                        "timestamp": "2023-02-18T09:27:59.701Z",
99                        "symbol": "XBTUSD",
100                        "side": "Sell",
101                        "size": 200,
102                        "price": 24564.5,
103                        "tickDirection": "MinusTick",
104                        "trdMatchID": "31e50cb7-e005-a44e-f354-86e88dff52eb",
105                        "grossValue": 814184,
106                        "homeNotional": 0.00814184,
107                        "foreignNotional": 200,
108                        "trdType": "Regular"
109                    }
110                    "#,
111                    expected: Ok(BitmexTradeInner {
112                        timestamp: Utc.with_ymd_and_hms(2023, 2, 18, 9, 27, 59).unwrap()
113                            + Duration::milliseconds(701),
114                        symbol: "XBTUSD".to_string(),
115                        side: Side::Sell,
116                        amount: 200.0,
117                        price: 24564.5,
118                        id: "31e50cb7-e005-a44e-f354-86e88dff52eb".to_string(),
119                    }),
120                },
121            ];
122
123            for (index, test) in tests.into_iter().enumerate() {
124                let actual = serde_json::from_str::<BitmexTradeInner>(test.input);
125                match (actual, test.expected) {
126                    (Ok(actual), Ok(expected)) => {
127                        assert_eq!(actual, expected, "TC{} failed", index)
128                    }
129                    (Err(_), Err(_)) => {
130                        // Test passed
131                    }
132                    (actual, expected) => {
133                        // Test failed
134                        panic!(
135                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
136                        );
137                    }
138                }
139            }
140        }
141
142        #[test]
143        fn test_bitmex_trade_payload() {
144            struct TestCase {
145                input: &'static str,
146                expected: Result<BitmexTrade, SocketError>,
147            }
148
149            let tests = vec![
150                // TC0: input BitmexTradePayload is deserialised
151                TestCase {
152                    input: r#"
153                    {
154                        "table": "trade",
155                        "action": "insert",
156                        "data": [
157                            {
158                                "timestamp": "2023-02-18T09:27:59.701Z",
159                                "symbol": "XBTUSD",
160                                "side": "Sell",
161                                "size": 200,
162                                "price": 24564.5,
163                                "tickDirection": "MinusTick",
164                                "trdMatchID": "31e50cb7-e005-a44e-f354-86e88dff52eb",
165                                "grossValue": 814184,
166                                "homeNotional": 0.00814184,
167                                "foreignNotional": 200,
168                                "trdType": "Regular"
169                            }
170                        ]
171                    }
172                    "#,
173                    expected: Ok(BitmexTrade {
174                        table: "trade".to_string(),
175                        data: vec![BitmexTradeInner {
176                            timestamp: Utc.with_ymd_and_hms(2023, 2, 18, 9, 27, 59).unwrap()
177                                + Duration::milliseconds(701),
178                            symbol: "XBTUSD".to_string(),
179                            side: Side::Sell,
180                            amount: 200.0,
181                            price: 24564.5,
182                            id: "31e50cb7-e005-a44e-f354-86e88dff52eb".to_string(),
183                        }],
184                    }),
185                },
186            ];
187
188            for (index, test) in tests.into_iter().enumerate() {
189                let actual = serde_json::from_str::<BitmexTrade>(test.input);
190                match (actual, test.expected) {
191                    (Ok(actual), Ok(expected)) => {
192                        assert_eq!(actual, expected, "TC{} failed", index)
193                    }
194                    (Err(_), Err(_)) => {
195                        // Test passed
196                    }
197                    (actual, expected) => {
198                        // Test failed
199                        panic!(
200                            "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
201                        );
202                    }
203                }
204            }
205        }
206    }
207}