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#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn deserialize_trade_event() {
260 let json = r#"{
261 "e": "trade",
262 "E": 1672515782136,
263 "s": "BTCUSDT",
264 "t": 12345,
265 "p": "42000.50",
266 "q": "0.001",
267 "T": 1672515782136,
268 "m": false
269 }"#;
270 let event: BinanceTradeEvent = serde_json::from_str(json).unwrap();
271 assert_eq!(event.symbol, "BTCUSDT");
272 assert!((event.price - 42000.50).abs() < f64::EPSILON);
273 assert!((event.qty - 0.001).abs() < f64::EPSILON);
274 assert_eq!(event.trade_id, 12345);
275 assert!(!event.is_buyer_maker);
276 }
277
278 #[test]
279 fn deserialize_order_response() {
280 let json = r#"{
281 "symbol": "BTCUSDT",
282 "orderId": 12345,
283 "clientOrderId": "sq-test",
284 "price": "0.00000000",
285 "origQty": "0.00100000",
286 "executedQty": "0.00100000",
287 "status": "FILLED",
288 "type": "MARKET",
289 "side": "BUY",
290 "fills": [
291 {
292 "price": "42000.50000000",
293 "qty": "0.00100000",
294 "commission": "0.00000100",
295 "commissionAsset": "BTC"
296 }
297 ]
298 }"#;
299 let resp: BinanceOrderResponse = serde_json::from_str(json).unwrap();
300 assert_eq!(resp.status, "FILLED");
301 assert_eq!(resp.fills.len(), 1);
302 assert!((resp.fills[0].price - 42000.50).abs() < 0.01);
303 }
304
305 #[test]
306 fn deserialize_all_order_item() {
307 let json = r#"{
308 "symbol": "BTCUSDT",
309 "orderId": 28,
310 "clientOrderId": "sq-abc12345",
311 "price": "0.00000000",
312 "origQty": "0.00100000",
313 "executedQty": "0.00100000",
314 "cummulativeQuoteQty": "42.50000000",
315 "status": "FILLED",
316 "timeInForce": "GTC",
317 "type": "MARKET",
318 "side": "BUY",
319 "time": 1700000000000,
320 "updateTime": 1700000001000,
321 "isWorking": true,
322 "workingTime": 1700000001000,
323 "origQuoteOrderQty": "0.00000000",
324 "selfTradePreventionMode": "NONE"
325 }"#;
326 let order: BinanceAllOrder = serde_json::from_str(json).unwrap();
327 assert_eq!(order.symbol, "BTCUSDT");
328 assert_eq!(order.order_id, 28);
329 assert_eq!(order.status, "FILLED");
330 assert!((order.executed_qty - 0.001).abs() < f64::EPSILON);
331 }
332
333 #[test]
334 fn deserialize_my_trade_item() {
335 let json = r#"{
336 "symbol": "BTCUSDT",
337 "id": 28457,
338 "orderId": 100234,
339 "price": "42000.50000000",
340 "qty": "0.00100000",
341 "commission": "0.00000100",
342 "commissionAsset": "BTC",
343 "time": 1700000001000,
344 "isBuyer": true,
345 "isMaker": false,
346 "isBestMatch": true
347 }"#;
348 let trade: BinanceMyTrade = serde_json::from_str(json).unwrap();
349 assert_eq!(trade.symbol, "BTCUSDT");
350 assert_eq!(trade.order_id, 100234);
351 assert!(trade.is_buyer);
352 assert!(!trade.is_maker);
353 assert!((trade.price - 42000.50).abs() < 0.01);
354 }
355}