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