ccxt_exchanges/binance/
rest.rs

1//! Binance REST API implementation.
2//!
3//! Implements all REST API endpoint operations for the Binance exchange.
4
5use super::{Binance, auth::BinanceAuth, parser};
6use ccxt_core::{
7    Error, ParseError, Result,
8    types::{
9        AccountConfig, AggTrade, Balance, BatchCancelResult, BatchOrderRequest, BatchOrderResult,
10        BatchOrderUpdate, BidAsk, BorrowInterest, BorrowRateHistory, CancelAllOrdersResult,
11        CommissionRate, Currency, DepositAddress, FeeFundingRate, FeeFundingRateHistory,
12        FeeTradingFee, FundingFee, IntoTickerParams, LastPrice, LeverageTier, MarginAdjustment,
13        MarginLoan, MarginRepay, MarkPrice, Market, MarketType, MaxBorrowable, MaxLeverage,
14        MaxTransferable, NextFundingRate, OcoOrder, OpenInterest, OpenInterestHistory, Order,
15        OrderBook, OrderSide, OrderStatus, OrderType, Stats24hr, Ticker, Trade, TradingLimits,
16        Transaction, TransactionType, Transfer,
17    },
18};
19use reqwest::header::HeaderMap;
20use rust_decimal::Decimal;
21use serde_json::Value;
22use std::collections::HashMap;
23use tracing::{debug, info, warn};
24
25impl Binance {
26    /// Fetch server timestamp for internal use.
27    ///
28    /// # Returns
29    ///
30    /// Returns the server timestamp in milliseconds.
31    ///
32    /// # Errors
33    ///
34    /// Returns an error if the request fails or the response is malformed.
35    async fn fetch_time_raw(&self) -> Result<u64> {
36        let url = format!("{}/time", self.urls().public);
37        let response = self.base().http_client.get(&url, None).await?;
38
39        response["serverTime"]
40            .as_u64()
41            .ok_or_else(|| ParseError::missing_field("serverTime").into())
42    }
43
44    /// Fetch exchange system status.
45    ///
46    /// # Returns
47    ///
48    /// Returns formatted exchange status information with the following structure:
49    /// ```json
50    /// {
51    ///     "status": "ok" | "maintenance",
52    ///     "updated": null,
53    ///     "eta": null,
54    ///     "url": null,
55    ///     "info": { ... }
56    /// }
57    /// ```
58    pub async fn fetch_status(&self) -> Result<Value> {
59        let url = format!("{}/system/status", self.urls().sapi);
60        let response = self.base().http_client.get(&url, None).await?;
61
62        // Response format: { "status": 0, "msg": "normal" }
63        // Status codes: 0 = normal, 1 = system maintenance
64        let status_raw = response
65            .get("status")
66            .and_then(|v| v.as_i64())
67            .ok_or_else(|| {
68                Error::from(ParseError::invalid_format(
69                    "status",
70                    "status field missing or not an integer",
71                ))
72            })?;
73
74        let status = match status_raw {
75            0 => "ok",
76            1 => "maintenance",
77            _ => "unknown",
78        };
79
80        // json! macro with literal values is infallible
81        #[allow(clippy::disallowed_methods)]
82        Ok(serde_json::json!({
83            "status": status,
84            "updated": null,
85            "eta": null,
86            "url": null,
87            "info": response
88        }))
89    }
90
91    /// Fetch all trading markets.
92    ///
93    /// # Returns
94    ///
95    /// Returns a vector of [`Market`] structures containing market information.
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the API request fails or response parsing fails.
100    ///
101    /// # Example
102    ///
103    /// ```no_run
104    /// # use ccxt_exchanges::binance::Binance;
105    /// # use ccxt_core::ExchangeConfig;
106    /// # async fn example() -> ccxt_core::Result<()> {
107    /// let binance = Binance::new(ExchangeConfig::default())?;
108    /// let markets = binance.fetch_markets().await?;
109    /// println!("Found {} markets", markets.len());
110    /// # Ok(())
111    /// # }
112    /// ```
113    pub async fn fetch_markets(&self) -> Result<Vec<Market>> {
114        let url = format!("{}/exchangeInfo", self.urls().public);
115        let data = self.base().http_client.get(&url, None).await?;
116
117        let symbols = data["symbols"]
118            .as_array()
119            .ok_or_else(|| Error::from(ParseError::missing_field("symbols")))?;
120
121        let mut markets = Vec::new();
122        for symbol in symbols {
123            match parser::parse_market(symbol) {
124                Ok(market) => markets.push(market),
125                Err(e) => {
126                    warn!(error = %e, "Failed to parse market");
127                }
128            }
129        }
130
131        let markets = self.base().set_markets(markets, None).await?;
132
133        Ok(markets)
134    }
135    /// Load and cache market data.
136    ///
137    /// Standard CCXT method for loading all market data from the exchange.
138    /// If markets are already loaded and `reload` is false, returns cached data.
139    ///
140    /// # Arguments
141    ///
142    /// * `reload` - Whether to force reload market data from the API.
143    ///
144    /// # Returns
145    ///
146    /// Returns a `HashMap` containing all market data, keyed by symbol (e.g., "BTC/USDT").
147    ///
148    /// # Errors
149    ///
150    /// Returns an error if the API request fails or response parsing fails.
151    ///
152    /// # Example
153    ///
154    /// ```no_run
155    /// # use ccxt_exchanges::binance::Binance;
156    /// # use ccxt_core::ExchangeConfig;
157    /// # async fn example() -> ccxt_core::error::Result<()> {
158    /// let binance = Binance::new(ExchangeConfig::default())?;
159    ///
160    /// // Load markets for the first time
161    /// let markets = binance.load_markets(false).await?;
162    /// println!("Loaded {} markets", markets.len());
163    ///
164    /// // Subsequent calls use cache (no API request)
165    /// let markets = binance.load_markets(false).await?;
166    ///
167    /// // Force reload
168    /// let markets = binance.load_markets(true).await?;
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>> {
173        {
174            let cache = self.base().market_cache.read().await;
175            if cache.loaded && !reload {
176                debug!(
177                    "Returning cached markets for Binance ({} markets)",
178                    cache.markets.len()
179                );
180                return Ok(cache.markets.clone());
181            }
182        }
183
184        info!("Loading markets for Binance (reload: {})", reload);
185        let _markets = self.fetch_markets().await?;
186
187        let cache = self.base().market_cache.read().await;
188        Ok(cache.markets.clone())
189    }
190
191    /// Fetch ticker for a single trading pair.
192    ///
193    /// # Arguments
194    ///
195    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
196    /// * `params` - Optional parameters to configure the ticker request.
197    ///
198    /// # Returns
199    ///
200    /// Returns [`Ticker`] data for the specified symbol.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if the market is not found or the API request fails.
205    pub async fn fetch_ticker(
206        &self,
207        symbol: &str,
208        params: impl IntoTickerParams,
209    ) -> Result<Ticker> {
210        let market = self.base().market(symbol).await?;
211
212        let params = params.into_ticker_params();
213        let rolling = params.rolling.unwrap_or(false);
214
215        let endpoint = if rolling { "ticker" } else { "ticker/24hr" };
216
217        let mut url = format!("{}/{}?symbol={}", self.urls().public, endpoint, market.id);
218
219        if let Some(window) = params.window_size {
220            url.push_str(&format!("&windowSize={}", window));
221        }
222
223        for (key, value) in &params.extras {
224            if key != "rolling" && key != "windowSize" {
225                url.push_str(&format!("&{}={}", key, value));
226            }
227        }
228
229        let data = self.base().http_client.get(&url, None).await?;
230
231        parser::parse_ticker(&data, Some(&market))
232    }
233
234    /// Fetch tickers for multiple trading pairs.
235    ///
236    /// # Arguments
237    ///
238    /// * `symbols` - Optional list of trading pair symbols; fetches all if `None`.
239    ///
240    /// # Returns
241    ///
242    /// Returns a vector of [`Ticker`] structures.
243    ///
244    /// # Errors
245    ///
246    /// Returns an error if markets are not loaded or the API request fails.
247    pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
248        let cache = self.base().market_cache.read().await;
249        if !cache.loaded {
250            drop(cache);
251            return Err(Error::exchange(
252                "-1",
253                "Markets not loaded. Call load_markets() first.",
254            ));
255        }
256        drop(cache);
257
258        let url = format!("{}/ticker/24hr", self.urls().public);
259        let data = self.base().http_client.get(&url, None).await?;
260
261        let tickers_array = data.as_array().ok_or_else(|| {
262            Error::from(ParseError::invalid_format(
263                "response",
264                "Expected array of tickers",
265            ))
266        })?;
267
268        let mut tickers = Vec::new();
269        for ticker_data in tickers_array {
270            if let Some(binance_symbol) = ticker_data["symbol"].as_str() {
271                let cache = self.base().market_cache.read().await;
272                if let Some(market) = cache.markets_by_id.get(binance_symbol) {
273                    let market_clone = market.clone();
274                    drop(cache);
275
276                    match parser::parse_ticker(ticker_data, Some(&market_clone)) {
277                        Ok(ticker) => {
278                            if let Some(ref syms) = symbols {
279                                if syms.contains(&ticker.symbol) {
280                                    tickers.push(ticker);
281                                }
282                            } else {
283                                tickers.push(ticker);
284                            }
285                        }
286                        Err(e) => {
287                            warn!(
288                                error = %e,
289                                symbol = %binance_symbol,
290                                "Failed to parse ticker"
291                            );
292                        }
293                    }
294                } else {
295                    drop(cache);
296                }
297            }
298        }
299
300        Ok(tickers)
301    }
302
303    /// Fetch order book for a trading pair.
304    ///
305    /// # Arguments
306    ///
307    /// * `symbol` - Trading pair symbol.
308    /// * `limit` - Optional depth limit (valid values: 5, 10, 20, 50, 100, 500, 1000, 5000).
309    ///
310    /// # Returns
311    ///
312    /// Returns [`OrderBook`] data containing bids and asks.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the market is not found or the API request fails.
317    pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
318        let market = self.base().market(symbol).await?;
319
320        let url = if let Some(l) = limit {
321            format!(
322                "{}/depth?symbol={}&limit={}",
323                self.urls().public,
324                market.id,
325                l
326            )
327        } else {
328            format!("{}/depth?symbol={}", self.urls().public, market.id)
329        };
330
331        let data = self.base().http_client.get(&url, None).await?;
332
333        parser::parse_orderbook(&data, market.symbol.clone())
334    }
335
336    /// Fetch recent public trades.
337    ///
338    /// # Arguments
339    ///
340    /// * `symbol` - Trading pair symbol.
341    /// * `limit` - Optional limit on number of trades (maximum: 1000).
342    ///
343    /// # Returns
344    ///
345    /// Returns a vector of [`Trade`] structures, sorted by timestamp in descending order.
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if the market is not found or the API request fails.
350    pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
351        let market = self.base().market(symbol).await?;
352
353        let url = if let Some(l) = limit {
354            format!(
355                "{}/trades?symbol={}&limit={}",
356                self.urls().public,
357                market.id,
358                l
359            )
360        } else {
361            format!("{}/trades?symbol={}", self.urls().public, market.id)
362        };
363
364        let data = self.base().http_client.get(&url, None).await?;
365
366        let trades_array = data.as_array().ok_or_else(|| {
367            Error::from(ParseError::invalid_format(
368                "data",
369                "Expected array of trades",
370            ))
371        })?;
372
373        let mut trades = Vec::new();
374        for trade_data in trades_array {
375            match parser::parse_trade(trade_data, Some(&market)) {
376                Ok(trade) => trades.push(trade),
377                Err(e) => {
378                    warn!(error = %e, "Failed to parse trade");
379                }
380            }
381        }
382
383        // CCXT convention: trades should be sorted by timestamp descending (newest first)
384        trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
385
386        Ok(trades)
387    }
388    /// Fetch recent public trades (alias for `fetch_trades`).
389    ///
390    /// # Arguments
391    ///
392    /// * `symbol` - Trading pair symbol.
393    /// * `limit` - Optional limit on number of trades (default: 500, maximum: 1000).
394    ///
395    /// # Returns
396    ///
397    /// Returns a vector of [`Trade`] structures for recent public trades.
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if the market is not found or the API request fails.
402    ///
403    /// # Example
404    ///
405    /// ```no_run
406    /// # use ccxt_exchanges::binance::Binance;
407    /// # use ccxt_core::ExchangeConfig;
408    /// # async fn example() -> ccxt_core::Result<()> {
409    /// # let binance = Binance::new(ExchangeConfig::default())?;
410    /// let recent_trades = binance.fetch_recent_trades("BTC/USDT", Some(100)).await?;
411    /// # Ok(())
412    /// # }
413    /// ```
414    pub async fn fetch_recent_trades(
415        &self,
416        symbol: &str,
417        limit: Option<u32>,
418    ) -> Result<Vec<Trade>> {
419        self.fetch_trades(symbol, limit).await
420    }
421
422    /// Fetch authenticated user's recent trades (private API).
423    ///
424    /// # Arguments
425    ///
426    /// * `symbol` - Trading pair symbol.
427    /// * `since` - Optional start timestamp in milliseconds.
428    /// * `limit` - Optional limit on number of trades (default: 500, maximum: 1000).
429    /// * `params` - Additional parameters.
430    ///
431    /// # Returns
432    ///
433    /// Returns a vector of [`Trade`] structures for the user's trades.
434    ///
435    /// # Errors
436    ///
437    /// Returns an error if authentication fails or the API request fails.
438    ///
439    /// # Example
440    ///
441    /// ```no_run
442    /// # use ccxt_exchanges::binance::Binance;
443    /// # use ccxt_core::ExchangeConfig;
444    /// # async fn example() -> ccxt_core::Result<()> {
445    /// # let binance = Binance::new(ExchangeConfig::default())?;
446    /// let my_trades = binance.fetch_my_recent_trades("BTC/USDT", None, Some(50), None).await?;
447    /// # Ok(())
448    /// # }
449    /// ```
450    pub async fn fetch_my_recent_trades(
451        &self,
452        symbol: &str,
453        since: Option<u64>,
454        limit: Option<u32>,
455        params: Option<HashMap<String, String>>,
456    ) -> Result<Vec<Trade>> {
457        let market = self.base().market(symbol).await?;
458
459        self.check_required_credentials()?;
460
461        let mut request_params = HashMap::new();
462        request_params.insert("symbol".to_string(), market.id.clone());
463
464        if let Some(s) = since {
465            request_params.insert("startTime".to_string(), s.to_string());
466        }
467
468        if let Some(l) = limit {
469            request_params.insert("limit".to_string(), l.to_string());
470        }
471
472        if let Some(p) = params {
473            for (k, v) in p {
474                request_params.insert(k, v);
475            }
476        }
477
478        let url = format!("{}/myTrades", self.urls().private);
479        let timestamp = self.fetch_time_raw().await?;
480        let auth = self.get_auth()?;
481        let signed_params =
482            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
483
484        let mut request_url = format!("{}?", url);
485        for (key, value) in &signed_params {
486            request_url.push_str(&format!("{}={}&", key, value));
487        }
488
489        let mut headers = HeaderMap::new();
490        auth.add_auth_headers_reqwest(&mut headers);
491
492        let data = self
493            .base()
494            .http_client
495            .get(&request_url, Some(headers))
496            .await?;
497
498        let trades_array = data.as_array().ok_or_else(|| {
499            Error::from(ParseError::invalid_format(
500                "data",
501                "Expected array of trades",
502            ))
503        })?;
504
505        let mut trades = Vec::new();
506        for trade_data in trades_array {
507            match parser::parse_trade(trade_data, Some(&market)) {
508                Ok(trade) => trades.push(trade),
509                Err(e) => {
510                    warn!(error = %e, "Failed to parse my trade");
511                }
512            }
513        }
514
515        Ok(trades)
516    }
517
518    /// Fetch aggregated trade data.
519    ///
520    /// # Arguments
521    ///
522    /// * `symbol` - Trading pair symbol.
523    /// * `since` - Optional start timestamp in milliseconds.
524    /// * `limit` - Optional limit on number of records (default: 500, maximum: 1000).
525    /// * `params` - Additional parameters that may include:
526    ///   - `fromId`: Start from specific aggTradeId.
527    ///   - `endTime`: End timestamp in milliseconds.
528    ///
529    /// # Returns
530    ///
531    /// Returns a vector of aggregated trade records.
532    ///
533    /// # Errors
534    ///
535    /// Returns an error if the market is not found or the API request fails.
536    ///
537    /// # Example
538    ///
539    /// ```no_run
540    /// # use ccxt_exchanges::binance::Binance;
541    /// # use ccxt_core::ExchangeConfig;
542    /// # async fn example() -> ccxt_core::Result<()> {
543    /// # let binance = Binance::new(ExchangeConfig::default())?;
544    /// let agg_trades = binance.fetch_agg_trades("BTC/USDT", None, Some(100), None).await?;
545    /// # Ok(())
546    /// # }
547    /// ```
548    pub async fn fetch_agg_trades(
549        &self,
550        symbol: &str,
551        since: Option<u64>,
552        limit: Option<u32>,
553        params: Option<HashMap<String, String>>,
554    ) -> Result<Vec<AggTrade>> {
555        let market = self.base().market(symbol).await?;
556
557        let mut url = format!("{}/aggTrades?symbol={}", self.urls().public, market.id);
558
559        if let Some(s) = since {
560            url.push_str(&format!("&startTime={}", s));
561        }
562
563        if let Some(l) = limit {
564            url.push_str(&format!("&limit={}", l));
565        }
566
567        if let Some(p) = params {
568            if let Some(from_id) = p.get("fromId") {
569                url.push_str(&format!("&fromId={}", from_id));
570            }
571            if let Some(end_time) = p.get("endTime") {
572                url.push_str(&format!("&endTime={}", end_time));
573            }
574        }
575
576        let data = self.base().http_client.get(&url, None).await?;
577
578        let agg_trades_array = data.as_array().ok_or_else(|| {
579            Error::from(ParseError::invalid_format(
580                "data",
581                "Expected array of agg trades",
582            ))
583        })?;
584
585        let mut agg_trades = Vec::new();
586        for agg_trade_data in agg_trades_array {
587            match parser::parse_agg_trade(agg_trade_data, Some(market.symbol.clone())) {
588                Ok(agg_trade) => agg_trades.push(agg_trade),
589                Err(e) => {
590                    warn!(error = %e, "Failed to parse agg trade");
591                }
592            }
593        }
594
595        Ok(agg_trades)
596    }
597
598    /// Fetch historical trade data (requires API key but not signature).
599    ///
600    /// Note: Binance API uses `fromId` parameter instead of timestamp.
601    ///
602    /// # Arguments
603    ///
604    /// * `symbol` - Trading pair symbol.
605    /// * `_since` - Optional start timestamp (unused, Binance uses `fromId` instead).
606    /// * `limit` - Optional limit on number of records (default: 500, maximum: 1000).
607    /// * `params` - Additional parameters that may include:
608    ///   - `fromId`: Start from specific tradeId.
609    ///
610    /// # Returns
611    ///
612    /// Returns a vector of historical [`Trade`] records.
613    ///
614    /// # Errors
615    ///
616    /// Returns an error if authentication fails or the API request fails.
617    ///
618    /// # Example
619    ///
620    /// ```no_run
621    /// # use ccxt_exchanges::binance::Binance;
622    /// # use ccxt_core::ExchangeConfig;
623    /// # async fn example() -> ccxt_core::Result<()> {
624    /// # let binance = Binance::new(ExchangeConfig::default())?;
625    /// let historical_trades = binance.fetch_historical_trades("BTC/USDT", None, Some(100), None).await?;
626    /// # Ok(())
627    /// # }
628    /// ```
629    pub async fn fetch_historical_trades(
630        &self,
631        symbol: &str,
632        _since: Option<u64>,
633        limit: Option<u32>,
634        params: Option<HashMap<String, String>>,
635    ) -> Result<Vec<Trade>> {
636        let market = self.base().market(symbol).await?;
637
638        self.check_required_credentials()?;
639
640        let mut url = format!(
641            "{}/historicalTrades?symbol={}",
642            self.urls().public,
643            market.id
644        );
645
646        // Binance historicalTrades endpoint uses fromId instead of timestamp
647        if let Some(p) = &params {
648            if let Some(from_id) = p.get("fromId") {
649                url.push_str(&format!("&fromId={}", from_id));
650            }
651        }
652
653        if let Some(l) = limit {
654            url.push_str(&format!("&limit={}", l));
655        }
656
657        let mut headers = HeaderMap::new();
658        let auth = self.get_auth()?;
659        auth.add_auth_headers_reqwest(&mut headers);
660
661        let data = self.base().http_client.get(&url, Some(headers)).await?;
662
663        let trades_array = data.as_array().ok_or_else(|| {
664            Error::from(ParseError::invalid_format(
665                "data",
666                "Expected array of trades",
667            ))
668        })?;
669
670        let mut trades = Vec::new();
671        for trade_data in trades_array {
672            match parser::parse_trade(trade_data, Some(&market)) {
673                Ok(trade) => trades.push(trade),
674                Err(e) => {
675                    warn!(error = %e, "Failed to parse historical trade");
676                }
677            }
678        }
679
680        Ok(trades)
681    }
682
683    /// Fetch 24-hour trading statistics.
684    ///
685    /// # Arguments
686    ///
687    /// * `symbol` - Optional trading pair symbol. If `None`, returns statistics for all pairs.
688    ///
689    /// # Returns
690    ///
691    /// Returns a vector of [`Stats24hr`] structures. Single symbol returns one item, all symbols return multiple items.
692    ///
693    /// # Errors
694    ///
695    /// Returns an error if the market is not found or the API request fails.
696    ///
697    /// # Example
698    ///
699    /// ```no_run
700    /// # use ccxt_exchanges::binance::Binance;
701    /// # use ccxt_core::ExchangeConfig;
702    /// # async fn example() -> ccxt_core::Result<()> {
703    /// # let binance = Binance::new(ExchangeConfig::default())?;
704    /// // Fetch statistics for a single pair
705    /// let stats = binance.fetch_24hr_stats(Some("BTC/USDT")).await?;
706    ///
707    /// // Fetch statistics for all pairs
708    /// let all_stats = binance.fetch_24hr_stats(None).await?;
709    /// # Ok(())
710    /// # }
711    /// ```
712    pub async fn fetch_24hr_stats(&self, symbol: Option<&str>) -> Result<Vec<Stats24hr>> {
713        let url = if let Some(sym) = symbol {
714            let market = self.base().market(sym).await?;
715            format!("{}/ticker/24hr?symbol={}", self.urls().public, market.id)
716        } else {
717            format!("{}/ticker/24hr", self.urls().public)
718        };
719
720        let data = self.base().http_client.get(&url, None).await?;
721
722        // Single symbol returns object, all symbols return array
723        let stats_vec = if data.is_array() {
724            let stats_array = data.as_array().ok_or_else(|| {
725                Error::from(ParseError::invalid_format(
726                    "data",
727                    "Expected array of 24hr stats",
728                ))
729            })?;
730
731            let mut stats = Vec::new();
732            for stats_data in stats_array {
733                match parser::parse_stats_24hr(stats_data) {
734                    Ok(stat) => stats.push(stat),
735                    Err(e) => {
736                        warn!(error = %e, "Failed to parse 24hr stats");
737                    }
738                }
739            }
740            stats
741        } else {
742            vec![parser::parse_stats_24hr(&data)?]
743        };
744
745        Ok(stats_vec)
746    }
747
748    /// Fetch trading limits information for a symbol.
749    ///
750    /// # Arguments
751    ///
752    /// * `symbol` - Trading pair symbol.
753    ///
754    /// # Returns
755    ///
756    /// Returns [`TradingLimits`] containing minimum/maximum order constraints.
757    ///
758    /// # Errors
759    ///
760    /// Returns an error if the market is not found or the API request fails.
761    ///
762    /// # Example
763    ///
764    /// ```no_run
765    /// # use ccxt_exchanges::binance::Binance;
766    /// # use ccxt_core::ExchangeConfig;
767    /// # async fn example() -> ccxt_core::Result<()> {
768    /// # let binance = Binance::new(ExchangeConfig::default())?;
769    /// let limits = binance.fetch_trading_limits("BTC/USDT").await?;
770    /// println!("Min amount: {:?}", limits.amount.min);
771    /// println!("Max amount: {:?}", limits.amount.max);
772    /// # Ok(())
773    /// # }
774    /// ```
775    pub async fn fetch_trading_limits(&self, symbol: &str) -> Result<TradingLimits> {
776        let market = self.base().market(symbol).await?;
777
778        let url = format!("{}/exchangeInfo?symbol={}", self.urls().public, market.id);
779        let data = self.base().http_client.get(&url, None).await?;
780
781        let symbols_array = data["symbols"].as_array().ok_or_else(|| {
782            Error::from(ParseError::invalid_format("data", "Expected symbols array"))
783        })?;
784
785        if symbols_array.is_empty() {
786            return Err(Error::from(ParseError::invalid_format(
787                "data",
788                format!("No symbol info found for {}", symbol),
789            )));
790        }
791
792        let symbol_data = &symbols_array[0];
793
794        parser::parse_trading_limits(symbol_data, market.symbol.clone())
795    }
796
797    /// Create a new order.
798    ///
799    /// # Arguments
800    ///
801    /// * `symbol` - Trading pair symbol.
802    /// * `order_type` - Order type (Market, Limit, StopLoss, etc.).
803    /// * `side` - Order side (Buy or Sell).
804    /// * `amount` - Order quantity.
805    /// * `price` - Optional price (required for limit orders).
806    /// * `params` - Additional parameters.
807    ///
808    /// # Returns
809    ///
810    /// Returns the created [`Order`] structure with order details.
811    ///
812    /// # Errors
813    ///
814    /// Returns an error if authentication fails, market is not found, or the API request fails.
815    pub async fn create_order(
816        &self,
817        symbol: &str,
818        order_type: OrderType,
819        side: OrderSide,
820        amount: f64,
821        price: Option<f64>,
822        params: Option<HashMap<String, String>>,
823    ) -> Result<Order> {
824        self.check_required_credentials()?;
825
826        let market = self.base().market(symbol).await?;
827        let mut request_params = HashMap::new();
828
829        request_params.insert("symbol".to_string(), market.id.clone());
830        request_params.insert(
831            "side".to_string(),
832            match side {
833                OrderSide::Buy => "BUY".to_string(),
834                OrderSide::Sell => "SELL".to_string(),
835            },
836        );
837        request_params.insert(
838            "type".to_string(),
839            match order_type {
840                OrderType::Market => "MARKET".to_string(),
841                OrderType::Limit => "LIMIT".to_string(),
842                OrderType::StopLoss => "STOP_LOSS".to_string(),
843                OrderType::StopLossLimit => "STOP_LOSS_LIMIT".to_string(),
844                OrderType::TakeProfit => "TAKE_PROFIT".to_string(),
845                OrderType::TakeProfitLimit => "TAKE_PROFIT_LIMIT".to_string(),
846                OrderType::LimitMaker => "LIMIT_MAKER".to_string(),
847                OrderType::StopMarket => "STOP_MARKET".to_string(),
848                OrderType::StopLimit => "STOP_LIMIT".to_string(),
849                OrderType::TrailingStop => "TRAILING_STOP_MARKET".to_string(),
850            },
851        );
852        request_params.insert("quantity".to_string(), amount.to_string());
853
854        if let Some(p) = price {
855            request_params.insert("price".to_string(), p.to_string());
856        }
857
858        // Limit orders require timeInForce parameter
859        if order_type == OrderType::Limit
860            || order_type == OrderType::StopLossLimit
861            || order_type == OrderType::TakeProfitLimit
862        {
863            if !request_params.contains_key("timeInForce") {
864                request_params.insert("timeInForce".to_string(), "GTC".to_string());
865            }
866        }
867
868        if let Some(extra) = params {
869            for (k, v) in extra {
870                request_params.insert(k, v);
871            }
872        }
873
874        // Handle cost parameter for market buy orders (quoteOrderQty)
875        // Convert cost parameter to Binance API's quoteOrderQty
876        if order_type == OrderType::Market && side == OrderSide::Buy {
877            if let Some(cost_str) = request_params.get("cost") {
878                request_params.insert("quoteOrderQty".to_string(), cost_str.clone());
879                // Remove quantity parameter (not needed with quoteOrderQty)
880                request_params.remove("quantity");
881                // Remove cost parameter (Binance API doesn't recognize it)
882                request_params.remove("cost");
883            }
884        }
885
886        // Handle conditional order parameters
887        // stopPrice: trigger price for stop-loss/take-profit orders
888        if matches!(
889            order_type,
890            OrderType::StopLoss
891                | OrderType::StopLossLimit
892                | OrderType::TakeProfit
893                | OrderType::TakeProfitLimit
894                | OrderType::StopMarket
895        ) {
896            // Use stopLossPrice or takeProfitPrice if stopPrice not provided
897            if !request_params.contains_key("stopPrice") {
898                if let Some(stop_loss) = request_params.get("stopLossPrice") {
899                    request_params.insert("stopPrice".to_string(), stop_loss.clone());
900                } else if let Some(take_profit) = request_params.get("takeProfitPrice") {
901                    request_params.insert("stopPrice".to_string(), take_profit.clone());
902                }
903            }
904        }
905
906        // trailingDelta: price offset for trailing stop (basis points)
907        // Spot market: requires trailingDelta parameter
908        // Futures market: uses callbackRate parameter
909        if order_type == OrderType::TrailingStop {
910            if market.is_spot() {
911                // Spot trailing stop: use trailingDelta
912                if !request_params.contains_key("trailingDelta") {
913                    // Convert trailingPercent to trailingDelta (basis points) if provided
914                    if let Some(percent_str) = request_params.get("trailingPercent") {
915                        if let Ok(percent) = percent_str.parse::<f64>() {
916                            let delta = (percent * 100.0) as i64;
917                            request_params.insert("trailingDelta".to_string(), delta.to_string());
918                            request_params.remove("trailingPercent");
919                        }
920                    }
921                }
922            } else if market.is_swap() || market.is_futures() {
923                // Futures trailing stop: use callbackRate
924                if !request_params.contains_key("callbackRate") {
925                    if let Some(percent_str) = request_params.get("trailingPercent") {
926                        request_params.insert("callbackRate".to_string(), percent_str.clone());
927                        request_params.remove("trailingPercent");
928                    }
929                }
930            }
931        }
932
933        // Futures order advanced parameters (Stage 24.3)
934        if market.is_swap() || market.is_futures() {
935            // reduceOnly: reduce-only flag (only reduces existing position, won't reverse)
936            if let Some(reduce_only) = request_params.get("reduceOnly") {
937                // Keep original value, Binance API accepts "true" or "false" strings
938                request_params.insert("reduceOnly".to_string(), reduce_only.clone());
939            }
940
941            // postOnly: maker-only flag (ensures order won't execute immediately)
942            if let Some(post_only) = request_params.get("postOnly") {
943                request_params.insert("postOnly".to_string(), post_only.clone());
944            }
945
946            // positionSide: position direction (LONG/SHORT/BOTH)
947            // Required in hedge mode, defaults to BOTH in one-way mode
948            if let Some(position_side) = request_params.get("positionSide") {
949                request_params.insert("positionSide".to_string(), position_side.clone());
950            }
951
952            // closePosition: close all positions flag (market orders only)
953            // When true, closes all positions; quantity can be omitted
954            if let Some(close_position) = request_params.get("closePosition") {
955                if order_type == OrderType::Market {
956                    request_params.insert("closePosition".to_string(), close_position.clone());
957                }
958            }
959        }
960
961        let timestamp = self.fetch_time_raw().await?;
962        let auth = self.get_auth()?;
963        let signed_params =
964            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
965
966        let url = format!("{}/order", self.urls().private);
967        let mut headers = HeaderMap::new();
968        auth.add_auth_headers_reqwest(&mut headers);
969
970        let body = serde_json::to_value(&signed_params).map_err(|e| {
971            Error::from(ParseError::invalid_format(
972                "data",
973                format!("Failed to serialize params: {}", e),
974            ))
975        })?;
976
977        let data = self
978            .base()
979            .http_client
980            .post(&url, Some(headers), Some(body))
981            .await?;
982
983        parser::parse_order(&data, Some(&market))
984    }
985
986    /// Cancel an order.
987    ///
988    /// # Arguments
989    ///
990    /// * `id` - Order ID.
991    /// * `symbol` - Trading pair symbol.
992    ///
993    /// # Returns
994    ///
995    /// Returns the cancelled [`Order`] information.
996    ///
997    /// # Errors
998    ///
999    /// Returns an error if authentication fails, market is not found, or the API request fails.
1000    pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
1001        self.check_required_credentials()?;
1002
1003        let market = self.base().market(symbol).await?;
1004        let mut params = HashMap::new();
1005        params.insert("symbol".to_string(), market.id.clone());
1006        params.insert("orderId".to_string(), id.to_string());
1007
1008        let timestamp = self.fetch_time_raw().await?;
1009        let auth = self.get_auth()?;
1010        let signed_params =
1011            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1012
1013        let url = format!("{}/order", self.urls().private);
1014        let mut headers = HeaderMap::new();
1015        auth.add_auth_headers_reqwest(&mut headers);
1016
1017        let body = serde_json::to_value(&signed_params).map_err(|e| {
1018            Error::from(ParseError::invalid_format(
1019                "data",
1020                format!("Failed to serialize params: {}", e),
1021            ))
1022        })?;
1023
1024        let data = self
1025            .base()
1026            .http_client
1027            .delete(&url, Some(headers), Some(body))
1028            .await?;
1029
1030        parser::parse_order(&data, Some(&market))
1031    }
1032
1033    /// Fetch order details.
1034    ///
1035    /// # Arguments
1036    ///
1037    /// * `id` - Order ID.
1038    /// * `symbol` - Trading pair symbol.
1039    ///
1040    /// # Returns
1041    ///
1042    /// Returns the [`Order`] information.
1043    ///
1044    /// # Errors
1045    ///
1046    /// Returns an error if authentication fails, market is not found, or the API request fails.
1047    pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
1048        self.check_required_credentials()?;
1049
1050        let market = self.base().market(symbol).await?;
1051        let mut params = HashMap::new();
1052        params.insert("symbol".to_string(), market.id.clone());
1053        params.insert("orderId".to_string(), id.to_string());
1054
1055        let timestamp = self.fetch_time_raw().await?;
1056        let auth = self.get_auth()?;
1057        let signed_params =
1058            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1059
1060        let mut url = format!("{}/order?", self.urls().private);
1061        for (key, value) in &signed_params {
1062            url.push_str(&format!("{}={}&", key, value));
1063        }
1064
1065        let mut headers = HeaderMap::new();
1066        auth.add_auth_headers_reqwest(&mut headers);
1067
1068        let data = self.base().http_client.get(&url, Some(headers)).await?;
1069
1070        parser::parse_order(&data, Some(&market))
1071    }
1072
1073    /// Fetch open (unfilled) orders.
1074    ///
1075    /// # Arguments
1076    ///
1077    /// * `symbol` - Optional trading pair symbol. If `None`, fetches all open orders.
1078    ///
1079    /// # Returns
1080    ///
1081    /// Returns a vector of open [`Order`] structures.
1082    ///
1083    /// # Errors
1084    ///
1085    /// Returns an error if authentication fails or the API request fails.
1086    pub async fn fetch_open_orders(&self, symbol: Option<&str>) -> Result<Vec<Order>> {
1087        self.check_required_credentials()?;
1088
1089        let mut params = HashMap::new();
1090        let market = if let Some(sym) = symbol {
1091            let m = self.base().market(sym).await?;
1092            params.insert("symbol".to_string(), m.id.clone());
1093            Some(m)
1094        } else {
1095            None
1096        };
1097
1098        let timestamp = self.fetch_time_raw().await?;
1099        let auth = self.get_auth()?;
1100        let signed_params =
1101            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1102
1103        let mut url = format!("{}/openOrders?", self.urls().private);
1104        for (key, value) in &signed_params {
1105            url.push_str(&format!("{}={}&", key, value));
1106        }
1107
1108        let mut headers = HeaderMap::new();
1109        auth.add_auth_headers_reqwest(&mut headers);
1110
1111        let data = self.base().http_client.get(&url, Some(headers)).await?;
1112
1113        let orders_array = data.as_array().ok_or_else(|| {
1114            Error::from(ParseError::invalid_format(
1115                "data",
1116                "Expected array of orders",
1117            ))
1118        })?;
1119
1120        let mut orders = Vec::new();
1121        for order_data in orders_array {
1122            match parser::parse_order(order_data, market.as_ref()) {
1123                Ok(order) => orders.push(order),
1124                Err(e) => {
1125                    warn!(error = %e, "Failed to parse order");
1126                }
1127            }
1128        }
1129
1130        Ok(orders)
1131    }
1132    /// Create a stop-loss order.
1133    ///
1134    /// # Arguments
1135    ///
1136    /// * `symbol` - Trading pair symbol.
1137    /// * `side` - Order side (Buy/Sell).
1138    /// * `amount` - Order quantity.
1139    /// * `stop_price` - Stop-loss trigger price.
1140    /// * `price` - Optional limit price (if `None`, creates market stop-loss order).
1141    /// * `params` - Optional additional parameters.
1142    ///
1143    /// # Returns
1144    ///
1145    /// Returns the created stop-loss [`Order`].
1146    ///
1147    /// # Errors
1148    ///
1149    /// Returns an error if authentication fails or the API request fails.
1150    ///
1151    /// # Example
1152    ///
1153    /// ```no_run
1154    /// # use ccxt_exchanges::binance::Binance;
1155    /// # use ccxt_core::ExchangeConfig;
1156    /// # use ccxt_core::types::{OrderSide, OrderType};
1157    /// # async fn example() -> ccxt_core::Result<()> {
1158    /// # let exchange = Binance::new(ExchangeConfig::default())?;
1159    /// // Create market stop-loss order
1160    /// let order = exchange.create_stop_loss_order(
1161    ///     "BTC/USDT",
1162    ///     OrderSide::Sell,
1163    ///     0.1,
1164    ///     45000.0,
1165    ///     None,
1166    ///     None
1167    /// ).await?;
1168    ///
1169    /// // Create limit stop-loss order
1170    /// let order = exchange.create_stop_loss_order(
1171    ///     "BTC/USDT",
1172    ///     OrderSide::Sell,
1173    ///     0.1,
1174    ///     45000.0,
1175    ///     Some(44900.0),
1176    ///     None
1177    /// ).await?;
1178    /// # Ok(())
1179    /// # }
1180    /// ```
1181    pub async fn create_stop_loss_order(
1182        &self,
1183        symbol: &str,
1184        side: OrderSide,
1185        amount: f64,
1186        stop_price: f64,
1187        price: Option<f64>,
1188        params: Option<HashMap<String, String>>,
1189    ) -> Result<Order> {
1190        let mut request_params = params.unwrap_or_default();
1191
1192        request_params.insert("stopPrice".to_string(), stop_price.to_string());
1193
1194        let order_type = if price.is_some() {
1195            OrderType::StopLossLimit
1196        } else {
1197            OrderType::StopLoss
1198        };
1199
1200        self.create_order(
1201            symbol,
1202            order_type,
1203            side,
1204            amount,
1205            price,
1206            Some(request_params),
1207        )
1208        .await
1209    }
1210
1211    /// Create a take-profit order.
1212    ///
1213    /// # Arguments
1214    ///
1215    /// * `symbol` - Trading pair symbol.
1216    /// * `side` - Order side (Buy/Sell).
1217    /// * `amount` - Order quantity.
1218    /// * `take_profit_price` - Take-profit trigger price.
1219    /// * `price` - Optional limit price (if `None`, creates market take-profit order).
1220    /// * `params` - Optional additional parameters.
1221    ///
1222    /// # Returns
1223    ///
1224    /// Returns the created take-profit [`Order`].
1225    ///
1226    /// # Errors
1227    ///
1228    /// Returns an error if authentication fails or the API request fails.
1229    ///
1230    /// # Example
1231    ///
1232    /// ```no_run
1233    /// # use ccxt_exchanges::binance::Binance;
1234    /// # use ccxt_core::ExchangeConfig;
1235    /// # use ccxt_core::types::{OrderSide, OrderType};
1236    /// # async fn example() -> ccxt_core::Result<()> {
1237    /// # let exchange = Binance::new(ExchangeConfig::default())?;
1238    /// // Create market take-profit order
1239    /// let order = exchange.create_take_profit_order(
1240    ///     "BTC/USDT",
1241    ///     OrderSide::Sell,
1242    ///     0.1,
1243    ///     55000.0,
1244    ///     None,
1245    ///     None
1246    /// ).await?;
1247    ///
1248    /// // Create limit take-profit order
1249    /// let order = exchange.create_take_profit_order(
1250    ///     "BTC/USDT",
1251    ///     OrderSide::Sell,
1252    ///     0.1,
1253    ///     55000.0,
1254    ///     Some(55100.0),
1255    ///     None
1256    /// ).await?;
1257    /// # Ok(())
1258    /// # }
1259    /// ```
1260    pub async fn create_take_profit_order(
1261        &self,
1262        symbol: &str,
1263        side: OrderSide,
1264        amount: f64,
1265        take_profit_price: f64,
1266        price: Option<f64>,
1267        params: Option<HashMap<String, String>>,
1268    ) -> Result<Order> {
1269        let mut request_params = params.unwrap_or_default();
1270
1271        request_params.insert("stopPrice".to_string(), take_profit_price.to_string());
1272
1273        let order_type = if price.is_some() {
1274            OrderType::TakeProfitLimit
1275        } else {
1276            OrderType::TakeProfit
1277        };
1278
1279        self.create_order(
1280            symbol,
1281            order_type,
1282            side,
1283            amount,
1284            price,
1285            Some(request_params),
1286        )
1287        .await
1288    }
1289
1290    /// Create a trailing stop order.
1291    ///
1292    /// # Arguments
1293    ///
1294    /// * `symbol` - Trading pair symbol.
1295    /// * `side` - Order side (Buy/Sell).
1296    /// * `amount` - Order quantity.
1297    /// * `trailing_percent` - Trailing percentage (e.g., 2.0 for 2%).
1298    /// * `activation_price` - Optional activation price (not supported for spot markets).
1299    /// * `params` - Optional additional parameters.
1300    ///
1301    /// # Returns
1302    ///
1303    /// Returns the created trailing stop [`Order`].
1304    ///
1305    /// # Errors
1306    ///
1307    /// Returns an error if authentication fails or the API request fails.
1308    ///
1309    /// # Example
1310    ///
1311    /// ```no_run
1312    /// # use ccxt_exchanges::binance::Binance;
1313    /// # use ccxt_core::ExchangeConfig;
1314    /// # use ccxt_core::types::{OrderSide, OrderType};
1315    /// # async fn example() -> ccxt_core::Result<()> {
1316    /// # let exchange = Binance::new(ExchangeConfig::default())?;
1317    /// // Spot market: create trailing stop order with 2% trail
1318    /// let order = exchange.create_trailing_stop_order(
1319    ///     "BTC/USDT",
1320    ///     OrderSide::Sell,
1321    ///     0.1,
1322    ///     2.0,
1323    ///     None,
1324    ///     None
1325    /// ).await?;
1326    ///
1327    /// // Futures market: create trailing stop order with 1.5% trail, activation price 50000
1328    /// let order = exchange.create_trailing_stop_order(
1329    ///     "BTC/USDT:USDT",
1330    ///     OrderSide::Sell,
1331    ///     0.1,
1332    ///     1.5,
1333    ///     Some(50000.0),
1334    ///     None
1335    /// ).await?;
1336    /// # Ok(())
1337    /// # }
1338    /// ```
1339    pub async fn create_trailing_stop_order(
1340        &self,
1341        symbol: &str,
1342        side: OrderSide,
1343        amount: f64,
1344        trailing_percent: f64,
1345        activation_price: Option<f64>,
1346        params: Option<HashMap<String, String>>,
1347    ) -> Result<Order> {
1348        let mut request_params = params.unwrap_or_default();
1349
1350        request_params.insert("trailingPercent".to_string(), trailing_percent.to_string());
1351
1352        if let Some(activation) = activation_price {
1353            request_params.insert("activationPrice".to_string(), activation.to_string());
1354        }
1355
1356        self.create_order(
1357            symbol,
1358            OrderType::TrailingStop,
1359            side,
1360            amount,
1361            None,
1362            Some(request_params),
1363        )
1364        .await
1365    }
1366
1367    /// Fetch account balance.
1368    ///
1369    /// # Returns
1370    ///
1371    /// Returns the account [`Balance`] information.
1372    ///
1373    /// # Errors
1374    ///
1375    /// Returns an error if authentication fails or the API request fails.
1376    pub async fn fetch_balance_simple(&self) -> Result<Balance> {
1377        self.check_required_credentials()?;
1378
1379        let params = HashMap::new();
1380        let timestamp = self.fetch_time_raw().await?;
1381        let auth = self.get_auth()?;
1382        let signed_params =
1383            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1384
1385        let mut url = format!("{}/account?", self.urls().private);
1386        for (key, value) in &signed_params {
1387            url.push_str(&format!("{}={}&", key, value));
1388        }
1389
1390        let mut headers = HeaderMap::new();
1391        auth.add_auth_headers_reqwest(&mut headers);
1392
1393        let data = self.base().http_client.get(&url, Some(headers)).await?;
1394
1395        parser::parse_balance(&data)
1396    }
1397
1398    /// Fetch user's trade history.
1399    ///
1400    /// # Arguments
1401    ///
1402    /// * `symbol` - Trading pair symbol.
1403    /// * `since` - Optional start timestamp.
1404    /// * `limit` - Optional limit on number of trades.
1405    ///
1406    /// # Returns
1407    ///
1408    /// Returns a vector of [`Trade`] structures for the user's trades.
1409    ///
1410    /// # Errors
1411    ///
1412    /// Returns an error if authentication fails, market is not found, or the API request fails.
1413    pub async fn fetch_my_trades(
1414        &self,
1415        symbol: &str,
1416        since: Option<u64>,
1417        limit: Option<u32>,
1418    ) -> Result<Vec<Trade>> {
1419        self.check_required_credentials()?;
1420
1421        let market = self.base().market(symbol).await?;
1422        let mut params = HashMap::new();
1423        params.insert("symbol".to_string(), market.id.clone());
1424
1425        if let Some(s) = since {
1426            params.insert("startTime".to_string(), s.to_string());
1427        }
1428
1429        if let Some(l) = limit {
1430            params.insert("limit".to_string(), l.to_string());
1431        }
1432
1433        let timestamp = self.fetch_time_raw().await?;
1434        let auth = self.get_auth()?;
1435        let signed_params =
1436            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1437
1438        let mut url = format!("{}/myTrades?", self.urls().private);
1439        for (key, value) in &signed_params {
1440            url.push_str(&format!("{}={}&", key, value));
1441        }
1442
1443        let mut headers = HeaderMap::new();
1444        auth.add_auth_headers_reqwest(&mut headers);
1445
1446        let data = self.base().http_client.get(&url, Some(headers)).await?;
1447
1448        let trades_array = data.as_array().ok_or_else(|| {
1449            Error::from(ParseError::invalid_format(
1450                "data",
1451                "Expected array of trades",
1452            ))
1453        })?;
1454
1455        let mut trades = Vec::new();
1456
1457        for trade_data in trades_array {
1458            match parser::parse_trade(trade_data, Some(&market)) {
1459                Ok(trade) => trades.push(trade),
1460                Err(e) => {
1461                    warn!(error = %e, "Failed to parse trade");
1462                }
1463            }
1464        }
1465
1466        Ok(trades)
1467    }
1468
1469    /// Fetch all currency information.
1470    ///
1471    /// # Returns
1472    ///
1473    /// Returns a vector of [`Currency`] structures.
1474    ///
1475    /// # Errors
1476    ///
1477    /// Returns an error if authentication fails or the API request fails.
1478    ///
1479    /// # Example
1480    ///
1481    /// ```no_run
1482    /// # use ccxt_exchanges::binance::Binance;
1483    /// # use ccxt_core::ExchangeConfig;
1484    /// # async fn example() -> ccxt_core::Result<()> {
1485    /// # let binance = Binance::new(ExchangeConfig::default())?;
1486    /// let currencies = binance.fetch_currencies().await?;
1487    /// for currency in &currencies {
1488    ///     println!("{}: {} - {}", currency.code, currency.name, currency.active);
1489    /// }
1490    /// # Ok(())
1491    /// # }
1492    /// ```
1493    pub async fn fetch_currencies(&self) -> Result<Vec<Currency>> {
1494        let url = format!("{}/capital/config/getall", self.urls().sapi);
1495
1496        // Private API requires signature
1497        self.check_required_credentials()?;
1498
1499        let params = HashMap::new();
1500        let timestamp = self.fetch_time_raw().await?;
1501        let auth = self.get_auth()?;
1502        let signed_params =
1503            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1504
1505        let mut request_url = format!("{}?", url);
1506        for (key, value) in &signed_params {
1507            request_url.push_str(&format!("{}={}&", key, value));
1508        }
1509
1510        let mut headers = HeaderMap::new();
1511        auth.add_auth_headers_reqwest(&mut headers);
1512
1513        let data = self
1514            .base()
1515            .http_client
1516            .get(&request_url, Some(headers))
1517            .await?;
1518
1519        parser::parse_currencies(&data)
1520    }
1521
1522    /// Fetch closed (completed) orders.
1523    ///
1524    /// # Arguments
1525    ///
1526    /// * `symbol` - Optional trading pair symbol.
1527    /// * `since` - Optional start timestamp (milliseconds).
1528    /// * `limit` - Optional limit on number of orders (default 500, max 1000).
1529    ///
1530    /// # Returns
1531    ///
1532    /// Returns a vector of closed [`Order`] structures.
1533    ///
1534    /// # Errors
1535    ///
1536    /// Returns an error if authentication fails or the API request fails.
1537    ///
1538    /// # Note
1539    ///
1540    /// This method calls `fetch_orders` to get all orders, then filters for "closed" status.
1541    /// This matches the Go version's implementation logic.
1542    pub async fn fetch_closed_orders(
1543        &self,
1544        symbol: Option<&str>,
1545        since: Option<u64>,
1546        limit: Option<u32>,
1547    ) -> Result<Vec<Order>> {
1548        let all_orders = self.fetch_orders(symbol, since, None).await?;
1549
1550        let mut closed_orders: Vec<Order> = all_orders
1551            .into_iter()
1552            .filter(|order| order.status == OrderStatus::Closed)
1553            .collect();
1554
1555        if let Some(l) = limit {
1556            closed_orders.truncate(l as usize);
1557        }
1558
1559        Ok(closed_orders)
1560    }
1561
1562    /// Cancel all open orders.
1563    ///
1564    /// # Arguments
1565    ///
1566    /// * `symbol` - Trading pair symbol.
1567    ///
1568    /// # Returns
1569    ///
1570    /// Returns a vector of cancelled [`Order`] structures.
1571    ///
1572    /// # Errors
1573    ///
1574    /// Returns an error if authentication fails, market is not found, or the API request fails.
1575    pub async fn cancel_all_orders(&self, symbol: &str) -> Result<Vec<Order>> {
1576        self.check_required_credentials()?;
1577
1578        let market = self.base().market(symbol).await?;
1579        let mut params = HashMap::new();
1580        params.insert("symbol".to_string(), market.id.clone());
1581
1582        let timestamp = self.fetch_time_raw().await?;
1583        let auth = self.get_auth()?;
1584        let signed_params =
1585            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1586
1587        let url = format!("{}/openOrders", self.urls().private);
1588        let mut headers = HeaderMap::new();
1589        auth.add_auth_headers_reqwest(&mut headers);
1590
1591        let body = serde_json::to_value(&signed_params).map_err(|e| {
1592            Error::from(ParseError::invalid_format(
1593                "data",
1594                format!("Failed to serialize params: {}", e),
1595            ))
1596        })?;
1597
1598        let data = self
1599            .base()
1600            .http_client
1601            .delete(&url, Some(headers), Some(body))
1602            .await?;
1603
1604        let orders_array = data.as_array().ok_or_else(|| {
1605            Error::from(ParseError::invalid_format(
1606                "data",
1607                "Expected array of orders",
1608            ))
1609        })?;
1610
1611        let mut orders = Vec::new();
1612        for order_data in orders_array {
1613            match parser::parse_order(order_data, Some(&market)) {
1614                Ok(order) => orders.push(order),
1615                Err(e) => {
1616                    warn!(error = %e, "Failed to parse order");
1617                }
1618            }
1619        }
1620
1621        Ok(orders)
1622    }
1623
1624    /// Cancel multiple orders.
1625    ///
1626    /// # Arguments
1627    ///
1628    /// * `ids` - Vector of order IDs to cancel.
1629    /// * `symbol` - Trading pair symbol.
1630    ///
1631    /// # Returns
1632    ///
1633    /// Returns a vector of cancelled [`Order`] structures.
1634    ///
1635    /// # Errors
1636    ///
1637    /// Returns an error if authentication fails, market is not found, or the API request fails.
1638    pub async fn cancel_orders(&self, ids: Vec<String>, symbol: &str) -> Result<Vec<Order>> {
1639        self.check_required_credentials()?;
1640
1641        let market = self.base().market(symbol).await?;
1642
1643        // Binance supports batch cancellation using orderIdList parameter
1644        let mut params = HashMap::new();
1645        params.insert("symbol".to_string(), market.id.clone());
1646
1647        let order_ids_json = serde_json::to_string(&ids).map_err(|e| {
1648            Error::from(ParseError::invalid_format(
1649                "data",
1650                format!("Failed to serialize order IDs: {}", e),
1651            ))
1652        })?;
1653        params.insert("orderIdList".to_string(), order_ids_json);
1654
1655        let timestamp = self.fetch_time_raw().await?;
1656        let auth = self.get_auth()?;
1657        let signed_params =
1658            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1659
1660        let url = format!("{}/openOrders", self.urls().private);
1661        let mut headers = HeaderMap::new();
1662        auth.add_auth_headers_reqwest(&mut headers);
1663
1664        let body = serde_json::to_value(&signed_params).map_err(|e| {
1665            Error::from(ParseError::invalid_format(
1666                "data",
1667                format!("Failed to serialize params: {}", e),
1668            ))
1669        })?;
1670
1671        let data = self
1672            .base()
1673            .http_client
1674            .delete(&url, Some(headers), Some(body))
1675            .await?;
1676
1677        let orders_array = data.as_array().ok_or_else(|| {
1678            Error::from(ParseError::invalid_format(
1679                "data",
1680                "Expected array of orders",
1681            ))
1682        })?;
1683
1684        let mut orders = Vec::new();
1685        for order_data in orders_array {
1686            match parser::parse_order(order_data, Some(&market)) {
1687                Ok(order) => orders.push(order),
1688                Err(e) => {
1689                    warn!(error = %e, "Failed to parse order");
1690                }
1691            }
1692        }
1693
1694        Ok(orders)
1695    }
1696
1697    /// Fetch all orders (historical and current).
1698    ///
1699    /// # Arguments
1700    ///
1701    /// * `symbol` - Optional trading pair symbol.
1702    /// * `since` - Optional start timestamp (milliseconds).
1703    /// * `limit` - Optional limit on number of orders (default 500, max 1000).
1704    ///
1705    /// # Returns
1706    ///
1707    /// Returns a vector of all [`Order`] structures.
1708    ///
1709    /// # Errors
1710    ///
1711    /// Returns an error if authentication fails or the API request fails.
1712    pub async fn fetch_orders(
1713        &self,
1714        symbol: Option<&str>,
1715        since: Option<u64>,
1716        limit: Option<u32>,
1717    ) -> Result<Vec<Order>> {
1718        self.check_required_credentials()?;
1719
1720        let mut params = HashMap::new();
1721        let market = if let Some(sym) = symbol {
1722            let m = self.base().market(sym).await?;
1723            params.insert("symbol".to_string(), m.id.clone());
1724            Some(m)
1725        } else {
1726            None
1727        };
1728
1729        if let Some(s) = since {
1730            params.insert("startTime".to_string(), s.to_string());
1731        }
1732
1733        if let Some(l) = limit {
1734            params.insert("limit".to_string(), l.to_string());
1735        }
1736
1737        let timestamp = self.fetch_time_raw().await?;
1738        let auth = self.get_auth()?;
1739        let signed_params =
1740            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
1741
1742        let mut url = format!("{}/allOrders?", self.urls().private);
1743        for (key, value) in &signed_params {
1744            url.push_str(&format!("{}={}&", key, value));
1745        }
1746
1747        let mut headers = HeaderMap::new();
1748        auth.add_auth_headers_reqwest(&mut headers);
1749
1750        let data = self.base().http_client.get(&url, Some(headers)).await?;
1751
1752        let orders_array = data.as_array().ok_or_else(|| {
1753            Error::from(ParseError::invalid_format(
1754                "data",
1755                "Expected array of orders",
1756            ))
1757        })?;
1758
1759        let mut orders = Vec::new();
1760        for order_data in orders_array {
1761            match parser::parse_order(order_data, market.as_ref()) {
1762                Ok(order) => orders.push(order),
1763                Err(e) => {
1764                    warn!(error = %e, "Failed to parse order");
1765                }
1766            }
1767        }
1768
1769        Ok(orders)
1770    }
1771
1772    /// Check required authentication credentials.
1773    fn check_required_credentials(&self) -> Result<()> {
1774        if self.base().config.api_key.is_none() || self.base().config.secret.is_none() {
1775            return Err(Error::authentication("API key and secret are required"));
1776        }
1777        Ok(())
1778    }
1779
1780    /// Get authenticator instance.
1781    fn get_auth(&self) -> Result<BinanceAuth> {
1782        let api_key = self
1783            .base()
1784            .config
1785            .api_key
1786            .as_ref()
1787            .ok_or_else(|| Error::authentication("Missing API key"))?;
1788        let secret = self
1789            .base()
1790            .config
1791            .secret
1792            .as_ref()
1793            .ok_or_else(|| Error::authentication("Missing secret"))?;
1794
1795        Ok(BinanceAuth::new(api_key, secret))
1796    }
1797    /// Fetch funding rate for a single trading pair.
1798    ///
1799    /// # Arguments
1800    ///
1801    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT:USDT").
1802    /// * `params` - Optional parameters.
1803    ///
1804    /// # Returns
1805    ///
1806    /// Returns the [`FundingRate`] information.
1807    ///
1808    /// # Errors
1809    ///
1810    /// Returns an error if the API request fails.
1811    ///
1812    /// # Example
1813    ///
1814    /// ```no_run
1815    /// # use ccxt_exchanges::binance::Binance;
1816    /// # use ccxt_core::ExchangeConfig;
1817    /// # async fn example() -> ccxt_core::Result<()> {
1818    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
1819    /// let funding_rate = binance.fetch_funding_rate("BTC/USDT:USDT", None).await?;
1820    /// # Ok(())
1821    /// # }
1822    /// ```
1823    /// Fetch funding rates for multiple trading pairs.
1824    ///
1825    /// # Arguments
1826    ///
1827    /// * `symbols` - Optional vector of trading pair symbols, fetches all if `None`.
1828    /// * `params` - Optional parameters.
1829    ///
1830    /// # Returns
1831    ///
1832    /// Returns a vector of [`FundingRate`] structures.
1833    ///
1834    /// # Errors
1835    ///
1836    /// Returns an error if the API request fails.
1837    ///
1838    /// # Example
1839    ///
1840    /// ```no_run
1841    /// # use ccxt_exchanges::binance::Binance;
1842    /// # use ccxt_core::ExchangeConfig;
1843    /// # async fn example() -> ccxt_core::Result<()> {
1844    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
1845    /// let funding_rates = binance.fetch_funding_rates(None, None).await?;
1846    /// # Ok(())
1847    /// # }
1848    /// ```
1849    /// Fetch funding rate history.
1850    ///
1851    /// # Arguments
1852    ///
1853    /// * `symbol` - Optional trading pair symbol.
1854    /// * `since` - Optional start timestamp (milliseconds).
1855    /// * `limit` - Optional limit on number of records (default 100, max 1000).
1856    /// * `params` - Optional parameters.
1857    ///
1858    /// # Returns
1859    ///
1860    /// Returns a vector of historical [`FundingRate`] structures.
1861    ///
1862    /// # Errors
1863    ///
1864    /// Returns an error if the API request fails.
1865    ///
1866    /// # Example
1867    ///
1868    /// ```no_run
1869    /// # use ccxt_exchanges::binance::Binance;
1870    /// # use ccxt_core::ExchangeConfig;
1871    /// # async fn example() -> ccxt_core::Result<()> {
1872    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
1873    /// let history = binance.fetch_funding_rate_history(
1874    ///     Some("BTC/USDT:USDT"),
1875    ///     None,
1876    ///     Some(100),
1877    ///     None
1878    /// ).await?;
1879    /// # Ok(())
1880    /// # }
1881    /// ```
1882    /// Fetch position for a single trading pair.
1883    ///
1884    /// # Arguments
1885    ///
1886    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT:USDT").
1887    /// * `params` - Optional parameters.
1888    ///
1889    /// # Returns
1890    ///
1891    /// Returns the [`Position`] information.
1892    ///
1893    /// # Errors
1894    ///
1895    /// Returns an error if authentication fails, market is not found, or the API request fails.
1896    ///
1897    /// # Example
1898    ///
1899    /// ```no_run
1900    /// # use ccxt_exchanges::binance::Binance;
1901    /// # use ccxt_core::ExchangeConfig;
1902    /// # async fn example() -> ccxt_core::Result<()> {
1903    /// let mut config = ExchangeConfig::default();
1904    /// config.api_key = Some("your_api_key".to_string());
1905    /// config.secret = Some("your_secret".to_string());
1906    /// let binance = Binance::new_futures(config)?;
1907    /// let position = binance.fetch_position("BTC/USDT:USDT", None).await?;
1908    /// # Ok(())
1909    /// # }
1910    /// ```
1911    pub async fn fetch_position(
1912        &self,
1913        symbol: &str,
1914        params: Option<Value>,
1915    ) -> Result<ccxt_core::types::Position> {
1916        self.check_required_credentials()?;
1917
1918        let market = self.base().market(symbol).await?;
1919        let mut request_params = HashMap::new();
1920        request_params.insert("symbol".to_string(), market.id.clone());
1921
1922        if let Some(params) = params {
1923            if let Some(obj) = params.as_object() {
1924                for (key, value) in obj {
1925                    if let Some(v) = value.as_str() {
1926                        request_params.insert(key.clone(), v.to_string());
1927                    }
1928                }
1929            }
1930        }
1931
1932        let timestamp = self.fetch_time_raw().await?;
1933        let auth = self.get_auth()?;
1934        let signed_params =
1935            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
1936
1937        // Determine API endpoint based on market type
1938        let url = if market.linear.unwrap_or(true) {
1939            format!("{}/positionRisk", self.urls().fapi_private)
1940        } else {
1941            format!("{}/positionRisk", self.urls().dapi_private)
1942        };
1943
1944        let mut request_url = format!("{}?", url);
1945        for (key, value) in &signed_params {
1946            request_url.push_str(&format!("{}={}&", key, value));
1947        }
1948
1949        let mut headers = HeaderMap::new();
1950        auth.add_auth_headers_reqwest(&mut headers);
1951
1952        let data = self
1953            .base()
1954            .http_client
1955            .get(&request_url, Some(headers))
1956            .await?;
1957
1958        // API returns array, find matching symbol
1959        let positions_array = data.as_array().ok_or_else(|| {
1960            Error::from(ParseError::invalid_format(
1961                "data",
1962                "Expected array of positions",
1963            ))
1964        })?;
1965
1966        for position_data in positions_array {
1967            if let Some(pos_symbol) = position_data["symbol"].as_str() {
1968                if pos_symbol == market.id {
1969                    return parser::parse_position(position_data, Some(&market));
1970                }
1971            }
1972        }
1973
1974        Err(Error::from(ParseError::missing_field_owned(format!(
1975            "Position not found for symbol: {}",
1976            symbol
1977        ))))
1978    }
1979
1980    /// Fetch all positions.
1981    ///
1982    /// # Arguments
1983    ///
1984    /// * `symbols` - Optional vector of trading pair symbols.
1985    /// * `params` - Optional parameters.
1986    ///
1987    /// # Returns
1988    ///
1989    /// Returns a vector of [`Position`] structures.
1990    ///
1991    /// # Errors
1992    ///
1993    /// Returns an error if authentication fails or the API request fails.
1994    ///
1995    /// # Example
1996    ///
1997    /// ```no_run
1998    /// # use ccxt_exchanges::binance::Binance;
1999    /// # use ccxt_core::ExchangeConfig;
2000    /// # async fn example() -> ccxt_core::Result<()> {
2001    /// let mut config = ExchangeConfig::default();
2002    /// config.api_key = Some("your_api_key".to_string());
2003    /// config.secret = Some("your_secret".to_string());
2004    /// let binance = Binance::new_futures(config)?;
2005    /// let positions = binance.fetch_positions(None, None).await?;
2006    /// # Ok(())
2007    /// # }
2008    /// ```
2009    pub async fn fetch_positions(
2010        &self,
2011        symbols: Option<Vec<String>>,
2012        params: Option<Value>,
2013    ) -> Result<Vec<ccxt_core::types::Position>> {
2014        self.check_required_credentials()?;
2015
2016        let mut request_params = HashMap::new();
2017
2018        if let Some(ref params) = params {
2019            if let Some(obj) = params.as_object() {
2020                for (key, value) in obj {
2021                    if let Some(v) = value.as_str() {
2022                        request_params.insert(key.clone(), v.to_string());
2023                    }
2024                }
2025            }
2026        }
2027
2028        let timestamp = self.fetch_time_raw().await?;
2029        let auth = self.get_auth()?;
2030        let signed_params =
2031            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
2032        let query_string = auth.build_query_string(&signed_params);
2033        // Default to USDT-M futures endpoint
2034        let use_coin_m = params
2035            .as_ref()
2036            .and_then(|p| p.get("type"))
2037            .and_then(|v| v.as_str())
2038            .map(|t| t == "delivery" || t == "coin_m")
2039            .unwrap_or(false);
2040
2041        let url = if use_coin_m {
2042            format!("{}/positionRisk", self.urls().dapi_private)
2043        } else {
2044            format!("{}/positionRisk", self.urls().fapi_private)
2045        };
2046
2047        let request_url = format!("{}?{}", url, query_string);
2048
2049        let mut headers = HeaderMap::new();
2050        auth.add_auth_headers_reqwest(&mut headers);
2051
2052        let data = self
2053            .base()
2054            .http_client
2055            .get(&request_url, Some(headers))
2056            .await?;
2057
2058        let positions_array = data.as_array().ok_or_else(|| {
2059            Error::from(ParseError::invalid_format(
2060                "data",
2061                "Expected array of positions",
2062            ))
2063        })?;
2064
2065        let mut positions = Vec::new();
2066        for position_data in positions_array {
2067            if let Some(binance_symbol) = position_data["symbol"].as_str() {
2068                let cache = self.base().market_cache.read().await;
2069                if let Some(market) = cache.markets_by_id.get(binance_symbol) {
2070                    let market_clone = market.clone();
2071                    drop(cache);
2072
2073                    match parser::parse_position(position_data, Some(&market_clone)) {
2074                        Ok(position) => {
2075                            // Only return positions with contracts > 0
2076                            if position.contracts.unwrap_or(0.0) > 0.0 {
2077                                // If symbols specified, only return matching ones
2078                                if let Some(ref syms) = symbols {
2079                                    if syms.contains(&position.symbol) {
2080                                        positions.push(position);
2081                                    }
2082                                } else {
2083                                    positions.push(position);
2084                                }
2085                            }
2086                        }
2087                        Err(e) => {
2088                            warn!(
2089                                error = %e,
2090                                symbol = %binance_symbol,
2091                                "Failed to parse position"
2092                            );
2093                        }
2094                    }
2095                } else {
2096                    drop(cache);
2097                }
2098            }
2099        }
2100
2101        Ok(positions)
2102    }
2103
2104    /// Fetch position risk information.
2105    ///
2106    /// This is an alias for [`fetch_positions`](Self::fetch_positions) provided for CCXT naming consistency.
2107    ///
2108    /// # Arguments
2109    ///
2110    /// * `symbols` - Optional list of trading pair symbols.
2111    /// * `params` - Optional additional parameters.
2112    ///
2113    /// # Returns
2114    ///
2115    /// Returns a vector of position risk information as [`Position`] structures.
2116    ///
2117    /// # Errors
2118    ///
2119    /// Returns an error if authentication fails or the API request fails.
2120    pub async fn fetch_positions_risk(
2121        &self,
2122        symbols: Option<Vec<String>>,
2123        params: Option<Value>,
2124    ) -> Result<Vec<ccxt_core::types::Position>> {
2125        self.fetch_positions(symbols, params).await
2126    }
2127
2128    /// Fetch leverage settings for multiple trading pairs.
2129    ///
2130    /// # Arguments
2131    ///
2132    /// * `symbols` - Optional list of trading pairs. `None` queries all pairs.
2133    /// * `params` - Optional parameters:
2134    ///   - `portfolioMargin`: Whether to use portfolio margin account.
2135    ///
2136    /// # Returns
2137    ///
2138    /// Returns a `HashMap` of leverage information keyed by trading pair symbol.
2139    ///
2140    /// # Errors
2141    ///
2142    /// Returns an error if authentication fails or the API request fails.
2143    ///
2144    /// # Examples
2145    ///
2146    /// ```no_run
2147    /// # use ccxt_exchanges::binance::Binance;
2148    /// # use std::collections::HashMap;
2149    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2150    /// let exchange = Binance::new(Default::default());
2151    ///
2152    /// // Query leverage settings for all trading pairs
2153    /// let leverages = exchange.fetch_leverages(None, None).await?;
2154    ///
2155    /// // Query leverage settings for specific pairs
2156    /// let symbols = vec!["BTC/USDT:USDT".to_string(), "ETH/USDT:USDT".to_string()];
2157    /// let leverages = exchange.fetch_leverages(Some(symbols), None).await?;
2158    /// # Ok(())
2159    /// # }
2160    /// ```
2161    pub async fn fetch_leverages(
2162        &self,
2163        symbols: Option<Vec<String>>,
2164        params: Option<Value>,
2165    ) -> Result<HashMap<String, ccxt_core::types::Leverage>> {
2166        self.load_markets(false).await?;
2167
2168        let mut params_map = if let Some(p) = params {
2169            serde_json::from_value::<HashMap<String, String>>(p).unwrap_or_default()
2170        } else {
2171            HashMap::new()
2172        };
2173
2174        let market_type = params_map
2175            .remove("type")
2176            .or_else(|| params_map.remove("marketType"))
2177            .unwrap_or_else(|| "future".to_string());
2178
2179        let sub_type = params_map
2180            .remove("subType")
2181            .unwrap_or_else(|| "linear".to_string());
2182
2183        let is_portfolio_margin = params_map
2184            .remove("portfolioMargin")
2185            .and_then(|v| v.parse::<bool>().ok())
2186            .unwrap_or(false);
2187
2188        let timestamp = self.fetch_time_raw().await?;
2189        let auth = self.get_auth()?;
2190        let signed_params =
2191            auth.sign_with_timestamp(&params_map, timestamp, Some(self.options().recv_window))?;
2192
2193        let url = if market_type == "future" && sub_type == "linear" {
2194            // USDT-M futures
2195            if is_portfolio_margin {
2196                format!(
2197                    "{}/account",
2198                    self.urls().fapi_private.replace("/fapi/v1", "/papi/v1/um")
2199                )
2200            } else {
2201                format!("{}/symbolConfig", self.urls().fapi_private)
2202            }
2203        } else if market_type == "future" && sub_type == "inverse" {
2204            // Coin-M futures
2205            if is_portfolio_margin {
2206                format!(
2207                    "{}/account",
2208                    self.urls().dapi_private.replace("/dapi/v1", "/papi/v1/cm")
2209                )
2210            } else {
2211                format!("{}/account", self.urls().dapi_private)
2212            }
2213        } else {
2214            return Err(Error::invalid_request(
2215                "fetchLeverages() supports linear and inverse contracts only",
2216            ));
2217        };
2218
2219        let mut request_url = format!("{}?", url);
2220        for (key, value) in &signed_params {
2221            request_url.push_str(&format!("{}={}&", key, value));
2222        }
2223
2224        let mut headers = HeaderMap::new();
2225        auth.add_auth_headers_reqwest(&mut headers);
2226
2227        let response = self
2228            .base()
2229            .http_client
2230            .get(&request_url, Some(headers))
2231            .await?;
2232
2233        let leverages_data = if let Some(positions) = response.get("positions") {
2234            positions.as_array().cloned().unwrap_or_default()
2235        } else if response.is_array() {
2236            response.as_array().cloned().unwrap_or_default()
2237        } else {
2238            vec![]
2239        };
2240
2241        let mut leverages = HashMap::new();
2242
2243        for item in leverages_data {
2244            if let Ok(leverage) = parser::parse_leverage(&item, None) {
2245                // If symbols specified, only keep matching ones
2246                if let Some(ref filter_symbols) = symbols {
2247                    if filter_symbols.contains(&leverage.symbol) {
2248                        leverages.insert(leverage.symbol.clone(), leverage);
2249                    }
2250                } else {
2251                    leverages.insert(leverage.symbol.clone(), leverage);
2252                }
2253            }
2254        }
2255
2256        Ok(leverages)
2257    }
2258
2259    /// Fetch leverage settings for a single trading pair.
2260    ///
2261    /// # Arguments
2262    ///
2263    /// * `symbol` - Trading pair symbol.
2264    /// * `params` - Optional additional parameters.
2265    ///
2266    /// # Returns
2267    ///
2268    /// Returns leverage information for the specified trading pair.
2269    ///
2270    /// # Errors
2271    ///
2272    /// Returns an error if the symbol is not found or the API request fails.
2273    ///
2274    /// # Examples
2275    ///
2276    /// ```no_run
2277    /// # use ccxt_exchanges::binance::Binance;
2278    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2279    /// let exchange = Binance::new(Default::default());
2280    ///
2281    /// // Query leverage settings for BTC/USDT futures
2282    /// let leverage = exchange.fetch_leverage("BTC/USDT:USDT", None).await?;
2283    /// println!("Long leverage: {:?}", leverage.long_leverage);
2284    /// println!("Short leverage: {:?}", leverage.short_leverage);
2285    /// println!("Margin mode: {:?}", leverage.margin_mode);
2286    /// # Ok(())
2287    /// # }
2288    /// ```
2289    pub async fn fetch_leverage(
2290        &self,
2291        symbol: &str,
2292        params: Option<Value>,
2293    ) -> Result<ccxt_core::types::Leverage> {
2294        let symbols = Some(vec![symbol.to_string()]);
2295        let leverages = self.fetch_leverages(symbols, params).await?;
2296
2297        leverages.get(symbol).cloned().ok_or_else(|| {
2298            Error::exchange("404", format!("Leverage not found for symbol: {}", symbol))
2299        })
2300    }
2301    /// Set leverage multiplier for a trading pair.
2302    ///
2303    /// # Arguments
2304    ///
2305    /// * `symbol` - Trading pair symbol.
2306    /// * `leverage` - Leverage multiplier (1-125).
2307    /// * `params` - Optional additional parameters.
2308    ///
2309    /// # Returns
2310    ///
2311    /// Returns the operation result as a `HashMap`.
2312    ///
2313    /// # Errors
2314    ///
2315    /// Returns an error if:
2316    /// - Authentication credentials are missing
2317    /// - Leverage is outside valid range (1-125)
2318    /// - The market is not a futures/swap market
2319    /// - The API request fails
2320    ///
2321    /// # Examples
2322    ///
2323    /// ```no_run
2324    /// # use ccxt_exchanges::binance::Binance;
2325    /// # use ccxt_core::ExchangeConfig;
2326    /// # async fn example() -> ccxt_core::Result<()> {
2327    /// let mut config = ExchangeConfig::default();
2328    /// config.api_key = Some("your_api_key".to_string());
2329    /// config.secret = Some("your_secret".to_string());
2330    /// let binance = Binance::new_futures(config)?;
2331    /// let result = binance.set_leverage("BTC/USDT:USDT", 10, None).await?;
2332    /// # Ok(())
2333    /// # }
2334    /// ```
2335    pub async fn set_leverage(
2336        &self,
2337        symbol: &str,
2338        leverage: i64,
2339        params: Option<HashMap<String, String>>,
2340    ) -> Result<HashMap<String, Value>> {
2341        self.check_required_credentials()?;
2342
2343        if leverage < 1 || leverage > 125 {
2344            return Err(Error::invalid_request(
2345                "Leverage must be between 1 and 125".to_string(),
2346            ));
2347        }
2348
2349        self.load_markets(false).await?;
2350        let market = self.base().market(symbol).await?;
2351
2352        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
2353            return Err(Error::invalid_request(
2354                "set_leverage() supports futures and swap markets only".to_string(),
2355            ));
2356        }
2357
2358        let mut request_params = HashMap::new();
2359        request_params.insert("symbol".to_string(), market.id.clone());
2360        request_params.insert("leverage".to_string(), leverage.to_string());
2361
2362        if let Some(p) = params {
2363            for (key, value) in p {
2364                request_params.insert(key, value);
2365            }
2366        }
2367
2368        // Select API endpoint based on market type
2369        let url = if market.linear.unwrap_or(true) {
2370            format!("{}/leverage", self.urls().fapi_private)
2371        } else if market.inverse.unwrap_or(false) {
2372            format!("{}/leverage", self.urls().dapi_private)
2373        } else {
2374            return Err(Error::invalid_request(
2375                "Unknown futures market type".to_string(),
2376            ));
2377        };
2378
2379        let timestamp = self.fetch_time_raw().await?;
2380        let auth = self.get_auth()?;
2381
2382        let signed_params =
2383            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
2384
2385        let mut headers = HeaderMap::new();
2386        auth.add_auth_headers_reqwest(&mut headers);
2387
2388        let body = serde_json::to_value(&signed_params).map_err(|e| {
2389            Error::from(ParseError::invalid_format(
2390                "data",
2391                format!("Failed to serialize params: {}", e),
2392            ))
2393        })?;
2394
2395        let response = self
2396            .base()
2397            .http_client
2398            .post(&url, Some(headers), Some(body))
2399            .await?;
2400
2401        let result: HashMap<String, Value> = serde_json::from_value(response).map_err(|e| {
2402            Error::from(ParseError::invalid_format(
2403                "data",
2404                format!("Failed to parse response: {}", e),
2405            ))
2406        })?;
2407
2408        Ok(result)
2409    }
2410
2411    /// Set margin mode for a trading pair.
2412    ///
2413    /// # Arguments
2414    ///
2415    /// * `symbol` - Trading pair symbol.
2416    /// * `margin_mode` - Margin mode (`isolated` or `cross`).
2417    /// * `params` - Optional additional parameters.
2418    ///
2419    /// # Returns
2420    ///
2421    /// Returns the operation result as a `HashMap`.
2422    ///
2423    /// # Errors
2424    ///
2425    /// Returns an error if:
2426    /// - Authentication credentials are missing
2427    /// - Margin mode is invalid (must be `isolated` or `cross`)
2428    /// - The market is not a futures/swap market
2429    /// - The API request fails
2430    ///
2431    /// # Examples
2432    ///
2433    /// ```no_run
2434    /// # use ccxt_exchanges::binance::Binance;
2435    /// # use ccxt_core::ExchangeConfig;
2436    /// # async fn example() -> ccxt_core::Result<()> {
2437    /// let mut config = ExchangeConfig::default();
2438    /// config.api_key = Some("your_api_key".to_string());
2439    /// config.secret = Some("your_secret".to_string());
2440    /// let binance = Binance::new_futures(config)?;
2441    /// let result = binance.set_margin_mode("BTC/USDT:USDT", "isolated", None).await?;
2442    /// # Ok(())
2443    /// # }
2444    /// ```
2445    pub async fn set_margin_mode(
2446        &self,
2447        symbol: &str,
2448        margin_mode: &str,
2449        params: Option<HashMap<String, String>>,
2450    ) -> Result<HashMap<String, Value>> {
2451        self.check_required_credentials()?;
2452
2453        let margin_type = match margin_mode.to_uppercase().as_str() {
2454            "ISOLATED" | "ISOLATED_MARGIN" => "ISOLATED",
2455            "CROSS" | "CROSSED" | "CROSS_MARGIN" => "CROSSED",
2456            _ => {
2457                return Err(Error::invalid_request(format!(
2458                    "Invalid margin mode: {}. Must be 'isolated' or 'cross'",
2459                    margin_mode
2460                )));
2461            }
2462        };
2463
2464        self.load_markets(false).await?;
2465        let market = self.base().market(symbol).await?;
2466
2467        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
2468            return Err(Error::invalid_request(
2469                "set_margin_mode() supports futures and swap markets only".to_string(),
2470            ));
2471        }
2472
2473        let mut request_params = HashMap::new();
2474        request_params.insert("symbol".to_string(), market.id.clone());
2475        request_params.insert("marginType".to_string(), margin_type.to_string());
2476
2477        if let Some(p) = params {
2478            for (key, value) in p {
2479                request_params.insert(key, value);
2480            }
2481        }
2482
2483        // Select API endpoint based on market type
2484        let url = if market.linear.unwrap_or(true) {
2485            format!("{}/marginType", self.urls().fapi_private)
2486        } else if market.inverse.unwrap_or(false) {
2487            format!("{}/marginType", self.urls().dapi_private)
2488        } else {
2489            return Err(Error::invalid_request(
2490                "Unknown futures market type".to_string(),
2491            ));
2492        };
2493
2494        let timestamp = self.fetch_time_raw().await?;
2495        let auth = self.get_auth()?;
2496        let signed_params =
2497            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
2498
2499        let mut headers = HeaderMap::new();
2500        auth.add_auth_headers_reqwest(&mut headers);
2501
2502        let body = serde_json::to_value(&signed_params).map_err(|e| {
2503            Error::from(ParseError::invalid_format(
2504                "data",
2505                format!("Failed to serialize params: {}", e),
2506            ))
2507        })?;
2508
2509        let response = self
2510            .base()
2511            .http_client
2512            .post(&url, Some(headers), Some(body))
2513            .await?;
2514
2515        let result: HashMap<String, Value> = serde_json::from_value(response).map_err(|e| {
2516            Error::from(ParseError::invalid_format(
2517                "data",
2518                format!("Failed to parse response: {}", e),
2519            ))
2520        })?;
2521
2522        Ok(result)
2523    }
2524
2525    // ==================== P2.2: Fee Management ====================
2526
2527    /// Fetch trading fee for a single trading pair.
2528    ///
2529    /// # Arguments
2530    ///
2531    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
2532    /// * `params` - Optional parameters.
2533    ///
2534    /// # Returns
2535    ///
2536    /// Returns trading fee information.
2537    ///
2538    /// # Errors
2539    ///
2540    /// Returns an error if:
2541    /// - Authentication credentials are missing
2542    /// - The market type is not supported
2543    /// - The API request fails
2544    ///
2545    /// # Example
2546    ///
2547    /// ```no_run
2548    /// # use ccxt_exchanges::binance::Binance;
2549    /// # use ccxt_core::ExchangeConfig;
2550    /// # async fn example() -> ccxt_core::Result<()> {
2551    /// let mut config = ExchangeConfig::default();
2552    /// config.api_key = Some("your_api_key".to_string());
2553    /// config.secret = Some("your_secret".to_string());
2554    /// let binance = Binance::new(config)?;
2555    /// let fee = binance.fetch_trading_fee("BTC/USDT", None).await?;
2556    /// println!("Maker: {}, Taker: {}", fee.maker, fee.taker);
2557    /// # Ok(())
2558    /// # }
2559    /// ```
2560    pub async fn fetch_trading_fee(
2561        &self,
2562        symbol: &str,
2563        params: Option<HashMap<String, String>>,
2564    ) -> Result<FeeTradingFee> {
2565        self.check_required_credentials()?;
2566
2567        self.load_markets(false).await?;
2568        let market = self.base().market(symbol).await?;
2569
2570        let mut request_params = HashMap::new();
2571        request_params.insert("symbol".to_string(), market.id.clone());
2572
2573        let is_portfolio_margin = params
2574            .as_ref()
2575            .and_then(|p| p.get("portfolioMargin"))
2576            .map(|v| v == "true")
2577            .unwrap_or(false);
2578
2579        if let Some(p) = params {
2580            for (key, value) in p {
2581                // Do not pass portfolioMargin parameter to API
2582                if key != "portfolioMargin" {
2583                    request_params.insert(key, value);
2584                }
2585            }
2586        }
2587
2588        // Select API endpoint based on market type and Portfolio Margin mode
2589        let url = match market.market_type {
2590            MarketType::Spot => format!("{}/asset/tradeFee", self.urls().sapi),
2591            MarketType::Futures | MarketType::Swap => {
2592                if is_portfolio_margin {
2593                    // Portfolio Margin mode uses papi endpoints
2594                    if market.is_linear() {
2595                        format!("{}/um/commissionRate", self.urls().papi)
2596                    } else {
2597                        format!("{}/cm/commissionRate", self.urls().papi)
2598                    }
2599                } else {
2600                    // Standard mode
2601                    if market.is_linear() {
2602                        format!("{}/commissionRate", self.urls().fapi_private)
2603                    } else {
2604                        format!("{}/commissionRate", self.urls().dapi_private)
2605                    }
2606                }
2607            }
2608            _ => {
2609                return Err(Error::invalid_request(format!(
2610                    "fetch_trading_fee not supported for market type: {:?}",
2611                    market.market_type
2612                )));
2613            }
2614        };
2615
2616        let timestamp = self.fetch_time_raw().await?;
2617        let auth = self.get_auth()?;
2618        let signed_params =
2619            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
2620
2621        let mut request_url = format!("{}?", url);
2622        for (key, value) in &signed_params {
2623            request_url.push_str(&format!("{}={}&", key, value));
2624        }
2625
2626        let mut headers = HeaderMap::new();
2627        auth.add_auth_headers_reqwest(&mut headers);
2628
2629        let response = self
2630            .base()
2631            .http_client
2632            .get(&request_url, Some(headers))
2633            .await?;
2634
2635        parser::parse_trading_fee(&response)
2636    }
2637
2638    /// Fetch trading fees for multiple trading pairs.
2639    ///
2640    /// # Arguments
2641    ///
2642    /// * `symbols` - Optional list of trading pairs. `None` fetches all pairs.
2643    /// * `params` - Optional parameters.
2644    ///
2645    /// # Returns
2646    ///
2647    /// Returns a `HashMap` of trading fees keyed by symbol.
2648    ///
2649    /// # Errors
2650    ///
2651    /// Returns an error if authentication fails or the API request fails.
2652    ///
2653    /// # Example
2654    ///
2655    /// ```no_run
2656    /// # use ccxt_exchanges::binance::Binance;
2657    /// # use ccxt_core::ExchangeConfig;
2658    /// # async fn example() -> ccxt_core::Result<()> {
2659    /// let mut config = ExchangeConfig::default();
2660    /// config.api_key = Some("your_api_key".to_string());
2661    /// config.secret = Some("your_secret".to_string());
2662    /// let binance = Binance::new(config)?;
2663    /// let fees = binance.fetch_trading_fees(None, None).await?;
2664    /// # Ok(())
2665    /// # }
2666    /// ```
2667    pub async fn fetch_trading_fees(
2668        &self,
2669        symbols: Option<Vec<String>>,
2670        params: Option<HashMap<String, String>>,
2671    ) -> Result<HashMap<String, FeeTradingFee>> {
2672        self.check_required_credentials()?;
2673
2674        self.load_markets(false).await?;
2675
2676        let mut request_params = HashMap::new();
2677
2678        if let Some(syms) = &symbols {
2679            let mut market_ids: Vec<String> = Vec::new();
2680            for s in syms {
2681                if let Ok(market) = self.base().market(s).await {
2682                    market_ids.push(market.id);
2683                }
2684            }
2685            if !market_ids.is_empty() {
2686                request_params.insert("symbols".to_string(), market_ids.join(","));
2687            }
2688        }
2689
2690        if let Some(p) = params {
2691            for (key, value) in p {
2692                request_params.insert(key, value);
2693            }
2694        }
2695
2696        let url = format!("{}/asset/tradeFee", self.urls().sapi);
2697
2698        let timestamp = self.fetch_time_raw().await?;
2699        let auth = self.get_auth()?;
2700        let signed_params =
2701            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
2702
2703        let mut request_url = format!("{}?", url);
2704        for (key, value) in &signed_params {
2705            request_url.push_str(&format!("{}={}&", key, value));
2706        }
2707
2708        let mut headers = HeaderMap::new();
2709        auth.add_auth_headers_reqwest(&mut headers);
2710
2711        let response = self
2712            .base()
2713            .http_client
2714            .get(&request_url, Some(headers))
2715            .await?;
2716
2717        let fees_array = response.as_array().ok_or_else(|| {
2718            Error::from(ParseError::invalid_format(
2719                "data",
2720                "Expected array of trading fees",
2721            ))
2722        })?;
2723
2724        let mut fees = HashMap::new();
2725        for fee_data in fees_array {
2726            if let Ok(symbol_id) = fee_data["symbol"]
2727                .as_str()
2728                .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
2729            {
2730                if let Ok(market) = self.base().market_by_id(symbol_id).await {
2731                    if let Ok(fee) = parser::parse_trading_fee(fee_data) {
2732                        fees.insert(market.symbol.clone(), fee);
2733                    }
2734                }
2735            }
2736        }
2737
2738        Ok(fees)
2739    }
2740
2741    /// Fetch current funding rate for a single trading pair.
2742    ///
2743    /// # Arguments
2744    ///
2745    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT:USDT").
2746    /// * `params` - Optional parameters.
2747    ///
2748    /// # Returns
2749    ///
2750    /// Returns current funding rate information.
2751    ///
2752    /// # Errors
2753    ///
2754    /// Returns an error if:
2755    /// - The market is not a futures or swap market
2756    /// - The API request fails
2757    ///
2758    /// # Example
2759    ///
2760    /// ```no_run
2761    /// # use ccxt_exchanges::binance::Binance;
2762    /// # use ccxt_core::ExchangeConfig;
2763    /// # async fn example() -> ccxt_core::Result<()> {
2764    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
2765    /// let rate = binance.fetch_funding_rate("BTC/USDT:USDT", None).await?;
2766    /// println!("Funding rate: {:?}", rate.funding_rate);
2767    /// # Ok(())
2768    /// # }
2769    /// ```
2770    pub async fn fetch_funding_rate(
2771        &self,
2772        symbol: &str,
2773        params: Option<HashMap<String, String>>,
2774    ) -> Result<FeeFundingRate> {
2775        self.load_markets(false).await?;
2776        let market = self.base().market(symbol).await?;
2777
2778        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
2779            return Err(Error::invalid_request(
2780                "fetch_funding_rate() supports futures and swap markets only".to_string(),
2781            ));
2782        }
2783
2784        let mut request_params = HashMap::new();
2785        request_params.insert("symbol".to_string(), market.id.clone());
2786
2787        if let Some(p) = params {
2788            for (key, value) in p {
2789                request_params.insert(key, value);
2790            }
2791        }
2792
2793        let url = if market.linear.unwrap_or(true) {
2794            format!("{}/premiumIndex", self.urls().fapi_public)
2795        } else {
2796            format!("{}/premiumIndex", self.urls().dapi_public)
2797        };
2798
2799        let mut request_url = format!("{}?", url);
2800        for (key, value) in &request_params {
2801            request_url.push_str(&format!("{}={}&", key, value));
2802        }
2803
2804        let response = self.base().http_client.get(&request_url, None).await?;
2805
2806        // COIN-M futures API returns array format - extract first element
2807        let data = if !market.linear.unwrap_or(true) {
2808            response
2809                .as_array()
2810                .and_then(|arr| arr.first())
2811                .ok_or_else(|| {
2812                    Error::from(ParseError::invalid_format(
2813                        "data",
2814                        "COIN-M funding rate response should be an array with at least one element",
2815                    ))
2816                })?
2817        } else {
2818            &response
2819        };
2820
2821        parser::parse_funding_rate(data, Some(&market))
2822    }
2823
2824    /// Fetch funding rate history for a trading pair.
2825    ///
2826    /// # Arguments
2827    ///
2828    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT:USDT").
2829    /// * `since` - Optional start timestamp in milliseconds.
2830    /// * `limit` - Optional record limit (default 100, max 1000).
2831    /// * `params` - Optional parameters.
2832    ///
2833    /// # Returns
2834    ///
2835    /// Returns a vector of historical funding rate records.
2836    ///
2837    /// # Errors
2838    ///
2839    /// Returns an error if:
2840    /// - The market is not a futures or swap market
2841    /// - The API request fails
2842    ///
2843    /// # Example
2844    ///
2845    /// ```no_run
2846    /// # use ccxt_exchanges::binance::Binance;
2847    /// # use ccxt_core::ExchangeConfig;
2848    /// # async fn example() -> ccxt_core::Result<()> {
2849    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
2850    /// let history = binance.fetch_funding_rate_history("BTC/USDT:USDT", None, Some(10), None).await?;
2851    /// println!("Found {} records", history.len());
2852    /// # Ok(())
2853    /// # }
2854    /// ```
2855    pub async fn fetch_funding_rate_history(
2856        &self,
2857        symbol: &str,
2858        since: Option<u64>,
2859        limit: Option<u32>,
2860        params: Option<HashMap<String, String>>,
2861    ) -> Result<Vec<FeeFundingRateHistory>> {
2862        self.load_markets(false).await?;
2863        let market = self.base().market(symbol).await?;
2864
2865        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
2866            return Err(Error::invalid_request(
2867                "fetch_funding_rate_history() supports futures and swap markets only".to_string(),
2868            ));
2869        }
2870
2871        let mut request_params = HashMap::new();
2872        request_params.insert("symbol".to_string(), market.id.clone());
2873
2874        if let Some(s) = since {
2875            request_params.insert("startTime".to_string(), s.to_string());
2876        }
2877
2878        if let Some(l) = limit {
2879            request_params.insert("limit".to_string(), l.to_string());
2880        }
2881
2882        if let Some(p) = params {
2883            for (key, value) in p {
2884                request_params.insert(key, value);
2885            }
2886        }
2887
2888        let url = if market.linear.unwrap_or(true) {
2889            format!("{}/fundingRate", self.urls().fapi_public)
2890        } else {
2891            format!("{}/fundingRate", self.urls().dapi_public)
2892        };
2893
2894        let mut request_url = format!("{}?", url);
2895        for (key, value) in &request_params {
2896            request_url.push_str(&format!("{}={}&", key, value));
2897        }
2898
2899        let response = self.base().http_client.get(&request_url, None).await?;
2900
2901        let history_array = response.as_array().ok_or_else(|| {
2902            Error::from(ParseError::invalid_format(
2903                "data",
2904                "Expected array of funding rate history",
2905            ))
2906        })?;
2907
2908        let mut history = Vec::new();
2909        for history_data in history_array {
2910            match parser::parse_funding_rate_history(history_data, Some(&market)) {
2911                Ok(record) => history.push(record),
2912                Err(e) => {
2913                    warn!(error = %e, "Failed to parse funding rate history");
2914                }
2915            }
2916        }
2917
2918        Ok(history)
2919    }
2920
2921    /// Fetch current funding rates for multiple trading pairs.
2922    ///
2923    /// # Arguments
2924    ///
2925    /// * `symbols` - Optional list of trading pairs. `None` fetches all pairs.
2926    /// * `params` - Optional parameters.
2927    ///
2928    /// # Returns
2929    ///
2930    /// Returns a `HashMap` of funding rates keyed by symbol.
2931    ///
2932    /// # Errors
2933    ///
2934    /// Returns an error if the API request fails.
2935    ///
2936    /// # Example
2937    ///
2938    /// ```no_run
2939    /// # use ccxt_exchanges::binance::Binance;
2940    /// # use ccxt_core::ExchangeConfig;
2941    /// # async fn example() -> ccxt_core::Result<()> {
2942    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
2943    /// let rates = binance.fetch_funding_rates(None, None).await?;
2944    /// println!("Found {} funding rates", rates.len());
2945    /// # Ok(())
2946    /// # }
2947    /// ```
2948    pub async fn fetch_funding_rates(
2949        &self,
2950        symbols: Option<Vec<String>>,
2951        params: Option<HashMap<String, String>>,
2952    ) -> Result<HashMap<String, FeeFundingRate>> {
2953        self.load_markets(false).await?;
2954
2955        let mut request_params = HashMap::new();
2956
2957        if let Some(p) = params {
2958            for (key, value) in p {
2959                request_params.insert(key, value);
2960            }
2961        }
2962
2963        let url = format!("{}/premiumIndex", self.urls().fapi_public);
2964
2965        let mut request_url = url.clone();
2966        if !request_params.is_empty() {
2967            request_url.push('?');
2968            for (key, value) in &request_params {
2969                request_url.push_str(&format!("{}={}&", key, value));
2970            }
2971        }
2972
2973        let response = self.base().http_client.get(&request_url, None).await?;
2974
2975        let mut rates = HashMap::new();
2976
2977        if let Some(rates_array) = response.as_array() {
2978            for rate_data in rates_array {
2979                if let Ok(symbol_id) = rate_data["symbol"]
2980                    .as_str()
2981                    .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
2982                {
2983                    if let Ok(market) = self.base().market_by_id(symbol_id).await {
2984                        if let Some(ref syms) = symbols {
2985                            if !syms.contains(&market.symbol) {
2986                                continue;
2987                            }
2988                        }
2989
2990                        if let Ok(rate) = parser::parse_funding_rate(rate_data, Some(&market)) {
2991                            rates.insert(market.symbol.clone(), rate);
2992                        }
2993                    }
2994                }
2995            }
2996        } else {
2997            if let Ok(symbol_id) = response["symbol"]
2998                .as_str()
2999                .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
3000            {
3001                if let Ok(market) = self.base().market_by_id(symbol_id).await {
3002                    if let Ok(rate) = parser::parse_funding_rate(&response, Some(&market)) {
3003                        rates.insert(market.symbol.clone(), rate);
3004                    }
3005                }
3006            }
3007        }
3008
3009        Ok(rates)
3010    }
3011
3012    /// Fetch leverage tier information for trading pairs.
3013    ///
3014    /// # Arguments
3015    ///
3016    /// * `symbols` - Optional list of trading pairs. `None` fetches all pairs.
3017    /// * `params` - Optional parameters.
3018    ///
3019    /// # Returns
3020    ///
3021    /// Returns a `HashMap` of leverage tiers keyed by symbol.
3022    ///
3023    /// # Errors
3024    ///
3025    /// Returns an error if authentication fails or the API request fails.
3026    ///
3027    /// # Example
3028    ///
3029    /// ```no_run
3030    /// # use ccxt_exchanges::binance::Binance;
3031    /// # use ccxt_core::ExchangeConfig;
3032    /// # async fn example() -> ccxt_core::Result<()> {
3033    /// let mut config = ExchangeConfig::default();
3034    /// config.api_key = Some("your_api_key".to_string());
3035    /// config.secret = Some("your_secret".to_string());
3036    /// let binance = Binance::new_futures(config)?;
3037    /// let tiers = binance.fetch_leverage_tiers(None, None).await?;
3038    /// # Ok(())
3039    /// # }
3040    /// ```
3041    pub async fn fetch_leverage_tiers(
3042        &self,
3043        symbols: Option<Vec<String>>,
3044        params: Option<HashMap<String, String>>,
3045    ) -> Result<HashMap<String, Vec<LeverageTier>>> {
3046        self.check_required_credentials()?;
3047
3048        self.load_markets(false).await?;
3049
3050        let mut request_params = HashMap::new();
3051
3052        let is_portfolio_margin = params
3053            .as_ref()
3054            .and_then(|p| p.get("portfolioMargin"))
3055            .map(|v| v == "true")
3056            .unwrap_or(false);
3057
3058        let market = if let Some(syms) = &symbols {
3059            if let Some(first_symbol) = syms.first() {
3060                let m = self.base().market(first_symbol).await?;
3061                request_params.insert("symbol".to_string(), m.id.clone());
3062                Some(m)
3063            } else {
3064                None
3065            }
3066        } else {
3067            None
3068        };
3069
3070        if let Some(p) = params {
3071            for (key, value) in p {
3072                // Do not pass portfolioMargin parameter to API
3073                if key != "portfolioMargin" {
3074                    request_params.insert(key, value);
3075                }
3076            }
3077        }
3078
3079        // Select API endpoint based on market type and Portfolio Margin mode
3080        let url = if let Some(ref m) = market {
3081            if is_portfolio_margin {
3082                // Portfolio Margin mode uses papi endpoints
3083                if m.is_linear() {
3084                    format!("{}/um/leverageBracket", self.urls().papi)
3085                } else {
3086                    format!("{}/cm/leverageBracket", self.urls().papi)
3087                }
3088            } else {
3089                // Standard mode
3090                if m.is_linear() {
3091                    format!("{}/leverageBracket", self.urls().fapi_private)
3092                } else {
3093                    format!("{}/v2/leverageBracket", self.urls().dapi_private)
3094                }
3095            }
3096        } else {
3097            format!("{}/leverageBracket", self.urls().fapi_private)
3098        };
3099
3100        let timestamp = self.fetch_time_raw().await?;
3101        let auth = self.get_auth()?;
3102        let signed_params =
3103            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3104
3105        let mut request_url = format!("{}?", url);
3106        for (key, value) in &signed_params {
3107            request_url.push_str(&format!("{}={}&", key, value));
3108        }
3109
3110        let mut headers = HeaderMap::new();
3111        auth.add_auth_headers_reqwest(&mut headers);
3112
3113        let response = self
3114            .base()
3115            .http_client
3116            .get(&request_url, Some(headers))
3117            .await?;
3118
3119        let mut tiers = HashMap::new();
3120
3121        if let Some(tiers_array) = response.as_array() {
3122            for tier_data in tiers_array {
3123                if let Ok(symbol_id) = tier_data["symbol"]
3124                    .as_str()
3125                    .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
3126                {
3127                    if let Ok(market) = self.base().market_by_id(symbol_id).await {
3128                        if let Some(ref syms) = symbols {
3129                            if !syms.contains(&market.symbol) {
3130                                continue;
3131                            }
3132                        }
3133
3134                        if let Some(brackets) = tier_data["brackets"].as_array() {
3135                            let mut tier_list = Vec::new();
3136                            for bracket in brackets {
3137                                match parser::parse_leverage_tier(bracket, &market) {
3138                                    Ok(tier) => tier_list.push(tier),
3139                                    Err(e) => {
3140                                        warn!(error = %e, "Failed to parse leverage tier");
3141                                    }
3142                                }
3143                            }
3144                            if !tier_list.is_empty() {
3145                                tiers.insert(market.symbol.clone(), tier_list);
3146                            }
3147                        }
3148                    }
3149                }
3150            }
3151        }
3152
3153        Ok(tiers)
3154    }
3155
3156    /// Fetch funding fee history.
3157    ///
3158    /// # Arguments
3159    ///
3160    /// * `symbol` - Optional trading pair symbol.
3161    /// * `since` - Optional start timestamp in milliseconds.
3162    /// * `limit` - Optional record limit (default 100, max 1000).
3163    /// * `params` - Optional parameters.
3164    ///
3165    /// # Returns
3166    ///
3167    /// Returns a vector of funding fee history records.
3168    ///
3169    /// # Errors
3170    ///
3171    /// Returns an error if authentication fails or the API request fails.
3172    ///
3173    /// # Example
3174    ///
3175    /// ```no_run
3176    /// # use ccxt_exchanges::binance::Binance;
3177    /// # use ccxt_core::ExchangeConfig;
3178    /// # async fn example() -> ccxt_core::Result<()> {
3179    /// let mut config = ExchangeConfig::default();
3180    /// config.api_key = Some("your_api_key".to_string());
3181    /// config.secret = Some("your_secret".to_string());
3182    /// let binance = Binance::new_futures(config)?;
3183    /// let history = binance.fetch_funding_history(
3184    ///     Some("BTC/USDT:USDT"),
3185    ///     None,
3186    ///     Some(100),
3187    ///     None
3188    /// ).await?;
3189    /// # Ok(())
3190    /// # }
3191    /// ```
3192    pub async fn fetch_funding_history(
3193        &self,
3194        symbol: Option<&str>,
3195        since: Option<u64>,
3196        limit: Option<u32>,
3197        params: Option<Value>,
3198    ) -> Result<Vec<ccxt_core::types::FundingHistory>> {
3199        self.check_required_credentials()?;
3200
3201        let mut request_params = HashMap::new();
3202
3203        request_params.insert("incomeType".to_string(), "FUNDING_FEE".to_string());
3204
3205        let market = if let Some(sym) = symbol {
3206            let m = self.base().market(sym).await?;
3207            request_params.insert("symbol".to_string(), m.id.clone());
3208            Some(m)
3209        } else {
3210            None
3211        };
3212
3213        if let Some(s) = since {
3214            request_params.insert("startTime".to_string(), s.to_string());
3215        }
3216
3217        if let Some(l) = limit {
3218            request_params.insert("limit".to_string(), l.to_string());
3219        }
3220
3221        if let Some(params) = params {
3222            if let Some(obj) = params.as_object() {
3223                for (key, value) in obj {
3224                    if let Some(v) = value.as_str() {
3225                        request_params.insert(key.clone(), v.to_string());
3226                    } else if let Some(v) = value.as_i64() {
3227                        request_params.insert(key.clone(), v.to_string());
3228                    }
3229                }
3230            }
3231        }
3232
3233        let timestamp = self.fetch_time_raw().await?;
3234        let auth = self.get_auth()?;
3235        let signed_params =
3236            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3237        let query_string = auth.build_query_string(&signed_params);
3238        let use_coin_m = market
3239            .as_ref()
3240            .map(|m| !m.linear.unwrap_or(false))
3241            .unwrap_or(false);
3242
3243        let url = if use_coin_m {
3244            format!("{}/income", self.urls().dapi_private)
3245        } else {
3246            format!("{}/income", self.urls().fapi_private)
3247        };
3248
3249        let request_url = format!("{}?{}", url, query_string);
3250
3251        let mut headers = HeaderMap::new();
3252        auth.add_auth_headers_reqwest(&mut headers);
3253
3254        let data = self
3255            .base()
3256            .http_client
3257            .get(&request_url, Some(headers))
3258            .await?;
3259
3260        let history_array = data.as_array().ok_or_else(|| {
3261            Error::from(ParseError::invalid_format(
3262                "data",
3263                "Expected array of funding history",
3264            ))
3265        })?;
3266
3267        let mut history = Vec::new();
3268        for history_data in history_array {
3269            match parser::parse_funding_history(history_data, market.as_ref()) {
3270                Ok(funding) => history.push(funding),
3271                Err(e) => {
3272                    warn!(error = %e, "Failed to parse funding history");
3273                }
3274            }
3275        }
3276
3277        Ok(history)
3278    }
3279    /// Fetch my funding fee history
3280    ///
3281    /// # Arguments
3282    ///
3283    /// * `symbol` - Optional trading pair symbol.
3284    /// * `start_time` - Optional start timestamp.
3285    /// * `end_time` - Optional end timestamp.
3286    /// * `limit` - Optional record limit (default 100, max 1000).
3287    ///
3288    /// # Returns
3289    ///
3290    /// Returns a vector of funding fee history records.
3291    ///
3292    /// # Errors
3293    ///
3294    /// Returns an error if authentication fails or the API request fails.
3295    ///
3296    /// # Example
3297    ///
3298    /// ```no_run
3299    /// # use ccxt_exchanges::binance::Binance;
3300    /// # use ccxt_core::ExchangeConfig;
3301    /// # async fn example() -> ccxt_core::Result<()> {
3302    /// let mut config = ExchangeConfig::default();
3303    /// config.api_key = Some("your_api_key".to_string());
3304    /// config.secret = Some("your_secret".to_string());
3305    /// let binance = Binance::new_futures(config)?;
3306    /// let fees = binance.fetch_my_funding_history(Some("BTC/USDT:USDT"), None, None, Some(50)).await?;
3307    /// # Ok(())
3308    /// # }
3309    /// ```
3310    pub async fn fetch_my_funding_history(
3311        &self,
3312        symbol: Option<&str>,
3313        start_time: Option<u64>,
3314        end_time: Option<u64>,
3315        limit: Option<u32>,
3316    ) -> Result<Vec<FundingFee>> {
3317        self.check_required_credentials()?;
3318
3319        let mut request_params = HashMap::new();
3320
3321        request_params.insert("incomeType".to_string(), "FUNDING_FEE".to_string());
3322
3323        let market = if let Some(sym) = symbol {
3324            let m = self.base().market(sym).await?;
3325            request_params.insert("symbol".to_string(), m.id.clone());
3326            Some(m)
3327        } else {
3328            None
3329        };
3330
3331        if let Some(s) = start_time {
3332            request_params.insert("startTime".to_string(), s.to_string());
3333        }
3334
3335        if let Some(e) = end_time {
3336            request_params.insert("endTime".to_string(), e.to_string());
3337        }
3338
3339        if let Some(l) = limit {
3340            request_params.insert("limit".to_string(), l.to_string());
3341        }
3342
3343        let timestamp = self.fetch_time_raw().await?;
3344        let auth = self.get_auth()?;
3345        let signed_params =
3346            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3347
3348        let use_coin_m = market
3349            .as_ref()
3350            .map(|m| !m.linear.unwrap_or(false))
3351            .unwrap_or(false);
3352
3353        let url = if use_coin_m {
3354            format!("{}/income", self.urls().dapi_private)
3355        } else {
3356            format!("{}/income", self.urls().fapi_private)
3357        };
3358        let query_string = auth.build_query_string(&signed_params);
3359        let request_url = format!("{}?{}", url, query_string);
3360
3361        let mut headers = HeaderMap::new();
3362        auth.add_auth_headers_reqwest(&mut headers);
3363
3364        let data = self
3365            .base()
3366            .http_client
3367            .get(&request_url, Some(headers))
3368            .await?;
3369
3370        let fees_array = data.as_array().ok_or_else(|| {
3371            Error::from(ParseError::invalid_format(
3372                "data",
3373                "Expected array of funding fees",
3374            ))
3375        })?;
3376
3377        let mut fees = Vec::new();
3378        for fee_data in fees_array {
3379            match parser::parse_funding_fee(fee_data, market.as_ref()) {
3380                Ok(fee) => fees.push(fee),
3381                Err(e) => {
3382                    warn!(error = %e, "Failed to parse funding fee");
3383                }
3384            }
3385        }
3386
3387        Ok(fees)
3388    }
3389
3390    /// Fetch next funding rate (prediction) for a trading pair.
3391    ///
3392    /// # Arguments
3393    ///
3394    /// * `symbol` - Trading pair symbol.
3395    ///
3396    /// # Returns
3397    ///
3398    /// Returns next funding rate information.
3399    ///
3400    /// # Errors
3401    ///
3402    /// Returns an error if the API request fails.
3403    ///
3404    /// # Example
3405    ///
3406    /// ```no_run
3407    /// # use ccxt_exchanges::binance::Binance;
3408    /// # use ccxt_core::ExchangeConfig;
3409    /// # async fn example() -> ccxt_core::Result<()> {
3410    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3411    /// let next_rate = binance.fetch_next_funding_rate("BTC/USDT:USDT").await?;
3412    /// # Ok(())
3413    /// # }
3414    /// ```
3415    pub async fn fetch_next_funding_rate(&self, symbol: &str) -> Result<NextFundingRate> {
3416        let market = self.base().market(symbol).await?;
3417
3418        let use_coin_m = !market.linear.unwrap_or(false);
3419
3420        let url = if use_coin_m {
3421            format!("{}/premiumIndex", self.urls().dapi_public)
3422        } else {
3423            format!("{}/premiumIndex", self.urls().fapi_public)
3424        };
3425
3426        let mut request_params = HashMap::new();
3427        request_params.insert("symbol".to_string(), market.id.clone());
3428
3429        let mut request_url = format!("{}?", url);
3430        for (key, value) in &request_params {
3431            request_url.push_str(&format!("{}={}&", key, value));
3432        }
3433
3434        let data = self.base().http_client.get(&request_url, None).await?;
3435
3436        parser::parse_next_funding_rate(&data, &market)
3437    }
3438
3439    /// Set position mode (one-way or hedge mode).
3440    ///
3441    /// # Arguments
3442    ///
3443    /// * `dual_side` - Enable hedge mode (dual-side position).
3444    ///   - `true`: Hedge mode (can hold both long and short positions simultaneously).
3445    ///   - `false`: One-way mode (can only hold one direction).
3446    /// * `params` - Optional parameters.
3447    ///
3448    /// # Returns
3449    ///
3450    /// Returns the operation result.
3451    ///
3452    /// # Errors
3453    ///
3454    /// Returns an error if:
3455    /// - Authentication fails
3456    /// - Positions are currently open (cannot switch modes with open positions)
3457    /// - The API request fails
3458    ///
3459    /// # Notes
3460    ///
3461    /// - This setting applies globally to the account, not per trading pair.
3462    /// - In hedge mode, orders must specify `positionSide` (LONG/SHORT).
3463    /// - In one-way mode, `positionSide` is fixed to BOTH.
3464    /// - Cannot switch modes while holding positions.
3465    ///
3466    /// # Example
3467    ///
3468    /// ```no_run
3469    /// # use ccxt_exchanges::binance::Binance;
3470    /// # use ccxt_core::ExchangeConfig;
3471    /// # async fn example() -> ccxt_core::Result<()> {
3472    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3473    ///
3474    /// // Enable hedge mode
3475    /// let result = binance.set_position_mode(true, None).await?;
3476    ///
3477    /// // Switch back to one-way mode
3478    /// let result = binance.set_position_mode(false, None).await?;
3479    /// # Ok(())
3480    /// # }
3481    /// ```
3482    pub async fn set_position_mode(&self, dual_side: bool, params: Option<Value>) -> Result<Value> {
3483        self.check_required_credentials()?;
3484
3485        let mut request_params = HashMap::new();
3486        request_params.insert("dualSidePosition".to_string(), dual_side.to_string());
3487
3488        if let Some(params) = params {
3489            if let Some(obj) = params.as_object() {
3490                for (key, value) in obj {
3491                    if let Some(v) = value.as_str() {
3492                        request_params.insert(key.clone(), v.to_string());
3493                    } else if let Some(v) = value.as_bool() {
3494                        request_params.insert(key.clone(), v.to_string());
3495                    } else if let Some(v) = value.as_i64() {
3496                        request_params.insert(key.clone(), v.to_string());
3497                    }
3498                }
3499            }
3500        }
3501
3502        let timestamp = self.fetch_time_raw().await?;
3503        let auth = self.get_auth()?;
3504        let signed_params =
3505            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3506
3507        let url = format!("{}/positionSide/dual", self.urls().fapi_private);
3508
3509        let mut headers = HeaderMap::new();
3510        auth.add_auth_headers_reqwest(&mut headers);
3511
3512        let body = serde_json::to_value(&signed_params).map_err(|e| {
3513            Error::from(ParseError::invalid_format(
3514                "data",
3515                format!("Failed to serialize params: {}", e),
3516            ))
3517        })?;
3518
3519        let data = self
3520            .base()
3521            .http_client
3522            .post(&url, Some(headers), Some(body))
3523            .await?;
3524
3525        Ok(data)
3526    }
3527
3528    /// Fetch current position mode.
3529    ///
3530    /// # Arguments
3531    ///
3532    /// * `params` - Optional parameters.
3533    ///
3534    /// # Returns
3535    ///
3536    /// Returns the current position mode:
3537    /// - `true`: Hedge mode (dual-side position).
3538    /// - `false`: One-way mode.
3539    ///
3540    /// # Errors
3541    ///
3542    /// Returns an error if authentication fails or the API request fails.
3543    ///
3544    /// # Example
3545    ///
3546    /// ```no_run
3547    /// # use ccxt_exchanges::binance::Binance;
3548    /// # use ccxt_core::ExchangeConfig;
3549    /// # async fn example() -> ccxt_core::Result<()> {
3550    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3551    ///
3552    /// let dual_side = binance.fetch_position_mode(None).await?;
3553    /// println!("Hedge mode enabled: {}", dual_side);
3554    /// # Ok(())
3555    /// # }
3556    /// ```
3557    pub async fn fetch_position_mode(&self, params: Option<Value>) -> Result<bool> {
3558        self.check_required_credentials()?;
3559
3560        let mut request_params = HashMap::new();
3561
3562        if let Some(params) = params {
3563            if let Some(obj) = params.as_object() {
3564                for (key, value) in obj {
3565                    if let Some(v) = value.as_str() {
3566                        request_params.insert(key.clone(), v.to_string());
3567                    }
3568                }
3569            }
3570        }
3571
3572        let timestamp = self.fetch_time_raw().await?;
3573        let auth = self.get_auth()?;
3574        let signed_params =
3575            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3576
3577        let query_string = signed_params
3578            .iter()
3579            .map(|(k, v)| format!("{}={}", k, v))
3580            .collect::<Vec<_>>()
3581            .join("&");
3582
3583        let url = format!(
3584            "{}/positionSide/dual?{}",
3585            self.urls().fapi_private,
3586            query_string
3587        );
3588
3589        let mut headers = HeaderMap::new();
3590        auth.add_auth_headers_reqwest(&mut headers);
3591
3592        let data = self.base().http_client.get(&url, Some(headers)).await?;
3593
3594        if let Some(dual_side) = data.get("dualSidePosition") {
3595            if let Some(value) = dual_side.as_bool() {
3596                return Ok(value);
3597            }
3598            if let Some(value_str) = dual_side.as_str() {
3599                return Ok(value_str.to_lowercase() == "true");
3600            }
3601        }
3602
3603        Err(Error::from(ParseError::invalid_format(
3604            "data",
3605            "Failed to parse position mode response",
3606        )))
3607    }
3608
3609    // ==================== Futures Margin Management ====================
3610
3611    /// Modify isolated position margin.
3612    ///
3613    /// # Arguments
3614    ///
3615    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
3616    /// * `amount` - Adjustment amount (positive to add, negative to reduce).
3617    /// * `params` - Optional parameters:
3618    ///   - `type`: Operation type (1=add, 2=reduce). If provided, `amount` sign is ignored.
3619    ///   - `positionSide`: Position side "LONG" | "SHORT" (required in hedge mode).
3620    ///
3621    /// # Returns
3622    ///
3623    /// Returns the adjustment result including the new margin amount.
3624    ///
3625    /// # Errors
3626    ///
3627    /// Returns an error if authentication fails or the API request fails.
3628    /// ```rust,no_run
3629    /// # use ccxt_exchanges::binance::Binance;
3630    /// # use ccxt_core::ExchangeConfig;
3631    /// # use rust_decimal_macros::dec;
3632    /// # #[tokio::main]
3633    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
3634    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3635    ///
3636    /// // Add 100 USDT to isolated margin
3637    /// binance.modify_isolated_position_margin("BTC/USDT", dec!(100.0), None).await?;
3638    ///
3639    /// // Reduce 50 USDT from isolated margin
3640    /// binance.modify_isolated_position_margin("BTC/USDT", dec!(-50.0), None).await?;
3641    ///
3642    /// // Adjust long position margin in hedge mode
3643    /// let params = serde_json::json!({"positionSide": "LONG"});
3644    /// binance.modify_isolated_position_margin("BTC/USDT", dec!(100.0), Some(params)).await?;
3645    /// # Ok(())
3646    /// # }
3647    /// ```
3648    pub async fn modify_isolated_position_margin(
3649        &self,
3650        symbol: &str,
3651        amount: Decimal,
3652        params: Option<Value>,
3653    ) -> Result<Value> {
3654        self.check_required_credentials()?;
3655
3656        let market = self.base().market(symbol).await?;
3657        let mut request_params = HashMap::new();
3658        request_params.insert("symbol".to_string(), market.id.clone());
3659        request_params.insert("amount".to_string(), amount.abs().to_string());
3660        request_params.insert(
3661            "type".to_string(),
3662            if amount > Decimal::ZERO {
3663                "1".to_string()
3664            } else {
3665                "2".to_string()
3666            },
3667        );
3668
3669        if let Some(params) = params {
3670            if let Some(obj) = params.as_object() {
3671                for (key, value) in obj {
3672                    if let Some(v) = value.as_str() {
3673                        request_params.insert(key.clone(), v.to_string());
3674                    }
3675                }
3676            }
3677        }
3678
3679        let timestamp = self.fetch_time_raw().await?;
3680        let auth = self.get_auth()?;
3681        let signed_params =
3682            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3683
3684        let url = if market.linear.unwrap_or(true) {
3685            format!("{}/positionMargin", self.urls().fapi_private)
3686        } else {
3687            format!("{}/positionMargin", self.urls().dapi_private)
3688        };
3689
3690        let mut headers = HeaderMap::new();
3691        auth.add_auth_headers_reqwest(&mut headers);
3692
3693        let body = serde_json::to_value(&signed_params).map_err(|e| {
3694            Error::from(ParseError::invalid_format(
3695                "data",
3696                format!("Failed to serialize params: {}", e),
3697            ))
3698        })?;
3699
3700        let data = self
3701            .base()
3702            .http_client
3703            .post(&url, Some(headers), Some(body))
3704            .await?;
3705
3706        Ok(data)
3707    }
3708
3709    /// Fetch position risk information.
3710    ///
3711    /// Retrieves risk information for all futures positions, including unrealized PnL,
3712    /// liquidation price, leverage, etc.
3713    ///
3714    /// # Arguments
3715    ///
3716    /// * `symbol` - Optional trading pair symbol. `None` returns all positions.
3717    /// * `params` - Optional parameters.
3718    ///
3719    /// # Returns
3720    ///
3721    /// Returns position risk information array.
3722    ///
3723    /// # Errors
3724    ///
3725    /// Returns an error if authentication fails or the API request fails.
3726    ///
3727    /// # Examples
3728    /// ```rust,no_run
3729    /// # use ccxt_exchanges::binance::Binance;
3730    /// # use ccxt_core::ExchangeConfig;
3731    /// # #[tokio::main]
3732    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
3733    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3734    ///
3735    /// // Fetch all position risks
3736    /// let risks = binance.fetch_position_risk(None, None).await?;
3737    ///
3738    /// // Fetch position risk for specific symbol
3739    /// let risk = binance.fetch_position_risk(Some("BTC/USDT"), None).await?;
3740    /// # Ok(())
3741    /// # }
3742    /// ```
3743    pub async fn fetch_position_risk(
3744        &self,
3745        symbol: Option<&str>,
3746        params: Option<Value>,
3747    ) -> Result<Value> {
3748        self.check_required_credentials()?;
3749
3750        let mut request_params = HashMap::new();
3751
3752        if let Some(sym) = symbol {
3753            let market = self.base().market(sym).await?;
3754            request_params.insert("symbol".to_string(), market.id.clone());
3755        }
3756
3757        if let Some(params) = params {
3758            if let Some(obj) = params.as_object() {
3759                for (key, value) in obj {
3760                    if let Some(v) = value.as_str() {
3761                        request_params.insert(key.clone(), v.to_string());
3762                    }
3763                }
3764            }
3765        }
3766
3767        let timestamp = self.fetch_time_raw().await?;
3768        let auth = self.get_auth()?;
3769        let signed_params =
3770            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3771
3772        let query_string: String = signed_params
3773            .iter()
3774            .map(|(k, v)| format!("{}={}", k, v))
3775            .collect::<Vec<_>>()
3776            .join("&");
3777
3778        let url = format!("{}/positionRisk?{}", self.urls().fapi_private, query_string);
3779
3780        let mut headers = HeaderMap::new();
3781        auth.add_auth_headers_reqwest(&mut headers);
3782
3783        let data = self.base().http_client.get(&url, Some(headers)).await?;
3784
3785        Ok(data)
3786    }
3787
3788    /// Fetch leverage bracket information.
3789    ///
3790    /// Retrieves leverage bracket information for specified or all trading pairs,
3791    /// showing maximum leverage for different notional value tiers.
3792    ///
3793    /// # Arguments
3794    ///
3795    /// * `symbol` - Optional trading pair symbol. `None` returns all pairs.
3796    /// * `params` - Optional parameters.
3797    ///
3798    /// # Returns
3799    ///
3800    /// Returns leverage bracket information including maximum notional value and
3801    /// corresponding maximum leverage for each tier.
3802    ///
3803    /// # Errors
3804    ///
3805    /// Returns an error if authentication fails or the API request fails.
3806    ///
3807    /// # Examples
3808    /// ```rust,no_run
3809    /// # use ccxt_exchanges::binance::Binance;
3810    /// # use ccxt_core::ExchangeConfig;
3811    /// # #[tokio::main]
3812    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
3813    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
3814    ///
3815    /// // Fetch all leverage brackets
3816    /// let brackets = binance.fetch_leverage_bracket(None, None).await?;
3817    ///
3818    /// // Fetch bracket for specific symbol
3819    /// let bracket = binance.fetch_leverage_bracket(Some("BTC/USDT"), None).await?;
3820    /// # Ok(())
3821    /// # }
3822    /// ```
3823    ///
3824    /// # Response Example
3825    /// ```json
3826    /// [
3827    ///   {
3828    ///     "symbol": "BTCUSDT",
3829    ///     "brackets": [
3830    ///       {
3831    ///         "bracket": 1,
3832    ///         "initialLeverage": 125,
3833    ///         "notionalCap": 50000,
3834    ///         "notionalFloor": 0,
3835    ///         "maintMarginRatio": 0.004,
3836    ///         "cum": 0
3837    ///       },
3838    ///       {
3839    ///         "bracket": 2,
3840    ///         "initialLeverage": 100,
3841    ///         "notionalCap": 250000,
3842    ///         "notionalFloor": 50000,
3843    ///         "maintMarginRatio": 0.005,
3844    ///         "cum": 50
3845    ///       }
3846    ///     ]
3847    ///   }
3848    /// ]
3849    /// ```
3850    pub async fn fetch_leverage_bracket(
3851        &self,
3852        symbol: Option<&str>,
3853        params: Option<Value>,
3854    ) -> Result<Value> {
3855        self.check_required_credentials()?;
3856
3857        let mut request_params = HashMap::new();
3858
3859        if let Some(sym) = symbol {
3860            let market = self.base().market(sym).await?;
3861            request_params.insert("symbol".to_string(), market.id.clone());
3862        }
3863
3864        if let Some(params) = params {
3865            if let Some(obj) = params.as_object() {
3866                for (key, value) in obj {
3867                    if let Some(v) = value.as_str() {
3868                        request_params.insert(key.clone(), v.to_string());
3869                    }
3870                }
3871            }
3872        }
3873
3874        let timestamp = self.fetch_time_raw().await?;
3875        let auth = self.get_auth()?;
3876        let signed_params =
3877            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
3878
3879        let query_string: String = signed_params
3880            .iter()
3881            .map(|(k, v)| format!("{}={}", k, v))
3882            .collect::<Vec<_>>()
3883            .join("&");
3884
3885        let url = format!(
3886            "{}/leverageBracket?{}",
3887            self.urls().fapi_private,
3888            query_string
3889        );
3890
3891        let mut headers = HeaderMap::new();
3892        auth.add_auth_headers_reqwest(&mut headers);
3893
3894        let data = self.base().http_client.get(&url, Some(headers)).await?;
3895
3896        Ok(data)
3897    }
3898
3899    // ==================== Margin Trading ====================
3900
3901    /// Borrow funds in cross margin mode.
3902    ///
3903    /// # Arguments
3904    ///
3905    /// * `currency` - Currency code.
3906    /// * `amount` - Borrow amount.
3907    ///
3908    /// # Returns
3909    ///
3910    /// Returns a margin loan record.
3911    ///
3912    /// # Errors
3913    ///
3914    /// Returns an error if authentication fails or the API request fails.
3915    pub async fn borrow_cross_margin(&self, currency: &str, amount: f64) -> Result<MarginLoan> {
3916        let mut params = HashMap::new();
3917        params.insert("asset".to_string(), currency.to_string());
3918        params.insert("amount".to_string(), amount.to_string());
3919
3920        let timestamp = self.fetch_time_raw().await?;
3921        let auth = self.get_auth()?;
3922        let signed_params =
3923            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
3924
3925        let url = format!("{}/sapi/v1/margin/loan", self.urls().sapi);
3926
3927        let mut headers = HeaderMap::new();
3928        auth.add_auth_headers_reqwest(&mut headers);
3929
3930        let body = serde_json::to_value(&signed_params).map_err(|e| {
3931            Error::from(ParseError::invalid_format(
3932                "data",
3933                format!("Failed to serialize params: {}", e),
3934            ))
3935        })?;
3936
3937        let data = self
3938            .base()
3939            .http_client
3940            .post(&url, Some(headers), Some(body))
3941            .await?;
3942
3943        parser::parse_margin_loan(&data)
3944    }
3945
3946    /// Borrow funds in isolated margin mode.
3947    ///
3948    /// # Arguments
3949    ///
3950    /// * `symbol` - Trading pair symbol.
3951    /// * `currency` - Currency code.
3952    /// * `amount` - Borrow amount.
3953    ///
3954    /// # Returns
3955    ///
3956    /// Returns a margin loan record.
3957    ///
3958    /// # Errors
3959    ///
3960    /// Returns an error if authentication fails or the API request fails.
3961    pub async fn borrow_isolated_margin(
3962        &self,
3963        symbol: &str,
3964        currency: &str,
3965        amount: f64,
3966    ) -> Result<MarginLoan> {
3967        self.load_markets(false).await?;
3968        let market = self.base().market(symbol).await?;
3969
3970        let mut params = HashMap::new();
3971        params.insert("asset".to_string(), currency.to_string());
3972        params.insert("amount".to_string(), amount.to_string());
3973        params.insert("symbol".to_string(), market.id.clone());
3974        params.insert("isIsolated".to_string(), "TRUE".to_string());
3975
3976        let timestamp = self.fetch_time_raw().await?;
3977        let auth = self.get_auth()?;
3978        let signed_params =
3979            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
3980
3981        let url = format!("{}/sapi/v1/margin/loan", self.urls().sapi);
3982
3983        let mut headers = HeaderMap::new();
3984        auth.add_auth_headers_reqwest(&mut headers);
3985
3986        let body = serde_json::to_value(&signed_params).map_err(|e| {
3987            Error::from(ParseError::invalid_format(
3988                "data",
3989                format!("Failed to serialize params: {}", e),
3990            ))
3991        })?;
3992
3993        let data = self
3994            .base()
3995            .http_client
3996            .post(&url, Some(headers), Some(body))
3997            .await?;
3998
3999        parser::parse_margin_loan(&data)
4000    }
4001
4002    /// Repay borrowed funds in cross margin mode.
4003    ///
4004    /// # Arguments
4005    ///
4006    /// * `currency` - Currency code.
4007    /// * `amount` - Repayment amount.
4008    ///
4009    /// # Returns
4010    ///
4011    /// Returns a margin repayment record.
4012    ///
4013    /// # Errors
4014    ///
4015    /// Returns an error if authentication fails or the API request fails.
4016    pub async fn repay_cross_margin(&self, currency: &str, amount: f64) -> Result<MarginRepay> {
4017        let mut params = HashMap::new();
4018        params.insert("asset".to_string(), currency.to_string());
4019        params.insert("amount".to_string(), amount.to_string());
4020
4021        let timestamp = self.fetch_time_raw().await?;
4022        let auth = self.get_auth()?;
4023        let signed_params =
4024            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
4025
4026        let url = format!("{}/sapi/v1/margin/repay", self.urls().sapi);
4027
4028        let mut headers = HeaderMap::new();
4029        auth.add_auth_headers_reqwest(&mut headers);
4030
4031        let body = serde_json::to_value(&signed_params).map_err(|e| {
4032            Error::from(ParseError::invalid_format(
4033                "data",
4034                format!("Failed to serialize params: {}", e),
4035            ))
4036        })?;
4037
4038        let data = self
4039            .base()
4040            .http_client
4041            .post(&url, Some(headers), Some(body))
4042            .await?;
4043
4044        let loan = parser::parse_margin_loan(&data)?;
4045
4046        Ok(MarginRepay {
4047            id: loan.id,
4048            currency: loan.currency,
4049            amount: loan.amount,
4050            symbol: loan.symbol,
4051            timestamp: loan.timestamp,
4052            datetime: loan.datetime,
4053            status: loan.status,
4054            is_isolated: loan.is_isolated,
4055            info: loan.info,
4056        })
4057    }
4058
4059    /// Repay borrowed funds in isolated margin mode.
4060    ///
4061    /// # Arguments
4062    ///
4063    /// * `symbol` - Trading pair symbol.
4064    /// * `currency` - Currency code.
4065    /// * `amount` - Repayment amount.
4066    ///
4067    /// # Returns
4068    ///
4069    /// Returns a margin repayment record.
4070    ///
4071    /// # Errors
4072    ///
4073    /// Returns an error if authentication fails or the API request fails.
4074    pub async fn repay_isolated_margin(
4075        &self,
4076        symbol: &str,
4077        currency: &str,
4078        amount: f64,
4079    ) -> Result<MarginRepay> {
4080        self.load_markets(false).await?;
4081        let market = self.base().market(symbol).await?;
4082
4083        let mut params = HashMap::new();
4084        params.insert("asset".to_string(), currency.to_string());
4085        params.insert("amount".to_string(), amount.to_string());
4086        params.insert("symbol".to_string(), market.id.clone());
4087        params.insert("isIsolated".to_string(), "TRUE".to_string());
4088
4089        let timestamp = self.fetch_time_raw().await?;
4090        let auth = self.get_auth()?;
4091        let signed_params =
4092            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
4093
4094        let url = format!("{}/sapi/v1/margin/repay", self.urls().sapi);
4095
4096        let mut headers = HeaderMap::new();
4097        auth.add_auth_headers_reqwest(&mut headers);
4098
4099        let body = serde_json::to_value(&signed_params).map_err(|e| {
4100            Error::from(ParseError::invalid_format(
4101                "data",
4102                format!("Failed to serialize params: {}", e),
4103            ))
4104        })?;
4105
4106        let data = self
4107            .base()
4108            .http_client
4109            .post(&url, Some(headers), Some(body))
4110            .await?;
4111
4112        let loan = parser::parse_margin_loan(&data)?;
4113
4114        Ok(MarginRepay {
4115            id: loan.id,
4116            currency: loan.currency,
4117            amount: loan.amount,
4118            symbol: loan.symbol,
4119            timestamp: loan.timestamp,
4120            datetime: loan.datetime,
4121            status: loan.status,
4122            is_isolated: loan.is_isolated,
4123            info: loan.info,
4124        })
4125    }
4126
4127    /// Fetch margin adjustment history.
4128    ///
4129    /// Retrieves liquidation records and margin adjustment history.
4130    ///
4131    /// # Arguments
4132    /// * `symbol` - Optional trading pair symbol (required for isolated margin)
4133    /// * `since` - Start timestamp in milliseconds
4134    /// * `limit` - Maximum number of records to return
4135    ///
4136    /// # Returns
4137    /// Returns a list of margin adjustments.
4138    ///
4139    /// # Errors
4140    /// Returns an error if authentication fails or the API request fails.
4141    pub async fn fetch_margin_adjustment_history(
4142        &self,
4143        symbol: Option<&str>,
4144        since: Option<i64>,
4145        limit: Option<i64>,
4146    ) -> Result<Vec<MarginAdjustment>> {
4147        let mut params = HashMap::new();
4148
4149        if let Some(sym) = symbol {
4150            self.load_markets(false).await?;
4151            let market = self.base().market(sym).await?;
4152            params.insert("symbol".to_string(), market.id.clone());
4153            params.insert("isolatedSymbol".to_string(), market.id.clone());
4154        }
4155
4156        if let Some(start_time) = since {
4157            params.insert("startTime".to_string(), start_time.to_string());
4158        }
4159
4160        if let Some(size) = limit {
4161            params.insert("size".to_string(), size.to_string());
4162        }
4163
4164        let timestamp = self.fetch_time_raw().await?;
4165        let auth = self.get_auth()?;
4166        let _signed_params =
4167            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
4168
4169        let url = format!("{}/sapi/v1/margin/forceLiquidationRec", self.urls().sapi);
4170
4171        let mut headers = HeaderMap::new();
4172        auth.add_auth_headers_reqwest(&mut headers);
4173
4174        let data = self.base().http_client.get(&url, Some(headers)).await?;
4175
4176        let rows = data["rows"].as_array().ok_or_else(|| {
4177            Error::from(ParseError::invalid_format(
4178                "data",
4179                "Expected rows array in response",
4180            ))
4181        })?;
4182
4183        let mut results = Vec::new();
4184        for item in rows {
4185            if let Ok(adjustment) = parser::parse_margin_adjustment(item) {
4186                results.push(adjustment);
4187            }
4188        }
4189
4190        Ok(results)
4191    }
4192    /// Fetch account balance.
4193    ///
4194    /// Retrieves balance information for various account types including spot,
4195    /// margin, futures, savings, and funding accounts.
4196    ///
4197    /// # Arguments
4198    /// * `params` - Optional parameters
4199    ///   - `type`: Account type ("spot", "margin", "isolated", "linear", "inverse", "savings", "funding", "papi")
4200    ///   - `marginMode`: Margin mode ("cross", "isolated")
4201    ///   - `symbols`: List of isolated margin trading pairs (used with "isolated" type)
4202    ///
4203    /// # Returns
4204    /// Returns account balance information.
4205    ///
4206    /// # Errors
4207    /// Returns an error if authentication fails or the API request fails.
4208    pub async fn fetch_balance(&self, params: Option<HashMap<String, Value>>) -> Result<Balance> {
4209        let params = params.unwrap_or_default();
4210
4211        let account_type = params
4212            .get("type")
4213            .and_then(|v| v.as_str())
4214            .unwrap_or("spot");
4215        let _margin_mode = params.get("marginMode").and_then(|v| v.as_str());
4216
4217        let (url, method) = match account_type {
4218            "spot" => (format!("{}/account", self.urls().public), "GET"),
4219            "margin" | "cross" => (
4220                format!("{}/sapi/v1/margin/account", self.urls().sapi),
4221                "GET",
4222            ),
4223            "isolated" => (
4224                format!("{}/sapi/v1/margin/isolated/account", self.urls().sapi),
4225                "GET",
4226            ),
4227            "linear" | "future" => (
4228                format!("{}/balance", self.urls().fapi_public.replace("/v1", "/v2")),
4229                "GET",
4230            ),
4231            "inverse" | "delivery" => (format!("{}/account", self.urls().dapi_public), "GET"),
4232            "savings" => (
4233                format!("{}/sapi/v1/lending/union/account", self.urls().sapi),
4234                "GET",
4235            ),
4236            "funding" => (
4237                format!("{}/asset/get-funding-asset", self.urls().sapi),
4238                "POST",
4239            ),
4240            "papi" => (format!("{}/papi/v1/balance", self.urls().sapi), "GET"),
4241            _ => (format!("{}/account", self.urls().public), "GET"),
4242        };
4243
4244        let mut request_params = HashMap::new();
4245
4246        if account_type == "isolated" {
4247            if let Some(symbols) = params.get("symbols").and_then(|v| v.as_array()) {
4248                let symbols_str: Vec<String> = symbols
4249                    .iter()
4250                    .filter_map(|v| v.as_str())
4251                    .map(|s| s.to_string())
4252                    .collect();
4253                if !symbols_str.is_empty() {
4254                    request_params.insert("symbols".to_string(), symbols_str.join(","));
4255                }
4256            }
4257        }
4258        let timestamp = self.fetch_time_raw().await?;
4259        let auth = self.get_auth()?;
4260        let signed_params =
4261            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
4262
4263        let query_string = {
4264            let mut pairs: Vec<_> = signed_params.iter().collect();
4265            pairs.sort_by_key(|(k, _)| *k);
4266            pairs
4267                .iter()
4268                .map(|(k, v)| format!("{}={}", k, v))
4269                .collect::<Vec<_>>()
4270                .join("&")
4271        };
4272        let full_url = format!("{}?{}", url, query_string);
4273
4274        let mut headers = HeaderMap::new();
4275        auth.add_auth_headers_reqwest(&mut headers);
4276
4277        let data = if method == "POST" {
4278            self.base()
4279                .http_client
4280                .post(&full_url, Some(headers), None)
4281                .await?
4282        } else {
4283            self.base()
4284                .http_client
4285                .get(&full_url, Some(headers))
4286                .await?
4287        };
4288
4289        parser::parse_balance_with_type(&data, account_type)
4290    }
4291
4292    /// Fetch maximum borrowable amount for cross margin.
4293    ///
4294    /// Queries the maximum amount that can be borrowed for a specific currency
4295    /// in cross margin mode.
4296    ///
4297    /// # Arguments
4298    /// * `code` - Currency code (e.g., "USDT")
4299    /// * `params` - Optional parameters (reserved for future use)
4300    ///
4301    /// # Returns
4302    /// Returns maximum borrowable amount information.
4303    ///
4304    /// # Errors
4305    /// Returns an error if authentication fails or the API request fails.
4306    pub async fn fetch_cross_margin_max_borrowable(
4307        &self,
4308        code: &str,
4309        params: Option<HashMap<String, Value>>,
4310    ) -> Result<MaxBorrowable> {
4311        let _params = params.unwrap_or_default();
4312
4313        let mut request_params = HashMap::new();
4314        request_params.insert("asset".to_string(), code.to_string());
4315
4316        let timestamp = self.fetch_time_raw().await?;
4317        let auth = self.get_auth()?;
4318        let signed_params =
4319            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
4320
4321        let query_string = {
4322            let mut pairs: Vec<_> = signed_params.iter().collect();
4323            pairs.sort_by_key(|(k, _)| *k);
4324            pairs
4325                .iter()
4326                .map(|(k, v)| format!("{}={}", k, v))
4327                .collect::<Vec<_>>()
4328                .join("&")
4329        };
4330        let url = format!(
4331            "{}/sapi/v1/margin/maxBorrowable?{}",
4332            self.urls().sapi,
4333            query_string
4334        );
4335
4336        let mut headers = HeaderMap::new();
4337        auth.add_auth_headers_reqwest(&mut headers);
4338
4339        let data = self.base().http_client.get(&url, Some(headers)).await?;
4340
4341        parser::parse_max_borrowable(&data, code, None)
4342    }
4343
4344    /// Fetch maximum borrowable amount for isolated margin.
4345    ///
4346    /// Queries the maximum amount that can be borrowed for a specific currency
4347    /// in a specific isolated margin trading pair.
4348    ///
4349    /// # Arguments
4350    /// * `code` - Currency code (e.g., "USDT")
4351    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
4352    /// * `params` - Optional parameters (reserved for future use)
4353    ///
4354    /// # Returns
4355    /// Returns maximum borrowable amount information.
4356    ///
4357    /// # Errors
4358    /// Returns an error if authentication fails or the API request fails.
4359    pub async fn fetch_isolated_margin_max_borrowable(
4360        &self,
4361        code: &str,
4362        symbol: &str,
4363        params: Option<HashMap<String, Value>>,
4364    ) -> Result<MaxBorrowable> {
4365        let _params = params.unwrap_or_default();
4366
4367        self.load_markets(false).await?;
4368        let market = self.base().market(symbol).await?;
4369
4370        let mut request_params = HashMap::new();
4371        request_params.insert("asset".to_string(), code.to_string());
4372        request_params.insert("isolatedSymbol".to_string(), market.id.clone());
4373
4374        let timestamp = self.fetch_time_raw().await?;
4375        let auth = self.get_auth()?;
4376        let signed_params =
4377            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
4378
4379        let query_string = signed_params
4380            .iter()
4381            .map(|(k, v)| format!("{}={}", k, v))
4382            .collect::<Vec<_>>()
4383            .join("&");
4384        let url = format!(
4385            "{}/sapi/v1/margin/isolated/maxBorrowable?{}",
4386            self.urls().sapi,
4387            query_string
4388        );
4389
4390        let mut headers = HeaderMap::new();
4391        auth.add_auth_headers_reqwest(&mut headers);
4392
4393        let data = self.base().http_client.get(&url, Some(headers)).await?;
4394
4395        parser::parse_max_borrowable(&data, code, Some(symbol.to_string()))
4396    }
4397
4398    /// Fetch maximum transferable amount.
4399    ///
4400    /// Queries the maximum amount that can be transferred out of margin account.
4401    ///
4402    /// # Arguments
4403    /// * `code` - Currency code (e.g., "USDT")
4404    /// * `params` - Optional parameters
4405    ///   - `symbol`: Isolated margin trading pair symbol (e.g., "BTC/USDT")
4406    ///
4407    /// # Returns
4408    /// Returns maximum transferable amount information.
4409    ///
4410    /// # Errors
4411    /// Returns an error if authentication fails or the API request fails.
4412    pub async fn fetch_max_transferable(
4413        &self,
4414        code: &str,
4415        params: Option<HashMap<String, Value>>,
4416    ) -> Result<MaxTransferable> {
4417        let params = params.unwrap_or_default();
4418
4419        let mut request_params = HashMap::new();
4420        request_params.insert("asset".to_string(), code.to_string());
4421
4422        let symbol_opt: Option<String> = if let Some(symbol_value) = params.get("symbol") {
4423            if let Some(symbol_str) = symbol_value.as_str() {
4424                self.load_markets(false).await?;
4425                let market = self.base().market(symbol_str).await?;
4426                request_params.insert("isolatedSymbol".to_string(), market.id.clone());
4427                Some(symbol_str.to_string())
4428            } else {
4429                None
4430            }
4431        } else {
4432            None
4433        };
4434
4435        let timestamp = self.fetch_time_raw().await?;
4436        let auth = self.get_auth()?;
4437        let signed_params =
4438            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
4439
4440        let query_string = signed_params
4441            .iter()
4442            .map(|(k, v)| format!("{}={}", k, v))
4443            .collect::<Vec<_>>()
4444            .join("&");
4445        let url = format!(
4446            "{}/sapi/v1/margin/maxTransferable?{}",
4447            self.urls().sapi,
4448            query_string
4449        );
4450
4451        let mut headers = HeaderMap::new();
4452        auth.add_auth_headers_reqwest(&mut headers);
4453
4454        let data = self.base().http_client.get(&url, Some(headers)).await?;
4455
4456        parser::parse_max_transferable(&data, code, symbol_opt)
4457    }
4458
4459    // ============================================================================
4460    // Enhanced Market Data API
4461    // ============================================================================
4462
4463    /// Fetch bid/ask prices (Book Ticker)
4464    ///
4465    /// # Arguments
4466    ///
4467    /// * `symbol` - Optional trading pair symbol; if omitted, returns all symbols
4468    ///
4469    /// # Returns
4470    ///
4471    /// Returns bid/ask price information
4472    ///
4473    /// # API Endpoint
4474    ///
4475    /// * GET `/api/v3/ticker/bookTicker`
4476    /// * Weight: 1 for single symbol, 2 for all symbols
4477    /// * Requires signature: No
4478    ///
4479    /// # Example
4480    ///
4481    /// ```no_run
4482    /// # use ccxt_exchanges::binance::Binance;
4483    /// # use ccxt_core::ExchangeConfig;
4484    /// # async fn example() -> ccxt_core::Result<()> {
4485    /// let binance = Binance::new(ExchangeConfig::default())?;
4486    ///
4487    /// // Fetch bid/ask for single symbol
4488    /// let bid_ask = binance.fetch_bids_asks(Some("BTC/USDT")).await?;
4489    /// println!("BTC/USDT bid: {}, ask: {}", bid_ask[0].bid, bid_ask[0].ask);
4490    ///
4491    /// // Fetch bid/ask for all symbols
4492    /// let all_bid_asks = binance.fetch_bids_asks(None).await?;
4493    /// println!("Total symbols: {}", all_bid_asks.len());
4494    /// # Ok(())
4495    /// # }
4496    /// ```
4497    pub async fn fetch_bids_asks(&self, symbol: Option<&str>) -> Result<Vec<BidAsk>> {
4498        self.load_markets(false).await?;
4499
4500        let url = if let Some(sym) = symbol {
4501            let market = self.base().market(sym).await?;
4502            format!(
4503                "{}/ticker/bookTicker?symbol={}",
4504                self.urls().public,
4505                market.id
4506            )
4507        } else {
4508            format!("{}/ticker/bookTicker", self.urls().public)
4509        };
4510
4511        let data = self.base().http_client.get(&url, None).await?;
4512
4513        parser::parse_bids_asks(&data)
4514    }
4515
4516    /// Fetch latest prices.
4517    ///
4518    /// Retrieves the most recent price for one or all trading pairs.
4519    ///
4520    /// # Arguments
4521    ///
4522    /// * `symbol` - Optional trading pair symbol; if omitted, returns all symbols
4523    ///
4524    /// # Returns
4525    ///
4526    /// Returns latest price information.
4527    ///
4528    /// # Errors
4529    ///
4530    /// Returns an error if the API request fails.
4531    ///
4532    /// # Examples
4533    ///
4534    /// ```no_run
4535    /// # use ccxt_exchanges::binance::Binance;
4536    /// # use ccxt_core::ExchangeConfig;
4537    /// # async fn example() -> ccxt_core::Result<()> {
4538    /// let binance = Binance::new(ExchangeConfig::default())?;
4539    ///
4540    /// // Fetch latest price for single symbol
4541    /// let price = binance.fetch_last_prices(Some("BTC/USDT")).await?;
4542    /// println!("BTC/USDT last price: {}", price[0].price);
4543    ///
4544    /// // Fetch latest prices for all symbols
4545    /// let all_prices = binance.fetch_last_prices(None).await?;
4546    /// println!("Total symbols: {}", all_prices.len());
4547    /// # Ok(())
4548    /// # }
4549    /// ```
4550    pub async fn fetch_last_prices(&self, symbol: Option<&str>) -> Result<Vec<LastPrice>> {
4551        self.load_markets(false).await?;
4552
4553        let url = if let Some(sym) = symbol {
4554            let market = self.base().market(sym).await?;
4555            format!("{}/ticker/price?symbol={}", self.urls().public, market.id)
4556        } else {
4557            format!("{}/ticker/price", self.urls().public)
4558        };
4559
4560        let data = self.base().http_client.get(&url, None).await?;
4561
4562        parser::parse_last_prices(&data)
4563    }
4564
4565    /// Fetch futures mark prices.
4566    ///
4567    /// Retrieves mark prices for futures contracts, used for calculating unrealized PnL.
4568    /// Includes funding rates and next funding time.
4569    ///
4570    /// # Arguments
4571    ///
4572    /// * `symbol` - Optional trading pair symbol; if omitted, returns all futures pairs
4573    ///
4574    /// # Returns
4575    ///
4576    /// Returns mark price information including funding rates.
4577    ///
4578    /// # Errors
4579    ///
4580    /// Returns an error if the API request fails.
4581    ///
4582    /// # Note
4583    ///
4584    /// This API only applies to futures markets.
4585    ///
4586    /// # Examples
4587    ///
4588    /// ```no_run
4589    /// # use ccxt_exchanges::binance::Binance;
4590    /// # use ccxt_core::ExchangeConfig;
4591    /// # async fn example() -> ccxt_core::Result<()> {
4592    /// let binance = Binance::new(ExchangeConfig::default())?;
4593    ///
4594    /// // Fetch mark price for single futures symbol
4595    /// let mark_price = binance.fetch_mark_price(Some("BTC/USDT")).await?;
4596    /// println!("BTC/USDT mark price: {}", mark_price[0].mark_price);
4597    /// println!("Funding rate: {}", mark_price[0].last_funding_rate);
4598    ///
4599    /// // Fetch mark prices for all futures symbols
4600    /// let all_mark_prices = binance.fetch_mark_price(None).await?;
4601    /// println!("Total futures symbols: {}", all_mark_prices.len());
4602    /// # Ok(())
4603    /// # }
4604    /// ```
4605    pub async fn fetch_mark_price(&self, symbol: Option<&str>) -> Result<Vec<MarkPrice>> {
4606        self.load_markets(false).await?;
4607
4608        let url = if let Some(sym) = symbol {
4609            let market = self.base().market(sym).await?;
4610            format!(
4611                "{}/premiumIndex?symbol={}",
4612                self.urls().fapi_public,
4613                market.id
4614            )
4615        } else {
4616            format!("{}/premiumIndex", self.urls().fapi_public)
4617        };
4618
4619        let data = self.base().http_client.get(&url, None).await?;
4620
4621        parser::parse_mark_prices(&data)
4622    }
4623    /// Parse timeframe string into seconds.
4624    ///
4625    /// Converts a timeframe string like "1m", "5m", "1h", "1d" into the equivalent number of seconds.
4626    ///
4627    /// # Arguments
4628    ///
4629    /// * `timeframe` - Timeframe string such as "1m", "5m", "1h", "1d"
4630    ///
4631    /// # Returns
4632    ///
4633    /// Returns the time interval in seconds.
4634    ///
4635    /// # Errors
4636    ///
4637    /// Returns an error if the timeframe is empty or has an invalid format.
4638    fn parse_timeframe(&self, timeframe: &str) -> Result<i64> {
4639        let unit_map = [
4640            ("s", 1),
4641            ("m", 60),
4642            ("h", 3600),
4643            ("d", 86400),
4644            ("w", 604800),
4645            ("M", 2592000),
4646            ("y", 31536000),
4647        ];
4648
4649        if timeframe.is_empty() {
4650            return Err(Error::invalid_request("timeframe cannot be empty"));
4651        }
4652
4653        let mut num_str = String::new();
4654        let mut unit_str = String::new();
4655
4656        for ch in timeframe.chars() {
4657            if ch.is_ascii_digit() {
4658                num_str.push(ch);
4659            } else {
4660                unit_str.push(ch);
4661            }
4662        }
4663
4664        let amount: i64 = if num_str.is_empty() {
4665            1
4666        } else {
4667            num_str.parse().map_err(|_| {
4668                Error::invalid_request(format!("Invalid timeframe format: {}", timeframe))
4669            })?
4670        };
4671
4672        let unit_seconds = unit_map
4673            .iter()
4674            .find(|(unit, _)| unit == &unit_str.as_str())
4675            .map(|(_, seconds)| *seconds)
4676            .ok_or_else(|| {
4677                Error::invalid_request(format!("Unsupported timeframe unit: {}", unit_str))
4678            })?;
4679
4680        Ok(amount * unit_seconds)
4681    }
4682
4683    /// Get OHLCV API endpoint based on market type and price type
4684    ///
4685    /// # Arguments
4686    /// * `market` - Market information
4687    /// * `price` - Price type: None (default) | "mark" | "index" | "premiumIndex"
4688    ///
4689    /// # Returns
4690    /// Returns tuple (base_url, endpoint, use_pair)
4691    /// * `base_url` - API base URL
4692    /// * `endpoint` - Specific endpoint path
4693    /// * `use_pair` - Whether to use pair instead of symbol (required for index price)
4694    ///
4695    /// # API Endpoint Mapping
4696    /// | Market Type | Price Type | API Endpoint |
4697    /// |-------------|------------|--------------|
4698    /// | spot | default | `/api/v3/klines` |
4699    /// | linear | default | `/fapi/v1/klines` |
4700    /// | linear | mark | `/fapi/v1/markPriceKlines` |
4701    /// | linear | index | `/fapi/v1/indexPriceKlines` |
4702    /// | linear | premiumIndex | `/fapi/v1/premiumIndexKlines` |
4703    /// | inverse | default | `/dapi/v1/klines` |
4704    /// | inverse | mark | `/dapi/v1/markPriceKlines` |
4705    /// | inverse | index | `/dapi/v1/indexPriceKlines` |
4706    /// | inverse | premiumIndex | `/dapi/v1/premiumIndexKlines` |
4707    /// | option | default | `/eapi/v1/klines` |
4708    fn get_ohlcv_endpoint(
4709        &self,
4710        market: &Market,
4711        price: Option<&str>,
4712    ) -> Result<(String, String, bool)> {
4713        use ccxt_core::types::MarketType;
4714
4715        if let Some(p) = price {
4716            if !["mark", "index", "premiumIndex"].contains(&p) {
4717                return Err(Error::invalid_request(format!(
4718                    "Unsupported price type: {}. Supported types: mark, index, premiumIndex",
4719                    p
4720                )));
4721            }
4722        }
4723
4724        match market.market_type {
4725            MarketType::Spot => {
4726                if let Some(p) = price {
4727                    return Err(Error::invalid_request(format!(
4728                        "Spot market does not support '{}' price type",
4729                        p
4730                    )));
4731                }
4732                Ok((self.urls().public.clone(), "/klines".to_string(), false))
4733            }
4734
4735            MarketType::Swap | MarketType::Futures => {
4736                let is_linear = market.linear.unwrap_or(false);
4737                let is_inverse = market.inverse.unwrap_or(false);
4738
4739                if is_linear {
4740                    let (endpoint, use_pair) = match price {
4741                        None => ("/klines".to_string(), false),
4742                        Some("mark") => ("/markPriceKlines".to_string(), false),
4743                        Some("index") => ("/indexPriceKlines".to_string(), true),
4744                        Some("premiumIndex") => ("/premiumIndexKlines".to_string(), false),
4745                        _ => unreachable!(),
4746                    };
4747                    Ok((self.urls().fapi_public.clone(), endpoint, use_pair))
4748                } else if is_inverse {
4749                    let (endpoint, use_pair) = match price {
4750                        None => ("/klines".to_string(), false),
4751                        Some("mark") => ("/markPriceKlines".to_string(), false),
4752                        Some("index") => ("/indexPriceKlines".to_string(), true),
4753                        Some("premiumIndex") => ("/premiumIndexKlines".to_string(), false),
4754                        _ => unreachable!(),
4755                    };
4756                    Ok((self.urls().dapi_public.clone(), endpoint, use_pair))
4757                } else {
4758                    Err(Error::invalid_request(
4759                        "Cannot determine futures contract type (linear or inverse)",
4760                    ))
4761                }
4762            }
4763
4764            MarketType::Option => {
4765                if let Some(p) = price {
4766                    return Err(Error::invalid_request(format!(
4767                        "Option market does not support '{}' price type",
4768                        p
4769                    )));
4770                }
4771                Ok((
4772                    self.urls().eapi_public.clone(),
4773                    "/klines".to_string(),
4774                    false,
4775                ))
4776            }
4777        }
4778    }
4779
4780    /// Fetch OHLCV (candlestick) data
4781    ///
4782    /// # Arguments
4783    ///
4784    /// * `symbol` - Trading pair symbol, e.g., "BTC/USDT"
4785    /// * `timeframe` - Time period, e.g., "1m", "5m", "1h", "1d"
4786    /// * `since` - Start timestamp in milliseconds
4787    /// * `limit` - Maximum number of candlesticks to return
4788    /// * `params` - Optional parameters
4789    ///   * `price` - Price type: "mark" | "index" | "premiumIndex" (futures only)
4790    ///   * `until` - End timestamp in milliseconds
4791    ///   * `paginate` - Whether to automatically paginate (default: false)
4792    ///
4793    /// # Returns
4794    ///
4795    /// Returns OHLCV data array: [timestamp, open, high, low, close, volume]
4796    ///
4797    /// # Examples
4798    ///
4799    /// ```no_run
4800    /// # use ccxt_exchanges::binance::Binance;
4801    /// # use std::collections::HashMap;
4802    /// # use serde_json::json;
4803    /// # async fn example() -> ccxt_core::Result<()> {
4804    /// let binance = Binance::new(Default::default());
4805    ///
4806    /// // Basic usage
4807    /// let ohlcv = binance.fetch_ohlcv("BTC/USDT", "1h", None, Some(100), None).await?;
4808    ///
4809    /// // Using time range
4810    /// let since = 1609459200000; // 2021-01-01
4811    /// let until = 1612137600000; // 2021-02-01
4812    /// let mut params = HashMap::new();
4813    /// params.insert("until".to_string(), json!(until));
4814    /// let ohlcv = binance.fetch_ohlcv("BTC/USDT", "1h", Some(since), None, Some(params)).await?;
4815    ///
4816    /// // Futures mark price candlesticks
4817    /// let mut params = HashMap::new();
4818    /// params.insert("price".to_string(), json!("mark"));
4819    /// let mark_ohlcv = binance.fetch_ohlcv("BTC/USDT:USDT", "1h", None, Some(100), Some(params)).await?;
4820    /// # Ok(())
4821    /// # }
4822    /// ```
4823    pub async fn fetch_ohlcv(
4824        &self,
4825        symbol: &str,
4826        timeframe: &str,
4827        since: Option<i64>,
4828        limit: Option<u32>,
4829        params: Option<HashMap<String, Value>>,
4830    ) -> Result<Vec<ccxt_core::types::OHLCV>> {
4831        self.load_markets(false).await?;
4832
4833        let price = params
4834            .as_ref()
4835            .and_then(|p| p.get("price"))
4836            .and_then(|v| v.as_str())
4837            .map(|s| s.to_string());
4838
4839        let until = params
4840            .as_ref()
4841            .and_then(|p| p.get("until"))
4842            .and_then(|v| v.as_i64());
4843
4844        let paginate = params
4845            .as_ref()
4846            .and_then(|p| p.get("paginate"))
4847            .and_then(|v| v.as_bool())
4848            .unwrap_or(false);
4849
4850        // TODO: Phase 4 - Implement pagination
4851        if paginate {
4852            return Err(Error::not_implemented(
4853                "Pagination feature not yet implemented, will be added in Phase 4",
4854            ));
4855        }
4856
4857        let market = self.base().market(symbol).await?;
4858
4859        let default_limit = 500u32;
4860        let max_limit = 1500u32;
4861
4862        let adjusted_limit = if since.is_some() && until.is_some() && limit.is_none() {
4863            max_limit
4864        } else if let Some(lim) = limit {
4865            lim.min(max_limit)
4866        } else {
4867            default_limit
4868        };
4869
4870        let (base_url, endpoint, use_pair) = self.get_ohlcv_endpoint(&market, price.as_deref())?;
4871
4872        let symbol_param = if use_pair {
4873            market.symbol.replace('/', "")
4874        } else {
4875            market.id.clone()
4876        };
4877
4878        let mut url = format!(
4879            "{}{}?symbol={}&interval={}&limit={}",
4880            base_url, endpoint, symbol_param, timeframe, adjusted_limit
4881        );
4882
4883        if let Some(start_time) = since {
4884            url.push_str(&format!("&startTime={}", start_time));
4885
4886            // Calculate endTime for inverse markets (ref: Go implementation)
4887            if market.inverse.unwrap_or(false) && start_time > 0 && until.is_none() {
4888                let duration = self.parse_timeframe(timeframe)?;
4889                let calculated_end_time =
4890                    start_time + (adjusted_limit as i64 * duration * 1000) - 1;
4891                // SAFETY: SystemTime::now() is always after UNIX_EPOCH on any modern system.
4892                // This can only fail if the system clock is set before 1970, which is not
4893                // a supported configuration for this library.
4894                let now = std::time::SystemTime::now()
4895                    .duration_since(std::time::UNIX_EPOCH)
4896                    .expect("System clock is set before UNIX_EPOCH (1970); this is not supported")
4897                    .as_millis() as i64;
4898                let end_time = calculated_end_time.min(now);
4899                url.push_str(&format!("&endTime={}", end_time));
4900            }
4901        }
4902
4903        if let Some(end_time) = until {
4904            url.push_str(&format!("&endTime={}", end_time));
4905        }
4906
4907        let data = self.base().http_client.get(&url, None).await?;
4908
4909        parser::parse_ohlcvs(&data)
4910    }
4911
4912    /// Fetch trading fee for a single trading pair
4913    ///
4914    /// # Arguments
4915    /// * `symbol` - Trading pair symbol (e.g., BTC/USDT)
4916    ///
4917    /// # Binance API
4918    /// - Endpoint: GET /sapi/v1/asset/tradeFee
4919    /// - Weight: 1
4920    /// - Requires signature: Yes
4921    ///
4922    /// # Examples
4923    /// ```rust,no_run
4924    /// # use ccxt_exchanges::binance::Binance;
4925    /// # use ccxt_core::ExchangeConfig;
4926    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4927    /// let config = ExchangeConfig {
4928    ///     api_key: Some("your-api-key".to_string()),
4929    ///     secret: Some("your-secret".to_string()),
4930    ///     ..Default::default()
4931    /// };
4932    /// let binance = Binance::new(config)?;
4933    ///
4934    /// // Fetch trading fee for BTC/USDT
4935    /// let fee = binance.fetch_trading_fee("BTC/USDT").await?;
4936    /// println!("Maker fee: {}%, Taker fee: {}%",
4937    ///     fee.maker * 100.0, fee.taker * 100.0);
4938    /// # Ok(())
4939    /// # }
4940    /// ```
4941    // pub async fn fetch_trading_fee(&self, symbol: &str) -> Result<ccxt_core::types::TradingFee> {
4942    //     self.base().load_markets(false).await?;
4943    //
4944    //     let market = self.base().market(symbol).await?;
4945    //     let mut params = std::collections::HashMap::new();
4946    //     params.insert("symbol".to_string(), market.id.clone());
4947    //
4948    //     // Sign parameters
4949    //     let auth = crate::binance::auth::BinanceAuth::new(
4950    //         self.base().config.api_key.as_ref().ok_or_else(|| {
4951    //             ccxt_core::error::Error::authentication("API key required")
4952    //         })?,
4953    //         self.base().config.secret.as_ref().ok_or_else(|| {
4954    //             ccxt_core::error::Error::authentication("API secret required")
4955    //         })?,
4956    //     );
4957    //
4958    //     let signed_params = auth.sign_params(&params)?;
4959    //
4960    //     // Build URL
4961    //     let query_string: Vec<String> = signed_params
4962    //         .iter()
4963    //         .map(|(k, v)| format!("{}={}", k, v))
4964    //         .collect();
4965    //     let url = format!("{}/asset/tradeFee?{}", self.urls().sapi, query_string.join("&"));
4966    //
4967    //     // Add API key to headers
4968    //     let mut headers = reqwest::header::HeaderMap::new();
4969    //     auth.add_auth_headers_reqwest(&mut headers);
4970    //
4971    //     let data = self.base().http_client.get(&url, Some(headers)).await?;
4972    //
4973    //     // Parse response array and take first element
4974    //     let fees = parser::parse_trading_fees(&data)?;
4975    //     fees.into_iter().next().ok_or_else(|| {
4976    //         ccxt_core::error::Error::invalid_request("No trading fee data returned")
4977    //     })
4978    // }
4979
4980    /// Fetch trading fees for multiple trading pairs
4981    ///
4982    /// # Arguments
4983    /// * `symbols` - Trading pair symbols (e.g., vec!["BTC/USDT", "ETH/USDT"]), or None for all pairs
4984    ///
4985    /// # Binance API
4986    /// - Endpoint: GET /sapi/v1/asset/tradeFee
4987    /// - Weight: 1
4988    /// - Signature required: Yes
4989    ///
4990    /// # Examples
4991    /// ```rust,no_run
4992    /// # use ccxt_exchanges::binance::Binance;
4993    /// # use ccxt_core::ExchangeConfig;
4994    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4995    /// let config = ExchangeConfig {
4996    ///     api_key: Some("your-api-key".to_string()),
4997    ///     secret: Some("your-secret".to_string()),
4998    ///     ..Default::default()
4999    /// };
5000    /// let binance = Binance::new(config)?;
5001    ///
5002    /// // Fetch fees for all trading pairs
5003    /// let all_fees = binance.fetch_trading_fees(None).await?;
5004    /// println!("Total symbols with fees: {}", all_fees.len());
5005    ///
5006    /// // Fetch fees for specific trading pairs
5007    /// let fees = binance.fetch_trading_fees(Some(vec!["BTC/USDT", "ETH/USDT"])).await?;
5008    /// for fee in &fees {
5009    ///     println!("{}: Maker {}%, Taker {}%",
5010    ///         fee.symbol, fee.maker * 100.0, fee.taker * 100.0);
5011    /// }
5012    /// # Ok(())
5013    /// # }
5014    /// ```
5015    /// Fetch server time
5016    ///
5017    /// # Binance API
5018    /// - Endpoint: GET /api/v3/time
5019    /// - Weight: 1
5020    /// - Data source: Memory
5021    ///
5022    /// # Examples
5023    /// ```rust,no_run
5024    /// # use ccxt_exchanges::binance::Binance;
5025    /// # use ccxt_core::ExchangeConfig;
5026    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5027    /// let config = ExchangeConfig::default();
5028    /// let binance = Binance::new(config)?;
5029    ///
5030    /// // Fetch Binance server time
5031    /// let server_time = binance.fetch_time().await?;
5032    /// println!("Server time: {} ({})", server_time.server_time, server_time.datetime);
5033    ///
5034    /// // Check local time offset
5035    /// let local_time = chrono::Utc::now().timestamp_millis();
5036    /// let offset = server_time.server_time - local_time;
5037    /// println!("Time offset: {} ms", offset);
5038    /// # Ok(())
5039    /// # }
5040    /// ```
5041    pub async fn fetch_time(&self) -> Result<ccxt_core::types::ServerTime> {
5042        let url = format!("{}/time", self.urls().public);
5043
5044        let data = self.base().http_client.get(&url, None).await?;
5045
5046        parser::parse_server_time(&data)
5047    }
5048
5049    /// Fetch canceled orders
5050    ///
5051    /// # Arguments
5052    ///
5053    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
5054    /// * `since` - Optional start timestamp in milliseconds
5055    /// * `limit` - Optional record limit (default 500, max 1000)
5056    ///
5057    /// # Returns
5058    ///
5059    /// Returns vector of canceled orders
5060    ///
5061    /// # Example
5062    ///
5063    /// ```no_run
5064    /// # use ccxt_exchanges::binance::Binance;
5065    /// # use ccxt_core::ExchangeConfig;
5066    /// # async fn example() -> ccxt_core::Result<()> {
5067    /// let binance = Binance::new(ExchangeConfig::default())?;
5068    /// let orders = binance.fetch_canceled_orders("BTC/USDT", None, Some(10)).await?;
5069    /// println!("Found {} canceled orders", orders.len());
5070    /// # Ok(())
5071    /// # }
5072    /// ```
5073    /// Fetch canceled and closed orders
5074    ///
5075    /// # Arguments
5076    ///
5077    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
5078    /// * `since` - Optional start timestamp in milliseconds
5079    /// * `limit` - Optional record limit (default 500, max 1000)
5080    ///
5081    /// # Returns
5082    ///
5083    /// Returns vector of canceled and closed orders
5084    ///
5085    /// # Example
5086    ///
5087    /// ```no_run
5088    /// # use ccxt_exchanges::binance::Binance;
5089    /// # use ccxt_core::ExchangeConfig;
5090    /// # async fn example() -> ccxt_core::Result<()> {
5091    /// let binance = Binance::new(ExchangeConfig::default())?;
5092    /// let orders = binance.fetch_canceled_and_closed_orders("BTC/USDT", None, Some(20)).await?;
5093    /// println!("Found {} closed/canceled orders", orders.len());
5094    /// # Ok(())
5095    /// # }
5096    /// ```
5097    pub async fn fetch_canceled_and_closed_orders(
5098        &self,
5099        symbol: &str,
5100        since: Option<u64>,
5101        limit: Option<u64>,
5102    ) -> Result<Vec<Order>> {
5103        self.check_required_credentials()?;
5104
5105        let market = self.base().market(symbol).await?;
5106        let market_id = &market.id;
5107
5108        let mut params = HashMap::new();
5109        params.insert("symbol".to_string(), market_id.to_string());
5110
5111        if let Some(ts) = since {
5112            params.insert("startTime".to_string(), ts.to_string());
5113        }
5114
5115        if let Some(l) = limit {
5116            params.insert("limit".to_string(), l.to_string());
5117        }
5118
5119        let timestamp = self.fetch_time_raw().await?;
5120        let auth = self.get_auth()?;
5121        let signed_params =
5122            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
5123
5124        let mut url = format!("{}/api/v3/allOrders?", self.urls().private);
5125        for (key, value) in &signed_params {
5126            url.push_str(&format!("{}={}&", key, value));
5127        }
5128
5129        let mut headers = HeaderMap::new();
5130        auth.add_auth_headers_reqwest(&mut headers);
5131
5132        let data = self.base().http_client.get(&url, Some(headers)).await?;
5133
5134        let all_orders = if let Some(array) = data.as_array() {
5135            array
5136                .iter()
5137                .filter_map(|item| parser::parse_order(item, None).ok())
5138                .collect::<Vec<_>>()
5139        } else {
5140            vec![]
5141        };
5142
5143        Ok(all_orders
5144            .into_iter()
5145            .filter(|order| {
5146                order.status == OrderStatus::Canceled || order.status == OrderStatus::Closed
5147            })
5148            .collect())
5149    }
5150
5151    /// Fetch a single open order
5152    ///
5153    /// # Arguments
5154    ///
5155    /// * `id` - Order ID
5156    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
5157    ///
5158    /// # Returns
5159    ///
5160    /// Returns order information
5161    ///
5162    /// # Example
5163    ///
5164    /// ```no_run
5165    /// # use ccxt_exchanges::binance::Binance;
5166    /// # use ccxt_core::ExchangeConfig;
5167    /// # async fn example() -> ccxt_core::Result<()> {
5168    /// let binance = Binance::new(ExchangeConfig::default())?;
5169    /// let order = binance.fetch_open_order("12345", "BTC/USDT").await?;
5170    /// println!("Order status: {:?}", order.status);
5171    /// # Ok(())
5172    /// # }
5173    /// ```
5174    pub async fn fetch_open_order(&self, id: &str, symbol: &str) -> Result<Order> {
5175        self.check_required_credentials()?;
5176
5177        let market = self.base().market(symbol).await?;
5178        let market_id = &market.id;
5179
5180        let mut params = HashMap::new();
5181        params.insert("symbol".to_string(), market_id.to_string());
5182        params.insert("orderId".to_string(), id.to_string());
5183
5184        let timestamp = self.fetch_time_raw().await?;
5185        let auth = self.get_auth()?;
5186        let signed_params =
5187            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
5188
5189        let mut url = format!("{}/api/v3/order?", self.urls().private);
5190        for (key, value) in &signed_params {
5191            url.push_str(&format!("{}={}&", key, value));
5192        }
5193
5194        let mut headers = HeaderMap::new();
5195        auth.add_auth_headers_reqwest(&mut headers);
5196
5197        let data = self.base().http_client.get(&url, Some(headers)).await?;
5198
5199        parser::parse_order(&data, None)
5200    }
5201
5202    /// Fetch trades for a specific order
5203    ///
5204    /// # Arguments
5205    ///
5206    /// * `id` - Order ID
5207    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
5208    /// * `since` - Optional start timestamp in milliseconds
5209    /// * `limit` - Optional record limit (default 500, max 1000)
5210    ///
5211    /// # Returns
5212    ///
5213    /// Returns vector of trade records
5214    ///
5215    /// # Example
5216    ///
5217    /// ```no_run
5218    /// # use ccxt_exchanges::binance::Binance;
5219    /// # use ccxt_core::ExchangeConfig;
5220    /// # async fn example() -> ccxt_core::Result<()> {
5221    /// let binance = Binance::new(ExchangeConfig::default())?;
5222    /// let trades = binance.fetch_order_trades("12345", "BTC/USDT", None, Some(50)).await?;
5223    /// println!("Order has {} trades", trades.len());
5224    /// # Ok(())
5225    /// # }
5226    /// ```
5227    /// Edit order (cancel and replace)
5228    ///
5229    /// # Arguments
5230    ///
5231    /// * `id` - Original order ID
5232    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
5233    /// * `order_type` - Order type
5234    /// * `side` - Order side (Buy/Sell)
5235    /// * `amount` - Order quantity
5236    /// * `price` - Optional order price (required for limit orders)
5237    /// * `params` - Optional additional parameters
5238    ///
5239    /// # Returns
5240    ///
5241    /// Returns new order information
5242    ///
5243    /// # Example
5244    ///
5245    /// ```no_run
5246    /// # use ccxt_exchanges::binance::Binance;
5247    /// # use ccxt_core::{ExchangeConfig, types::{OrderType, OrderSide}};
5248    /// # use rust_decimal_macros::dec;
5249    /// # async fn example() -> ccxt_core::Result<()> {
5250    /// let binance = Binance::new(ExchangeConfig::default())?;
5251    /// let order = binance.edit_order(
5252    ///     "12345",
5253    ///     "BTC/USDT",
5254    ///     OrderType::Limit,
5255    ///     OrderSide::Buy,
5256    ///     dec!(0.01),
5257    ///     Some(dec!(50000.0)),
5258    ///     None
5259    /// ).await?;
5260    /// println!("New order ID: {}", order.id);
5261    /// # Ok(())
5262    /// # }
5263    /// ```
5264    pub async fn edit_order(
5265        &self,
5266        id: &str,
5267        symbol: &str,
5268        order_type: OrderType,
5269        side: OrderSide,
5270        amount: rust_decimal::Decimal,
5271        price: Option<rust_decimal::Decimal>,
5272        params: Option<HashMap<String, String>>,
5273    ) -> Result<Order> {
5274        self.check_required_credentials()?;
5275
5276        let market = self.base().market(symbol).await?;
5277        let market_id = &market.id;
5278
5279        let mut request_params = params.unwrap_or_default();
5280
5281        request_params.insert("symbol".to_string(), market_id.to_string());
5282        request_params.insert("cancelOrderId".to_string(), id.to_string());
5283        request_params.insert("side".to_string(), side.to_string().to_uppercase());
5284        request_params.insert("type".to_string(), order_type.to_string().to_uppercase());
5285        request_params.insert("quantity".to_string(), amount.to_string());
5286
5287        if let Some(p) = price {
5288            request_params.insert("price".to_string(), p.to_string());
5289        }
5290
5291        if !request_params.contains_key("cancelReplaceMode") {
5292            request_params.insert(
5293                "cancelReplaceMode".to_string(),
5294                "STOP_ON_FAILURE".to_string(),
5295            );
5296        }
5297
5298        let timestamp = self.fetch_time_raw().await?;
5299        let auth = self.get_auth()?;
5300        let signed_params =
5301            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
5302
5303        let mut url = format!("{}/api/v3/order/cancelReplace?", self.urls().private);
5304        for (key, value) in &signed_params {
5305            url.push_str(&format!("{}={}&", key, value));
5306        }
5307
5308        let mut headers = HeaderMap::new();
5309        auth.add_auth_headers_reqwest(&mut headers);
5310
5311        let data = self
5312            .base()
5313            .http_client
5314            .post(&url, Some(headers), None)
5315            .await?;
5316
5317        parser::parse_edit_order_result(&data, None)
5318    }
5319
5320    /// Edit multiple orders in batch
5321    ///
5322    /// # Arguments
5323    ///
5324    /// * `orders` - Vector of order edit parameters, each element contains:
5325    ///   - id: Original order ID
5326    ///   - symbol: Trading pair symbol
5327    ///   - order_type: Order type
5328    ///   - side: Order side
5329    ///   - amount: Order quantity
5330    ///   - price: Optional order price
5331    ///
5332    /// # Returns
5333    ///
5334    /// Returns vector of new order information
5335    ///
5336    /// # Note
5337    ///
5338    /// Binance does not support native batch order editing API. This method implements batch editing by concurrent calls to single order edits.
5339    ///
5340    /// # Example
5341    ///
5342    /// ```no_run
5343    /// # use ccxt_exchanges::binance::Binance;
5344    /// # use ccxt_core::{ExchangeConfig, types::{OrderType, OrderSide}};
5345    /// # use rust_decimal_macros::dec;
5346    /// # async fn example() -> ccxt_core::Result<()> {
5347    /// let binance = Binance::new(ExchangeConfig::default())?;
5348    /// let order_params = vec![
5349    ///     ("12345", "BTC/USDT", OrderType::Limit, OrderSide::Buy, dec!(0.01), Some(dec!(50000.0))),
5350    ///     ("12346", "ETH/USDT", OrderType::Limit, OrderSide::Sell, dec!(0.1), Some(dec!(3000.0))),
5351    /// ];
5352    /// let orders = binance.edit_orders(order_params).await?;
5353    /// println!("Edited {} orders", orders.len());
5354    /// # Ok(())
5355    /// # }
5356    /// ```
5357    pub async fn edit_orders(
5358        &self,
5359        orders: Vec<(
5360            &str,                          // id
5361            &str,                          // symbol
5362            OrderType,                     // order_type
5363            OrderSide,                     // side
5364            rust_decimal::Decimal,         // amount
5365            Option<rust_decimal::Decimal>, // price
5366        )>,
5367    ) -> Result<Vec<Order>> {
5368        let futures: Vec<_> = orders
5369            .into_iter()
5370            .map(|(id, symbol, order_type, side, amount, price)| {
5371                self.edit_order(id, symbol, order_type, side, amount, price, None)
5372            })
5373            .collect();
5374
5375        let results = futures::future::join_all(futures).await;
5376
5377        results.into_iter().collect()
5378    }
5379
5380    // ==================== WebSocket User Data Stream (Listen Key Management) ====================
5381
5382    /// Create listen key for user data stream
5383    ///
5384    /// Creates a listen key with 60-minute validity for subscribing to WebSocket user data stream.
5385    /// The listen key must be refreshed periodically to keep the connection active.
5386    ///
5387    /// # Returns
5388    ///
5389    /// Returns the listen key string
5390    ///
5391    /// # Errors
5392    ///
5393    /// - If API credentials are not configured
5394    /// - If API request fails
5395    ///
5396    /// # Example
5397    ///
5398    /// ```no_run
5399    /// # use ccxt_exchanges::binance::Binance;
5400    /// # use ccxt_core::ExchangeConfig;
5401    /// # async fn example() -> ccxt_core::Result<()> {
5402    /// let mut config = ExchangeConfig::default();
5403    /// config.api_key = Some("your_api_key".to_string());
5404    /// config.secret = Some("your_secret".to_string());
5405    /// let binance = Binance::new(config)?;
5406    ///
5407    /// let listen_key = binance.create_listen_key().await?;
5408    /// println!("Listen Key: {}", listen_key);
5409    /// # Ok(())
5410    /// # }
5411    /// ```
5412    pub async fn create_listen_key(&self) -> Result<String> {
5413        self.check_required_credentials()?;
5414
5415        let url = format!("{}/userDataStream", self.urls().public);
5416        let mut headers = HeaderMap::new();
5417
5418        let auth = self.get_auth()?;
5419        auth.add_auth_headers_reqwest(&mut headers);
5420
5421        let response = self
5422            .base()
5423            .http_client
5424            .post(&url, Some(headers), None)
5425            .await?;
5426
5427        response["listenKey"]
5428            .as_str()
5429            .map(|s| s.to_string())
5430            .ok_or_else(|| Error::from(ParseError::missing_field("listenKey")))
5431    }
5432
5433    /// Refresh listen key to extend validity
5434    ///
5435    /// Extends the listen key validity by 60 minutes. Recommended to call every 30 minutes to maintain the connection.
5436    ///
5437    /// # Arguments
5438    ///
5439    /// * `listen_key` - The listen key to refresh
5440    ///
5441    /// # Returns
5442    ///
5443    /// Returns `Ok(())` on success
5444    ///
5445    /// # Errors
5446    ///
5447    /// - If API credentials are not configured
5448    /// - If listen key is invalid or expired
5449    /// - If API request fails
5450    ///
5451    /// # Example
5452    ///
5453    /// ```no_run
5454    /// # use ccxt_exchanges::binance::Binance;
5455    /// # use ccxt_core::ExchangeConfig;
5456    /// # async fn example() -> ccxt_core::Result<()> {
5457    /// # let mut config = ExchangeConfig::default();
5458    /// # config.api_key = Some("your_api_key".to_string());
5459    /// # config.secret = Some("your_secret".to_string());
5460    /// # let binance = Binance::new(config)?;
5461    /// let listen_key = binance.create_listen_key().await?;
5462    ///
5463    /// // Refresh after 30 minutes
5464    /// binance.refresh_listen_key(&listen_key).await?;
5465    /// # Ok(())
5466    /// # }
5467    /// ```
5468    pub async fn refresh_listen_key(&self, listen_key: &str) -> Result<()> {
5469        self.check_required_credentials()?;
5470
5471        let url = format!(
5472            "{}/userDataStream?listenKey={}",
5473            self.urls().public,
5474            listen_key
5475        );
5476        let mut headers = HeaderMap::new();
5477
5478        let auth = self.get_auth()?;
5479        auth.add_auth_headers_reqwest(&mut headers);
5480
5481        let _response = self
5482            .base()
5483            .http_client
5484            .put(&url, Some(headers), None)
5485            .await?;
5486
5487        Ok(())
5488    }
5489
5490    /// Delete listen key to close user data stream
5491    ///
5492    /// Closes the user data stream connection and invalidates the listen key.
5493    ///
5494    /// # Arguments
5495    ///
5496    /// * `listen_key` - The listen key to delete
5497    ///
5498    /// # Returns
5499    ///
5500    /// Returns `Ok(())` on success
5501    ///
5502    /// # Errors
5503    ///
5504    /// - If API credentials are not configured
5505    /// - If API request fails
5506    ///
5507    /// # Example
5508    ///
5509    /// ```no_run
5510    /// # use ccxt_exchanges::binance::Binance;
5511    /// # use ccxt_core::ExchangeConfig;
5512    /// # async fn example() -> ccxt_core::Result<()> {
5513    /// # let mut config = ExchangeConfig::default();
5514    /// # config.api_key = Some("your_api_key".to_string());
5515    /// # config.secret = Some("your_secret".to_string());
5516    /// # let binance = Binance::new(config)?;
5517    /// let listen_key = binance.create_listen_key().await?;
5518    ///
5519    /// // Delete after use
5520    /// binance.delete_listen_key(&listen_key).await?;
5521    /// # Ok(())
5522    /// # }
5523    /// ```
5524    pub async fn delete_listen_key(&self, listen_key: &str) -> Result<()> {
5525        self.check_required_credentials()?;
5526
5527        let url = format!(
5528            "{}/userDataStream?listenKey={}",
5529            self.urls().public,
5530            listen_key
5531        );
5532        let mut headers = HeaderMap::new();
5533
5534        let auth = self.get_auth()?;
5535        auth.add_auth_headers_reqwest(&mut headers);
5536
5537        let _response = self
5538            .base()
5539            .http_client
5540            .delete(&url, Some(headers), None)
5541            .await?;
5542
5543        Ok(())
5544    }
5545    /// Create orders in batch (up to 5 orders)
5546    ///
5547    /// Creates multiple orders in a single API request to improve order placement efficiency.
5548    ///
5549    /// # Arguments
5550    /// - `orders`: List of order requests (maximum 5)
5551    /// - `params`: Optional parameters
5552    ///
5553    /// # Returns
5554    /// Returns list of batch order results
5555    ///
5556    /// # Examples
5557    /// ```rust,no_run
5558    /// # use ccxt_exchanges::binance::Binance;
5559    /// # use ccxt_core::ExchangeConfig;
5560    /// # use ccxt_core::types::order::BatchOrderRequest;
5561    /// # #[tokio::main]
5562    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
5563    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
5564    ///
5565    /// // Batch create orders
5566    /// let orders = vec![
5567    ///     BatchOrderRequest {
5568    ///         symbol: "BTCUSDT".to_string(),
5569    ///         side: "BUY".to_string(),
5570    ///         order_type: "LIMIT".to_string(),
5571    ///         quantity: "0.001".to_string(),
5572    ///         price: Some("40000".to_string()),
5573    ///         time_in_force: Some("GTC".to_string()),
5574    ///         reduce_only: None,
5575    ///         position_side: Some("LONG".to_string()),
5576    ///         new_client_order_id: None,
5577    ///     },
5578    ///     BatchOrderRequest {
5579    ///         symbol: "ETHUSDT".to_string(),
5580    ///         side: "SELL".to_string(),
5581    ///         order_type: "LIMIT".to_string(),
5582    ///         quantity: "0.01".to_string(),
5583    ///         price: Some("3000".to_string()),
5584    ///         time_in_force: Some("GTC".to_string()),
5585    ///         reduce_only: None,
5586    ///         position_side: Some("SHORT".to_string()),
5587    ///         new_client_order_id: None,
5588    ///     },
5589    /// ];
5590    /// let results = binance.create_orders(orders, None).await?;
5591    /// # Ok(())
5592    /// # }
5593    /// ```
5594    ///
5595    /// # API Endpoint
5596    /// - Endpoint: POST /fapi/v1/batchOrders
5597    /// - Weight: 5
5598    /// - Requires signature: Yes
5599    pub async fn create_orders(
5600        &self,
5601        orders: Vec<BatchOrderRequest>,
5602        params: Option<Value>,
5603    ) -> Result<Vec<BatchOrderResult>> {
5604        self.check_required_credentials()?;
5605
5606        if orders.is_empty() {
5607            return Err(Error::invalid_request("Orders list cannot be empty"));
5608        }
5609
5610        if orders.len() > 5 {
5611            return Err(Error::invalid_request(
5612                "Cannot create more than 5 orders at once",
5613            ));
5614        }
5615
5616        let batch_orders_json = serde_json::to_string(&orders).map_err(|e| {
5617            Error::from(ParseError::invalid_format(
5618                "data",
5619                format!("Failed to serialize orders: {}", e),
5620            ))
5621        })?;
5622
5623        let mut request_params = HashMap::new();
5624        request_params.insert("batchOrders".to_string(), batch_orders_json);
5625
5626        if let Some(params) = params {
5627            if let Some(obj) = params.as_object() {
5628                for (key, value) in obj {
5629                    if let Some(v) = value.as_str() {
5630                        request_params.insert(key.clone(), v.to_string());
5631                    }
5632                }
5633            }
5634        }
5635
5636        let timestamp = self.fetch_time_raw().await?;
5637        let auth = self.get_auth()?;
5638        let signed_params =
5639            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
5640
5641        let url = format!("{}/batchOrders", self.urls().fapi_private);
5642
5643        let mut headers = HeaderMap::new();
5644        auth.add_auth_headers_reqwest(&mut headers);
5645
5646        let body = serde_json::to_value(&signed_params).map_err(|e| {
5647            Error::from(ParseError::invalid_format(
5648                "data",
5649                format!("Failed to serialize params: {}", e),
5650            ))
5651        })?;
5652
5653        let data = self
5654            .base()
5655            .http_client
5656            .post(&url, Some(headers), Some(body))
5657            .await?;
5658
5659        let results: Vec<BatchOrderResult> = serde_json::from_value(data).map_err(|e| {
5660            Error::from(ParseError::invalid_format(
5661                "data",
5662                format!("Failed to parse batch order results: {}", e),
5663            ))
5664        })?;
5665
5666        Ok(results)
5667    }
5668
5669    /// Modify multiple orders in batch
5670    ///
5671    /// Modifies the price and quantity of multiple orders in a single API request.
5672    ///
5673    /// # Arguments
5674    /// - `updates`: List of order update requests (maximum 5)
5675    /// - `params`: Optional parameters
5676    ///
5677    /// # Returns
5678    /// Returns list of batch order results
5679    ///
5680    /// # Examples
5681    /// ```rust,no_run
5682    /// # use ccxt_exchanges::binance::Binance;
5683    /// # use ccxt_core::ExchangeConfig;
5684    /// # use ccxt_core::types::order::BatchOrderUpdate;
5685    /// # #[tokio::main]
5686    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
5687    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
5688    ///
5689    /// // Batch modify orders
5690    /// let updates = vec![
5691    ///     BatchOrderUpdate {
5692    ///         order_id: Some(12345),
5693    ///         orig_client_order_id: None,
5694    ///         symbol: "BTCUSDT".to_string(),
5695    ///         side: "BUY".to_string(),
5696    ///         quantity: "0.002".to_string(),
5697    ///         price: "41000".to_string(),
5698    ///     },
5699    /// ];
5700    /// let results = binance.edit_orders(updates, None).await?;
5701    /// # Ok(())
5702    /// # }
5703    /// ```
5704    ///
5705    /// # API Endpoint
5706    /// - Endpoint: PUT /fapi/v1/batchOrders
5707    /// - Weight: 5
5708    /// - Requires signature: Yes
5709    pub async fn batch_edit_orders(
5710        &self,
5711        updates: Vec<BatchOrderUpdate>,
5712        params: Option<Value>,
5713    ) -> Result<Vec<BatchOrderResult>> {
5714        self.check_required_credentials()?;
5715
5716        if updates.is_empty() {
5717            return Err(Error::invalid_request("Updates list cannot be empty"));
5718        }
5719
5720        if updates.len() > 5 {
5721            return Err(Error::invalid_request(
5722                "Cannot update more than 5 orders at once",
5723            ));
5724        }
5725
5726        let batch_orders_json = serde_json::to_string(&updates).map_err(|e| {
5727            Error::from(ParseError::invalid_format(
5728                "data",
5729                format!("Failed to serialize updates: {}", e),
5730            ))
5731        })?;
5732
5733        let mut request_params = HashMap::new();
5734        request_params.insert("batchOrders".to_string(), batch_orders_json);
5735
5736        if let Some(params) = params {
5737            if let Some(obj) = params.as_object() {
5738                for (key, value) in obj {
5739                    if let Some(v) = value.as_str() {
5740                        request_params.insert(key.clone(), v.to_string());
5741                    }
5742                }
5743            }
5744        }
5745
5746        let timestamp = self.fetch_time_raw().await?;
5747        let auth = self.get_auth()?;
5748        let signed_params =
5749            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
5750
5751        let url = format!("{}/batchOrders", self.urls().fapi_private);
5752
5753        let mut headers = HeaderMap::new();
5754        auth.add_auth_headers_reqwest(&mut headers);
5755
5756        let body = serde_json::to_value(&signed_params).map_err(|e| {
5757            Error::from(ParseError::invalid_format(
5758                "data",
5759                format!("Failed to serialize params: {}", e),
5760            ))
5761        })?;
5762
5763        let data = self
5764            .base()
5765            .http_client
5766            .put(&url, Some(headers), Some(body))
5767            .await?;
5768
5769        let results: Vec<BatchOrderResult> = serde_json::from_value(data).map_err(|e| {
5770            Error::from(ParseError::invalid_format(
5771                "data",
5772                format!("Failed to parse batch order results: {}", e),
5773            ))
5774        })?;
5775
5776        Ok(results)
5777    }
5778
5779    /// Cancel orders in batch by order IDs
5780    ///
5781    /// Cancels multiple orders in a single API request.
5782    ///
5783    /// # Arguments
5784    /// - `symbol`: Trading pair symbol
5785    /// - `order_ids`: List of order IDs (maximum 10)
5786    /// - `params`: Optional parameters
5787    ///   - `origClientOrderIdList`: List of original client order IDs (alternative to order_ids)
5788    ///
5789    /// # Returns
5790    /// Returns list of batch cancel results
5791    ///
5792    /// # Examples
5793    /// ```rust,no_run
5794    /// # use ccxt_exchanges::binance::Binance;
5795    /// # use ccxt_core::ExchangeConfig;
5796    /// # #[tokio::main]
5797    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
5798    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
5799    ///
5800    /// // Batch cancel orders
5801    /// let order_ids = vec![12345, 12346, 12347];
5802    /// let results = binance.cancel_orders("BTC/USDT", order_ids, None).await?;
5803    /// # Ok(())
5804    /// # }
5805    /// ```
5806    ///
5807    /// # API Endpoint
5808    /// - Endpoint: DELETE /fapi/v1/batchOrders
5809    /// - Weight: 1
5810    /// - Requires signature: Yes
5811    pub async fn batch_cancel_orders(
5812        &self,
5813        symbol: &str,
5814        order_ids: Vec<i64>,
5815        params: Option<Value>,
5816    ) -> Result<Vec<BatchCancelResult>> {
5817        self.check_required_credentials()?;
5818
5819        if order_ids.is_empty() {
5820            return Err(Error::invalid_request("Order IDs list cannot be empty"));
5821        }
5822
5823        if order_ids.len() > 10 {
5824            return Err(Error::invalid_request(
5825                "Cannot cancel more than 10 orders at once",
5826            ));
5827        }
5828
5829        let market = self.base().market(symbol).await?;
5830
5831        let mut request_params = HashMap::new();
5832        request_params.insert("symbol".to_string(), market.id.clone());
5833
5834        let order_id_list_json = serde_json::to_string(&order_ids).map_err(|e| {
5835            Error::from(ParseError::invalid_format(
5836                "data",
5837                format!("Failed to serialize order IDs: {}", e),
5838            ))
5839        })?;
5840        request_params.insert("orderIdList".to_string(), order_id_list_json);
5841
5842        if let Some(params) = params {
5843            if let Some(obj) = params.as_object() {
5844                for (key, value) in obj {
5845                    if key == "origClientOrderIdList" {
5846                        if let Some(v) = value.as_str() {
5847                            request_params.insert(key.clone(), v.to_string());
5848                        }
5849                    }
5850                }
5851            }
5852        }
5853
5854        let timestamp = self.fetch_time_raw().await?;
5855        let auth = self.get_auth()?;
5856        let signed_params =
5857            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
5858
5859        let url = if market.linear.unwrap_or(true) {
5860            format!("{}/batchOrders", self.urls().fapi_private)
5861        } else {
5862            format!("{}/batchOrders", self.urls().dapi_private)
5863        };
5864
5865        let mut headers = HeaderMap::new();
5866        auth.add_auth_headers_reqwest(&mut headers);
5867
5868        let body = serde_json::to_value(&signed_params).map_err(|e| {
5869            Error::from(ParseError::invalid_format(
5870                "data",
5871                format!("Failed to serialize params: {}", e),
5872            ))
5873        })?;
5874
5875        let data = self
5876            .base()
5877            .http_client
5878            .delete(&url, Some(headers), Some(body))
5879            .await?;
5880
5881        let results: Vec<BatchCancelResult> = serde_json::from_value(data).map_err(|e| {
5882            Error::from(ParseError::invalid_format(
5883                "data",
5884                format!("Failed to parse batch cancel results: {}", e),
5885            ))
5886        })?;
5887
5888        Ok(results)
5889    }
5890
5891    /// Cancel all open orders for a trading pair
5892    ///
5893    /// Cancels all open orders for the specified trading pair.
5894    ///
5895    /// # Arguments
5896    /// - `symbol`: Trading pair symbol
5897    /// - `params`: Optional parameters
5898    ///
5899    /// # Returns
5900    /// Returns result of canceling all orders
5901    ///
5902    /// # Examples
5903    /// ```rust,no_run
5904    /// # use ccxt_exchanges::binance::Binance;
5905    /// # use ccxt_core::ExchangeConfig;
5906    /// # #[tokio::main]
5907    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
5908    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
5909    ///
5910    /// // Cancel all open orders for BTC/USDT
5911    /// let result = binance.cancel_all_orders("BTC/USDT", None).await?;
5912    /// # Ok(())
5913    /// # }
5914    /// ```
5915    ///
5916    /// # API Endpoint
5917    /// - Endpoint: DELETE /fapi/v1/allOpenOrders
5918    /// - Weight: 1
5919    /// - Requires signature: Yes
5920    pub async fn cancel_all_orders_futures(
5921        &self,
5922        symbol: &str,
5923        params: Option<Value>,
5924    ) -> Result<CancelAllOrdersResult> {
5925        self.check_required_credentials()?;
5926
5927        let market = self.base().market(symbol).await?;
5928
5929        let mut request_params = HashMap::new();
5930        request_params.insert("symbol".to_string(), market.id.clone());
5931
5932        if let Some(params) = params {
5933            if let Some(obj) = params.as_object() {
5934                for (key, value) in obj {
5935                    if let Some(v) = value.as_str() {
5936                        request_params.insert(key.clone(), v.to_string());
5937                    }
5938                }
5939            }
5940        }
5941
5942        let timestamp = self.fetch_time_raw().await?;
5943        let auth = self.get_auth()?;
5944        let signed_params =
5945            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
5946
5947        let url = if market.linear.unwrap_or(true) {
5948            format!("{}/allOpenOrders", self.urls().fapi_private)
5949        } else {
5950            format!("{}/allOpenOrders", self.urls().dapi_private)
5951        };
5952
5953        let mut headers = HeaderMap::new();
5954        auth.add_auth_headers_reqwest(&mut headers);
5955
5956        let body = serde_json::to_value(&signed_params).map_err(|e| {
5957            Error::from(ParseError::invalid_format(
5958                "data",
5959                format!("Failed to serialize params: {}", e),
5960            ))
5961        })?;
5962
5963        let data = self
5964            .base()
5965            .http_client
5966            .delete(&url, Some(headers), Some(body))
5967            .await?;
5968
5969        let result: CancelAllOrdersResult = serde_json::from_value(data).map_err(|e| {
5970            Error::from(ParseError::invalid_format(
5971                "data",
5972                format!("Failed to parse cancel all orders result: {}", e),
5973            ))
5974        })?;
5975
5976        Ok(result)
5977    }
5978    // ============================================================================
5979    // Account Configuration Management
5980    // ============================================================================
5981
5982    /// Fetch account configuration information
5983    ///
5984    /// Retrieves futures account configuration parameters including multi-asset mode status and fee tier.
5985    ///
5986    /// # Returns
5987    ///
5988    /// Returns account configuration information
5989    ///
5990    /// # Examples
5991    ///
5992    /// ```no_run
5993    /// # use ccxt_exchanges::binance::Binance;
5994    /// # use ccxt_core::ExchangeConfig;
5995    /// # async fn example() -> ccxt_core::Result<()> {
5996    /// let mut config = ExchangeConfig::default();
5997    /// config.api_key = Some("your_api_key".to_string());
5998    /// config.secret = Some("your_secret".to_string());
5999    /// let binance = Binance::new_futures(config)?;
6000    /// let account_config = binance.fetch_account_configuration().await?;
6001    /// println!("Multi-asset mode: {}", account_config.multi_assets_margin);
6002    /// println!("Fee tier: {}", account_config.fee_tier);
6003    /// # Ok(())
6004    /// # }
6005    /// ```
6006    pub async fn fetch_account_configuration(&self) -> Result<AccountConfig> {
6007        self.check_required_credentials()?;
6008
6009        let request_params = HashMap::new();
6010
6011        let timestamp = self.fetch_time_raw().await?;
6012        let auth = self.get_auth()?;
6013        let signed_params =
6014            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6015
6016        let mut request_url = format!("{}/account?", self.urls().fapi_private);
6017        for (key, value) in &signed_params {
6018            request_url.push_str(&format!("{}={}&", key, value));
6019        }
6020
6021        let mut headers = HeaderMap::new();
6022        auth.add_auth_headers_reqwest(&mut headers);
6023
6024        let data = self
6025            .base()
6026            .http_client
6027            .get(&request_url, Some(headers))
6028            .await?;
6029
6030        parser::parse_account_config(&data)
6031    }
6032
6033    /// Set multi-asset mode
6034    ///
6035    /// Enable or disable multi-asset margin mode.
6036    ///
6037    /// # Arguments
6038    ///
6039    /// * `multi_assets` - `true` to enable multi-asset mode, `false` for single-asset mode
6040    ///
6041    /// # Returns
6042    ///
6043    /// Returns operation result
6044    ///
6045    /// # Examples
6046    ///
6047    /// ```no_run
6048    /// # use ccxt_exchanges::binance::Binance;
6049    /// # use ccxt_core::ExchangeConfig;
6050    /// # async fn example() -> ccxt_core::Result<()> {
6051    /// let mut config = ExchangeConfig::default();
6052    /// config.api_key = Some("your_api_key".to_string());
6053    /// config.secret = Some("your_secret".to_string());
6054    /// let binance = Binance::new_futures(config)?;
6055    /// binance.set_multi_assets_mode(true).await?;
6056    /// # Ok(())
6057    /// # }
6058    /// ```
6059    pub async fn set_multi_assets_mode(&self, multi_assets: bool) -> Result<()> {
6060        self.check_required_credentials()?;
6061
6062        let mut request_params = HashMap::new();
6063        request_params.insert("multiAssetsMargin".to_string(), multi_assets.to_string());
6064
6065        let timestamp = self.fetch_time_raw().await?;
6066        let auth = self.get_auth()?;
6067        let signed_params =
6068            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6069
6070        let url = format!("{}/multiAssetsMargin", self.urls().fapi_private);
6071
6072        let mut headers = HeaderMap::new();
6073        auth.add_auth_headers_reqwest(&mut headers);
6074
6075        let body = serde_json::to_value(&signed_params).map_err(|e| {
6076            Error::from(ParseError::invalid_format(
6077                "data",
6078                format!("Failed to serialize params: {}", e),
6079            ))
6080        })?;
6081
6082        let _data = self
6083            .base()
6084            .http_client
6085            .post(&url, Some(headers), Some(body))
6086            .await?;
6087
6088        Ok(())
6089    }
6090
6091    /// Fetch futures commission rate
6092    ///
6093    /// Retrieves maker and taker commission rates for the specified trading pair.
6094    ///
6095    /// # Arguments
6096    ///
6097    /// * `symbol` - Trading pair symbol
6098    ///
6099    /// # Returns
6100    ///
6101    /// Returns commission rate information
6102    ///
6103    /// # Examples
6104    ///
6105    /// ```no_run
6106    /// # use ccxt_exchanges::binance::Binance;
6107    /// # use ccxt_core::ExchangeConfig;
6108    /// # async fn example() -> ccxt_core::Result<()> {
6109    /// let mut config = ExchangeConfig::default();
6110    /// config.api_key = Some("your_api_key".to_string());
6111    /// config.secret = Some("your_secret".to_string());
6112    /// let binance = Binance::new_futures(config)?;
6113    /// let rate = binance.fetch_commission_rate("BTC/USDT:USDT").await?;
6114    /// println!("Maker rate: {}", rate.maker_commission_rate);
6115    /// println!("Taker rate: {}", rate.taker_commission_rate);
6116    /// # Ok(())
6117    /// # }
6118    /// ```
6119    pub async fn fetch_commission_rate(&self, symbol: &str) -> Result<CommissionRate> {
6120        self.check_required_credentials()?;
6121
6122        let market = self.base().market(symbol).await?;
6123        let mut request_params = HashMap::new();
6124        request_params.insert("symbol".to_string(), market.id.clone());
6125
6126        let timestamp = self.fetch_time_raw().await?;
6127        let auth = self.get_auth()?;
6128        let signed_params =
6129            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6130
6131        let mut request_url = format!("{}/commissionRate?", self.urls().fapi_private);
6132        for (key, value) in &signed_params {
6133            request_url.push_str(&format!("{}={}&", key, value));
6134        }
6135
6136        let mut headers = HeaderMap::new();
6137        auth.add_auth_headers_reqwest(&mut headers);
6138
6139        let data = self
6140            .base()
6141            .http_client
6142            .get(&request_url, Some(headers))
6143            .await?;
6144
6145        parser::parse_commission_rate(&data, &market)
6146    }
6147    // ==================== Stage 24.6: Risk and Limit Queries ====================
6148
6149    /// Fetch futures open interest
6150    ///
6151    /// Retrieves the total amount of outstanding (open) contracts.
6152    ///
6153    /// # Arguments
6154    ///
6155    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT:USDT"
6156    ///
6157    /// # Returns
6158    ///
6159    /// Returns open interest information
6160    ///
6161    /// # API Endpoint
6162    ///
6163    /// GET /fapi/v1/openInterest (MARKET_DATA)
6164    ///
6165    /// # Examples
6166    ///
6167    /// ```no_run
6168    /// # use ccxt_exchanges::binance::Binance;
6169    /// # use ccxt_core::ExchangeConfig;
6170    /// # async fn example() -> ccxt_core::Result<()> {
6171    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
6172    /// let open_interest = binance.fetch_open_interest("BTC/USDT:USDT").await?;
6173    /// println!("Open interest: {}", open_interest.open_interest);
6174    /// # Ok(())
6175    /// # }
6176    /// ```
6177    pub async fn fetch_open_interest(&self, symbol: &str) -> Result<OpenInterest> {
6178        let market = self.base().market(symbol).await?;
6179        let mut request_params = HashMap::new();
6180        request_params.insert("symbol".to_string(), market.id.clone());
6181
6182        let mut request_url = format!("{}/openInterest?", self.urls().fapi_public);
6183        for (key, value) in &request_params {
6184            request_url.push_str(&format!("{}={}&", key, value));
6185        }
6186
6187        let data = self.base().http_client.get(&request_url, None).await?;
6188
6189        parser::parse_open_interest(&data, &market)
6190    }
6191
6192    /// Fetch open interest history
6193    ///
6194    /// # Arguments
6195    ///
6196    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT:USDT"
6197    /// * `period` - Time period: "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d"
6198    /// * `limit` - Number of records to return (default 30, maximum 500)
6199    /// * `start_time` - Start timestamp in milliseconds
6200    /// * `end_time` - End timestamp in milliseconds
6201    ///
6202    /// # Returns
6203    ///
6204    /// Returns list of open interest history records
6205    ///
6206    /// # API Endpoint
6207    ///
6208    /// GET /futures/data/openInterestHist (MARKET_DATA)
6209    ///
6210    /// # Examples
6211    ///
6212    /// ```no_run
6213    /// # use ccxt_exchanges::binance::Binance;
6214    /// # use ccxt_core::ExchangeConfig;
6215    /// # async fn example() -> ccxt_core::Result<()> {
6216    /// let binance = Binance::new_futures(ExchangeConfig::default())?;
6217    /// let history = binance.fetch_open_interest_history(
6218    ///     "BTC/USDT:USDT",
6219    ///     "1h",
6220    ///     Some(100),
6221    ///     None,
6222    ///     None
6223    /// ).await?;
6224    /// println!("History records: {}", history.len());
6225    /// # Ok(())
6226    /// # }
6227    /// ```
6228    pub async fn fetch_open_interest_history(
6229        &self,
6230        symbol: &str,
6231        period: &str,
6232        limit: Option<u32>,
6233        start_time: Option<u64>,
6234        end_time: Option<u64>,
6235    ) -> Result<Vec<OpenInterestHistory>> {
6236        let market = self.base().market(symbol).await?;
6237        let mut request_params = HashMap::new();
6238        request_params.insert("symbol".to_string(), market.id.clone());
6239        request_params.insert("period".to_string(), period.to_string());
6240
6241        if let Some(limit) = limit {
6242            request_params.insert("limit".to_string(), limit.to_string());
6243        }
6244        if let Some(start_time) = start_time {
6245            request_params.insert("startTime".to_string(), start_time.to_string());
6246        }
6247        if let Some(end_time) = end_time {
6248            request_params.insert("endTime".to_string(), end_time.to_string());
6249        }
6250
6251        let mut request_url = format!("{}/data/openInterestHist?", self.urls().fapi_public);
6252        for (key, value) in &request_params {
6253            request_url.push_str(&format!("{}={}&", key, value));
6254        }
6255
6256        let data = self.base().http_client.get(&request_url, None).await?;
6257
6258        parser::parse_open_interest_history(&data, &market)
6259    }
6260
6261    /// Fetch maximum available leverage for trading pair
6262    ///
6263    /// # Arguments
6264    ///
6265    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT:USDT"
6266    ///
6267    /// # Returns
6268    ///
6269    /// Returns maximum leverage information
6270    ///
6271    /// # Notes
6272    ///
6273    /// This method retrieves maximum leverage by querying leverage bracket data.
6274    ///
6275    /// # Examples
6276    ///
6277    /// ```no_run
6278    /// # use ccxt_exchanges::binance::Binance;
6279    /// # use ccxt_core::ExchangeConfig;
6280    /// # async fn example() -> ccxt_core::Result<()> {
6281    /// let mut config = ExchangeConfig::default();
6282    /// config.api_key = Some("your-api-key".to_string());
6283    /// config.secret = Some("your-secret".to_string());
6284    /// let binance = Binance::new_futures(config)?;
6285    /// let max_leverage = binance.fetch_max_leverage("BTC/USDT:USDT").await?;
6286    /// println!("Maximum leverage: {}x", max_leverage.max_leverage);
6287    /// # Ok(())
6288    /// # }
6289    /// ```
6290    pub async fn fetch_max_leverage(&self, symbol: &str) -> Result<MaxLeverage> {
6291        self.check_required_credentials()?;
6292
6293        let market = self.base().market(symbol).await?;
6294        let mut request_params = HashMap::new();
6295
6296        // Optional: if symbol not provided, returns leverage brackets for all pairs
6297        request_params.insert("symbol".to_string(), market.id.clone());
6298
6299        let timestamp = self.fetch_time_raw().await?;
6300        let auth = self.get_auth()?;
6301        let signed_params =
6302            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6303
6304        let mut request_url = format!("{}/leverageBracket?", self.urls().fapi_private);
6305        for (key, value) in &signed_params {
6306            request_url.push_str(&format!("{}={}&", key, value));
6307        }
6308
6309        let mut headers = HeaderMap::new();
6310        auth.add_auth_headers_reqwest(&mut headers);
6311
6312        let data = self
6313            .base()
6314            .http_client
6315            .get(&request_url, Some(headers))
6316            .await?;
6317
6318        parser::parse_max_leverage(&data, &market)
6319    }
6320    // ========================================================================
6321    // Futures Market Data Queries
6322    // ========================================================================
6323
6324    /// Fetch index price
6325    ///
6326    /// # Arguments
6327    ///
6328    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT"
6329    ///
6330    /// # Returns
6331    ///
6332    /// Returns index price information
6333    ///
6334    /// # Examples
6335    ///
6336    /// ```no_run
6337    /// use ccxt_exchanges::binance::Binance;
6338    /// use ccxt_core::ExchangeConfig;
6339    ///
6340    /// #[tokio::main]
6341    /// async fn main() {
6342    ///     let config = ExchangeConfig::default();
6343    ///     let binance = Binance::new(config).unwrap();
6344    ///     
6345    ///     let index_price = binance.fetch_index_price("BTC/USDT").await.unwrap();
6346    ///     println!("Index Price: {}", index_price.index_price);
6347    /// }
6348    /// ```
6349    pub async fn fetch_index_price(&self, symbol: &str) -> Result<ccxt_core::types::IndexPrice> {
6350        let market = self.base().market(symbol).await?;
6351
6352        let url = format!(
6353            "{}/premiumIndex?symbol={}",
6354            self.urls().fapi_public,
6355            market.id
6356        );
6357
6358        let data = self.base().http_client.get(&url, None).await?;
6359
6360        parser::parse_index_price(&data, &market)
6361    }
6362
6363    /// Fetch premium index including mark price and funding rate
6364    ///
6365    /// # Arguments
6366    ///
6367    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT"; if `None`, returns all trading pairs
6368    ///
6369    /// # Returns
6370    ///
6371    /// Returns premium index information (single or multiple)
6372    ///
6373    /// # Examples
6374    ///
6375    /// ```no_run
6376    /// use ccxt_exchanges::binance::Binance;
6377    /// use ccxt_core::ExchangeConfig;
6378    ///
6379    /// #[tokio::main]
6380    /// async fn main() {
6381    ///     let config = ExchangeConfig::default();
6382    ///     let binance = Binance::new(config).unwrap();
6383    ///     
6384    ///     // Query single trading pair
6385    ///     let premium = binance.fetch_premium_index(Some("BTC/USDT")).await.unwrap();
6386    ///     println!("Mark Price: {}", premium[0].mark_price);
6387    ///
6388    ///     // Query all trading pairs
6389    ///     let all_premiums = binance.fetch_premium_index(None).await.unwrap();
6390    ///     println!("Total pairs: {}", all_premiums.len());
6391    /// }
6392    /// ```
6393    pub async fn fetch_premium_index(
6394        &self,
6395        symbol: Option<&str>,
6396    ) -> Result<Vec<ccxt_core::types::PremiumIndex>> {
6397        let url = if let Some(sym) = symbol {
6398            let market = self.base().market(sym).await?;
6399            format!(
6400                "{}/premiumIndex?symbol={}",
6401                self.urls().fapi_public,
6402                market.id
6403            )
6404        } else {
6405            format!("{}/premiumIndex", self.urls().fapi_public)
6406        };
6407
6408        let data = self.base().http_client.get(&url, None).await?;
6409
6410        if let Some(array) = data.as_array() {
6411            let mut results = Vec::new();
6412
6413            let cache = self.base().market_cache.read().await;
6414
6415            for item in array {
6416                if let Some(symbol_str) = item["symbol"].as_str() {
6417                    if let Some(market) = cache.markets_by_id.get(symbol_str) {
6418                        if let Ok(premium) = parser::parse_premium_index(item, market) {
6419                            results.push(premium);
6420                        }
6421                    }
6422                }
6423            }
6424
6425            Ok(results)
6426        } else {
6427            // When symbol is provided, the API returns a single object (not an array).
6428            // In this case, symbol must be Some because we only reach this branch
6429            // when a specific symbol was requested.
6430            let sym = symbol.ok_or_else(|| {
6431                Error::from(ParseError::missing_field(
6432                    "symbol required when API returns single object",
6433                ))
6434            })?;
6435            let market = self.base().market(sym).await?;
6436            let premium = parser::parse_premium_index(&data, &market)?;
6437            Ok(vec![premium])
6438        }
6439    }
6440
6441    /// Fetch liquidation orders
6442    ///
6443    /// # Arguments
6444    ///
6445    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT"; if `None`, returns all trading pairs
6446    /// * `start_time` - Start time (millisecond timestamp)
6447    /// * `end_time` - End time (millisecond timestamp)
6448    /// * `limit` - Quantity limit (default 100, maximum 1000)
6449    ///
6450    /// # Returns
6451    ///
6452    /// Returns list of liquidation orders
6453    ///
6454    /// # Examples
6455    ///
6456    /// ```no_run
6457    /// use ccxt_exchanges::binance::Binance;
6458    /// use ccxt_core::ExchangeConfig;
6459    ///
6460    /// #[tokio::main]
6461    /// async fn main() {
6462    ///     let config = ExchangeConfig::default();
6463    ///     let binance = Binance::new(config).unwrap();
6464    ///     
6465    ///     // Query recent liquidation orders
6466    ///     let liquidations = binance.fetch_liquidations(
6467    ///         Some("BTC/USDT"),
6468    ///         None,
6469    ///         None,
6470    ///         Some(50)
6471    ///     ).await.unwrap();
6472    ///     
6473    ///     for liq in liquidations {
6474    ///         println!("Liquidation: {} {} @ {}", liq.side, liq.quantity, liq.price);
6475    ///     }
6476    /// }
6477    /// ```
6478    pub async fn fetch_liquidations(
6479        &self,
6480        symbol: Option<&str>,
6481        start_time: Option<u64>,
6482        end_time: Option<u64>,
6483        limit: Option<u32>,
6484    ) -> Result<Vec<ccxt_core::types::Liquidation>> {
6485        let mut request_params = HashMap::new();
6486
6487        if let Some(sym) = symbol {
6488            let market = self.base().market(sym).await?;
6489            request_params.insert("symbol".to_string(), market.id.clone());
6490        }
6491
6492        if let Some(start) = start_time {
6493            request_params.insert("startTime".to_string(), start.to_string());
6494        }
6495
6496        if let Some(end) = end_time {
6497            request_params.insert("endTime".to_string(), end.to_string());
6498        }
6499
6500        if let Some(lim) = limit {
6501            request_params.insert("limit".to_string(), lim.to_string());
6502        }
6503
6504        let mut url = format!("{}/allForceOrders?", self.urls().fapi_public);
6505        for (key, value) in &request_params {
6506            url.push_str(&format!("{}={}&", key, value));
6507        }
6508
6509        let data = self.base().http_client.get(&url, None).await?;
6510
6511        let array = data.as_array().ok_or_else(|| {
6512            Error::from(ParseError::invalid_value("data", "Expected array response"))
6513        })?;
6514
6515        let mut results = Vec::new();
6516
6517        let cache = self.base().market_cache.read().await;
6518
6519        for item in array {
6520            // Get symbol and find corresponding market
6521            if let Some(symbol_str) = item["symbol"].as_str() {
6522                if let Some(market) = cache.markets_by_id.get(symbol_str) {
6523                    if let Ok(liquidation) = parser::parse_liquidation(item, market) {
6524                        results.push(liquidation);
6525                    }
6526                }
6527            }
6528        }
6529
6530        Ok(results)
6531    }
6532    // ============================================================================
6533    // Internal Transfer System (P0.1)
6534    // ============================================================================
6535
6536    /// Execute internal transfer
6537    ///
6538    /// Transfer assets between different account types (spot, margin, futures, funding, etc.)
6539    ///
6540    /// # Arguments
6541    ///
6542    /// * `currency` - Asset code (e.g. "USDT", "BTC")
6543    /// * `amount` - Transfer amount
6544    /// * `from_account` - Source account type (spot/margin/future/funding, etc.)
6545    /// * `to_account` - Target account type
6546    /// * `params` - Optional additional parameters
6547    ///
6548    /// # Binance API
6549    /// - Endpoint: POST /sapi/v1/asset/transfer
6550    /// - Weight: 900 (UID)
6551    /// - Required permission: TRADE
6552    /// - Signature required: Yes
6553    ///
6554    /// # Supported Account Types
6555    /// - MAIN: Spot account
6556    /// - UMFUTURE: USDT-margined futures account
6557    /// - CMFUTURE: Coin-margined futures account
6558    /// - MARGIN: Cross margin account
6559    /// - ISOLATED_MARGIN: Isolated margin account
6560    /// - FUNDING: Funding account
6561    /// - OPTION: Options account
6562    ///
6563    /// # Examples
6564    /// ```rust,no_run
6565    /// # use ccxt_exchanges::binance::Binance;
6566    /// # use ccxt_core::ExchangeConfig;
6567    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
6568    /// let config = ExchangeConfig {
6569    ///     api_key: Some("your_api_key".to_string()),
6570    ///     secret: Some("your_secret".to_string()),
6571    ///     ..Default::default()
6572    /// };
6573    /// let binance = Binance::new(config)?;
6574    ///
6575    /// // Transfer 100 USDT from spot to futures account
6576    /// let transfer = binance.transfer(
6577    ///     "USDT",
6578    ///     100.0,
6579    ///     "spot",      // From spot
6580    ///     "future",    // To futures
6581    ///     None
6582    /// ).await?;
6583    ///
6584    /// println!("Transfer ID: {:?}", transfer.id);
6585    /// println!("Status: {}", transfer.status);
6586    /// # Ok(())
6587    /// # }
6588    /// ```
6589    pub async fn transfer(
6590        &self,
6591        currency: &str,
6592        amount: f64,
6593        from_account: &str,
6594        to_account: &str,
6595        params: Option<HashMap<String, String>>,
6596    ) -> Result<Transfer> {
6597        println!(
6598            "🚨 [TRANSFER ENTRY] Method called with currency={}, amount={}, from={}, to={}",
6599            currency, amount, from_account, to_account
6600        );
6601
6602        println!("🚨 [TRANSFER STEP 1] Checking credentials...");
6603        self.check_required_credentials()?;
6604        println!("🚨 [TRANSFER STEP 1] ✅ Credentials check passed");
6605
6606        let from_upper = from_account.to_uppercase();
6607        let to_upper = to_account.to_uppercase();
6608
6609        // Normalize account names to Binance API format
6610        let from_normalized = match from_upper.as_str() {
6611            "SPOT" => "MAIN",
6612            "FUTURE" | "FUTURES" => "UMFUTURE",
6613            "MARGIN" => "MARGIN",
6614            "FUNDING" => "FUNDING",
6615            "OPTION" => "OPTION",
6616            other => other,
6617        };
6618
6619        let to_normalized = match to_upper.as_str() {
6620            "SPOT" => "MAIN",
6621            "FUTURE" | "FUTURES" => "UMFUTURE",
6622            "MARGIN" => "MARGIN",
6623            "FUNDING" => "FUNDING",
6624            "OPTION" => "OPTION",
6625            other => other,
6626        };
6627
6628        let transfer_type = format!("{}_{}", from_normalized, to_normalized);
6629
6630        let mut request_params = params.unwrap_or_default();
6631        request_params.insert("type".to_string(), transfer_type);
6632        request_params.insert("asset".to_string(), currency.to_uppercase());
6633        request_params.insert("amount".to_string(), amount.to_string());
6634
6635        println!(
6636            "🚨 [TRANSFER STEP 2] Request params prepared: {:?}",
6637            request_params
6638        );
6639
6640        // Sign request with timestamp
6641        println!("🚨 [TRANSFER STEP 3] Fetching server timestamp...");
6642        let timestamp = self.fetch_time_raw().await?;
6643        println!("🚨 [TRANSFER STEP 3] ✅ Got timestamp: {}", timestamp);
6644        println!("🔍 [TRANSFER DEBUG] Timestamp: {}", timestamp);
6645        println!(
6646            "🔍 [TRANSFER DEBUG] Request params before signing: {:?}",
6647            request_params
6648        );
6649
6650        println!("🚨 [TRANSFER STEP 4] Getting auth object...");
6651        let auth = self.get_auth()?;
6652        println!("🚨 [TRANSFER STEP 4] ✅ Got auth, now signing params...");
6653
6654        // Use new tuple return value: (HashMap, sorted query string)
6655        let signed_params =
6656            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6657        println!("🚨 [TRANSFER STEP 4] ✅ Params signed successfully");
6658        let query_string = auth.build_query_string(&signed_params);
6659        println!(
6660            "🔍 [TRANSFER DEBUG] Query string (signed and sorted): {}",
6661            query_string
6662        );
6663
6664        // Use sorted query string from Auth module to ensure consistent parameter order
6665        let url = format!("{}/asset/transfer?{}", self.urls().sapi, &query_string);
6666
6667        println!("🔍 [TRANSFER DEBUG] Final URL: {}", url);
6668
6669        let mut headers = reqwest::header::HeaderMap::new();
6670        auth.add_auth_headers_reqwest(&mut headers);
6671
6672        println!("🚨 [TRANSFER STEP 5] Sending POST request to URL: {}", url);
6673        let data = self
6674            .base()
6675            .http_client
6676            .post(&url, Some(headers), None)
6677            .await?;
6678        println!("🚨 [TRANSFER STEP 5] ✅ Got response, parsing...");
6679
6680        println!("🚨 [TRANSFER STEP 6] Parsing transfer response...");
6681        let result = parser::parse_transfer(&data);
6682        match &result {
6683            Ok(_) => tracing::error!("🚨 [TRANSFER STEP 6] ✅ Parse successful"),
6684            Err(e) => tracing::error!("🚨 [TRANSFER STEP 6] ❌ Parse failed: {:?}", e),
6685        }
6686        result
6687    }
6688
6689    /// Fetch internal transfer history
6690    ///
6691    /// Retrieve historical records of transfers between accounts
6692    ///
6693    /// # Arguments
6694    ///
6695    /// * `currency` - Optional asset code filter
6696    /// * `since` - Optional start timestamp (milliseconds)
6697    /// * `limit` - Optional quantity limit (default 100, maximum 100)
6698    /// * `params` - Optional additional parameters (may include fromSymbol/toSymbol to specify transfer direction)
6699    ///
6700    /// # API Endpoint
6701    /// - Endpoint: GET /sapi/v1/asset/transfer
6702    /// - Weight: 1
6703    /// - Required permission: USER_DATA
6704    /// - Signature required: Yes
6705    ///
6706    /// # Examples
6707    /// ```rust,no_run
6708    /// # use ccxt_exchanges::binance::Binance;
6709    /// # use ccxt_core::ExchangeConfig;
6710    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
6711    /// let config = ExchangeConfig {
6712    ///     api_key: Some("your_api_key".to_string()),
6713    ///     secret: Some("your_secret".to_string()),
6714    ///     ..Default::default()
6715    /// };
6716    /// let binance = Binance::new(config)?;
6717    ///
6718    ///     // Query recent USDT transfer records
6719    ///     let transfers = binance.fetch_transfers(
6720    ///     Some("USDT"),
6721    ///     None,
6722    ///     Some(50),
6723    ///     None
6724    /// ).await?;
6725    ///
6726    /// for transfer in transfers {
6727    ///     println!("{} {} from {:?} to {:?}",
6728    ///         transfer.amount,
6729    ///         transfer.currency,
6730    ///         transfer.from_account,
6731    ///         transfer.to_account
6732    ///     );
6733    /// }
6734    /// # Ok(())
6735    /// # }
6736    /// ```
6737    pub async fn fetch_transfers(
6738        &self,
6739        currency: Option<&str>,
6740        since: Option<u64>,
6741        limit: Option<u64>,
6742        params: Option<HashMap<String, String>>,
6743    ) -> Result<Vec<Transfer>> {
6744        self.check_required_credentials()?;
6745
6746        let mut request_params = params.unwrap_or_default();
6747
6748        // The 'type' parameter is required; return error if not provided in params
6749        let transfer_type = request_params
6750            .get("type")
6751            .ok_or_else(|| {
6752                Error::invalid_request(
6753                    "type parameter is required for fetch_transfers. Examples: MAIN_UMFUTURE, MAIN_CMFUTURE, MAIN_MARGIN, etc."
6754                )
6755            })?
6756            .clone();
6757
6758        // Validate fromSymbol and toSymbol are provided when required
6759        // fromSymbol is required when type is ISOLATEDMARGIN_MARGIN or ISOLATEDMARGIN_ISOLATEDMARGIN
6760        if transfer_type == "ISOLATEDMARGIN_MARGIN"
6761            || transfer_type == "ISOLATEDMARGIN_ISOLATEDMARGIN"
6762        {
6763            if !request_params.contains_key("fromSymbol") {
6764                return Err(Error::invalid_request(format!(
6765                    "fromSymbol is required when type is {}",
6766                    transfer_type
6767                )));
6768            }
6769        }
6770
6771        // toSymbol is required when type is MARGIN_ISOLATEDMARGIN or ISOLATEDMARGIN_ISOLATEDMARGIN
6772        if transfer_type == "MARGIN_ISOLATEDMARGIN"
6773            || transfer_type == "ISOLATEDMARGIN_ISOLATEDMARGIN"
6774        {
6775            if !request_params.contains_key("toSymbol") {
6776                return Err(Error::invalid_request(format!(
6777                    "toSymbol is required when type is {}",
6778                    transfer_type
6779                )));
6780            }
6781        }
6782
6783        if let Some(code) = currency {
6784            request_params.insert("asset".to_string(), code.to_uppercase());
6785        }
6786
6787        if let Some(ts) = since {
6788            request_params.insert("startTime".to_string(), ts.to_string());
6789        }
6790
6791        let size = limit.unwrap_or(10).min(100);
6792        request_params.insert("size".to_string(), size.to_string());
6793
6794        if !request_params.contains_key("current") {
6795            request_params.insert("current".to_string(), "1".to_string());
6796        }
6797
6798        let timestamp = self.fetch_time_raw().await?;
6799        let auth = self.get_auth()?;
6800        let signed_params =
6801            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
6802
6803        let query_string = auth.build_query_string(&signed_params);
6804        let url = format!(
6805            "{}/sapi/v1/asset/transfer?{}",
6806            self.urls().sapi,
6807            query_string
6808        );
6809
6810        let mut headers = reqwest::header::HeaderMap::new();
6811        auth.add_auth_headers_reqwest(&mut headers);
6812
6813        let data = self.base().http_client.get(&url, Some(headers)).await?;
6814
6815        // Binance response format: { "total": 2, "rows": [...] }
6816        let rows = data["rows"]
6817            .as_array()
6818            .ok_or_else(|| Error::from(ParseError::missing_field("rows")))?;
6819
6820        let mut transfers = Vec::new();
6821        for row in rows {
6822            match parser::parse_transfer(row) {
6823                Ok(transfer) => transfers.push(transfer),
6824                Err(e) => {
6825                    warn!(error = %e, "Failed to parse transfer");
6826                }
6827            }
6828        }
6829
6830        Ok(transfers)
6831    }
6832    // ============================================================================
6833    // Futures Transfer API
6834    // ============================================================================
6835
6836    /// Execute futures account transfer
6837    ///
6838    /// Transfer assets between spot account and futures accounts
6839    ///
6840    /// # Arguments
6841    ///
6842    /// * `currency` - Asset code (e.g. "USDT", "BTC")
6843    /// * `amount` - Transfer quantity
6844    /// * `transfer_type` - Transfer type:
6845    ///   - 1: Spot account → USDT-M futures account
6846    ///   - 2: USDT-M futures account → Spot account
6847    ///   - 3: Spot account → COIN-M futures account
6848    ///   - 4: COIN-M futures account → Spot account
6849    /// * `params` - Optional additional parameters
6850    ///
6851    /// # API Endpoint
6852    /// - Endpoint: POST /sapi/v1/futures/transfer
6853    /// - Weight: 1
6854    /// - Required permission: TRADE
6855    /// - Signature required: Yes
6856    ///
6857    /// # Differences from transfer()
6858    /// - `futures_transfer()`: Futures-specific API with concise parameters, only requires type (1-4)
6859    /// - `transfer()`: Generic API supporting 8 account types, requires fromSymbol/toSymbol parameters
6860    ///
6861    /// # Examples
6862    /// ```rust,no_run
6863    /// # use ccxt_exchanges::binance::Binance;
6864    /// # use ccxt_core::ExchangeConfig;
6865    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
6866    /// let config = ExchangeConfig {
6867    ///     api_key: Some("your_api_key".to_string()),
6868    ///     secret: Some("your_secret".to_string()),
6869    ///     ..Default::default()
6870    /// };
6871    /// let binance = Binance::new(config)?;
6872    ///
6873    ///     // Transfer 100 USDT from spot to USDT-M futures account
6874    ///     let transfer = binance.futures_transfer(
6875    ///     "USDT",
6876    ///     100.0,
6877    ///     1,  // type 1 = Spot → USDT-M futures
6878    ///     None
6879    /// ).await?;
6880    ///
6881    /// println!("Transfer ID: {:?}", transfer.id);
6882    /// println!("Status: {}", transfer.status);
6883    /// # Ok(())
6884    /// # }
6885    /// ```
6886    pub async fn futures_transfer(
6887        &self,
6888        currency: &str,
6889        amount: f64,
6890        transfer_type: i32,
6891        params: Option<HashMap<String, String>>,
6892    ) -> Result<Transfer> {
6893        self.check_required_credentials()?;
6894
6895        // Validate transfer_type parameter range
6896        if !(1..=4).contains(&transfer_type) {
6897            return Err(Error::invalid_request(format!(
6898                "Invalid futures transfer type: {}. Must be between 1 and 4",
6899                transfer_type
6900            )));
6901        }
6902
6903        // Use parser helper function to get account names (for response parsing)
6904        let (from_account, to_account) = parser::parse_futures_transfer_type(transfer_type)?;
6905
6906        let mut request_params = params.unwrap_or_default();
6907        request_params.insert("asset".to_string(), currency.to_uppercase());
6908        request_params.insert("amount".to_string(), amount.to_string());
6909        request_params.insert("type".to_string(), transfer_type.to_string());
6910
6911        let auth = self.get_auth()?;
6912        let signed_params = auth.sign_params(&request_params)?;
6913
6914        let query_string: Vec<String> = signed_params
6915            .iter()
6916            .map(|(k, v)| format!("{}={}", k, v))
6917            .collect();
6918        let url = format!(
6919            "{}/futures/transfer?{}",
6920            self.urls().sapi,
6921            query_string.join("&")
6922        );
6923
6924        let mut headers = reqwest::header::HeaderMap::new();
6925        auth.add_auth_headers_reqwest(&mut headers);
6926
6927        let data = self
6928            .base()
6929            .http_client
6930            .post(&url, Some(headers), None)
6931            .await?;
6932
6933        // Binance response format: { "tranId": 100000001 }
6934        // Manually construct Transfer object
6935        let tran_id = data["tranId"]
6936            .as_i64()
6937            .ok_or_else(|| Error::from(ParseError::missing_field("tranId")))?;
6938
6939        let timestamp = chrono::Utc::now().timestamp_millis();
6940        let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
6941            .map(|dt| dt.to_rfc3339())
6942            .unwrap_or_default();
6943
6944        Ok(Transfer {
6945            id: Some(tran_id.to_string()),
6946            timestamp: timestamp as u64,
6947            datetime,
6948            currency: currency.to_uppercase(),
6949            amount,
6950            from_account: Some(from_account.to_string()),
6951            to_account: Some(to_account.to_string()),
6952            status: "pending".to_string(), // Transfer request submitted, but status unknown
6953            info: Some(data),
6954        })
6955    }
6956
6957    /// Fetch futures transfer history
6958    ///
6959    /// Retrieve historical records of transfers between spot and futures accounts
6960    ///
6961    /// # Arguments
6962    ///
6963    /// * `currency` - Asset code (e.g. "USDT", "BTC")
6964    /// * `since` - Optional start timestamp (milliseconds)
6965    /// * `limit` - Optional quantity limit (default 10, maximum 100)
6966    /// * `params` - Optional additional parameters
6967    ///   - `endTime`: End timestamp (milliseconds)
6968    ///   - `current`: Current page number (default 1)
6969    ///
6970    /// # API Endpoint
6971    /// - Endpoint: GET /sapi/v1/futures/transfer
6972    /// - Weight: 10
6973    /// - Required permission: USER_DATA
6974    /// - Signature required: Yes
6975    ///
6976    /// # Return Fields
6977    /// - tranId: Transfer ID
6978    /// - asset: Asset code
6979    /// - amount: Transfer quantity
6980    /// - type: Transfer type (1-4)
6981    /// - timestamp: Transfer time
6982    /// - status: Transfer status (CONFIRMED/FAILED/PENDING)
6983    ///
6984    /// # Examples
6985    /// ```rust,no_run
6986    /// # use ccxt_exchanges::binance::Binance;
6987    /// # use ccxt_core::ExchangeConfig;
6988    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
6989    /// let config = ExchangeConfig {
6990    ///     api_key: Some("your_api_key".to_string()),
6991    ///     secret: Some("your_secret".to_string()),
6992    ///     ..Default::default()
6993    /// };
6994    /// let binance = Binance::new(config)?;
6995    ///
6996    ///     // Query USDT transfer records from the last 30 days
6997    ///     let since = chrono::Utc::now().timestamp_millis() as u64 - 30 * 24 * 60 * 60 * 1000;
6998    /// let transfers = binance.fetch_futures_transfers(
6999    ///     "USDT",
7000    ///     Some(since),
7001    ///     Some(50),
7002    ///     None
7003    /// ).await?;
7004    ///
7005    /// for transfer in transfers {
7006    ///     println!("{} {} from {:?} to {:?} - Status: {}",
7007    ///         transfer.amount,
7008    ///         transfer.currency,
7009    ///         transfer.from_account,
7010    ///         transfer.to_account,
7011    ///         transfer.status
7012    ///     );
7013    /// }
7014    /// # Ok(())
7015    /// # }
7016    /// ```
7017    pub async fn fetch_futures_transfers(
7018        &self,
7019        currency: &str,
7020        since: Option<u64>,
7021        limit: Option<u64>,
7022        params: Option<HashMap<String, String>>,
7023    ) -> Result<Vec<Transfer>> {
7024        self.check_required_credentials()?;
7025
7026        let mut request_params = params.unwrap_or_default();
7027
7028        request_params.insert("asset".to_string(), currency.to_uppercase());
7029
7030        if let Some(ts) = since {
7031            request_params.insert("startTime".to_string(), ts.to_string());
7032        }
7033
7034        // Binance limit: size maximum 100
7035        let size = limit.unwrap_or(10).min(100);
7036        request_params.insert("size".to_string(), size.to_string());
7037
7038        if !request_params.contains_key("current") {
7039            request_params.insert("current".to_string(), "1".to_string());
7040        }
7041
7042        let auth = self.get_auth()?;
7043        let signed_params = auth.sign_params(&request_params)?;
7044
7045        let query_string: Vec<String> = signed_params
7046            .iter()
7047            .map(|(k, v)| format!("{}={}", k, v))
7048            .collect();
7049        let url = format!(
7050            "{}/futures/transfer?{}",
7051            self.urls().sapi,
7052            query_string.join("&")
7053        );
7054
7055        let mut headers = reqwest::header::HeaderMap::new();
7056        auth.add_auth_headers_reqwest(&mut headers);
7057
7058        let data = self.base().http_client.get(&url, Some(headers)).await?;
7059
7060        // Binance response format: { "total": 2, "rows": [...] }
7061        let rows = data["rows"]
7062            .as_array()
7063            .ok_or_else(|| Error::from(ParseError::missing_field("rows")))?;
7064
7065        let mut transfers = Vec::new();
7066        for row in rows {
7067            // Parse type field and convert to account names
7068            if let Some(transfer_type) = row["type"].as_i64() {
7069                if let Ok((from_account, to_account)) =
7070                    parser::parse_futures_transfer_type(transfer_type as i32)
7071                {
7072                    let tran_id = row["tranId"].as_i64().map(|id| id.to_string());
7073
7074                    let timestamp = row["timestamp"]
7075                        .as_i64()
7076                        .unwrap_or_else(|| chrono::Utc::now().timestamp_millis());
7077
7078                    let datetime = chrono::DateTime::from_timestamp_millis(timestamp)
7079                        .map(|dt| dt.to_rfc3339())
7080                        .unwrap_or_default();
7081
7082                    let asset = row["asset"].as_str().unwrap_or(currency).to_string();
7083
7084                    let amount = if let Some(amount_str) = row["amount"].as_str() {
7085                        amount_str.parse::<f64>().unwrap_or(0.0)
7086                    } else {
7087                        row["amount"].as_f64().unwrap_or(0.0)
7088                    };
7089
7090                    let status = row["status"].as_str().unwrap_or("SUCCESS").to_lowercase();
7091
7092                    transfers.push(Transfer {
7093                        id: tran_id,
7094                        timestamp: timestamp as u64,
7095                        datetime,
7096                        currency: asset,
7097                        amount,
7098                        from_account: Some(from_account.to_string()),
7099                        to_account: Some(to_account.to_string()),
7100                        status,
7101                        info: Some(row.clone()),
7102                    });
7103                } else {
7104                    warn!(
7105                        transfer_type = transfer_type,
7106                        "Invalid futures transfer type"
7107                    );
7108                }
7109            }
7110        }
7111
7112        Ok(transfers)
7113    }
7114
7115    /// Fetch deposit and withdrawal fee information
7116    ///
7117    /// Query deposit and withdrawal fee configuration for all assets
7118    ///
7119    /// # Arguments
7120    ///
7121    /// * `currency` - Optional asset code filter (returns all assets if not specified)
7122    /// * `params` - Optional additional parameters
7123    ///
7124    /// # API Endpoint
7125    /// - Endpoint: GET /sapi/v1/capital/config/getall
7126    /// - Weight: 10
7127    /// - Required permission: USER_DATA
7128    /// - Signature required: Yes
7129    ///
7130    /// # Return Information
7131    /// - Withdrawal fee
7132    /// - Minimum/maximum withdrawal amount
7133    /// - Deposit/withdrawal support status
7134    /// - Network configurations (BTC, ETH, BSC, etc.)
7135    /// - Confirmation requirements
7136    ///
7137    /// # Examples
7138    /// ```rust,no_run
7139    /// # use ccxt_exchanges::binance::Binance;
7140    /// # use ccxt_core::ExchangeConfig;
7141    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
7142    /// let config = ExchangeConfig {
7143    ///     api_key: Some("your_api_key".to_string()),
7144    ///     secret: Some("your_secret".to_string()),
7145    ///     ..Default::default()
7146    /// };
7147    /// let binance = Binance::new(config)?;
7148    ///
7149    ///     // Fetch fee information for all assets
7150    ///     let fees = binance.fetch_deposit_withdraw_fees(None, None).await?;
7151    ///
7152    ///     // Fetch fee information for specific asset (e.g. USDT)
7153    ///     let usdt_fees = binance.fetch_deposit_withdraw_fees(Some("USDT"), None).await?;
7154    ///
7155    /// for fee in usdt_fees {
7156    ///     println!("{}: withdraw fee = {}, min = {}, max = {}",
7157    ///         fee.currency,
7158    ///         fee.withdraw_fee,
7159    ///         fee.withdraw_min,
7160    ///         fee.withdraw_max
7161    ///     );
7162    ///     
7163    ///     for network in &fee.networks {
7164    ///         println!("  Network {}: fee = {}", network.network, network.withdraw_fee);
7165    ///     }
7166    /// }
7167    /// # Ok(())
7168    /// # }
7169    /// ```
7170    pub async fn fetch_deposit_withdraw_fees(
7171        &self,
7172        currency: Option<&str>,
7173        params: Option<HashMap<String, String>>,
7174    ) -> Result<Vec<ccxt_core::types::DepositWithdrawFee>> {
7175        self.check_required_credentials()?;
7176
7177        let request_params = params.unwrap_or_default();
7178
7179        // Note: Binance /sapi/v1/capital/config/getall endpoint returns all currencies
7180        // If currency is specified, client-side filtering is required
7181
7182        let auth = self.get_auth()?;
7183        let signed_params = auth.sign_params(&request_params)?;
7184
7185        let query_string: Vec<String> = signed_params
7186            .iter()
7187            .map(|(k, v)| format!("{}={}", k, v))
7188            .collect();
7189        let url = format!(
7190            "{}/capital/config/getall?{}",
7191            self.urls().sapi,
7192            query_string.join("&")
7193        );
7194
7195        let mut headers = reqwest::header::HeaderMap::new();
7196        auth.add_auth_headers_reqwest(&mut headers);
7197
7198        let data = self.base().http_client.get(&url, Some(headers)).await?;
7199
7200        let all_fees = parser::parse_deposit_withdraw_fees(&data)?;
7201
7202        // Filter results if currency is specified
7203        if let Some(code) = currency {
7204            let code_upper = code.to_uppercase();
7205            Ok(all_fees
7206                .into_iter()
7207                .filter(|fee| fee.currency == code_upper)
7208                .collect())
7209        } else {
7210            Ok(all_fees)
7211        }
7212    }
7213
7214    // ============================================================================
7215    // Deposit/Withdrawal API - P1.1
7216    // ============================================================================
7217
7218    /// Withdraw to external address
7219    ///
7220    /// # Arguments
7221    /// * `code` - Currency code (e.g. "BTC", "USDT")
7222    /// * `amount` - Withdrawal amount
7223    /// * `address` - Withdrawal address
7224    /// * `params` - Optional parameters
7225    ///   - `tag`: Address tag (e.g. XRP tag)
7226    ///   - `network`: Network type (e.g. "ETH", "BSC", "TRX")
7227    ///   - `addressTag`: Address tag (alias)
7228    ///   - `name`: Address memo name
7229    ///   - `walletType`: Wallet type (0=spot, 1=funding)
7230    ///
7231    /// # API Endpoint
7232    /// - Endpoint: POST /sapi/v1/capital/withdraw/apply
7233    /// - Required permission: API key with withdrawal enabled
7234    ///
7235    /// # Examples
7236    /// ```no_run
7237    /// # use ccxt_exchanges::binance::Binance;
7238    /// # use ccxt_core::ExchangeConfig;
7239    /// # use std::collections::HashMap;
7240    /// # async fn example() -> ccxt_core::Result<()> {
7241    /// let binance = Binance::new(ExchangeConfig::default())?;
7242    ///
7243    ///     // Basic withdrawal
7244    ///     let tx = binance.withdraw("USDT", "100.0", "TXxxx...", None).await?;
7245    ///     println!("Withdrawal ID: {}", tx.id);
7246    ///
7247    ///     // Withdrawal with specific network
7248    ///     let mut params = HashMap::new();
7249    /// params.insert("network".to_string(), "TRX".to_string());
7250    /// let tx = binance.withdraw("USDT", "100.0", "TXxxx...", Some(params)).await?;
7251    /// # Ok(())
7252    /// # }
7253    /// ```
7254    pub async fn withdraw(
7255        &self,
7256        code: &str,
7257        amount: &str,
7258        address: &str,
7259        params: Option<HashMap<String, String>>,
7260    ) -> Result<Transaction> {
7261        self.check_required_credentials()?;
7262
7263        let mut request_params = params.unwrap_or_default();
7264
7265        request_params.insert("coin".to_string(), code.to_uppercase());
7266        request_params.insert("address".to_string(), address.to_string());
7267        request_params.insert("amount".to_string(), amount.to_string());
7268
7269        // Handle optional tag parameter (supports both 'tag' and 'addressTag' names)
7270        if let Some(tag) = request_params.get("tag").cloned() {
7271            request_params.insert("addressTag".to_string(), tag.clone());
7272        }
7273
7274        let auth = self.get_auth()?;
7275        let signed_params = auth.sign_params(&request_params)?;
7276
7277        let query_string: Vec<String> = signed_params
7278            .iter()
7279            .map(|(k, v)| format!("{}={}", k, v))
7280            .collect();
7281        let url = format!(
7282            "{}/capital/withdraw/apply?{}",
7283            self.urls().sapi,
7284            query_string.join("&")
7285        );
7286
7287        let mut headers = reqwest::header::HeaderMap::new();
7288        auth.add_auth_headers_reqwest(&mut headers);
7289
7290        let data = self
7291            .base()
7292            .http_client
7293            .post(&url, Some(headers), None)
7294            .await?;
7295
7296        parser::parse_transaction(&data, TransactionType::Withdrawal)
7297    }
7298
7299    /// Fetch deposit history
7300    ///
7301    /// # Arguments
7302    /// * `code` - Optional currency code (e.g. "BTC", "USDT")
7303    /// * `since` - Optional start timestamp (milliseconds)
7304    /// * `limit` - Optional quantity limit
7305    /// * `params` - Optional parameters
7306    ///   - `coin`: Currency (overrides code parameter)
7307    ///   - `status`: Status filter (0=pending, 6=credited, 1=success)
7308    ///   - `startTime`: Start timestamp (milliseconds)
7309    ///   - `endTime`: End timestamp (milliseconds)
7310    ///   - `offset`: Offset (for pagination)
7311    ///   - `txId`: Transaction ID filter
7312    ///
7313    /// # API Endpoint
7314    /// - Endpoint: GET /sapi/v1/capital/deposit/hisrec
7315    /// - Required permission: API key
7316    ///
7317    /// # Time Range Limit
7318    /// - Maximum query range: 90 days
7319    /// - Default returns last 90 days if no time range provided
7320    ///
7321    /// # Examples
7322    /// ```no_run
7323    /// # use ccxt_exchanges::binance::Binance;
7324    /// # use ccxt_core::ExchangeConfig;
7325    /// # async fn example() -> ccxt_core::Result<()> {
7326    /// let binance = Binance::new(ExchangeConfig::default())?;
7327    ///
7328    ///     // Query all deposits
7329    /// let deposits = binance.fetch_deposits(None, None, Some(100), None).await?;
7330    /// println!("Total deposits: {}", deposits.len());
7331    ///
7332    ///     // Query BTC deposits
7333    ///     let btc_deposits = binance.fetch_deposits(Some("BTC"), None, None, None).await?;
7334    /// # Ok(())
7335    /// # }
7336    /// ```
7337    pub async fn fetch_deposits(
7338        &self,
7339        code: Option<&str>,
7340        since: Option<i64>,
7341        limit: Option<i64>,
7342        params: Option<HashMap<String, String>>,
7343    ) -> Result<Vec<Transaction>> {
7344        self.check_required_credentials()?;
7345
7346        let mut request_params = params.unwrap_or_default();
7347
7348        if let Some(coin) = code {
7349            request_params
7350                .entry("coin".to_string())
7351                .or_insert_with(|| coin.to_uppercase());
7352        }
7353
7354        if let Some(start_time) = since {
7355            request_params
7356                .entry("startTime".to_string())
7357                .or_insert_with(|| start_time.to_string());
7358        }
7359
7360        if let Some(lim) = limit {
7361            request_params.insert("limit".to_string(), lim.to_string());
7362        }
7363
7364        let auth = self.get_auth()?;
7365        let signed_params = auth.sign_params(&request_params)?;
7366
7367        let query_string: Vec<String> = signed_params
7368            .iter()
7369            .map(|(k, v)| format!("{}={}", k, v))
7370            .collect();
7371        let url = format!(
7372            "{}/capital/deposit/hisrec?{}",
7373            self.urls().sapi,
7374            query_string.join("&")
7375        );
7376
7377        let mut headers = reqwest::header::HeaderMap::new();
7378        auth.add_auth_headers_reqwest(&mut headers);
7379
7380        let data = self.base().http_client.get(&url, Some(headers)).await?;
7381
7382        if let Some(arr) = data.as_array() {
7383            arr.iter()
7384                .map(|item| parser::parse_transaction(item, TransactionType::Deposit))
7385                .collect()
7386        } else {
7387            Err(Error::invalid_request("Expected array response"))
7388        }
7389    }
7390
7391    /// Fetch withdrawal history
7392    ///
7393    /// # Arguments
7394    /// * `code` - Optional currency code (e.g. "BTC", "USDT")
7395    /// * `since` - Optional start timestamp (milliseconds)
7396    /// * `limit` - Optional quantity limit
7397    /// * `params` - Optional parameters
7398    ///   - `coin`: Currency (overrides code parameter)
7399    ///   - `withdrawOrderId`: Withdrawal order ID
7400    ///   - `status`: Status filter (0=email sent, 1=cancelled, 2=awaiting approval, 3=rejected, 4=processing, 5=failure, 6=completed)
7401    ///   - `startTime`: Start timestamp (milliseconds)
7402    ///   - `endTime`: End timestamp (milliseconds)
7403    ///   - `offset`: Offset (for pagination)
7404    ///
7405    /// # API Endpoint
7406    /// - Endpoint: GET /sapi/v1/capital/withdraw/history
7407    /// - Required permission: API key
7408    ///
7409    /// # Time Range Limit
7410    /// - Maximum query range: 90 days
7411    /// - Default returns last 90 days if no time range provided
7412    ///
7413    /// # Examples
7414    /// ```no_run
7415    /// # use ccxt_exchanges::binance::Binance;
7416    /// # use ccxt_core::ExchangeConfig;
7417    /// # async fn example() -> ccxt_core::Result<()> {
7418    /// let binance = Binance::new(ExchangeConfig::default())?;
7419    ///
7420    ///     // Query all withdrawals
7421    ///     let withdrawals = binance.fetch_withdrawals(None, None, Some(100), None).await?;
7422    ///     println!("Total withdrawals: {}", withdrawals.len());
7423    ///
7424    ///     // Query USDT withdrawals
7425    ///     let usdt_withdrawals = binance.fetch_withdrawals(Some("USDT"), None, None, None).await?;
7426    /// # Ok(())
7427    /// # }
7428    /// ```
7429    pub async fn fetch_withdrawals(
7430        &self,
7431        code: Option<&str>,
7432        since: Option<i64>,
7433        limit: Option<i64>,
7434        params: Option<HashMap<String, String>>,
7435    ) -> Result<Vec<Transaction>> {
7436        self.check_required_credentials()?;
7437
7438        let mut request_params = params.unwrap_or_default();
7439
7440        if let Some(coin) = code {
7441            request_params
7442                .entry("coin".to_string())
7443                .or_insert_with(|| coin.to_uppercase());
7444        }
7445
7446        if let Some(start_time) = since {
7447            request_params
7448                .entry("startTime".to_string())
7449                .or_insert_with(|| start_time.to_string());
7450        }
7451
7452        if let Some(lim) = limit {
7453            request_params.insert("limit".to_string(), lim.to_string());
7454        }
7455
7456        let auth = self.get_auth()?;
7457        let signed_params = auth.sign_params(&request_params)?;
7458
7459        let query_string: Vec<String> = signed_params
7460            .iter()
7461            .map(|(k, v)| format!("{}={}", k, v))
7462            .collect();
7463        let url = format!(
7464            "{}/capital/withdraw/history?{}",
7465            self.urls().sapi,
7466            query_string.join("&")
7467        );
7468
7469        let mut headers = reqwest::header::HeaderMap::new();
7470        auth.add_auth_headers_reqwest(&mut headers);
7471
7472        let data = self.base().http_client.get(&url, Some(headers)).await?;
7473
7474        if let Some(arr) = data.as_array() {
7475            arr.iter()
7476                .map(|item| parser::parse_transaction(item, TransactionType::Withdrawal))
7477                .collect()
7478        } else {
7479            Err(Error::invalid_request("Expected array response"))
7480        }
7481    }
7482
7483    /// Fetch deposit address
7484    ///
7485    /// # Arguments
7486    /// * `code` - Currency code (e.g. "BTC", "USDT")
7487    /// * `params` - Optional parameters
7488    ///   - `network`: Network type (e.g. "ETH", "BSC", "TRX")
7489    ///   - `coin`: Currency (overrides code parameter)
7490    ///
7491    /// # API Endpoint
7492    /// - Endpoint: GET /sapi/v1/capital/deposit/address
7493    /// - Required permission: API key
7494    ///
7495    /// # Notes
7496    /// - Binance automatically generates new address if none exists
7497    /// - Some currencies require network parameter (e.g. USDT can be ETH/BSC/TRX network)
7498    ///
7499    /// # Examples
7500    /// ```no_run
7501    /// # use ccxt_exchanges::binance::Binance;
7502    /// # use ccxt_core::ExchangeConfig;
7503    /// # use std::collections::HashMap;
7504    /// # async fn example() -> ccxt_core::Result<()> {
7505    /// let binance = Binance::new(ExchangeConfig::default())?;
7506    ///
7507    ///     // Fetch BTC deposit address
7508    ///     let addr = binance.fetch_deposit_address("BTC", None).await?;
7509    ///     println!("BTC address: {}", addr.address);
7510    ///
7511    ///     // Fetch USDT deposit address on TRX network
7512    ///     let mut params = HashMap::new();
7513    /// params.insert("network".to_string(), "TRX".to_string());
7514    /// let addr = binance.fetch_deposit_address("USDT", Some(params)).await?;
7515    /// println!("USDT-TRX address: {}", addr.address);
7516    /// # Ok(())
7517    /// # }
7518    /// ```
7519    pub async fn fetch_deposit_address(
7520        &self,
7521        code: &str,
7522        params: Option<HashMap<String, String>>,
7523    ) -> Result<DepositAddress> {
7524        self.check_required_credentials()?;
7525
7526        let mut request_params = params.unwrap_or_default();
7527
7528        request_params
7529            .entry("coin".to_string())
7530            .or_insert_with(|| code.to_uppercase());
7531
7532        let auth = self.get_auth()?;
7533        let signed_params = auth.sign_params(&request_params)?;
7534
7535        let query_string: Vec<String> = signed_params
7536            .iter()
7537            .map(|(k, v)| format!("{}={}", k, v))
7538            .collect();
7539        let url = format!(
7540            "{}/capital/deposit/address?{}",
7541            self.urls().sapi,
7542            query_string.join("&")
7543        );
7544
7545        let mut headers = reqwest::header::HeaderMap::new();
7546        auth.add_auth_headers_reqwest(&mut headers);
7547
7548        let data = self.base().http_client.get(&url, Some(headers)).await?;
7549
7550        parser::parse_deposit_address(&data)
7551    }
7552
7553    // ============================================================================
7554    // OCO Order Management Methods (P1.3)
7555    // ============================================================================
7556
7557    /// Create OCO order (One-Cancels-the-Other)
7558    ///
7559    /// OCO order contains two orders: take-profit order (limit) + stop-loss order (stop-limit).
7560    /// When one order is filled, the other is automatically cancelled.
7561    ///
7562    /// # Arguments
7563    ///
7564    /// * `symbol` - Trading pair symbol, e.g. "BTC/USDT"
7565    /// * `side` - Order side (Buy or Sell)
7566    /// * `amount` - Order quantity
7567    /// * `price` - Take-profit price (limit order price)
7568    /// * `stop_price` - Stop-loss trigger price
7569    /// * `stop_limit_price` - Optional stop-limit order price, defaults to stop_price
7570    /// * `params` - Optional additional parameters
7571    ///
7572    /// # Returns
7573    ///
7574    /// Returns created OCO order information
7575    ///
7576    /// # Example
7577    ///
7578    /// ```no_run
7579    /// # use ccxt_exchanges::binance::Binance;
7580    /// # use ccxt_core::{ExchangeConfig, types::OrderSide};
7581    /// # async fn example() -> ccxt_core::Result<()> {
7582    /// let binance = Binance::new(ExchangeConfig::default())?;
7583    /// // Long BTC position: take profit at 50000, stop loss at 45000
7584    /// let oco = binance.create_oco_order(
7585    ///     "BTC/USDT",
7586    ///     OrderSide::Sell,
7587    ///     0.1,
7588    ///     50000.0,
7589    ///     45000.0,
7590    ///     Some(44500.0),
7591    ///     None
7592    /// ).await?;
7593    /// println!("OCO order ID: {}", oco.order_list_id);
7594    /// # Ok(())
7595    /// # }
7596    /// ```
7597    pub async fn create_oco_order(
7598        &self,
7599        symbol: &str,
7600        side: OrderSide,
7601        amount: f64,
7602        price: f64,
7603        stop_price: f64,
7604        stop_limit_price: Option<f64>,
7605        params: Option<HashMap<String, String>>,
7606    ) -> Result<OcoOrder> {
7607        self.check_required_credentials()?;
7608
7609        let market = self.base().market(symbol).await?;
7610        let mut request_params = HashMap::new();
7611
7612        request_params.insert("symbol".to_string(), market.id.clone());
7613        request_params.insert(
7614            "side".to_string(),
7615            match side {
7616                OrderSide::Buy => "BUY".to_string(),
7617                OrderSide::Sell => "SELL".to_string(),
7618            },
7619        );
7620        request_params.insert("quantity".to_string(), amount.to_string());
7621        request_params.insert("price".to_string(), price.to_string());
7622        request_params.insert("stopPrice".to_string(), stop_price.to_string());
7623
7624        // Default stop-limit price to stop_price if not specified
7625        let stop_limit = stop_limit_price.unwrap_or(stop_price);
7626        request_params.insert("stopLimitPrice".to_string(), stop_limit.to_string());
7627
7628        request_params.insert("stopLimitTimeInForce".to_string(), "GTC".to_string());
7629
7630        if let Some(extra) = params {
7631            for (k, v) in extra {
7632                request_params.insert(k, v);
7633            }
7634        }
7635
7636        let timestamp = self.fetch_time_raw().await?;
7637        let auth = self.get_auth()?;
7638        let signed_params =
7639            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
7640
7641        let url = format!("{}/order/oco", self.urls().private);
7642        let mut headers = HeaderMap::new();
7643        auth.add_auth_headers_reqwest(&mut headers);
7644
7645        let body = serde_json::to_value(&signed_params).map_err(|e| {
7646            Error::from(ParseError::invalid_format(
7647                "data",
7648                format!("Failed to serialize params: {}", e),
7649            ))
7650        })?;
7651
7652        let data = self
7653            .base()
7654            .http_client
7655            .post(&url, Some(headers), Some(body))
7656            .await?;
7657
7658        parser::parse_oco_order(&data)
7659    }
7660
7661    /// Fetch single OCO order
7662    ///
7663    /// # Arguments
7664    ///
7665    /// * `order_list_id` - OCO order list ID
7666    /// * `symbol` - Trading pair symbol
7667    /// * `params` - Optional additional parameters
7668    ///
7669    /// # Returns
7670    ///
7671    /// Returns OCO order information
7672    pub async fn fetch_oco_order(
7673        &self,
7674        order_list_id: i64,
7675        symbol: &str,
7676        params: Option<HashMap<String, String>>,
7677    ) -> Result<OcoOrder> {
7678        self.check_required_credentials()?;
7679
7680        let market = self.base().market(symbol).await?;
7681        let mut request_params = HashMap::new();
7682
7683        request_params.insert("orderListId".to_string(), order_list_id.to_string());
7684        request_params.insert("symbol".to_string(), market.id.clone());
7685
7686        if let Some(extra) = params {
7687            for (k, v) in extra {
7688                request_params.insert(k, v);
7689            }
7690        }
7691
7692        let timestamp = self.fetch_time_raw().await?;
7693        let auth = self.get_auth()?;
7694        let signed_params =
7695            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
7696
7697        let mut url = format!("{}/orderList?", self.urls().private);
7698        for (key, value) in &signed_params {
7699            url.push_str(&format!("{}={}&", key, value));
7700        }
7701
7702        let mut headers = HeaderMap::new();
7703        auth.add_auth_headers_reqwest(&mut headers);
7704
7705        let data = self.base().http_client.get(&url, Some(headers)).await?;
7706
7707        parser::parse_oco_order(&data)
7708    }
7709
7710    /// Fetch all OCO orders
7711    ///
7712    /// # Arguments
7713    ///
7714    /// * `symbol` - Trading pair symbol
7715    /// * `since` - Optional start timestamp (milliseconds)
7716    /// * `limit` - Optional record quantity limit (default 500, maximum 1000)
7717    /// * `params` - Optional additional parameters
7718    ///
7719    /// # Returns
7720    ///
7721    /// Returns list of OCO orders
7722    pub async fn fetch_oco_orders(
7723        &self,
7724        symbol: &str,
7725        since: Option<u64>,
7726        limit: Option<u32>,
7727        params: Option<HashMap<String, String>>,
7728    ) -> Result<Vec<OcoOrder>> {
7729        self.check_required_credentials()?;
7730
7731        let market = self.base().market(symbol).await?;
7732        let mut request_params = HashMap::new();
7733
7734        request_params.insert("symbol".to_string(), market.id.clone());
7735
7736        if let Some(s) = since {
7737            request_params.insert("startTime".to_string(), s.to_string());
7738        }
7739
7740        if let Some(l) = limit {
7741            request_params.insert("limit".to_string(), l.to_string());
7742        }
7743
7744        if let Some(extra) = params {
7745            for (k, v) in extra {
7746                request_params.insert(k, v);
7747            }
7748        }
7749
7750        let timestamp = self.fetch_time_raw().await?;
7751        let auth = self.get_auth()?;
7752        let signed_params =
7753            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
7754
7755        let mut url = format!("{}/allOrderList?", self.urls().private);
7756        for (key, value) in &signed_params {
7757            url.push_str(&format!("{}={}&", key, value));
7758        }
7759
7760        let mut headers = HeaderMap::new();
7761        auth.add_auth_headers_reqwest(&mut headers);
7762
7763        let data = self.base().http_client.get(&url, Some(headers)).await?;
7764
7765        let oco_array = data.as_array().ok_or_else(|| {
7766            Error::from(ParseError::invalid_format(
7767                "data",
7768                "Expected array of OCO orders",
7769            ))
7770        })?;
7771
7772        let mut oco_orders = Vec::new();
7773        for oco_data in oco_array {
7774            match parser::parse_oco_order(oco_data) {
7775                Ok(oco) => oco_orders.push(oco),
7776                Err(e) => {
7777                    warn!(error = %e, "Failed to parse OCO order");
7778                }
7779            }
7780        }
7781
7782        Ok(oco_orders)
7783    }
7784
7785    /// Cancel OCO order
7786    ///
7787    /// # Arguments
7788    ///
7789    /// * `order_list_id` - OCO order list ID
7790    /// * `symbol` - Trading pair symbol
7791    /// * `params` - Optional additional parameters
7792    ///
7793    /// # Returns
7794    ///
7795    /// Returns cancelled OCO order information
7796    pub async fn cancel_oco_order(
7797        &self,
7798        order_list_id: i64,
7799        symbol: &str,
7800        params: Option<HashMap<String, String>>,
7801    ) -> Result<OcoOrder> {
7802        self.check_required_credentials()?;
7803
7804        let market = self.base().market(symbol).await?;
7805        let mut request_params = HashMap::new();
7806
7807        request_params.insert("symbol".to_string(), market.id.clone());
7808        request_params.insert("orderListId".to_string(), order_list_id.to_string());
7809
7810        if let Some(extra) = params {
7811            for (k, v) in extra {
7812                request_params.insert(k, v);
7813            }
7814        }
7815
7816        let timestamp = self.fetch_time_raw().await?;
7817        let auth = self.get_auth()?;
7818        let signed_params =
7819            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
7820
7821        let url = format!("{}/orderList", self.urls().private);
7822        let mut headers = HeaderMap::new();
7823        auth.add_auth_headers_reqwest(&mut headers);
7824
7825        let body = serde_json::to_value(&signed_params).map_err(|e| {
7826            Error::from(ParseError::invalid_format(
7827                "data",
7828                format!("Failed to serialize params: {}", e),
7829            ))
7830        })?;
7831
7832        let data = self
7833            .base()
7834            .http_client
7835            .delete(&url, Some(headers), Some(body))
7836            .await?;
7837
7838        parser::parse_oco_order(&data)
7839    }
7840
7841    /// Create test order (does not place actual order)
7842    ///
7843    /// Used to test if order parameters are correct without submitting to exchange.
7844    ///
7845    /// # Arguments
7846    ///
7847    /// * `symbol` - Trading pair symbol
7848    /// * `order_type` - Order type
7849    /// * `side` - Order side (Buy/Sell)
7850    /// * `amount` - Order quantity
7851    /// * `price` - Optional price
7852    /// * `params` - Optional additional parameters
7853    ///
7854    /// # Returns
7855    ///
7856    /// Returns empty order information (validation result only)
7857    ///
7858    /// # Example
7859    ///
7860    /// ```no_run
7861    /// # use ccxt_exchanges::binance::Binance;
7862    /// # use ccxt_core::{ExchangeConfig, types::{OrderType, OrderSide}};
7863    /// # async fn example() -> ccxt_core::Result<()> {
7864    /// let binance = Binance::new(ExchangeConfig::default())?;
7865    /// // Test if order parameters are correct
7866    /// let test = binance.create_test_order(
7867    ///     "BTC/USDT",
7868    ///     OrderType::Limit,
7869    ///     OrderSide::Buy,
7870    ///     0.001,
7871    ///     40000.0,
7872    ///     None
7873    /// ).await?;
7874    /// println!("Order parameters validated successfully");
7875    /// # Ok(())
7876    /// # }
7877    /// ```
7878    pub async fn create_test_order(
7879        &self,
7880        symbol: &str,
7881        order_type: OrderType,
7882        side: OrderSide,
7883        amount: f64,
7884        price: Option<f64>,
7885        params: Option<HashMap<String, String>>,
7886    ) -> Result<Order> {
7887        self.check_required_credentials()?;
7888
7889        let market = self.base().market(symbol).await?;
7890        let mut request_params = HashMap::new();
7891
7892        request_params.insert("symbol".to_string(), market.id.clone());
7893        request_params.insert(
7894            "side".to_string(),
7895            match side {
7896                OrderSide::Buy => "BUY".to_string(),
7897                OrderSide::Sell => "SELL".to_string(),
7898            },
7899        );
7900        request_params.insert(
7901            "type".to_string(),
7902            match order_type {
7903                OrderType::Market => "MARKET".to_string(),
7904                OrderType::Limit => "LIMIT".to_string(),
7905                OrderType::StopLoss => "STOP_LOSS".to_string(),
7906                OrderType::StopLossLimit => "STOP_LOSS_LIMIT".to_string(),
7907                OrderType::TakeProfit => "TAKE_PROFIT".to_string(),
7908                OrderType::TakeProfitLimit => "TAKE_PROFIT_LIMIT".to_string(),
7909                OrderType::LimitMaker => "LIMIT_MAKER".to_string(),
7910                OrderType::StopMarket => "STOP_MARKET".to_string(),
7911                OrderType::StopLimit => "STOP_LIMIT".to_string(),
7912                OrderType::TrailingStop => "TRAILING_STOP_MARKET".to_string(),
7913            },
7914        );
7915        request_params.insert("quantity".to_string(), amount.to_string());
7916
7917        if let Some(p) = price {
7918            request_params.insert("price".to_string(), p.to_string());
7919        }
7920
7921        // Limit orders require timeInForce
7922        if order_type == OrderType::Limit
7923            || order_type == OrderType::StopLossLimit
7924            || order_type == OrderType::TakeProfitLimit
7925        {
7926            if !request_params.contains_key("timeInForce") {
7927                request_params.insert("timeInForce".to_string(), "GTC".to_string());
7928            }
7929        }
7930
7931        if let Some(extra) = params {
7932            for (k, v) in extra {
7933                request_params.insert(k, v);
7934            }
7935        }
7936
7937        let timestamp = self.fetch_time_raw().await?;
7938        let auth = self.get_auth()?;
7939        let signed_params =
7940            auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
7941
7942        // Use test endpoint
7943        let url = format!("{}/order/test", self.urls().private);
7944        let mut headers = HeaderMap::new();
7945        auth.add_auth_headers_reqwest(&mut headers);
7946
7947        let body = serde_json::to_value(&signed_params).map_err(|e| {
7948            Error::from(ParseError::invalid_format(
7949                "data",
7950                format!("Failed to serialize params: {}", e),
7951            ))
7952        })?;
7953
7954        let data = self
7955            .base()
7956            .http_client
7957            .post(&url, Some(headers), Some(body))
7958            .await?;
7959
7960        // Test order returns empty object {}, return a dummy order
7961        Ok(Order {
7962            id: "test".to_string(),
7963            client_order_id: None,
7964            timestamp: Some(timestamp as i64),
7965            datetime: Some(
7966                chrono::DateTime::from_timestamp(timestamp as i64 / 1000, 0)
7967                    .map(|dt| dt.to_rfc3339())
7968                    .unwrap_or_default(),
7969            ),
7970            last_trade_timestamp: None,
7971            status: OrderStatus::Open,
7972            symbol: symbol.to_string(),
7973            order_type,
7974            time_in_force: Some("GTC".to_string()),
7975            side,
7976            price: price.map(Decimal::from_f64_retain).flatten(),
7977            average: None,
7978            amount: Decimal::from_f64_retain(amount)
7979                .ok_or_else(|| Error::from(ParseError::missing_field("amount")))?,
7980            filled: Some(Decimal::ZERO),
7981            remaining: Decimal::from_f64_retain(amount).map(Some).unwrap_or(None),
7982            cost: None,
7983            trades: None,
7984            fee: None,
7985            post_only: None,
7986            reduce_only: None,
7987            trigger_price: None,
7988            stop_price: None,
7989            take_profit_price: None,
7990            stop_loss_price: None,
7991            trailing_delta: None,
7992            trailing_percent: None,
7993            activation_price: None,
7994            callback_rate: None,
7995            working_type: None,
7996            fees: Some(Vec::new()),
7997            info: data
7998                .as_object()
7999                .map(|obj| obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
8000                .unwrap_or_default(),
8001        })
8002    }
8003
8004    /// Fetch all isolated margin borrow rates
8005    ///
8006    /// # Arguments
8007    /// * `params` - Optional parameters
8008    ///
8009    /// # Returns
8010    /// HashMap<Symbol, IsolatedBorrowRate>
8011    ///
8012    /// # API Endpoint
8013    /// GET /sapi/v1/margin/isolatedMarginData
8014    ///
8015    /// # Examples
8016    /// ```no_run
8017    /// # use ccxt_exchanges::binance::Binance;
8018    /// # use ccxt_core::ExchangeConfig;
8019    /// # async fn example() -> ccxt_core::Result<()> {
8020    /// let binance = Binance::new(ExchangeConfig::default())?;
8021    /// let rates = binance.fetch_isolated_borrow_rates(None).await?;
8022    /// println!("Got {} symbols", rates.len());
8023    /// # Ok(())
8024    /// # }
8025    /// ```
8026    pub async fn fetch_isolated_borrow_rates(
8027        &self,
8028        params: Option<HashMap<String, String>>,
8029    ) -> Result<HashMap<String, ccxt_core::types::IsolatedBorrowRate>> {
8030        self.check_required_credentials()?;
8031
8032        let request_params = params.unwrap_or_default();
8033
8034        let auth = self.get_auth()?;
8035        let signed_params = auth.sign_params(&request_params)?;
8036
8037        let query_string: Vec<String> = signed_params
8038            .iter()
8039            .map(|(k, v)| format!("{}={}", k, v))
8040            .collect();
8041        let url = format!(
8042            "{}/margin/isolatedMarginData?{}",
8043            self.urls().sapi,
8044            query_string.join("&")
8045        );
8046
8047        let mut headers = reqwest::header::HeaderMap::new();
8048        auth.add_auth_headers_reqwest(&mut headers);
8049
8050        let response = self.base().http_client.get(&url, Some(headers)).await?;
8051
8052        parser::parse_isolated_borrow_rates(&response)
8053    }
8054
8055    /// Fetch borrow interest history
8056    ///
8057    /// # Arguments
8058    /// * `code` - Asset code (optional, e.g., "BTC")
8059    /// * `symbol` - Trading pair symbol (optional, for isolated margin only, e.g., "BTC/USDT")
8060    /// * `since` - Start timestamp (milliseconds)
8061    /// * `limit` - Number of records to return (default 10, max 100)
8062    /// * `params` - Optional parameters
8063    ///   - `endTime`: End timestamp
8064    ///   - `isolatedSymbol`: Isolated margin trading pair (overrides symbol parameter)
8065    ///   - `archived`: Whether to query archived data (default false)
8066    ///
8067    /// # API Endpoint
8068    /// - Spot Margin: GET /sapi/v1/margin/interestHistory
8069    /// - Portfolio Margin: GET /papi/v1/margin/marginInterestHistory
8070    ///
8071    /// # Examples
8072    /// ```no_run
8073    /// # use ccxt_exchanges::binance::Binance;
8074    /// # use ccxt_core::ExchangeConfig;
8075    /// # async fn example() -> ccxt_core::Result<()> {
8076    /// let binance = Binance::new(ExchangeConfig::default())?;
8077    ///
8078    /// // Fetch cross-margin BTC borrow interest
8079    /// let interests = binance.fetch_borrow_interest(
8080    ///     Some("BTC"),
8081    ///     None,
8082    ///     None,
8083    ///     Some(10),
8084    ///     None
8085    /// ).await?;
8086    ///
8087    /// // Fetch isolated margin BTC/USDT borrow interest
8088    /// let interests = binance.fetch_borrow_interest(
8089    ///     None,
8090    ///     Some("BTC/USDT"),
8091    ///     None,
8092    ///     Some(10),
8093    ///     None
8094    /// ).await?;
8095    /// # Ok(())
8096    /// # }
8097    /// ```
8098    pub async fn fetch_borrow_interest(
8099        &self,
8100        code: Option<&str>,
8101        symbol: Option<&str>,
8102        since: Option<u64>,
8103        limit: Option<u64>,
8104        params: Option<HashMap<String, String>>,
8105    ) -> Result<Vec<BorrowInterest>> {
8106        self.check_required_credentials()?;
8107
8108        let mut request_params = params.unwrap_or_default();
8109
8110        if let Some(c) = code {
8111            request_params.insert("asset".to_string(), c.to_string());
8112        }
8113
8114        if let Some(s) = symbol {
8115            self.load_markets(false).await?;
8116            let market = self.base().market(s).await?;
8117            request_params.insert("isolatedSymbol".to_string(), market.id.clone());
8118        }
8119
8120        if let Some(st) = since {
8121            request_params.insert("startTime".to_string(), st.to_string());
8122        }
8123
8124        if let Some(l) = limit {
8125            request_params.insert("size".to_string(), l.to_string());
8126        }
8127
8128        let is_portfolio = request_params
8129            .get("type")
8130            .map(|t| t == "PORTFOLIO")
8131            .unwrap_or(false);
8132
8133        let auth = self.get_auth()?;
8134        let signed_params = auth.sign_params(&request_params)?;
8135
8136        let query_string: Vec<String> = signed_params
8137            .iter()
8138            .map(|(k, v)| format!("{}={}", k, v))
8139            .collect();
8140
8141        let url = if is_portfolio {
8142            format!(
8143                "{}/margin/marginInterestHistory?{}",
8144                self.urls().papi,
8145                query_string.join("&")
8146            )
8147        } else {
8148            format!(
8149                "{}/margin/interestHistory?{}",
8150                self.urls().sapi,
8151                query_string.join("&")
8152            )
8153        };
8154
8155        let mut headers = reqwest::header::HeaderMap::new();
8156        auth.add_auth_headers_reqwest(&mut headers);
8157
8158        let response = self.base().http_client.get(&url, Some(headers)).await?;
8159
8160        parser::parse_borrow_interests(&response)
8161    }
8162
8163    /// Fetch margin borrow rate history
8164    ///
8165    /// # Arguments
8166    /// * `code` - Asset code (e.g., "USDT")
8167    /// * `since` - Start timestamp in milliseconds
8168    /// * `limit` - Number of records to return (default 10, max 100, but actual limit is 92)
8169    /// * `params` - Optional parameters
8170    ///   - `endTime`: End timestamp
8171    ///   - `vipLevel`: VIP level
8172    ///
8173    /// # Notes
8174    /// - Binance API has special limit restriction, cannot exceed 92
8175    /// - For more data, use multiple requests with pagination
8176    ///
8177    /// # API Endpoint
8178    /// GET /sapi/v1/margin/interestRateHistory
8179    ///
8180    /// # Examples
8181    /// ```no_run
8182    /// # use ccxt_exchanges::binance::Binance;
8183    /// # use ccxt_core::ExchangeConfig;
8184    /// # async fn example() -> ccxt_core::Result<()> {
8185    /// let binance = Binance::new(ExchangeConfig::default())?;
8186    /// let history = binance.fetch_borrow_rate_history(
8187    ///     "USDT",
8188    ///     None,
8189    ///     Some(50),
8190    ///     None
8191    /// ).await?;
8192    /// println!("Got {} records", history.len());
8193    /// # Ok(())
8194    /// # }
8195    /// ```
8196    pub async fn fetch_borrow_rate_history(
8197        &self,
8198        code: &str,
8199        since: Option<u64>,
8200        limit: Option<u64>,
8201        params: Option<HashMap<String, String>>,
8202    ) -> Result<Vec<BorrowRateHistory>> {
8203        self.check_required_credentials()?;
8204
8205        let mut request_params = params.unwrap_or_default();
8206        request_params.insert("asset".to_string(), code.to_string());
8207
8208        // Binance API limit: cannot exceed 92
8209        let actual_limit = limit.unwrap_or(10).min(92);
8210
8211        if let Some(st) = since {
8212            request_params.insert("startTime".to_string(), st.to_string());
8213
8214            // Binance requires limited time range per request
8215            if !request_params.contains_key("endTime") {
8216                // Each record represents 1 day, so calculate endTime = startTime + (limit * 1 day)
8217                let end_time = st + (actual_limit * 86400000); // 86400000 ms = 1 day
8218                request_params.insert("endTime".to_string(), end_time.to_string());
8219            }
8220        }
8221
8222        let auth = self.get_auth()?;
8223        let signed_params = auth.sign_params(&request_params)?;
8224
8225        let query_string: Vec<String> = signed_params
8226            .iter()
8227            .map(|(k, v)| format!("{}={}", k, v))
8228            .collect();
8229        let url = format!(
8230            "{}/margin/interestRateHistory?{}",
8231            self.urls().sapi,
8232            query_string.join("&")
8233        );
8234
8235        let mut headers = reqwest::header::HeaderMap::new();
8236        auth.add_auth_headers_reqwest(&mut headers);
8237
8238        let response = self.base().http_client.get(&url, Some(headers)).await?;
8239
8240        let records = response
8241            .as_array()
8242            .ok_or_else(|| Error::from(ParseError::invalid_format("data", "Expected array")))?;
8243
8244        let mut history = Vec::new();
8245        for record in records {
8246            let rate = parser::parse_borrow_rate_history(record, code)?;
8247            history.push(rate);
8248        }
8249
8250        Ok(history)
8251    }
8252
8253    // ========================================================================
8254    // Ledger Methods
8255    // ========================================================================
8256
8257    /// Fetch ledger history
8258    ///
8259    /// **Important Notes**:
8260    /// - Only supports futures wallets (option, USDT-M, COIN-M)
8261    /// - Spot wallet is not supported, returns NotSupported error
8262    /// - For portfolioMargin accounts, uses unified account API
8263    ///
8264    /// # API Endpoint Mapping
8265    ///
8266    /// | Market Type | Endpoint | Field Format |
8267    /// |-------------|----------|--------------|
8268    /// | Option | GET /eapi/v1/bill | id, asset, amount, type |
8269    /// | USDT-M (linear) | GET /fapi/v1/income | tranId, asset, income, incomeType |
8270    /// | USDT-M+Portfolio | GET /papi/v1/um/income | tranId, asset, income, incomeType |
8271    /// | COIN-M (inverse) | GET /dapi/v1/income | tranId, asset, income, incomeType |
8272    /// | COIN-M+Portfolio | GET /papi/v1/cm/income | tranId, asset, income, incomeType |
8273    ///
8274    /// # Arguments
8275    ///
8276    /// * `code` - Currency code (required for option market)
8277    /// * `since` - Start timestamp in milliseconds
8278    /// * `limit` - Maximum number of records to return
8279    /// * `params` - Additional parameters:
8280    ///   - `until`: End timestamp in milliseconds
8281    ///   - `portfolioMargin`: Whether to use portfolio margin account (unified account)
8282    ///   - `type`: Market type ("spot", "margin", "future", "swap", "option")
8283    ///   - `subType`: Sub-type ("linear", "inverse")
8284    ///
8285    /// # Returns
8286    ///
8287    /// Returns list of LedgerEntry, sorted by time in descending order
8288    ///
8289    /// # Errors
8290    ///
8291    /// - Returns NotSupported error if market type is spot
8292    /// - Returns ArgumentsRequired error if code parameter is not provided for option market
8293    ///
8294    /// # Examples
8295    ///
8296    /// ```rust,no_run
8297    /// use ccxt_exchanges::binance::Binance;
8298    /// use ccxt_core::ExchangeConfig;
8299    /// use std::collections::HashMap;
8300    ///
8301    /// #[tokio::main]
8302    /// async fn main() {
8303    ///     let mut config = ExchangeConfig::default();
8304    ///     config.api_key = Some("your-api-key".to_string());
8305    ///     config.secret = Some("your-api-secret".to_string());
8306    ///     
8307    ///     let binance = Binance::new(config).unwrap();
8308    ///     
8309    ///     // USDT-M futures ledger
8310    ///     let mut params = HashMap::new();
8311    ///     params.insert("type".to_string(), "future".to_string());
8312    ///     params.insert("subType".to_string(), "linear".to_string());
8313    ///     let ledger = binance.fetch_ledger(
8314    ///         Some("USDT".to_string()),
8315    ///         None,
8316    ///         Some(100),
8317    ///         Some(params)
8318    ///     ).await.unwrap();
8319    ///
8320    ///     // Option ledger (code is required)
8321    ///     let mut params = HashMap::new();
8322    ///     params.insert("type".to_string(), "option".to_string());
8323    ///     let ledger = binance.fetch_ledger(
8324    ///         Some("USDT".to_string()),
8325    ///         None,
8326    ///         Some(100),
8327    ///         Some(params)
8328    ///     ).await.unwrap();
8329    /// }
8330    /// ```
8331    pub async fn fetch_ledger(
8332        &self,
8333        code: Option<String>,
8334        since: Option<i64>,
8335        limit: Option<i64>,
8336        params: Option<HashMap<String, String>>,
8337    ) -> Result<Vec<ccxt_core::types::LedgerEntry>> {
8338        self.check_required_credentials()?;
8339
8340        let params = params.unwrap_or_default();
8341
8342        let market_type = params.get("type").map(|s| s.as_str()).unwrap_or("future");
8343        let sub_type = params.get("subType").map(|s| s.as_str());
8344        let portfolio_margin = params
8345            .get("portfolioMargin")
8346            .and_then(|s| s.parse::<bool>().ok())
8347            .unwrap_or(false);
8348
8349        if market_type == "spot" {
8350            return Err(Error::not_implemented(
8351                "fetch_ledger() is not supported for spot market, only for futures/swap/option",
8352            ));
8353        }
8354
8355        let (endpoint_base, path) = if market_type == "option" {
8356            if code.is_none() {
8357                return Err(Error::invalid_request(
8358                    "fetch_ledger() requires code for option market",
8359                ));
8360            }
8361            (self.urls().eapi.clone(), "/eapi/v1/bill")
8362        } else if portfolio_margin {
8363            if sub_type == Some("inverse") {
8364                (self.urls().papi.clone(), "/papi/v1/cm/income")
8365            } else {
8366                (self.urls().papi.clone(), "/papi/v1/um/income")
8367            }
8368        } else if sub_type == Some("inverse") {
8369            (self.urls().dapi.clone(), "/dapi/v1/income")
8370        } else {
8371            (self.urls().fapi.clone(), "/fapi/v1/income")
8372        };
8373
8374        let mut request_params = HashMap::new();
8375
8376        if let Some(currency) = &code {
8377            request_params.insert("asset".to_string(), currency.clone());
8378        }
8379
8380        if let Some(start_time) = since {
8381            request_params.insert("startTime".to_string(), start_time.to_string());
8382        }
8383        if let Some(end_time_str) = params.get("until") {
8384            request_params.insert("endTime".to_string(), end_time_str.clone());
8385        }
8386
8387        if let Some(limit_val) = limit {
8388            request_params.insert("limit".to_string(), limit_val.to_string());
8389        }
8390
8391        let auth = self.get_auth()?;
8392        let signed_params = auth.sign_params(&request_params)?;
8393
8394        let query_string: Vec<String> = signed_params
8395            .iter()
8396            .map(|(k, v)| format!("{}={}", k, v))
8397            .collect();
8398        let url = format!("{}{}?{}", endpoint_base, path, query_string.join("&"));
8399
8400        let mut headers = reqwest::header::HeaderMap::new();
8401        auth.add_auth_headers_reqwest(&mut headers);
8402
8403        let response = self.base().http_client.get(&url, Some(headers)).await?;
8404
8405        let records = response
8406            .as_array()
8407            .ok_or_else(|| Error::from(ParseError::invalid_format("data", "Expected array")))?;
8408
8409        let mut ledger_entries = Vec::new();
8410        for record in records {
8411            let entry = parser::parse_ledger_entry(record)?;
8412            ledger_entries.push(entry);
8413        }
8414
8415        Ok(ledger_entries)
8416    }
8417    /// Fetch trades for a specific order
8418    ///
8419    /// # Arguments
8420    ///
8421    /// * `id` - Order ID
8422    /// * `symbol` - Trading pair symbol (required)
8423    /// * `since` - Optional start timestamp in milliseconds
8424    /// * `limit` - Optional maximum number of records
8425    ///
8426    /// # Returns
8427    ///
8428    /// Returns all trades for the specified order
8429    ///
8430    /// # Notes
8431    ///
8432    /// - `symbol` parameter is required
8433    /// - Only supports spot markets
8434    /// - Internally calls fetch_my_trades method with orderId filter
8435    /// - API endpoint: GET /api/v3/myTrades
8436    ///
8437    /// # Examples
8438    ///
8439    /// ```no_run
8440    /// # use ccxt_exchanges::binance::Binance;
8441    /// # use ccxt_core::ExchangeConfig;
8442    /// # async fn example() -> ccxt_core::Result<()> {
8443    /// let mut config = ExchangeConfig::default();
8444    /// config.api_key = Some("your-api-key".to_string());
8445    /// config.secret = Some("your-secret".to_string());
8446    /// let binance = Binance::new(config)?;
8447    ///
8448    /// let trades = binance.fetch_order_trades("123456789", "BTC/USDT", None, None).await?;
8449    /// println!("Order has {} trades", trades.len());
8450    /// # Ok(())
8451    /// # }
8452    /// ```
8453    pub async fn fetch_order_trades(
8454        &self,
8455        id: &str,
8456        symbol: &str,
8457        since: Option<u64>,
8458        limit: Option<u32>,
8459    ) -> Result<Vec<Trade>> {
8460        self.check_required_credentials()?;
8461
8462        let market = self.base().market(symbol).await?;
8463        if !market.is_spot() {
8464            return Err(Error::not_implemented(
8465                "fetch_order_trades() supports spot markets only",
8466            ));
8467        }
8468
8469        let mut params = HashMap::new();
8470        params.insert("symbol".to_string(), market.id.clone());
8471        params.insert("orderId".to_string(), id.to_string());
8472
8473        if let Some(s) = since {
8474            params.insert("startTime".to_string(), s.to_string());
8475        }
8476
8477        if let Some(l) = limit {
8478            params.insert("limit".to_string(), l.to_string());
8479        }
8480
8481        let timestamp = self.fetch_time_raw().await?;
8482        let auth = self.get_auth()?;
8483        let signed_params =
8484            auth.sign_with_timestamp(&params, timestamp, Some(self.options().recv_window))?;
8485
8486        let mut url = format!("{}/myTrades?", self.urls().private);
8487        for (key, value) in &signed_params {
8488            url.push_str(&format!("{}={}&", key, value));
8489        }
8490
8491        let mut headers = HeaderMap::new();
8492        auth.add_auth_headers_reqwest(&mut headers);
8493
8494        let data = self.base().http_client.get(&url, Some(headers)).await?;
8495
8496        let trades_array = data.as_array().ok_or_else(|| {
8497            Error::from(ParseError::invalid_format(
8498                "data",
8499                "Expected array of trades",
8500            ))
8501        })?;
8502
8503        let mut trades = Vec::new();
8504        for trade_data in trades_array {
8505            match parser::parse_trade(trade_data, Some(&market)) {
8506                Ok(trade) => trades.push(trade),
8507                Err(e) => {
8508                    warn!(error = %e, "Failed to parse trade");
8509                }
8510            }
8511        }
8512
8513        Ok(trades)
8514    }
8515
8516    /// Fetch canceled orders
8517    ///
8518    /// # Arguments
8519    ///
8520    /// * `symbol` - Trading pair symbol (required)
8521    /// * `since` - Optional start timestamp in milliseconds
8522    /// * `limit` - Optional maximum number of records
8523    ///
8524    /// # Returns
8525    ///
8526    /// Returns list of canceled orders
8527    ///
8528    /// # Notes
8529    ///
8530    /// - `symbol` parameter is required
8531    /// - Calls fetch_orders to get all orders, then filters for canceled status
8532    /// - `limit` parameter is applied client-side (fetch all orders first, then take first N)
8533    /// - Implementation logic matches Go version
8534    ///
8535    /// # Examples
8536    ///
8537    /// ```no_run
8538    /// # use ccxt_exchanges::binance::Binance;
8539    /// # use ccxt_core::ExchangeConfig;
8540    /// # async fn example() -> ccxt_core::Result<()> {
8541    /// let mut config = ExchangeConfig::default();
8542    /// config.api_key = Some("your-api-key".to_string());
8543    /// config.secret = Some("your-secret".to_string());
8544    /// let binance = Binance::new(config)?;
8545    ///
8546    /// let canceled_orders = binance.fetch_canceled_orders("BTC/USDT", None, Some(10)).await?;
8547    /// println!("Found {} canceled orders", canceled_orders.len());
8548    /// # Ok(())
8549    /// # }
8550    /// ```
8551    pub async fn fetch_canceled_orders(
8552        &self,
8553        symbol: &str,
8554        since: Option<u64>,
8555        limit: Option<u32>,
8556    ) -> Result<Vec<Order>> {
8557        let all_orders = self.fetch_orders(Some(symbol), since, None).await?;
8558
8559        let mut canceled_orders: Vec<Order> = all_orders
8560            .into_iter()
8561            .filter(|order| order.status == OrderStatus::Canceled)
8562            .collect();
8563
8564        if let Some(since_time) = since {
8565            canceled_orders.retain(|order| {
8566                order
8567                    .timestamp
8568                    .map(|ts| ts >= since_time as i64)
8569                    .unwrap_or(true)
8570            });
8571        }
8572
8573        if let Some(limit_val) = limit {
8574            canceled_orders.truncate(limit_val as usize);
8575        }
8576
8577        Ok(canceled_orders)
8578    }
8579
8580    /// Create market buy order by cost amount
8581    ///
8582    /// # Arguments
8583    ///
8584    /// * `symbol` - Trading pair symbol
8585    /// * `cost` - Amount in quote currency to spend (e.g., spend 100 USDT to buy BTC)
8586    /// * `params` - Optional additional parameters
8587    ///
8588    /// # Returns
8589    ///
8590    /// Returns created order information
8591    ///
8592    /// # Notes
8593    ///
8594    /// - Only supports spot markets
8595    /// - `cost` parameter is converted to Binance API's `quoteOrderQty` parameter
8596    /// - This is a convenience method to simplify market buy order creation
8597    /// - Internally calls create_order method
8598    ///
8599    /// # Examples
8600    ///
8601    /// ```no_run
8602    /// # use ccxt_exchanges::binance::Binance;
8603    /// # use ccxt_core::ExchangeConfig;
8604    /// # async fn example() -> ccxt_core::Result<()> {
8605    /// let mut config = ExchangeConfig::default();
8606    /// config.api_key = Some("your-api-key".to_string());
8607    /// config.secret = Some("your-secret".to_string());
8608    /// let binance = Binance::new(config)?;
8609    ///
8610    /// // Spend 100 USDT to buy BTC (market order)
8611    /// let order = binance.create_market_buy_order_with_cost("BTC/USDT", 100.0, None).await?;
8612    /// println!("Order created: {:?}", order);
8613    /// # Ok(())
8614    /// # }
8615    /// ```
8616    pub async fn create_market_buy_order_with_cost(
8617        &self,
8618        symbol: &str,
8619        cost: f64,
8620        params: Option<HashMap<String, String>>,
8621    ) -> Result<Order> {
8622        self.check_required_credentials()?;
8623
8624        let market = self.base().market(symbol).await?;
8625        if !market.is_spot() {
8626            return Err(Error::not_implemented(
8627                "create_market_buy_order_with_cost() supports spot orders only",
8628            ));
8629        }
8630
8631        let mut order_params = params.unwrap_or_default();
8632        order_params.insert("cost".to_string(), cost.to_string());
8633
8634        // The create_order method detects the cost parameter and converts it to quoteOrderQty
8635        self.create_order(
8636            symbol,
8637            OrderType::Market,
8638            OrderSide::Buy,
8639            cost,
8640            None,
8641            Some(order_params),
8642        )
8643        .await
8644    }
8645}
8646
8647#[cfg(test)]
8648mod tests {
8649    use super::*;
8650    use ccxt_core::ExchangeConfig;
8651
8652    #[tokio::test]
8653    #[ignore] // Requires actual API connection
8654    async fn test_fetch_markets() {
8655        let config = ExchangeConfig::default();
8656        let binance = Binance::new(config).unwrap();
8657
8658        let markets = binance.fetch_markets().await;
8659        assert!(markets.is_ok());
8660
8661        let markets = markets.unwrap();
8662        assert!(!markets.is_empty());
8663    }
8664
8665    #[tokio::test]
8666    #[ignore] // Requires actual API connection
8667    async fn test_fetch_ticker() {
8668        let config = ExchangeConfig::default();
8669        let binance = Binance::new(config).unwrap();
8670
8671        let _ = binance.fetch_markets().await;
8672
8673        let ticker = binance
8674            .fetch_ticker("BTC/USDT", ccxt_core::types::TickerParams::default())
8675            .await;
8676        assert!(ticker.is_ok());
8677
8678        let ticker = ticker.unwrap();
8679        assert_eq!(ticker.symbol, "BTC/USDT");
8680        assert!(ticker.last.is_some());
8681    }
8682
8683    #[tokio::test]
8684    #[ignore] // Requires actual API connection
8685    async fn test_fetch_order_book() {
8686        let config = ExchangeConfig::default();
8687        let binance = Binance::new(config).unwrap();
8688
8689        let _ = binance.fetch_markets().await;
8690
8691        let orderbook = binance.fetch_order_book("BTC/USDT", Some(10)).await;
8692        assert!(orderbook.is_ok());
8693
8694        let orderbook = orderbook.unwrap();
8695        assert_eq!(orderbook.symbol, "BTC/USDT");
8696        assert!(!orderbook.bids.is_empty());
8697        assert!(!orderbook.asks.is_empty());
8698    }
8699
8700    #[tokio::test]
8701    #[ignore] // Requires actual API connection
8702    async fn test_fetch_trades() {
8703        let config = ExchangeConfig::default();
8704        let binance = Binance::new(config).unwrap();
8705
8706        let _ = binance.fetch_markets().await;
8707
8708        let trades = binance.fetch_trades("BTC/USDT", Some(10)).await;
8709        assert!(trades.is_ok());
8710
8711        let trades = trades.unwrap();
8712        assert!(!trades.is_empty());
8713        assert!(trades.len() <= 10);
8714    }
8715}