ccxt_exchanges/binance/parser/
misc.rs

1#![allow(dead_code, clippy::unnecessary_wraps)]
2
3use super::{parse_decimal, value_to_hashmap};
4use ccxt_core::{
5    Result,
6    error::{Error, ParseError},
7    types::{
8        AccountConfig, CommissionRate, FeeTradingFee, IndexPrice, LedgerDirection, LedgerEntry,
9        LedgerEntryType, Liquidation, MarkPrice, Market, MaxLeverage, MinMax, OpenInterest,
10        OpenInterestHistory, PremiumIndex, Stats24hr, TradingLimits,
11    },
12};
13use rust_decimal::Decimal;
14use rust_decimal::prelude::{FromPrimitive, FromStr, ToPrimitive};
15use serde_json::Value;
16
17/// Parse server time data from Binance API.
18pub fn parse_server_time(data: &Value) -> Result<ccxt_core::types::ServerTime> {
19    use ccxt_core::types::ServerTime;
20
21    let server_time = data["serverTime"]
22        .as_i64()
23        .ok_or_else(|| Error::from(ParseError::missing_field("serverTime")))?;
24
25    Ok(ServerTime::new(server_time))
26}
27
28/// Parse trading fee data from Binance API.
29pub fn parse_trading_fee(data: &Value) -> Result<FeeTradingFee> {
30    let symbol = data["symbol"]
31        .as_str()
32        .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
33        .to_string();
34
35    let maker = data["makerCommission"]
36        .as_str()
37        .and_then(|s| Decimal::from_str(s).ok())
38        .or_else(|| data["makerCommission"].as_f64().and_then(Decimal::from_f64))
39        .ok_or_else(|| {
40            Error::from(ParseError::invalid_format(
41                "data",
42                "Invalid maker commission",
43            ))
44        })?;
45
46    let taker = data["takerCommission"]
47        .as_str()
48        .and_then(|s| Decimal::from_str(s).ok())
49        .or_else(|| data["takerCommission"].as_f64().and_then(Decimal::from_f64))
50        .ok_or_else(|| {
51            Error::from(ParseError::invalid_format(
52                "data",
53                "Invalid taker commission",
54            ))
55        })?;
56
57    Ok(FeeTradingFee::new(symbol, maker, taker))
58}
59
60/// Parse multiple trading fee data entries from Binance API.
61pub fn parse_trading_fees(data: &Value) -> Result<Vec<FeeTradingFee>> {
62    if let Some(array) = data.as_array() {
63        array.iter().map(|item| parse_trading_fee(item)).collect()
64    } else {
65        Ok(vec![parse_trading_fee(data)?])
66    }
67}
68
69/// Parse 24-hour statistics from Binance API response.
70pub fn parse_stats_24hr(data: &Value) -> Result<ccxt_core::types::Stats24hr> {
71    let symbol = data["symbol"]
72        .as_str()
73        .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
74        .to_string();
75
76    let price_change = data["priceChange"]
77        .as_str()
78        .and_then(|s| Decimal::from_str(s).ok());
79
80    let price_change_percent = data["priceChangePercent"]
81        .as_str()
82        .and_then(|s| Decimal::from_str(s).ok());
83
84    let weighted_avg_price = data["weightedAvgPrice"]
85        .as_str()
86        .and_then(|s| Decimal::from_str(s).ok());
87
88    let prev_close_price = data["prevClosePrice"]
89        .as_str()
90        .and_then(|s| Decimal::from_str(s).ok());
91
92    let last_price = data["lastPrice"]
93        .as_str()
94        .and_then(|s| Decimal::from_str(s).ok());
95
96    let last_qty = data["lastQty"]
97        .as_str()
98        .and_then(|s| Decimal::from_str(s).ok());
99
100    let bid_price = data["bidPrice"]
101        .as_str()
102        .and_then(|s| Decimal::from_str(s).ok());
103
104    let bid_qty = data["bidQty"]
105        .as_str()
106        .and_then(|s| Decimal::from_str(s).ok());
107
108    let ask_price = data["askPrice"]
109        .as_str()
110        .and_then(|s| Decimal::from_str(s).ok());
111
112    let ask_qty = data["askQty"]
113        .as_str()
114        .and_then(|s| Decimal::from_str(s).ok());
115
116    let open_price = data["openPrice"]
117        .as_str()
118        .and_then(|s| Decimal::from_str(s).ok());
119
120    let high_price = data["highPrice"]
121        .as_str()
122        .and_then(|s| Decimal::from_str(s).ok());
123
124    let low_price = data["lowPrice"]
125        .as_str()
126        .and_then(|s| Decimal::from_str(s).ok());
127
128    let volume = data["volume"]
129        .as_str()
130        .and_then(|s| Decimal::from_str(s).ok());
131
132    let quote_volume = data["quoteVolume"]
133        .as_str()
134        .and_then(|s| Decimal::from_str(s).ok());
135
136    let open_time = data["openTime"].as_i64();
137    let close_time = data["closeTime"].as_i64();
138    let first_id = data["firstId"].as_i64();
139    let last_id = data["lastId"].as_i64();
140    let count = data["count"].as_i64();
141
142    Ok(Stats24hr {
143        symbol,
144        price_change,
145        price_change_percent,
146        weighted_avg_price,
147        prev_close_price,
148        last_price,
149        last_qty,
150        bid_price,
151        bid_qty,
152        ask_price,
153        ask_qty,
154        open_price,
155        high_price,
156        low_price,
157        volume,
158        quote_volume,
159        open_time,
160        close_time,
161        first_id,
162        last_id,
163        count,
164        info: value_to_hashmap(data),
165    })
166}
167
168/// Parse trading limits from Binance API response.
169pub fn parse_trading_limits(
170    data: &Value,
171    _symbol: String,
172) -> Result<ccxt_core::types::TradingLimits> {
173    let mut price_limits = MinMax::default();
174    let mut amount_limits = MinMax::default();
175    let mut cost_limits = MinMax::default();
176
177    if let Some(filters) = data["filters"].as_array() {
178        for filter in filters {
179            let filter_type = filter["filterType"].as_str().unwrap_or("");
180
181            match filter_type {
182                "PRICE_FILTER" => {
183                    price_limits.min = filter["minPrice"]
184                        .as_str()
185                        .and_then(|s| Decimal::from_str(s).ok());
186                    price_limits.max = filter["maxPrice"]
187                        .as_str()
188                        .and_then(|s| Decimal::from_str(s).ok());
189                }
190                "LOT_SIZE" => {
191                    amount_limits.min = filter["minQty"]
192                        .as_str()
193                        .and_then(|s| Decimal::from_str(s).ok());
194                    amount_limits.max = filter["maxQty"]
195                        .as_str()
196                        .and_then(|s| Decimal::from_str(s).ok());
197                }
198                "MIN_NOTIONAL" | "NOTIONAL" => {
199                    cost_limits.min = filter["minNotional"]
200                        .as_str()
201                        .and_then(|s| Decimal::from_str(s).ok());
202                }
203                _ => {}
204            }
205        }
206    }
207
208    Ok(TradingLimits {
209        min: None,
210        max: None,
211        amount: Some(amount_limits),
212        price: Some(price_limits),
213        cost: Some(cost_limits),
214    })
215}
216
217/// Parse account configuration from Binance futures account info.
218pub fn parse_account_config(data: &Value) -> Result<AccountConfig> {
219    let multi_assets_margin = data["multiAssetsMargin"].as_bool().unwrap_or(false);
220    let fee_tier = data["feeTier"].as_i64().unwrap_or(0) as i32;
221    let can_trade = data["canTrade"].as_bool().unwrap_or(true);
222    let can_deposit = data["canDeposit"].as_bool().unwrap_or(true);
223    let can_withdraw = data["canWithdraw"].as_bool().unwrap_or(true);
224    let update_time = data["updateTime"].as_i64().unwrap_or(0);
225
226    Ok(AccountConfig {
227        info: Some(data.clone()),
228        multi_assets_margin,
229        fee_tier,
230        can_trade,
231        can_deposit,
232        can_withdraw,
233        update_time,
234    })
235}
236
237/// Parse commission rate information from Binance.
238pub fn parse_commission_rate(data: &Value, market: &Market) -> Result<CommissionRate> {
239    let maker_commission_rate = data["makerCommissionRate"]
240        .as_str()
241        .and_then(|s| s.parse::<f64>().ok())
242        .unwrap_or(0.0);
243
244    let taker_commission_rate = data["takerCommissionRate"]
245        .as_str()
246        .and_then(|s| s.parse::<f64>().ok())
247        .unwrap_or(0.0);
248
249    Ok(CommissionRate {
250        info: Some(data.clone()),
251        symbol: market.symbol.clone(),
252        maker_commission_rate,
253        taker_commission_rate,
254    })
255}
256
257/// Parse open interest data from Binance futures API.
258pub fn parse_open_interest(data: &Value, market: &Market) -> Result<OpenInterest> {
259    let open_interest = data["openInterest"]
260        .as_str()
261        .and_then(|s| s.parse::<f64>().ok())
262        .or_else(|| data["openInterest"].as_f64())
263        .unwrap_or(0.0);
264
265    let timestamp = data["time"]
266        .as_i64()
267        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
268
269    let contract_size = market
270        .contract_size
271        .unwrap_or_else(|| rust_decimal::Decimal::from(1))
272        .to_f64()
273        .unwrap_or(1.0);
274    let open_interest_value = open_interest * contract_size;
275
276    Ok(OpenInterest {
277        info: Some(data.clone()),
278        symbol: market.symbol.clone(),
279        open_interest,
280        open_interest_value,
281        timestamp,
282    })
283}
284
285/// Parse open interest history data from Binance futures API.
286pub fn parse_open_interest_history(
287    data: &Value,
288    market: &Market,
289) -> Result<Vec<OpenInterestHistory>> {
290    let array = data.as_array().ok_or_else(|| {
291        Error::from(ParseError::invalid_value(
292            "data",
293            "Expected array for open interest history",
294        ))
295    })?;
296
297    let mut result = Vec::new();
298
299    for item in array {
300        let sum_open_interest = item["sumOpenInterest"]
301            .as_str()
302            .and_then(|s| s.parse::<f64>().ok())
303            .or_else(|| item["sumOpenInterest"].as_f64())
304            .unwrap_or(0.0);
305
306        let sum_open_interest_value = item["sumOpenInterestValue"]
307            .as_str()
308            .and_then(|s| s.parse::<f64>().ok())
309            .or_else(|| item["sumOpenInterestValue"].as_f64())
310            .unwrap_or(0.0);
311
312        let timestamp = item["timestamp"]
313            .as_i64()
314            .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
315
316        result.push(OpenInterestHistory {
317            info: Some(item.clone()),
318            symbol: market.symbol.clone(),
319            sum_open_interest,
320            sum_open_interest_value,
321            timestamp,
322        });
323    }
324
325    Ok(result)
326}
327
328/// Parse maximum leverage data from Binance leverage brackets.
329pub fn parse_max_leverage(data: &Value, market: &Market) -> Result<MaxLeverage> {
330    let target_data = if let Some(array) = data.as_array() {
331        array
332            .iter()
333            .find(|item| item["symbol"].as_str().unwrap_or("") == market.id)
334            .ok_or_else(|| {
335                Error::from(ParseError::invalid_value(
336                    "symbol",
337                    format!("Symbol {} not found in leverage brackets", market.id),
338                ))
339            })?
340    } else {
341        data
342    };
343
344    let brackets = target_data["brackets"]
345        .as_array()
346        .ok_or_else(|| Error::from(ParseError::missing_field("brackets")))?;
347
348    if brackets.is_empty() {
349        return Err(Error::from(ParseError::invalid_value(
350            "data",
351            "Empty brackets array",
352        )));
353    }
354
355    let first_bracket = &brackets[0];
356    let max_leverage = first_bracket["initialLeverage"].as_i64().unwrap_or(1) as i32;
357
358    let notional = first_bracket["notionalCap"]
359        .as_f64()
360        .or_else(|| {
361            first_bracket["notionalCap"]
362                .as_str()
363                .and_then(|s| s.parse::<f64>().ok())
364        })
365        .unwrap_or(0.0);
366
367    Ok(MaxLeverage {
368        info: Some(data.clone()),
369        symbol: market.symbol.clone(),
370        max_leverage,
371        notional,
372    })
373}
374
375/// Parse index price data from Binance futures API.
376pub fn parse_index_price(data: &Value, market: &Market) -> Result<IndexPrice> {
377    let index_price = data["indexPrice"]
378        .as_f64()
379        .or_else(|| {
380            data["indexPrice"]
381                .as_str()
382                .and_then(|s| s.parse::<f64>().ok())
383        })
384        .ok_or_else(|| Error::from(ParseError::missing_field("indexPrice")))?;
385
386    let timestamp = data["time"]
387        .as_i64()
388        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
389
390    Ok(IndexPrice {
391        info: Some(data.clone()),
392        symbol: market.symbol.clone(),
393        index_price,
394        timestamp,
395    })
396}
397
398/// Parse premium index data from Binance futures API.
399pub fn parse_premium_index(data: &Value, market: &Market) -> Result<PremiumIndex> {
400    let mark_price = data["markPrice"]
401        .as_f64()
402        .or_else(|| {
403            data["markPrice"]
404                .as_str()
405                .and_then(|s| s.parse::<f64>().ok())
406        })
407        .ok_or_else(|| Error::from(ParseError::missing_field("markPrice")))?;
408
409    let index_price = data["indexPrice"]
410        .as_f64()
411        .or_else(|| {
412            data["indexPrice"]
413                .as_str()
414                .and_then(|s| s.parse::<f64>().ok())
415        })
416        .ok_or_else(|| Error::from(ParseError::missing_field("indexPrice")))?;
417
418    let estimated_settle_price = data["estimatedSettlePrice"]
419        .as_f64()
420        .or_else(|| {
421            data["estimatedSettlePrice"]
422                .as_str()
423                .and_then(|s| s.parse::<f64>().ok())
424        })
425        .unwrap_or(0.0);
426
427    let last_funding_rate = data["lastFundingRate"]
428        .as_f64()
429        .or_else(|| {
430            data["lastFundingRate"]
431                .as_str()
432                .and_then(|s| s.parse::<f64>().ok())
433        })
434        .unwrap_or(0.0);
435
436    let next_funding_time = data["nextFundingTime"].as_i64().unwrap_or(0);
437
438    let time = data["time"]
439        .as_i64()
440        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
441
442    Ok(PremiumIndex {
443        info: Some(data.clone()),
444        symbol: market.symbol.clone(),
445        mark_price,
446        index_price,
447        estimated_settle_price,
448        last_funding_rate,
449        next_funding_time,
450        time,
451    })
452}
453
454/// Parse liquidation order data from Binance futures API.
455pub fn parse_liquidation(data: &Value, market: &Market) -> Result<Liquidation> {
456    let side = data["side"]
457        .as_str()
458        .ok_or_else(|| Error::from(ParseError::missing_field("side")))?
459        .to_string();
460
461    let order_type = data["type"].as_str().unwrap_or("LIMIT").to_string();
462
463    let time = data["time"]
464        .as_i64()
465        .ok_or_else(|| Error::from(ParseError::missing_field("time")))?;
466
467    let price = data["price"]
468        .as_f64()
469        .or_else(|| data["price"].as_str().and_then(|s| s.parse::<f64>().ok()))
470        .ok_or_else(|| Error::from(ParseError::missing_field("price")))?;
471
472    let quantity = data["origQty"]
473        .as_f64()
474        .or_else(|| data["origQty"].as_str().and_then(|s| s.parse::<f64>().ok()))
475        .ok_or_else(|| Error::from(ParseError::missing_field("origQty")))?;
476
477    let average_price = data["averagePrice"]
478        .as_f64()
479        .or_else(|| {
480            data["averagePrice"]
481                .as_str()
482                .and_then(|s| s.parse::<f64>().ok())
483        })
484        .unwrap_or(price);
485
486    Ok(Liquidation {
487        info: Some(data.clone()),
488        symbol: market.symbol.clone(),
489        side,
490        order_type,
491        time,
492        price,
493        quantity,
494        average_price,
495    })
496}
497
498/// Parse mark price data from Binance futures API.
499pub fn parse_mark_price(data: &Value) -> Result<ccxt_core::types::MarkPrice> {
500    let symbol = data["symbol"]
501        .as_str()
502        .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
503        .to_string();
504
505    let formatted_symbol = if symbol.len() >= 6 {
506        let quote_currencies = ["USDT", "BUSD", "USDC", "BTC", "ETH", "BNB"];
507        let mut found = false;
508        let mut formatted = symbol.clone();
509
510        for quote in &quote_currencies {
511            if symbol.ends_with(quote) {
512                let base = &symbol[..symbol.len() - quote.len()];
513                formatted = format!("{}/{}", base, quote);
514                found = true;
515                break;
516            }
517        }
518
519        if found { formatted } else { symbol.clone() }
520    } else {
521        symbol.clone()
522    };
523
524    let mark_price = parse_decimal(data, "markPrice").unwrap_or(Decimal::ZERO);
525    let index_price = parse_decimal(data, "indexPrice");
526    let estimated_settle_price = parse_decimal(data, "estimatedSettlePrice");
527    let last_funding_rate = parse_decimal(data, "lastFundingRate");
528
529    let next_funding_time = data["nextFundingTime"].as_i64();
530
531    let timestamp = data["time"]
532        .as_i64()
533        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
534
535    Ok(MarkPrice {
536        symbol: formatted_symbol,
537        mark_price,
538        index_price,
539        estimated_settle_price,
540        last_funding_rate,
541        next_funding_time,
542        interest_rate: None,
543        timestamp,
544    })
545}
546
547/// Parse multiple mark price data entries from Binance futures API.
548pub fn parse_mark_prices(data: &Value) -> Result<Vec<ccxt_core::types::MarkPrice>> {
549    if let Some(array) = data.as_array() {
550        array.iter().map(|item| parse_mark_price(item)).collect()
551    } else {
552        Ok(vec![parse_mark_price(data)?])
553    }
554}
555
556/// Parse WebSocket mark price data from Binance futures streams.
557pub fn parse_ws_mark_price(data: &Value) -> Result<MarkPrice> {
558    let symbol = data["s"]
559        .as_str()
560        .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
561        .to_string();
562
563    let mark_price = data["p"]
564        .as_str()
565        .and_then(|s| s.parse::<Decimal>().ok())
566        .ok_or_else(|| Error::from(ParseError::missing_field("mark_price")))?;
567
568    let index_price = data["i"].as_str().and_then(|s| s.parse::<Decimal>().ok());
569    let estimated_settle_price = data["P"].as_str().and_then(|s| s.parse::<Decimal>().ok());
570    let last_funding_rate = data["r"].as_str().and_then(|s| s.parse::<Decimal>().ok());
571    let next_funding_time = data["T"].as_i64();
572    let interest_rate = None;
573    let timestamp = data["E"].as_i64().unwrap_or(0);
574
575    Ok(MarkPrice {
576        symbol,
577        mark_price,
578        index_price,
579        estimated_settle_price,
580        last_funding_rate,
581        next_funding_time,
582        interest_rate,
583        timestamp,
584    })
585}
586
587/// Parse ledger entry from Binance API response.
588pub fn parse_ledger_entry(data: &Value) -> Result<LedgerEntry> {
589    let id = data["tranId"]
590        .as_i64()
591        .or_else(|| data["id"].as_i64())
592        .map(|v| v.to_string())
593        .or_else(|| data["tranId"].as_str().map(ToString::to_string))
594        .or_else(|| data["id"].as_str().map(ToString::to_string))
595        .unwrap_or_default();
596
597    let currency = data["asset"]
598        .as_str()
599        .or_else(|| data["currency"].as_str())
600        .unwrap_or("")
601        .to_string();
602
603    let amount = data["amount"]
604        .as_str()
605        .and_then(|s| s.parse::<f64>().ok())
606        .or_else(|| data["amount"].as_f64())
607        .or_else(|| data["qty"].as_str().and_then(|s| s.parse::<f64>().ok()))
608        .or_else(|| data["qty"].as_f64())
609        .unwrap_or(0.0);
610
611    let timestamp = data["timestamp"]
612        .as_i64()
613        .or_else(|| data["time"].as_i64())
614        .unwrap_or(0);
615
616    let type_str = data["type"].as_str().unwrap_or("");
617    let (direction, entry_type) = match type_str {
618        "DEPOSIT" => (LedgerDirection::In, LedgerEntryType::Deposit),
619        "WITHDRAW" => (LedgerDirection::Out, LedgerEntryType::Withdrawal),
620        "FEE" => (LedgerDirection::Out, LedgerEntryType::Fee),
621        "REBATE" => (LedgerDirection::In, LedgerEntryType::Rebate),
622        "TRANSFER" => (
623            if amount >= 0.0 {
624                LedgerDirection::In
625            } else {
626                LedgerDirection::Out
627            },
628            LedgerEntryType::Transfer,
629        ),
630        _ => (
631            if amount >= 0.0 {
632                LedgerDirection::In
633            } else {
634                LedgerDirection::Out
635            },
636            LedgerEntryType::Trade,
637        ),
638    };
639
640    let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
641        .map(|dt| dt.to_rfc3339())
642        .unwrap_or_default();
643
644    Ok(LedgerEntry {
645        id,
646        currency,
647        account: None,
648        reference_account: None,
649        reference_id: None,
650        type_: entry_type,
651        direction,
652        amount: amount.abs(),
653        timestamp,
654        datetime,
655        before: None,
656        after: None,
657        status: None,
658        fee: None,
659        info: data.clone(),
660    })
661}