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