Skip to main content

ccxt_exchanges/binance/parser/
margin.rs

1#![allow(dead_code, clippy::unnecessary_wraps)]
2
3use ccxt_core::{
4    Result,
5    error::{Error, ParseError},
6    types::{
7        BorrowInterest, BorrowRate, BorrowRateHistory, MarginAdjustment, MarginLoan, MaxBorrowable,
8        MaxTransferable,
9    },
10};
11use serde_json::Value;
12
13/// Parse borrow interest rate data from Binance margin API.
14pub fn parse_borrow_rate(data: &Value, currency: &str, symbol: Option<&str>) -> Result<BorrowRate> {
15    let rate = if let Some(rate_str) = data["dailyInterestRate"].as_str() {
16        rate_str.parse::<f64>().unwrap_or(0.0)
17    } else {
18        data["dailyInterestRate"].as_f64().unwrap_or(0.0)
19    };
20
21    let timestamp = data["timestamp"]
22        .as_i64()
23        .or_else(|| {
24            data["vipLevel"]
25                .as_i64()
26                .map(|_| chrono::Utc::now().timestamp_millis())
27        })
28        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
29
30    if let Some(sym) = symbol {
31        Ok(BorrowRate::new_isolated(
32            currency.to_string(),
33            sym.to_string(),
34            rate,
35            timestamp,
36            data.clone(),
37        ))
38    } else {
39        Ok(BorrowRate::new_cross(
40            currency.to_string(),
41            rate,
42            timestamp,
43            data.clone(),
44        ))
45    }
46}
47
48/// Parse margin loan record data from Binance margin API.
49pub fn parse_margin_loan(data: &Value) -> Result<MarginLoan> {
50    let id = data["tranId"]
51        .as_i64()
52        .or_else(|| data["txId"].as_i64())
53        .map(|id| id.to_string())
54        .unwrap_or_default();
55
56    let currency = data["asset"]
57        .as_str()
58        .ok_or_else(|| Error::from(ParseError::missing_field("asset")))?
59        .to_string();
60
61    let symbol = data["symbol"].as_str().map(ToString::to_string);
62
63    let amount = if let Some(amount_str) = data["amount"].as_str() {
64        amount_str.parse::<f64>().unwrap_or(0.0)
65    } else {
66        data["amount"].as_f64().unwrap_or(0.0)
67    };
68
69    let timestamp = data["timestamp"]
70        .as_i64()
71        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
72
73    let status = data["status"].as_str().unwrap_or("CONFIRMED").to_string();
74
75    Ok(MarginLoan::new(
76        id,
77        currency,
78        symbol,
79        amount,
80        timestamp,
81        status,
82        data.clone(),
83    ))
84}
85
86/// Parse margin adjustment (transfer) data from Binance margin API.
87pub fn parse_margin_adjustment(data: &Value) -> Result<MarginAdjustment> {
88    let id = data["tranId"]
89        .as_i64()
90        .or_else(|| data["txId"].as_i64())
91        .map(|id| id.to_string())
92        .unwrap_or_default();
93
94    let symbol = data["symbol"].as_str().map(ToString::to_string);
95
96    let currency = data["asset"]
97        .as_str()
98        .ok_or_else(|| Error::from(ParseError::missing_field("asset")))?
99        .to_string();
100
101    let amount = if let Some(amount_str) = data["amount"].as_str() {
102        amount_str.parse::<f64>().unwrap_or(0.0)
103    } else {
104        data["amount"].as_f64().unwrap_or(0.0)
105    };
106
107    let transfer_type = data["type"]
108        .as_str()
109        .or_else(|| data["transFrom"].as_str())
110        .map_or("IN", |t| {
111            if t.contains("MAIN") || t.eq("1") || t.eq("ROLL_IN") {
112                "IN"
113            } else {
114                "OUT"
115            }
116        })
117        .to_string();
118
119    let timestamp = data["timestamp"]
120        .as_i64()
121        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
122
123    let status = data["status"].as_str().unwrap_or("SUCCESS").to_string();
124
125    Ok(MarginAdjustment::new(
126        id,
127        symbol,
128        currency,
129        amount,
130        transfer_type,
131        timestamp,
132        status,
133        data.clone(),
134    ))
135}
136
137/// Parse borrow interest accrual data from Binance margin API.
138pub fn parse_borrow_interest(data: &Value) -> Result<BorrowInterest> {
139    let id = data["txId"]
140        .as_i64()
141        .or_else(|| {
142            data["isolatedSymbol"]
143                .as_str()
144                .map(|_| chrono::Utc::now().timestamp_millis())
145        })
146        .map(|id| id.to_string())
147        .unwrap_or_default();
148
149    let currency = data["asset"]
150        .as_str()
151        .ok_or_else(|| Error::from(ParseError::missing_field("asset")))?
152        .to_string();
153
154    let symbol = data["isolatedSymbol"].as_str().map(ToString::to_string);
155
156    let interest = if let Some(interest_str) = data["interest"].as_str() {
157        interest_str.parse::<f64>().unwrap_or(0.0)
158    } else {
159        data["interest"].as_f64().unwrap_or(0.0)
160    };
161
162    let interest_rate = if let Some(rate_str) = data["interestRate"].as_str() {
163        rate_str.parse::<f64>().unwrap_or(0.0)
164    } else {
165        data["interestRate"].as_f64().unwrap_or(0.0)
166    };
167
168    let principal = if let Some(principal_str) = data["principal"].as_str() {
169        principal_str.parse::<f64>().unwrap_or(0.0)
170    } else {
171        data["principal"].as_f64().unwrap_or(0.0)
172    };
173
174    let timestamp = data["interestAccuredTime"]
175        .as_i64()
176        .or_else(|| data["timestamp"].as_i64())
177        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
178
179    Ok(BorrowInterest::new(
180        id,
181        currency,
182        symbol,
183        interest,
184        interest_rate,
185        principal,
186        timestamp,
187        data.clone(),
188    ))
189}
190
191/// Parse multiple borrow interest records from Binance API response.
192pub fn parse_borrow_interests(data: &Value) -> Result<Vec<BorrowInterest>> {
193    let mut interests = Vec::new();
194
195    if let Some(array) = data.as_array() {
196        for item in array {
197            match parse_borrow_interest(item) {
198                Ok(interest) => interests.push(interest),
199                Err(e) => {
200                    eprintln!("Failed to parse borrow interest: {}", e);
201                }
202            }
203        }
204    }
205
206    Ok(interests)
207}
208
209/// Parse borrow rate history from Binance API response.
210pub fn parse_borrow_rate_history(data: &Value, currency: &str) -> Result<BorrowRateHistory> {
211    let timestamp = data["timestamp"]
212        .as_i64()
213        .or_else(|| data["time"].as_i64())
214        .unwrap_or(0);
215
216    let rate = data["hourlyInterestRate"]
217        .as_str()
218        .or_else(|| data["dailyInterestRate"].as_str())
219        .or_else(|| data["rate"].as_str())
220        .and_then(|s| s.parse::<f64>().ok())
221        .or_else(|| data["hourlyInterestRate"].as_f64())
222        .or_else(|| data["dailyInterestRate"].as_f64())
223        .or_else(|| data["rate"].as_f64())
224        .unwrap_or(0.0);
225
226    let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
227        .map(|dt| dt.to_rfc3339())
228        .unwrap_or_default();
229
230    let symbol = data["symbol"].as_str().map(ToString::to_string);
231    let vip_level = data["vipLevel"].as_i64().map(|v| v as i32);
232
233    Ok(BorrowRateHistory {
234        currency: currency.to_string(),
235        symbol,
236        rate,
237        timestamp,
238        datetime,
239        vip_level,
240        info: data.clone(),
241    })
242}
243
244/// Parse isolated margin borrow rates from Binance API response.
245pub fn parse_isolated_borrow_rates(
246    data: &Value,
247) -> Result<std::collections::HashMap<String, ccxt_core::types::IsolatedBorrowRate>> {
248    use ccxt_core::types::IsolatedBorrowRate;
249    use std::collections::HashMap;
250
251    let mut rates_map = HashMap::new();
252
253    if let Some(array) = data.as_array() {
254        for item in array {
255            let symbol = item["symbol"].as_str().unwrap_or("");
256            let base = item["base"].as_str().unwrap_or("");
257            let quote = item["quote"].as_str().unwrap_or("");
258
259            let base_rate = item["dailyInterestRate"]
260                .as_str()
261                .and_then(|s| s.parse::<f64>().ok())
262                .unwrap_or(0.0);
263
264            let quote_rate = item["quoteDailyInterestRate"]
265                .as_str()
266                .and_then(|s| s.parse::<f64>().ok())
267                .or_else(|| {
268                    item["dailyInterestRate"]
269                        .as_str()
270                        .and_then(|s| s.parse::<f64>().ok())
271                })
272                .unwrap_or(0.0);
273
274            let timestamp = item["timestamp"].as_i64().or_else(|| item["time"].as_i64());
275
276            let datetime = timestamp.and_then(|ts| {
277                chrono::DateTime::from_timestamp_millis(ts).map(|dt| dt.to_rfc3339())
278            });
279
280            let isolated_rate = IsolatedBorrowRate {
281                symbol: symbol.to_string(),
282                base: base.to_string(),
283                base_rate,
284                quote: quote.to_string(),
285                quote_rate,
286                period: 86400000,
287                timestamp,
288                datetime,
289                info: item.clone(),
290            };
291
292            rates_map.insert(symbol.to_string(), isolated_rate);
293        }
294    }
295
296    Ok(rates_map)
297}
298
299/// Parse maximum borrowable amount data from Binance margin API.
300pub fn parse_max_borrowable(
301    data: &Value,
302    currency: &str,
303    symbol: Option<String>,
304) -> Result<MaxBorrowable> {
305    let amount = if let Some(amount_str) = data["amount"].as_str() {
306        amount_str.parse::<f64>().unwrap_or(0.0)
307    } else {
308        data["amount"].as_f64().unwrap_or(0.0)
309    };
310
311    let borrow_limit = if let Some(limit_str) = data["borrowLimit"].as_str() {
312        Some(limit_str.parse::<f64>().unwrap_or(0.0))
313    } else {
314        data["borrowLimit"].as_f64()
315    };
316
317    let timestamp = chrono::Utc::now().timestamp_millis();
318    let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
319        .map(|dt| dt.to_rfc3339())
320        .unwrap_or_default();
321
322    Ok(MaxBorrowable {
323        currency: currency.to_string(),
324        amount,
325        borrow_limit,
326        symbol,
327        timestamp,
328        datetime,
329        info: data.clone(),
330    })
331}
332
333/// Parse maximum transferable amount data from Binance margin API.
334pub fn parse_max_transferable(
335    data: &Value,
336    currency: &str,
337    symbol: Option<String>,
338) -> Result<MaxTransferable> {
339    let amount = if let Some(amount_str) = data["amount"].as_str() {
340        amount_str.parse::<f64>().unwrap_or(0.0)
341    } else {
342        data["amount"].as_f64().unwrap_or(0.0)
343    };
344
345    let timestamp = chrono::Utc::now().timestamp_millis();
346    let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
347        .map(|dt| dt.to_rfc3339())
348        .unwrap_or_default();
349
350    Ok(MaxTransferable {
351        currency: currency.to_string(),
352        amount,
353        symbol,
354        timestamp,
355        datetime,
356        info: data.clone(),
357    })
358}