Skip to main content

sandbox_quant/binance/
types.rs

1use serde::Deserialize;
2
3/// Deserialize Binance string-encoded numbers to f64.
4pub 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/// Binance trade stream event (symbol@trade).
28#[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/// Binance order response (newOrderRespType=FULL).
47#[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/// Binance all orders response item (GET /api/v3/allOrders).
79#[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/// Binance my trades response item (GET /api/v3/myTrades).
101#[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/// Binance API error response.
122#[derive(Debug, Deserialize)]
123pub struct BinanceApiErrorResponse {
124    pub code: i64,
125    pub msg: String,
126}
127
128/// Binance server time response.
129#[derive(Debug, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ServerTimeResponse {
132    pub server_time: u64,
133}
134
135/// Binance account info response (GET /api/v3/account).
136#[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/// Binance futures order response (POST /fapi/v1/order, RESULT/FULL-like fields).
151#[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/// Binance futures account info (GET /fapi/v2/account).
217#[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}