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