1use serde::Deserialize;
2
3pub fn string_to_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
5where
6 D: serde::Deserializer<'de>,
7{
8 let s = String::deserialize(deserializer)?;
9 s.parse::<f64>().map_err(serde::de::Error::custom)
10}
11
12pub fn string_or_number_to_f64_default<'de, D>(deserializer: D) -> Result<f64, D::Error>
13where
14 D: serde::Deserializer<'de>,
15{
16 let v = serde_json::Value::deserialize(deserializer)?;
17 match v {
18 serde_json::Value::Null => Ok(0.0),
19 serde_json::Value::String(s) => s.parse::<f64>().map_err(serde::de::Error::custom),
20 serde_json::Value::Number(n) => n
21 .as_f64()
22 .ok_or_else(|| serde::de::Error::custom("invalid number")),
23 _ => Err(serde::de::Error::custom("invalid numeric value")),
24 }
25}
26
27#[derive(Debug, Deserialize)]
29pub struct BinanceTradeEvent {
30 #[serde(rename = "e")]
31 pub event_type: String,
32 #[serde(rename = "E")]
33 pub event_time: u64,
34 #[serde(rename = "s")]
35 pub symbol: String,
36 #[serde(rename = "t")]
37 pub trade_id: u64,
38 #[serde(rename = "p", deserialize_with = "string_to_f64")]
39 pub price: f64,
40 #[serde(rename = "q", deserialize_with = "string_to_f64")]
41 pub qty: f64,
42 #[serde(rename = "m")]
43 pub is_buyer_maker: bool,
44}
45
46#[derive(Debug, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct BinanceOrderResponse {
50 pub symbol: String,
51 pub order_id: u64,
52 pub client_order_id: String,
53 #[serde(deserialize_with = "string_to_f64")]
54 pub price: f64,
55 #[serde(deserialize_with = "string_to_f64")]
56 pub orig_qty: f64,
57 #[serde(deserialize_with = "string_to_f64")]
58 pub executed_qty: f64,
59 pub status: String,
60 pub r#type: String,
61 pub side: String,
62 #[serde(default)]
63 pub fills: Vec<BinanceFill>,
64}
65
66#[derive(Debug, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct BinanceFill {
69 #[serde(deserialize_with = "string_to_f64")]
70 pub price: f64,
71 #[serde(deserialize_with = "string_to_f64")]
72 pub qty: f64,
73 #[serde(deserialize_with = "string_to_f64")]
74 pub commission: f64,
75 pub commission_asset: String,
76}
77
78#[derive(Debug, Deserialize, Clone)]
80#[serde(rename_all = "camelCase")]
81pub struct BinanceAllOrder {
82 pub symbol: String,
83 pub order_id: u64,
84 pub client_order_id: String,
85 #[serde(deserialize_with = "string_to_f64")]
86 pub price: f64,
87 #[serde(deserialize_with = "string_to_f64")]
88 pub orig_qty: f64,
89 #[serde(deserialize_with = "string_to_f64")]
90 pub executed_qty: f64,
91 #[serde(deserialize_with = "string_to_f64")]
92 pub cummulative_quote_qty: f64,
93 pub status: String,
94 pub r#type: String,
95 pub side: String,
96 pub time: u64,
97 pub update_time: u64,
98}
99
100#[derive(Debug, Deserialize, Clone)]
102#[serde(rename_all = "camelCase")]
103pub struct BinanceMyTrade {
104 pub symbol: String,
105 pub id: u64,
106 pub order_id: u64,
107 #[serde(deserialize_with = "string_to_f64")]
108 pub price: f64,
109 #[serde(deserialize_with = "string_to_f64")]
110 pub qty: f64,
111 #[serde(deserialize_with = "string_to_f64")]
112 pub commission: f64,
113 pub commission_asset: String,
114 pub time: u64,
115 pub is_buyer: bool,
116 pub is_maker: bool,
117 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
118 pub realized_pnl: f64,
119}
120
121#[derive(Debug, Deserialize)]
123pub struct BinanceApiErrorResponse {
124 pub code: i64,
125 pub msg: String,
126}
127
128#[derive(Debug, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ServerTimeResponse {
132 pub server_time: u64,
133}
134
135#[derive(Debug, Deserialize)]
137pub struct AccountInfo {
138 pub balances: Vec<AccountBalance>,
139}
140
141#[derive(Debug, Deserialize)]
142pub struct AccountBalance {
143 pub asset: String,
144 #[serde(deserialize_with = "string_to_f64")]
145 pub free: f64,
146 #[serde(deserialize_with = "string_to_f64")]
147 pub locked: f64,
148}
149
150#[derive(Debug, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct BinanceFuturesOrderResponse {
154 pub symbol: String,
155 pub order_id: u64,
156 pub client_order_id: String,
157 #[serde(default, deserialize_with = "string_to_f64")]
158 pub price: f64,
159 #[serde(default, deserialize_with = "string_to_f64")]
160 pub orig_qty: f64,
161 #[serde(default, deserialize_with = "string_to_f64")]
162 pub executed_qty: f64,
163 #[serde(default, deserialize_with = "string_to_f64")]
164 pub avg_price: f64,
165 pub status: String,
166 pub r#type: String,
167 pub side: String,
168}
169
170#[derive(Debug, Deserialize, Clone)]
171#[serde(rename_all = "camelCase")]
172pub struct BinanceFuturesAllOrder {
173 pub symbol: String,
174 pub order_id: u64,
175 pub client_order_id: String,
176 #[serde(deserialize_with = "string_or_number_to_f64_default")]
177 pub price: f64,
178 #[serde(deserialize_with = "string_or_number_to_f64_default")]
179 pub orig_qty: f64,
180 #[serde(deserialize_with = "string_or_number_to_f64_default")]
181 pub executed_qty: f64,
182 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
183 pub cum_quote: f64,
184 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
185 pub avg_price: f64,
186 pub status: String,
187 pub r#type: String,
188 pub side: String,
189 pub time: u64,
190 pub update_time: u64,
191}
192
193#[derive(Debug, Deserialize, Clone)]
194#[serde(rename_all = "camelCase")]
195pub struct BinanceFuturesUserTrade {
196 pub symbol: String,
197 pub id: u64,
198 pub order_id: u64,
199 #[serde(deserialize_with = "string_or_number_to_f64_default")]
200 pub price: f64,
201 #[serde(deserialize_with = "string_or_number_to_f64_default")]
202 pub qty: f64,
203 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
204 pub commission: f64,
205 #[serde(default)]
206 pub commission_asset: String,
207 pub time: u64,
208 #[serde(default)]
209 pub buyer: bool,
210 #[serde(default)]
211 pub maker: bool,
212 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
213 pub realized_pnl: f64,
214}
215
216#[derive(Debug, Deserialize)]
218#[serde(rename_all = "camelCase")]
219pub struct BinanceFuturesAccountInfo {
220 #[serde(default)]
221 pub assets: Vec<BinanceFuturesAssetBalance>,
222}
223
224#[derive(Debug, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct BinanceFuturesAssetBalance {
227 pub asset: String,
228 #[serde(default, deserialize_with = "string_to_f64")]
229 pub wallet_balance: f64,
230 #[serde(default, deserialize_with = "string_to_f64")]
231 pub available_balance: f64,
232}
233
234#[derive(Debug, Deserialize, Clone)]
236#[serde(rename_all = "camelCase")]
237pub struct BinanceFuturesPositionRisk {
238 pub symbol: String,
239 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
240 pub position_amt: f64,
241 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
242 pub entry_price: f64,
243 #[serde(default, deserialize_with = "string_or_number_to_f64_default")]
244 pub mark_price: f64,
245 #[serde(
246 default,
247 rename = "unRealizedProfit",
248 alias = "unrealizedProfit",
249 deserialize_with = "string_or_number_to_f64_default"
250 )]
251 pub unrealized_profit: f64,
252}
253
254#[derive(Debug, Deserialize)]
255pub struct BinanceListenKeyResponse {
256 #[serde(rename = "listenKey")]
257 pub listen_key: String,
258}
259
260#[derive(Debug, Deserialize)]
261#[serde(tag = "e")]
262pub enum BinanceFuturesUserDataEvent {
263 #[serde(rename = "ACCOUNT_UPDATE")]
264 AccountUpdate(BinanceFuturesAccountUpdateEvent),
265 #[serde(other)]
266 Unknown,
267}
268
269#[derive(Debug, Deserialize)]
270pub struct BinanceFuturesAccountUpdateEvent {
271 #[serde(rename = "E")]
272 pub event_time: u64,
273 #[serde(rename = "a")]
274 pub account: BinanceFuturesAccountUpdateData,
275}
276
277#[derive(Debug, Deserialize)]
278pub struct BinanceFuturesAccountUpdateData {
279 #[serde(rename = "P", default)]
280 pub positions: Vec<BinanceFuturesAccountUpdatePosition>,
281}
282
283#[derive(Debug, Deserialize, Clone)]
284pub struct BinanceFuturesAccountUpdatePosition {
285 #[serde(rename = "s")]
286 pub symbol: String,
287 #[serde(rename = "pa", deserialize_with = "string_or_number_to_f64_default")]
288 pub position_amt: f64,
289 #[serde(rename = "ep", deserialize_with = "string_or_number_to_f64_default")]
290 pub entry_price: f64,
291 #[serde(rename = "up", deserialize_with = "string_or_number_to_f64_default")]
292 pub unrealized_pnl: f64,
293}
294
295#[derive(Debug, Deserialize)]
296#[serde(tag = "e")]
297pub enum BinanceSpotUserDataEvent {
298 #[serde(rename = "executionReport")]
299 ExecutionReport(BinanceSpotExecutionReportEvent),
300 #[serde(rename = "outboundAccountPosition")]
301 OutboundAccountPosition(BinanceSpotOutboundAccountPositionEvent),
302 #[serde(other)]
303 Unknown,
304}
305
306#[derive(Debug, Deserialize)]
307pub struct BinanceSpotExecutionReportEvent {
308 #[serde(rename = "s")]
309 pub symbol: String,
310}
311
312#[derive(Debug, Deserialize)]
313pub struct BinanceSpotOutboundAccountPositionEvent {}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn deserialize_trade_event() {
321 let json = r#"{
322 "e": "trade",
323 "E": 1672515782136,
324 "s": "BTCUSDT",
325 "t": 12345,
326 "p": "42000.50",
327 "q": "0.001",
328 "T": 1672515782136,
329 "m": false
330 }"#;
331 let event: BinanceTradeEvent = serde_json::from_str(json).unwrap();
332 assert_eq!(event.symbol, "BTCUSDT");
333 assert!((event.price - 42000.50).abs() < f64::EPSILON);
334 assert!((event.qty - 0.001).abs() < f64::EPSILON);
335 assert_eq!(event.trade_id, 12345);
336 assert!(!event.is_buyer_maker);
337 }
338
339 #[test]
340 fn deserialize_order_response() {
341 let json = r#"{
342 "symbol": "BTCUSDT",
343 "orderId": 12345,
344 "clientOrderId": "sq-test",
345 "price": "0.00000000",
346 "origQty": "0.00100000",
347 "executedQty": "0.00100000",
348 "status": "FILLED",
349 "type": "MARKET",
350 "side": "BUY",
351 "fills": [
352 {
353 "price": "42000.50000000",
354 "qty": "0.00100000",
355 "commission": "0.00000100",
356 "commissionAsset": "BTC"
357 }
358 ]
359 }"#;
360 let resp: BinanceOrderResponse = serde_json::from_str(json).unwrap();
361 assert_eq!(resp.status, "FILLED");
362 assert_eq!(resp.fills.len(), 1);
363 assert!((resp.fills[0].price - 42000.50).abs() < 0.01);
364 }
365
366 #[test]
367 fn deserialize_all_order_item() {
368 let json = r#"{
369 "symbol": "BTCUSDT",
370 "orderId": 28,
371 "clientOrderId": "sq-abc12345",
372 "price": "0.00000000",
373 "origQty": "0.00100000",
374 "executedQty": "0.00100000",
375 "cummulativeQuoteQty": "42.50000000",
376 "status": "FILLED",
377 "timeInForce": "GTC",
378 "type": "MARKET",
379 "side": "BUY",
380 "time": 1700000000000,
381 "updateTime": 1700000001000,
382 "isWorking": true,
383 "workingTime": 1700000001000,
384 "origQuoteOrderQty": "0.00000000",
385 "selfTradePreventionMode": "NONE"
386 }"#;
387 let order: BinanceAllOrder = serde_json::from_str(json).unwrap();
388 assert_eq!(order.symbol, "BTCUSDT");
389 assert_eq!(order.order_id, 28);
390 assert_eq!(order.status, "FILLED");
391 assert!((order.executed_qty - 0.001).abs() < f64::EPSILON);
392 }
393
394 #[test]
395 fn deserialize_my_trade_item() {
396 let json = r#"{
397 "symbol": "BTCUSDT",
398 "id": 28457,
399 "orderId": 100234,
400 "price": "42000.50000000",
401 "qty": "0.00100000",
402 "commission": "0.00000100",
403 "commissionAsset": "BTC",
404 "time": 1700000001000,
405 "isBuyer": true,
406 "isMaker": false,
407 "isBestMatch": true
408 }"#;
409 let trade: BinanceMyTrade = serde_json::from_str(json).unwrap();
410 assert_eq!(trade.symbol, "BTCUSDT");
411 assert_eq!(trade.order_id, 100234);
412 assert!(trade.is_buyer);
413 assert!(!trade.is_maker);
414 assert!((trade.price - 42000.50).abs() < 0.01);
415 }
416}