Skip to main content

ccxt_exchanges/binance/parser/
balance.rs

1use super::{parse_decimal, value_to_hashmap};
2use ccxt_core::{
3    Result,
4    error::{Error, ParseError},
5    types::{Balance, BalanceEntry},
6};
7use rust_decimal::Decimal;
8use rust_decimal::prelude::FromPrimitive;
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// Parse balance data from Binance account info.
13pub fn parse_balance(data: &Value) -> Result<Balance> {
14    let mut balances = HashMap::new();
15
16    if let Some(balances_array) = data["balances"].as_array() {
17        for balance in balances_array {
18            let currency = balance["asset"]
19                .as_str()
20                .ok_or_else(|| Error::from(ParseError::missing_field("asset")))?
21                .to_string();
22
23            let free = parse_decimal(balance, "free").unwrap_or(Decimal::ZERO);
24            let locked = parse_decimal(balance, "locked").unwrap_or(Decimal::ZERO);
25            let total = free + locked;
26
27            balances.insert(
28                currency,
29                BalanceEntry {
30                    free,
31                    used: locked,
32                    total,
33                },
34            );
35        }
36    }
37
38    Ok(Balance {
39        balances,
40        info: value_to_hashmap(data),
41    })
42}
43
44/// Parse balance information with account type support.
45pub fn parse_balance_with_type(data: &Value, account_type: &str) -> Result<Balance> {
46    let mut balances = HashMap::new();
47
48    match account_type {
49        "spot" => {
50            if let Some(balances_array) = data["balances"].as_array() {
51                for item in balances_array {
52                    if let Some(asset) = item["asset"].as_str() {
53                        let free = if let Some(free_str) = item["free"].as_str() {
54                            free_str.parse::<f64>().unwrap_or(0.0)
55                        } else {
56                            item["free"].as_f64().unwrap_or(0.0)
57                        };
58
59                        let locked = if let Some(locked_str) = item["locked"].as_str() {
60                            locked_str.parse::<f64>().unwrap_or(0.0)
61                        } else {
62                            item["locked"].as_f64().unwrap_or(0.0)
63                        };
64
65                        balances.insert(
66                            asset.to_string(),
67                            BalanceEntry {
68                                free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
69                                used: Decimal::from_f64(locked).unwrap_or(Decimal::ZERO),
70                                total: Decimal::from_f64(free + locked).unwrap_or(Decimal::ZERO),
71                            },
72                        );
73                    }
74                }
75            }
76        }
77        "margin" | "cross" => {
78            if let Some(user_assets) = data["userAssets"].as_array() {
79                for item in user_assets {
80                    if let Some(asset) = item["asset"].as_str() {
81                        let free = if let Some(free_str) = item["free"].as_str() {
82                            free_str.parse::<f64>().unwrap_or(0.0)
83                        } else {
84                            item["free"].as_f64().unwrap_or(0.0)
85                        };
86
87                        let locked = if let Some(locked_str) = item["locked"].as_str() {
88                            locked_str.parse::<f64>().unwrap_or(0.0)
89                        } else {
90                            item["locked"].as_f64().unwrap_or(0.0)
91                        };
92
93                        balances.insert(
94                            asset.to_string(),
95                            BalanceEntry {
96                                free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
97                                used: Decimal::from_f64(locked).unwrap_or(Decimal::ZERO),
98                                total: Decimal::from_f64(free + locked).unwrap_or(Decimal::ZERO),
99                            },
100                        );
101                    }
102                }
103            }
104        }
105        "isolated" => {
106            if let Some(assets) = data["assets"].as_array() {
107                for item in assets {
108                    if let Some(base_asset) = item["baseAsset"].as_object() {
109                        if let Some(asset) = base_asset["asset"].as_str() {
110                            let free = if let Some(free_str) = base_asset["free"].as_str() {
111                                free_str.parse::<f64>().unwrap_or(0.0)
112                            } else {
113                                base_asset["free"].as_f64().unwrap_or(0.0)
114                            };
115
116                            let locked = if let Some(locked_str) = base_asset["locked"].as_str() {
117                                locked_str.parse::<f64>().unwrap_or(0.0)
118                            } else {
119                                base_asset["locked"].as_f64().unwrap_or(0.0)
120                            };
121
122                            balances.insert(
123                                asset.to_string(),
124                                BalanceEntry {
125                                    free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
126                                    used: Decimal::from_f64(locked).unwrap_or(Decimal::ZERO),
127                                    total: Decimal::from_f64(free + locked)
128                                        .unwrap_or(Decimal::ZERO),
129                                },
130                            );
131                        }
132                    }
133
134                    if let Some(quote_asset) = item["quoteAsset"].as_object() {
135                        if let Some(asset) = quote_asset["asset"].as_str() {
136                            let free = if let Some(free_str) = quote_asset["free"].as_str() {
137                                free_str.parse::<f64>().unwrap_or(0.0)
138                            } else {
139                                quote_asset["free"].as_f64().unwrap_or(0.0)
140                            };
141
142                            let locked = if let Some(locked_str) = quote_asset["locked"].as_str() {
143                                locked_str.parse::<f64>().unwrap_or(0.0)
144                            } else {
145                                quote_asset["locked"].as_f64().unwrap_or(0.0)
146                            };
147
148                            balances.insert(
149                                asset.to_string(),
150                                BalanceEntry {
151                                    free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
152                                    used: Decimal::from_f64(locked).unwrap_or(Decimal::ZERO),
153                                    total: Decimal::from_f64(free + locked)
154                                        .unwrap_or(Decimal::ZERO),
155                                },
156                            );
157                        }
158                    }
159                }
160            }
161        }
162        "linear" | "future" | "inverse" | "delivery" => {
163            let assets = if let Some(arr) = data.as_array() {
164                arr.clone()
165            } else if let Some(arr) = data["assets"].as_array() {
166                arr.clone()
167            } else {
168                vec![]
169            };
170
171            for item in &assets {
172                if let Some(asset) = item["asset"].as_str() {
173                    let available_balance =
174                        if let Some(balance_str) = item["availableBalance"].as_str() {
175                            balance_str.parse::<f64>().unwrap_or(0.0)
176                        } else {
177                            item["availableBalance"].as_f64().unwrap_or(0.0)
178                        };
179
180                    let wallet_balance = if let Some(balance_str) = item["walletBalance"].as_str() {
181                        balance_str.parse::<f64>().unwrap_or(0.0)
182                    } else {
183                        item["walletBalance"].as_f64().unwrap_or(0.0)
184                    };
185
186                    let wallet_balance = if wallet_balance == 0.0 {
187                        if let Some(balance_str) = item["balance"].as_str() {
188                            balance_str.parse::<f64>().unwrap_or(0.0)
189                        } else {
190                            item["balance"].as_f64().unwrap_or(wallet_balance)
191                        }
192                    } else {
193                        wallet_balance
194                    };
195
196                    let used = wallet_balance - available_balance;
197
198                    balances.insert(
199                        asset.to_string(),
200                        BalanceEntry {
201                            free: Decimal::from_f64(available_balance).unwrap_or(Decimal::ZERO),
202                            used: Decimal::from_f64(used).unwrap_or(Decimal::ZERO),
203                            total: Decimal::from_f64(wallet_balance).unwrap_or(Decimal::ZERO),
204                        },
205                    );
206                }
207            }
208        }
209        "funding" => {
210            if let Some(assets) = data.as_array() {
211                for item in assets {
212                    if let Some(asset) = item["asset"].as_str() {
213                        let free = if let Some(free_str) = item["free"].as_str() {
214                            free_str.parse::<f64>().unwrap_or(0.0)
215                        } else {
216                            item["free"].as_f64().unwrap_or(0.0)
217                        };
218
219                        let locked = if let Some(locked_str) = item["locked"].as_str() {
220                            locked_str.parse::<f64>().unwrap_or(0.0)
221                        } else {
222                            item["locked"].as_f64().unwrap_or(0.0)
223                        };
224
225                        let total = free + locked;
226
227                        balances.insert(
228                            asset.to_string(),
229                            BalanceEntry {
230                                free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
231                                used: Decimal::from_f64(locked).unwrap_or(Decimal::ZERO),
232                                total: Decimal::from_f64(total).unwrap_or(Decimal::ZERO),
233                            },
234                        );
235                    }
236                }
237            }
238        }
239        "option" => {
240            if let Some(asset) = data["asset"].as_str() {
241                let equity = if let Some(equity_str) = data["equity"].as_str() {
242                    equity_str.parse::<f64>().unwrap_or(0.0)
243                } else {
244                    data["equity"].as_f64().unwrap_or(0.0)
245                };
246
247                let available = if let Some(available_str) = data["available"].as_str() {
248                    available_str.parse::<f64>().unwrap_or(0.0)
249                } else {
250                    data["available"].as_f64().unwrap_or(0.0)
251                };
252
253                let used = equity - available;
254
255                balances.insert(
256                    asset.to_string(),
257                    BalanceEntry {
258                        free: Decimal::from_f64(available).unwrap_or(Decimal::ZERO),
259                        used: Decimal::from_f64(used).unwrap_or(Decimal::ZERO),
260                        total: Decimal::from_f64(equity).unwrap_or(Decimal::ZERO),
261                    },
262                );
263            }
264        }
265        "portfolio" => {
266            let assets = if let Some(arr) = data.as_array() {
267                arr.clone()
268            } else if data.is_object() {
269                vec![data.clone()]
270            } else {
271                vec![]
272            };
273
274            for item in &assets {
275                if let Some(asset) = item["asset"].as_str() {
276                    let total_wallet_balance =
277                        if let Some(balance_str) = item["totalWalletBalance"].as_str() {
278                            balance_str.parse::<f64>().unwrap_or(0.0)
279                        } else {
280                            item["totalWalletBalance"].as_f64().unwrap_or(0.0)
281                        };
282
283                    let available_balance =
284                        if let Some(balance_str) = item["availableBalance"].as_str() {
285                            balance_str.parse::<f64>().unwrap_or(0.0)
286                        } else {
287                            item["availableBalance"].as_f64().unwrap_or(0.0)
288                        };
289
290                    let free = if available_balance > 0.0 {
291                        available_balance
292                    } else if let Some(cross_str) = item["crossWalletBalance"].as_str() {
293                        cross_str.parse::<f64>().unwrap_or(0.0)
294                    } else {
295                        item["crossWalletBalance"].as_f64().unwrap_or(0.0)
296                    };
297
298                    let used = total_wallet_balance - free;
299
300                    balances.insert(
301                        asset.to_string(),
302                        BalanceEntry {
303                            free: Decimal::from_f64(free).unwrap_or(Decimal::ZERO),
304                            used: Decimal::from_f64(used).unwrap_or(Decimal::ZERO),
305                            total: Decimal::from_f64(total_wallet_balance).unwrap_or(Decimal::ZERO),
306                        },
307                    );
308                }
309            }
310        }
311        _ => {
312            return Err(Error::from(ParseError::invalid_value(
313                "account_type",
314                format!("Unsupported account type: {}", account_type),
315            )));
316        }
317    }
318
319    let mut info_map = HashMap::new();
320    if let Some(obj) = data.as_object() {
321        for (k, v) in obj {
322            info_map.insert(k.clone(), v.clone());
323        }
324    }
325
326    Ok(Balance {
327        balances,
328        info: info_map,
329    })
330}