rustrade_data/exchange/binance/
trade.rs1use super::BinanceChannel;
2use crate::{
3 Identifier,
4 event::{MarketEvent, MarketIter},
5 exchange::ExchangeSub,
6 subscription::trade::PublicTrade,
7};
8use chrono::{DateTime, Utc};
9use rust_decimal::Decimal;
10use rustrade_instrument::{Side, exchange::ExchangeId};
11use rustrade_integration::subscription::SubscriptionId;
12use serde::Deserialize;
13use smol_str::format_smolstr;
14
15#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize)]
60pub struct BinanceTrade {
61 #[serde(alias = "s", deserialize_with = "de_trade_subscription_id")]
62 pub subscription_id: SubscriptionId,
63 #[serde(
64 alias = "T",
65 deserialize_with = "rustrade_integration::serde::de::de_u64_epoch_ms_as_datetime_utc"
66 )]
67 pub time: DateTime<Utc>,
68 #[serde(alias = "t")]
69 pub id: u64,
70 #[serde(
71 alias = "p",
72 deserialize_with = "rustrade_integration::serde::de::de_str"
73 )]
74 pub price: Decimal,
75 #[serde(
76 alias = "q",
77 deserialize_with = "rustrade_integration::serde::de::de_str"
78 )]
79 pub amount: Decimal,
80 #[serde(alias = "m", deserialize_with = "de_side_from_buyer_is_maker")]
81 pub side: Side,
82}
83
84impl Identifier<Option<SubscriptionId>> for BinanceTrade {
85 fn id(&self) -> Option<SubscriptionId> {
86 Some(self.subscription_id.clone())
87 }
88}
89
90impl<InstrumentKey> From<(ExchangeId, InstrumentKey, BinanceTrade)>
91 for MarketIter<InstrumentKey, PublicTrade>
92{
93 fn from((exchange_id, instrument, trade): (ExchangeId, InstrumentKey, BinanceTrade)) -> Self {
94 Self(vec![Ok(MarketEvent {
95 time_exchange: trade.time,
96 time_received: Utc::now(),
97 exchange: exchange_id,
98 instrument,
99 kind: PublicTrade {
100 id: format_smolstr!("{}", trade.id),
101 price: trade.price,
102 amount: trade.amount,
103 side: Some(trade.side),
104 },
105 })])
106 }
107}
108
109pub fn de_trade_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::TRADES, market)).id())
117}
118
119pub fn de_side_from_buyer_is_maker<'de, D>(deserializer: D) -> Result<Side, D::Error>
125where
126 D: serde::de::Deserializer<'de>,
127{
128 Deserialize::deserialize(deserializer).map(|buyer_is_maker| {
129 if buyer_is_maker {
130 Side::Sell
131 } else {
132 Side::Buy
133 }
134 })
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 mod de {
142 use std::time::Duration;
143
144 use super::*;
145 use rust_decimal_macros::dec;
146 use rustrade_integration::{
147 error::SocketError, serde::de::datetime_utc_from_epoch_duration,
148 };
149 use serde::de::Error;
150
151 #[test]
152 fn test_binance_trade() {
153 struct TestCase {
154 input: &'static str,
155 expected: Result<BinanceTrade, SocketError>,
156 }
157
158 let tests = vec![
159 TestCase {
160 input: r#"
162 {
163 "e":"trade","E":1649324825173,"s":"ETHUSDT","t":1000000000,
164 "p":"10000.19","q":"0.239000","b":10108767791,"a":10108764858,
165 "T":1749354825200,"m":false,"M":true
166 }
167 "#,
168 expected: Ok(BinanceTrade {
169 subscription_id: SubscriptionId::from("@trade|ETHUSDT"),
170 time: datetime_utc_from_epoch_duration(Duration::from_millis(
171 1749354825200,
172 )),
173 id: 1000000000,
174 price: dec!(10000.19),
175 amount: dec!(0.239000),
176 side: Side::Buy,
177 }),
178 },
179 TestCase {
180 input: r#"{
182 "e":"trade","E":1649324825173,"s":"ETHUSDT","t":1000000000,
183 "p":"10000.19000000","q":"0.239000","b":10108767791,"a":10108764858,
184 "T":1649324825173,"m":"yes","M":true
185 }"#,
186 expected: Err(SocketError::Deserialise {
187 error: serde_json::Error::custom(""),
188 payload: "".to_owned(),
189 }),
190 },
191 TestCase {
192 input: r#"
194 {
195 "e": "trade","E": 1649839266194,"T": 1749354825200,"s": "ETHUSDT",
196 "t": 1000000000,"p":"10000.19","q":"0.239000","X": "MARKET","m": true
197 }
198 "#,
199 expected: Ok(BinanceTrade {
200 subscription_id: SubscriptionId::from("@trade|ETHUSDT"),
201 time: datetime_utc_from_epoch_duration(Duration::from_millis(
202 1749354825200,
203 )),
204 id: 1000000000,
205 price: dec!(10000.19),
206 amount: dec!(0.239000),
207 side: Side::Sell,
208 }),
209 },
210 TestCase {
211 input: r#"
213 {
214 "e": "trade","E": 1649839266194,"T": 1749354825200,"s": "ETHUSDT",
215 "t": 1000000000,"p":"10000.19","q":"0.239000","X": "LIQUIDATION","m": false
216 }
217 "#,
218 expected: Ok(BinanceTrade {
219 subscription_id: SubscriptionId::from("@trade|ETHUSDT"),
220 time: datetime_utc_from_epoch_duration(Duration::from_millis(
221 1749354825200,
222 )),
223 id: 1000000000,
224 price: dec!(10000.19),
225 amount: dec!(0.239000),
226 side: Side::Buy,
227 }),
228 },
229 TestCase {
230 input: r#"{
232 "e": "trade","E": 1649839266194,"T": 1749354825200,"s": "ETHUSDT",
233 "t": 1000000000,"p":"10000.19","q":"0.239000","X": "INSURANCE_FUND","m": false
234 }"#,
235 expected: Ok(BinanceTrade {
236 subscription_id: SubscriptionId::from("@trade|ETHUSDT"),
237 time: datetime_utc_from_epoch_duration(Duration::from_millis(
238 1749354825200,
239 )),
240 id: 1000000000,
241 price: dec!(10000.19),
242 amount: dec!(0.239000),
243 side: Side::Buy,
244 }),
245 },
246 ];
247
248 for (index, test) in tests.into_iter().enumerate() {
249 let actual = serde_json::from_str::<BinanceTrade>(test.input);
250 match (actual, test.expected) {
251 (Ok(actual), Ok(expected)) => {
252 assert_eq!(actual, expected, "TC{} failed", index)
253 }
254 (Err(_), Err(_)) => {
255 }
257 (actual, expected) => {
258 panic!(
260 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
261 );
262 }
263 }
264 }
265 }
266 }
267}