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