ccxt_exchanges/binance/rest/
delivery.rs

1//! Binance delivery futures (DAPI) operations.
2//!
3//! This module provides DAPI (coin-margined delivery futures) specific methods
4//! that either don't have direct equivalents in the unified `futures` module
5//! or require DAPI-specific implementations.
6//!
7//! # Overview
8//!
9//! The DAPI (Delivery API) is Binance's API for coin-margined futures contracts,
10//! where contracts are settled in the base currency (e.g., BTC/USD:BTC is settled in BTC).
11//! This differs from FAPI (Futures API) which handles USDT-margined contracts.
12//!
13//! # DAPI vs FAPI Differences
14//!
15//! | Feature | FAPI (Linear) | DAPI (Inverse) |
16//! |---------|---------------|----------------|
17//! | Settlement | USDT | Base currency (BTC, ETH, etc.) |
18//! | Symbol format | BTC/USDT:USDT | BTC/USD:BTC |
19//! | Base URL | /fapi/v1/* | /dapi/v1/* |
20//! | Margin type | Quote currency | Base currency |
21//! | PnL calculation | In USDT | In base currency |
22//!
23//! # Automatic Routing
24//!
25//! Most DAPI operations are handled automatically by the methods in the `futures` module.
26//! When you call methods like `fetch_position`, `set_leverage`, or `fetch_funding_rate`
27//! with a coin-margined symbol (e.g., "BTC/USD:BTC"), the implementation automatically
28//! routes to the appropriate DAPI endpoint based on the market's `inverse` flag.
29//!
30//! # Available Methods
31//!
32//! This module provides the following DAPI-specific methods:
33//!
34//! | Method | Description | Endpoint |
35//! |--------|-------------|----------|
36//! | [`set_position_mode_dapi`](Binance::set_position_mode_dapi) | Set hedge/one-way mode | `/dapi/v1/positionSide/dual` |
37//! | [`fetch_position_mode_dapi`](Binance::fetch_position_mode_dapi) | Get current position mode | `/dapi/v1/positionSide/dual` |
38//! | [`fetch_dapi_account`](Binance::fetch_dapi_account) | Get account information | `/dapi/v1/account` |
39//! | [`fetch_dapi_income`](Binance::fetch_dapi_income) | Get income history | `/dapi/v1/income` |
40//! | [`fetch_dapi_commission_rate`](Binance::fetch_dapi_commission_rate) | Get commission rates | `/dapi/v1/commissionRate` |
41//! | [`fetch_dapi_adl_quantile`](Binance::fetch_dapi_adl_quantile) | Get ADL quantile | `/dapi/v1/adlQuantile` |
42//! | [`fetch_dapi_force_orders`](Binance::fetch_dapi_force_orders) | Get force liquidation orders | `/dapi/v1/forceOrders` |
43//!
44//! # DAPI-Specific vs Unified Methods
45//!
46//! - **DAPI-specific**: Methods in this module (prefixed with `dapi_` or suffixed with `_dapi`)
47//!   are exclusively for coin-margined futures and use DAPI endpoints directly.
48//! - **Unified**: Methods in the `futures` module automatically route to FAPI or DAPI
49//!   based on the symbol's market type (linear vs inverse).
50//!
51//! # Authentication
52//!
53//! All methods in this module require authentication. You must provide valid API credentials
54//! when creating the Binance instance.
55//!
56//! # Example
57//!
58//! ```no_run
59//! # use ccxt_exchanges::binance::Binance;
60//! # use ccxt_core::ExchangeConfig;
61//! # async fn example() -> ccxt_core::Result<()> {
62//! let mut config = ExchangeConfig::default();
63//! config.api_key = Some("your_api_key".to_string());
64//! config.secret = Some("your_secret".to_string());
65//! let binance = Binance::new_swap(config)?;
66//!
67//! // Fetch DAPI position mode
68//! let is_hedge_mode = binance.fetch_position_mode_dapi(None).await?;
69//! println!("DAPI Hedge mode: {}", is_hedge_mode);
70//!
71//! // Set DAPI position mode to hedge mode
72//! let result = binance.set_position_mode_dapi(true, None).await?;
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! # Error Handling
78//!
79//! All methods return `Result<T>` and can fail with the following error types:
80//!
81//! - **AuthenticationError**: Missing or invalid API credentials
82//! - **ExchangeError**: API returned an error (e.g., invalid symbol, insufficient balance)
83//! - **NetworkError**: Connection or timeout issues
84//! - **ParseError**: Failed to parse API response
85//!
86//! ```no_run
87//! # use ccxt_exchanges::binance::Binance;
88//! # use ccxt_core::{ExchangeConfig, Error};
89//! # async fn example() -> ccxt_core::Result<()> {
90//! let config = ExchangeConfig::default(); // No credentials
91//! let binance = Binance::new_swap(config)?;
92//!
93//! // This will fail with AuthenticationError
94//! match binance.fetch_dapi_account(None).await {
95//!     Ok(account) => println!("Account: {:?}", account),
96//!     Err(e) => {
97//!         eprintln!("Error fetching account: {}", e);
98//!         // Handle specific error types
99//!     }
100//! }
101//! # Ok(())
102//! # }
103//! ```
104
105use super::super::Binance;
106use super::super::signed_request::HttpMethod;
107use ccxt_core::{Error, ParseError, Result};
108use serde_json::Value;
109
110impl Binance {
111    // ==================== DAPI Position Mode Methods ====================
112
113    /// Set position mode for coin-margined futures (DAPI).
114    ///
115    /// This method sets the position mode (hedge mode or one-way mode) specifically
116    /// for coin-margined (inverse) futures contracts using the DAPI endpoint.
117    ///
118    /// # Arguments
119    ///
120    /// * `dual_side` - `true` for hedge mode (dual-side position), `false` for one-way mode.
121    /// * `params` - Optional additional parameters to include in the request.
122    ///
123    /// # Returns
124    ///
125    /// Returns the API response as a JSON `Value`.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if:
130    /// - Authentication credentials are missing
131    /// - The API request fails
132    /// - The response cannot be parsed
133    ///
134    /// # Example
135    ///
136    /// ```no_run
137    /// # use ccxt_exchanges::binance::Binance;
138    /// # use ccxt_core::ExchangeConfig;
139    /// # async fn example() -> ccxt_core::Result<()> {
140    /// let mut config = ExchangeConfig::default();
141    /// config.api_key = Some("your_api_key".to_string());
142    /// config.secret = Some("your_secret".to_string());
143    /// let binance = Binance::new_swap(config)?;
144    ///
145    /// // Enable hedge mode for DAPI
146    /// let result = binance.set_position_mode_dapi(true, None).await?;
147    /// println!("Result: {:?}", result);
148    ///
149    /// // Switch back to one-way mode for DAPI
150    /// let result = binance.set_position_mode_dapi(false, None).await?;
151    /// # Ok(())
152    /// # }
153    /// ```
154    ///
155    /// # Error Handling Example
156    ///
157    /// ```no_run
158    /// # use ccxt_exchanges::binance::Binance;
159    /// # use ccxt_core::ExchangeConfig;
160    /// # async fn example() -> ccxt_core::Result<()> {
161    /// let mut config = ExchangeConfig::default();
162    /// config.api_key = Some("your_api_key".to_string());
163    /// config.secret = Some("your_secret".to_string());
164    /// let binance = Binance::new_swap(config)?;
165    ///
166    /// match binance.set_position_mode_dapi(true, None).await {
167    ///     Ok(result) => println!("Position mode set successfully: {:?}", result),
168    ///     Err(e) => {
169    ///         eprintln!("Failed to set position mode: {}", e);
170    ///         // Handle error - could be auth error, network error, or exchange error
171    ///     }
172    /// }
173    /// # Ok(())
174    /// # }
175    /// ```
176    pub async fn set_position_mode_dapi(
177        &self,
178        dual_side: bool,
179        params: Option<Value>,
180    ) -> Result<Value> {
181        // Use DAPI endpoint for coin-margined futures
182        let url = format!("{}/positionSide/dual", self.urls().dapi_private);
183
184        self.signed_request(url)
185            .method(HttpMethod::Post)
186            .param("dualSidePosition", dual_side)
187            .merge_json_params(params)
188            .execute()
189            .await
190    }
191
192    /// Fetch current position mode for coin-margined futures (DAPI).
193    ///
194    /// This method retrieves the current position mode (hedge mode or one-way mode)
195    /// specifically for coin-margined (inverse) futures contracts using the DAPI endpoint.
196    ///
197    /// # Arguments
198    ///
199    /// * `params` - Optional additional parameters to include in the request.
200    ///
201    /// # Returns
202    ///
203    /// Returns the current position mode:
204    /// - `true`: Hedge mode (dual-side position) is enabled.
205    /// - `false`: One-way mode is enabled.
206    ///
207    /// # Errors
208    ///
209    /// Returns an error if:
210    /// - Authentication credentials are missing
211    /// - The API request fails
212    /// - The response cannot be parsed
213    ///
214    /// # Example
215    ///
216    /// ```no_run
217    /// # use ccxt_exchanges::binance::Binance;
218    /// # use ccxt_core::ExchangeConfig;
219    /// # async fn example() -> ccxt_core::Result<()> {
220    /// let mut config = ExchangeConfig::default();
221    /// config.api_key = Some("your_api_key".to_string());
222    /// config.secret = Some("your_secret".to_string());
223    /// let binance = Binance::new_swap(config)?;
224    ///
225    /// let is_hedge_mode = binance.fetch_position_mode_dapi(None).await?;
226    /// if is_hedge_mode {
227    ///     println!("DAPI is in hedge mode (dual-side positions)");
228    /// } else {
229    ///     println!("DAPI is in one-way mode");
230    /// }
231    /// # Ok(())
232    /// # }
233    /// ```
234    ///
235    /// # Error Handling Example
236    ///
237    /// ```no_run
238    /// # use ccxt_exchanges::binance::Binance;
239    /// # use ccxt_core::ExchangeConfig;
240    /// # async fn example() -> ccxt_core::Result<()> {
241    /// let mut config = ExchangeConfig::default();
242    /// config.api_key = Some("your_api_key".to_string());
243    /// config.secret = Some("your_secret".to_string());
244    /// let binance = Binance::new_swap(config)?;
245    ///
246    /// match binance.fetch_position_mode_dapi(None).await {
247    ///     Ok(is_hedge) => {
248    ///         let mode = if is_hedge { "hedge" } else { "one-way" };
249    ///         println!("Current DAPI position mode: {}", mode);
250    ///     }
251    ///     Err(e) => eprintln!("Failed to fetch position mode: {}", e),
252    /// }
253    /// # Ok(())
254    /// # }
255    /// ```
256    pub async fn fetch_position_mode_dapi(&self, params: Option<Value>) -> Result<bool> {
257        // Use DAPI endpoint for coin-margined futures
258        let url = format!("{}/positionSide/dual", self.urls().dapi_private);
259
260        let data = self
261            .signed_request(url)
262            .merge_json_params(params)
263            .execute()
264            .await?;
265
266        // Parse the response to extract the dualSidePosition value
267        if let Some(dual_side) = data.get("dualSidePosition") {
268            if let Some(value) = dual_side.as_bool() {
269                return Ok(value);
270            }
271            if let Some(value_str) = dual_side.as_str() {
272                return Ok(value_str.to_lowercase() == "true");
273            }
274        }
275
276        Err(Error::from(ParseError::invalid_format(
277            "data",
278            "Failed to parse DAPI position mode response: missing or invalid dualSidePosition field",
279        )))
280    }
281
282    // ==================== DAPI Account Methods ====================
283
284    /// Fetch coin-margined futures account information.
285    ///
286    /// This method retrieves account information specifically for coin-margined (inverse)
287    /// futures contracts using the DAPI endpoint. The response includes wallet balance,
288    /// unrealized profit, margin balance, available balance, and position information.
289    ///
290    /// # Arguments
291    ///
292    /// * `params` - Optional additional parameters to include in the request.
293    ///
294    /// # Returns
295    ///
296    /// Returns the account information as a raw JSON `Value`. The response includes:
297    /// - `totalWalletBalance`: Total wallet balance
298    /// - `totalUnrealizedProfit`: Total unrealized profit
299    /// - `totalMarginBalance`: Total margin balance
300    /// - `availableBalance`: Available balance for new positions
301    /// - `maxWithdrawAmount`: Maximum withdraw amount
302    /// - `assets`: List of asset balances
303    /// - `positions`: List of positions
304    ///
305    /// # Errors
306    ///
307    /// Returns an error if:
308    /// - Authentication credentials are missing
309    /// - The API request fails
310    /// - The response cannot be parsed
311    ///
312    /// # Example
313    ///
314    /// ```no_run
315    /// # use ccxt_exchanges::binance::Binance;
316    /// # use ccxt_core::ExchangeConfig;
317    /// # async fn example() -> ccxt_core::Result<()> {
318    /// let mut config = ExchangeConfig::default();
319    /// config.api_key = Some("your_api_key".to_string());
320    /// config.secret = Some("your_secret".to_string());
321    /// let binance = Binance::new_swap(config)?;
322    ///
323    /// // Fetch DAPI account information
324    /// let account = binance.fetch_dapi_account(None).await?;
325    /// println!("Account: {:?}", account);
326    ///
327    /// // Access specific fields
328    /// if let Some(balance) = account.get("totalWalletBalance") {
329    ///     println!("Total wallet balance: {}", balance);
330    /// }
331    /// if let Some(positions) = account.get("positions") {
332    ///     println!("Positions: {:?}", positions);
333    /// }
334    /// # Ok(())
335    /// # }
336    /// ```
337    ///
338    /// # Error Handling Example
339    ///
340    /// ```no_run
341    /// # use ccxt_exchanges::binance::Binance;
342    /// # use ccxt_core::ExchangeConfig;
343    /// # async fn example() -> ccxt_core::Result<()> {
344    /// let mut config = ExchangeConfig::default();
345    /// config.api_key = Some("your_api_key".to_string());
346    /// config.secret = Some("your_secret".to_string());
347    /// let binance = Binance::new_swap(config)?;
348    ///
349    /// match binance.fetch_dapi_account(None).await {
350    ///     Ok(account) => {
351    ///         // Process account data
352    ///         if let Some(balance) = account.get("totalWalletBalance") {
353    ///             println!("Wallet balance: {}", balance);
354    ///         }
355    ///     }
356    ///     Err(e) => {
357    ///         eprintln!("Failed to fetch DAPI account: {}", e);
358    ///         // Could be authentication error, network error, or API error
359    ///     }
360    /// }
361    /// # Ok(())
362    /// # }
363    /// ```
364    pub async fn fetch_dapi_account(&self, params: Option<Value>) -> Result<Value> {
365        // Use DAPI endpoint for coin-margined futures account
366        let url = format!("{}/account", self.urls().dapi_private);
367
368        let data = self
369            .signed_request(url)
370            .merge_json_params(params)
371            .execute()
372            .await?;
373
374        // Check for API errors in response
375        if let Some(code) = data.get("code") {
376            if let Some(msg) = data.get("msg") {
377                return Err(Error::exchange(
378                    code.to_string(),
379                    msg.as_str().unwrap_or("Unknown error").to_string(),
380                ));
381            }
382        }
383
384        Ok(data)
385    }
386
387    // ==================== DAPI Income History Methods ====================
388
389    /// Fetch income history for coin-margined futures.
390    ///
391    /// This method retrieves income history specifically for coin-margined (inverse)
392    /// futures contracts using the DAPI endpoint. Income types include realized PnL,
393    /// funding fees, commissions, and other transaction types.
394    ///
395    /// # Arguments
396    ///
397    /// * `symbol` - Optional symbol to filter by (e.g., "BTC/USD:BTC").
398    /// * `income_type` - Optional income type to filter by. Valid values:
399    ///   - `"TRANSFER"`: Transfer in/out
400    ///   - `"WELCOME_BONUS"`: Welcome bonus
401    ///   - `"REALIZED_PNL"`: Realized profit and loss
402    ///   - `"FUNDING_FEE"`: Funding fee
403    ///   - `"COMMISSION"`: Trading commission
404    ///   - `"INSURANCE_CLEAR"`: Insurance fund clear
405    ///   - `"REFERRAL_KICKBACK"`: Referral kickback
406    ///   - `"COMMISSION_REBATE"`: Commission rebate
407    ///   - `"DELIVERED_SETTELMENT"`: Delivered settlement
408    /// * `since` - Optional start timestamp in milliseconds.
409    /// * `limit` - Optional record limit (default 100, max 1000).
410    /// * `params` - Optional additional parameters. Supports:
411    ///   - `endTime`: End timestamp in milliseconds.
412    ///
413    /// # Returns
414    ///
415    /// Returns income history records as a raw JSON `Value` array. Each record includes:
416    /// - `symbol`: Trading pair symbol
417    /// - `incomeType`: Type of income
418    /// - `income`: Income amount
419    /// - `asset`: Asset currency
420    /// - `info`: Additional information
421    /// - `time`: Timestamp
422    /// - `tranId`: Transaction ID
423    /// - `tradeId`: Trade ID (if applicable)
424    ///
425    /// # Errors
426    ///
427    /// Returns an error if:
428    /// - Authentication credentials are missing
429    /// - The API request fails
430    /// - The response cannot be parsed
431    ///
432    /// # Example
433    ///
434    /// ```no_run
435    /// # use ccxt_exchanges::binance::Binance;
436    /// # use ccxt_core::ExchangeConfig;
437    /// # use serde_json::json;
438    /// # async fn example() -> ccxt_core::Result<()> {
439    /// let mut config = ExchangeConfig::default();
440    /// config.api_key = Some("your_api_key".to_string());
441    /// config.secret = Some("your_secret".to_string());
442    /// let binance = Binance::new_swap(config)?;
443    ///
444    /// // Fetch all income history
445    /// let income = binance.fetch_dapi_income(None, None, None, None, None).await?;
446    /// println!("Income history: {:?}", income);
447    ///
448    /// // Fetch funding fees for a specific symbol
449    /// let funding_fees = binance.fetch_dapi_income(
450    ///     Some("BTC/USD:BTC"),
451    ///     Some("FUNDING_FEE"),
452    ///     None,
453    ///     Some(100),
454    ///     None,
455    /// ).await?;
456    ///
457    /// // Fetch income with time range
458    /// let params = json!({
459    ///     "endTime": 1704067200000_i64
460    /// });
461    /// let income = binance.fetch_dapi_income(
462    ///     None,
463    ///     None,
464    ///     Some(1703980800000),  // since
465    ///     Some(50),             // limit
466    ///     Some(params),
467    /// ).await?;
468    /// # Ok(())
469    /// # }
470    /// ```
471    ///
472    /// # Error Handling Example
473    ///
474    /// ```no_run
475    /// # use ccxt_exchanges::binance::Binance;
476    /// # use ccxt_core::ExchangeConfig;
477    /// # async fn example() -> ccxt_core::Result<()> {
478    /// let mut config = ExchangeConfig::default();
479    /// config.api_key = Some("your_api_key".to_string());
480    /// config.secret = Some("your_secret".to_string());
481    /// let binance = Binance::new_swap(config)?;
482    ///
483    /// match binance.fetch_dapi_income(None, Some("FUNDING_FEE"), None, Some(100), None).await {
484    ///     Ok(income) => {
485    ///         if let Some(records) = income.as_array() {
486    ///             println!("Found {} income records", records.len());
487    ///             for record in records {
488    ///                 println!("Income: {:?}", record.get("income"));
489    ///             }
490    ///         }
491    ///     }
492    ///     Err(e) => eprintln!("Failed to fetch income history: {}", e),
493    /// }
494    /// # Ok(())
495    /// # }
496    /// ```
497    pub async fn fetch_dapi_income(
498        &self,
499        symbol: Option<&str>,
500        income_type: Option<&str>,
501        since: Option<i64>,
502        limit: Option<u32>,
503        params: Option<Value>,
504    ) -> Result<Value> {
505        // Use DAPI endpoint for coin-margined futures income
506        let url = format!("{}/income", self.urls().dapi_private);
507
508        let mut builder = self.signed_request(url);
509
510        // Add symbol parameter if provided
511        if let Some(sym) = symbol {
512            // Load markets to convert unified symbol to exchange symbol
513            self.load_markets(false).await?;
514            let market = self.base().market(sym).await?;
515            builder = builder.param("symbol", &market.id);
516        }
517
518        // Add income type filter if provided
519        if let Some(income_t) = income_type {
520            builder = builder.param("incomeType", income_t);
521        }
522
523        let data = builder
524            .optional_param("startTime", since)
525            .optional_param("limit", limit)
526            .merge_json_params(params)
527            .execute()
528            .await?;
529
530        // Check for API errors in response
531        if let Some(code) = data.get("code") {
532            if let Some(msg) = data.get("msg") {
533                return Err(Error::exchange(
534                    code.to_string(),
535                    msg.as_str().unwrap_or("Unknown error").to_string(),
536                ));
537            }
538        }
539
540        Ok(data)
541    }
542
543    // ==================== DAPI Commission Rate Methods ====================
544
545    /// Fetch commission rate for a coin-margined futures symbol.
546    ///
547    /// This method retrieves the maker and taker commission rates for a specific
548    /// coin-margined (inverse) futures symbol using the DAPI endpoint.
549    ///
550    /// # Arguments
551    ///
552    /// * `symbol` - Trading pair symbol (e.g., "BTC/USD:BTC"). This parameter is required.
553    /// * `params` - Optional additional parameters to include in the request.
554    ///
555    /// # Returns
556    ///
557    /// Returns the commission rate information as a raw JSON `Value`. The response includes:
558    /// - `symbol`: Trading pair symbol
559    /// - `makerCommissionRate`: Maker commission rate (e.g., "0.0002")
560    /// - `takerCommissionRate`: Taker commission rate (e.g., "0.0004")
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if:
565    /// - Authentication credentials are missing
566    /// - The symbol parameter is invalid or not found
567    /// - The API request fails
568    /// - The response cannot be parsed
569    ///
570    /// # Example
571    ///
572    /// ```no_run
573    /// # use ccxt_exchanges::binance::Binance;
574    /// # use ccxt_core::ExchangeConfig;
575    /// # async fn example() -> ccxt_core::Result<()> {
576    /// let mut config = ExchangeConfig::default();
577    /// config.api_key = Some("your_api_key".to_string());
578    /// config.secret = Some("your_secret".to_string());
579    /// let binance = Binance::new_swap(config)?;
580    ///
581    /// // Fetch commission rate for BTC/USD:BTC
582    /// let commission = binance.fetch_dapi_commission_rate("BTC/USD:BTC", None).await?;
583    /// println!("Commission rate: {:?}", commission);
584    ///
585    /// // Access specific fields
586    /// if let Some(maker_rate) = commission.get("makerCommissionRate") {
587    ///     println!("Maker rate: {}", maker_rate);
588    /// }
589    /// if let Some(taker_rate) = commission.get("takerCommissionRate") {
590    ///     println!("Taker rate: {}", taker_rate);
591    /// }
592    /// # Ok(())
593    /// # }
594    /// ```
595    ///
596    /// # Error Handling Example
597    ///
598    /// ```no_run
599    /// # use ccxt_exchanges::binance::Binance;
600    /// # use ccxt_core::ExchangeConfig;
601    /// # async fn example() -> ccxt_core::Result<()> {
602    /// let mut config = ExchangeConfig::default();
603    /// config.api_key = Some("your_api_key".to_string());
604    /// config.secret = Some("your_secret".to_string());
605    /// let binance = Binance::new_swap(config)?;
606    ///
607    /// match binance.fetch_dapi_commission_rate("BTC/USD:BTC", None).await {
608    ///     Ok(commission) => {
609    ///         let maker = commission.get("makerCommissionRate")
610    ///             .and_then(serde_json::Value::as_str)
611    ///             .unwrap_or("N/A");
612    ///         let taker = commission.get("takerCommissionRate")
613    ///             .and_then(serde_json::Value::as_str)
614    ///             .unwrap_or("N/A");
615    ///         println!("Maker: {}, Taker: {}", maker, taker);
616    ///     }
617    ///     Err(e) => eprintln!("Failed to fetch commission rate: {}", e),
618    /// }
619    /// # Ok(())
620    /// # }
621    /// ```
622    pub async fn fetch_dapi_commission_rate(
623        &self,
624        symbol: &str,
625        params: Option<Value>,
626    ) -> Result<Value> {
627        // Load markets to convert unified symbol to exchange symbol
628        self.load_markets(false).await?;
629        let market = self.base().market(symbol).await?;
630
631        // Use DAPI endpoint for coin-margined futures commission rate
632        let url = format!("{}/commissionRate", self.urls().dapi_private);
633
634        let data = self
635            .signed_request(url)
636            .param("symbol", &market.id)
637            .merge_json_params(params)
638            .execute()
639            .await?;
640
641        // Check for API errors in response
642        if let Some(code) = data.get("code") {
643            if let Some(msg) = data.get("msg") {
644                return Err(Error::exchange(
645                    code.to_string(),
646                    msg.as_str().unwrap_or("Unknown error").to_string(),
647                ));
648            }
649        }
650
651        Ok(data)
652    }
653
654    // ==================== DAPI ADL Quantile Methods ====================
655
656    /// Fetch ADL (Auto-Deleveraging) quantile for coin-margined futures.
657    ///
658    /// This method retrieves the ADL quantile information for coin-margined (inverse)
659    /// futures positions using the DAPI endpoint. The ADL quantile indicates the
660    /// priority of a position for auto-deleveraging during extreme market conditions.
661    ///
662    /// A higher quantile means a higher priority for auto-deleveraging. Traders with
663    /// profitable positions and high leverage are more likely to be auto-deleveraged
664    /// when the insurance fund is insufficient to cover liquidation losses.
665    ///
666    /// # Arguments
667    ///
668    /// * `symbol` - Optional symbol to filter by (e.g., "BTC/USD:BTC"). If not provided,
669    ///   returns ADL quantile for all positions.
670    /// * `params` - Optional additional parameters to include in the request.
671    ///
672    /// # Returns
673    ///
674    /// Returns the ADL quantile information as a raw JSON `Value`. The response includes:
675    /// - `symbol`: Trading pair symbol
676    /// - `adlQuantile`: ADL quantile object containing:
677    ///   - `LONG`: Quantile for long positions (1-5, where 5 is highest priority)
678    ///   - `SHORT`: Quantile for short positions (1-5, where 5 is highest priority)
679    ///   - `HEDGE`: Quantile for hedge mode positions (if applicable)
680    ///
681    /// # Errors
682    ///
683    /// Returns an error if:
684    /// - Authentication credentials are missing
685    /// - The symbol parameter is invalid or not found (if provided)
686    /// - The API request fails
687    /// - The response cannot be parsed
688    ///
689    /// # Example
690    ///
691    /// ```no_run
692    /// # use ccxt_exchanges::binance::Binance;
693    /// # use ccxt_core::ExchangeConfig;
694    /// # async fn example() -> ccxt_core::Result<()> {
695    /// let mut config = ExchangeConfig::default();
696    /// config.api_key = Some("your_api_key".to_string());
697    /// config.secret = Some("your_secret".to_string());
698    /// let binance = Binance::new_swap(config)?;
699    ///
700    /// // Fetch ADL quantile for all positions
701    /// let adl = binance.fetch_dapi_adl_quantile(None, None).await?;
702    /// println!("ADL quantile: {:?}", adl);
703    ///
704    /// // Fetch ADL quantile for a specific symbol
705    /// let adl = binance.fetch_dapi_adl_quantile(Some("BTC/USD:BTC"), None).await?;
706    /// println!("BTC ADL quantile: {:?}", adl);
707    ///
708    /// // Access specific fields from the response
709    /// if let Some(arr) = adl.as_array() {
710    ///     for item in arr {
711    ///         if let Some(symbol) = item.get("symbol") {
712    ///             println!("Symbol: {}", symbol);
713    ///         }
714    ///         if let Some(quantile) = item.get("adlQuantile") {
715    ///             if let Some(long_q) = quantile.get("LONG") {
716    ///                 println!("Long ADL quantile: {}", long_q);
717    ///             }
718    ///             if let Some(short_q) = quantile.get("SHORT") {
719    ///                 println!("Short ADL quantile: {}", short_q);
720    ///             }
721    ///         }
722    ///     }
723    /// }
724    /// # Ok(())
725    /// # }
726    /// ```
727    ///
728    /// # Error Handling Example
729    ///
730    /// ```no_run
731    /// # use ccxt_exchanges::binance::Binance;
732    /// # use ccxt_core::ExchangeConfig;
733    /// # async fn example() -> ccxt_core::Result<()> {
734    /// let mut config = ExchangeConfig::default();
735    /// config.api_key = Some("your_api_key".to_string());
736    /// config.secret = Some("your_secret".to_string());
737    /// let binance = Binance::new_swap(config)?;
738    ///
739    /// match binance.fetch_dapi_adl_quantile(Some("BTC/USD:BTC"), None).await {
740    ///     Ok(adl) => {
741    ///         // Check ADL risk level
742    ///         if let Some(arr) = adl.as_array() {
743    ///             for item in arr {
744    ///                 if let Some(quantile) = item.get("adlQuantile") {
745    ///                     if let Some(long_q) = quantile.get("LONG").and_then(serde_json::Value::as_i64) {
746    ///                         if long_q >= 4 {
747    ///                             println!("Warning: High ADL risk for long position!");
748    ///                         }
749    ///                     }
750    ///                 }
751    ///             }
752    ///         }
753    ///     }
754    ///     Err(e) => eprintln!("Failed to fetch ADL quantile: {}", e),
755    /// }
756    /// # Ok(())
757    /// # }
758    /// ```
759    pub async fn fetch_dapi_adl_quantile(
760        &self,
761        symbol: Option<&str>,
762        params: Option<Value>,
763    ) -> Result<Value> {
764        // Use DAPI endpoint for coin-margined futures ADL quantile
765        let url = format!("{}/adlQuantile", self.urls().dapi_private);
766
767        let mut builder = self.signed_request(url);
768
769        // Add symbol parameter if provided
770        if let Some(sym) = symbol {
771            // Load markets to convert unified symbol to exchange symbol
772            self.load_markets(false).await?;
773            let market = self.base().market(sym).await?;
774            builder = builder.param("symbol", &market.id);
775        }
776
777        let data = builder.merge_json_params(params).execute().await?;
778
779        // Check for API errors in response
780        if let Some(code) = data.get("code") {
781            if let Some(msg) = data.get("msg") {
782                return Err(Error::exchange(
783                    code.to_string(),
784                    msg.as_str().unwrap_or("Unknown error").to_string(),
785                ));
786            }
787        }
788
789        Ok(data)
790    }
791
792    // ==================== DAPI Force Orders Methods ====================
793
794    /// Fetch force liquidation orders for coin-margined futures.
795    ///
796    /// This method retrieves force liquidation orders (liquidations and auto-deleveraging)
797    /// for coin-margined (inverse) futures contracts using the DAPI endpoint.
798    ///
799    /// Force orders occur when:
800    /// - **Liquidation**: A position is forcibly closed due to insufficient margin
801    /// - **ADL (Auto-Deleveraging)**: A profitable position is reduced to cover losses
802    ///   from liquidated positions when the insurance fund is insufficient
803    ///
804    /// # Arguments
805    ///
806    /// * `symbol` - Optional symbol to filter by (e.g., "BTC/USD:BTC").
807    /// * `auto_close_type` - Optional auto-close type to filter by. Valid values:
808    ///   - `"LIQUIDATION"`: Liquidation orders
809    ///   - `"ADL"`: Auto-deleveraging orders
810    /// * `since` - Optional start timestamp in milliseconds.
811    /// * `limit` - Optional record limit (default 50, max 100).
812    /// * `params` - Optional additional parameters. Supports:
813    ///   - `endTime`: End timestamp in milliseconds.
814    ///
815    /// # Returns
816    ///
817    /// Returns force liquidation order records as a raw JSON `Value` array. Each record includes:
818    /// - `orderId`: Order ID
819    /// - `symbol`: Trading pair symbol
820    /// - `status`: Order status
821    /// - `clientOrderId`: Client order ID
822    /// - `price`: Order price
823    /// - `avgPrice`: Average fill price
824    /// - `origQty`: Original quantity
825    /// - `executedQty`: Executed quantity
826    /// - `cumBase`: Cumulative base asset
827    /// - `timeInForce`: Time in force
828    /// - `type`: Order type
829    /// - `reduceOnly`: Whether reduce-only
830    /// - `side`: Order side (BUY/SELL)
831    /// - `positionSide`: Position side (LONG/SHORT/BOTH)
832    /// - `origType`: Original order type
833    /// - `time`: Order time
834    /// - `updateTime`: Last update time
835    ///
836    /// # Errors
837    ///
838    /// Returns an error if:
839    /// - Authentication credentials are missing
840    /// - The symbol parameter is invalid or not found (if provided)
841    /// - The API request fails
842    /// - The response cannot be parsed
843    ///
844    /// # Example
845    ///
846    /// ```no_run
847    /// # use ccxt_exchanges::binance::Binance;
848    /// # use ccxt_core::ExchangeConfig;
849    /// # use serde_json::json;
850    /// # async fn example() -> ccxt_core::Result<()> {
851    /// let mut config = ExchangeConfig::default();
852    /// config.api_key = Some("your_api_key".to_string());
853    /// config.secret = Some("your_secret".to_string());
854    /// let binance = Binance::new_swap(config)?;
855    ///
856    /// // Fetch all force orders
857    /// let force_orders = binance.fetch_dapi_force_orders(None, None, None, None, None).await?;
858    /// println!("Force orders: {:?}", force_orders);
859    ///
860    /// // Fetch liquidation orders for a specific symbol
861    /// let liquidations = binance.fetch_dapi_force_orders(
862    ///     Some("BTC/USD:BTC"),
863    ///     Some("LIQUIDATION"),
864    ///     None,
865    ///     Some(50),
866    ///     None,
867    /// ).await?;
868    ///
869    /// // Fetch ADL orders with time range
870    /// let params = json!({
871    ///     "endTime": 1704067200000_i64
872    /// });
873    /// let adl_orders = binance.fetch_dapi_force_orders(
874    ///     None,
875    ///     Some("ADL"),
876    ///     Some(1703980800000),  // since
877    ///     Some(100),            // limit
878    ///     Some(params),
879    /// ).await?;
880    ///
881    /// // Process the results
882    /// if let Some(orders) = force_orders.as_array() {
883    ///     for order in orders {
884    ///         if let Some(order_id) = order.get("orderId") {
885    ///             println!("Order ID: {}", order_id);
886    ///         }
887    ///         if let Some(symbol) = order.get("symbol") {
888    ///             println!("Symbol: {}", symbol);
889    ///         }
890    ///         if let Some(side) = order.get("side") {
891    ///             println!("Side: {}", side);
892    ///         }
893    ///     }
894    /// }
895    /// # Ok(())
896    /// # }
897    /// ```
898    ///
899    /// # Error Handling Example
900    ///
901    /// ```no_run
902    /// # use ccxt_exchanges::binance::Binance;
903    /// # use ccxt_core::ExchangeConfig;
904    /// # async fn example() -> ccxt_core::Result<()> {
905    /// let mut config = ExchangeConfig::default();
906    /// config.api_key = Some("your_api_key".to_string());
907    /// config.secret = Some("your_secret".to_string());
908    /// let binance = Binance::new_swap(config)?;
909    ///
910    /// match binance.fetch_dapi_force_orders(None, Some("LIQUIDATION"), None, Some(50), None).await {
911    ///     Ok(orders) => {
912    ///         if let Some(arr) = orders.as_array() {
913    ///             if arr.is_empty() {
914    ///                 println!("No liquidation orders found");
915    ///             } else {
916    ///                 println!("Found {} liquidation orders", arr.len());
917    ///                 for order in arr {
918    ///                     println!("Order: {:?}", order.get("orderId"));
919    ///                 }
920    ///             }
921    ///         }
922    ///     }
923    ///     Err(e) => eprintln!("Failed to fetch force orders: {}", e),
924    /// }
925    /// # Ok(())
926    /// # }
927    /// ```
928    pub async fn fetch_dapi_force_orders(
929        &self,
930        symbol: Option<&str>,
931        auto_close_type: Option<&str>,
932        since: Option<i64>,
933        limit: Option<u32>,
934        params: Option<Value>,
935    ) -> Result<Value> {
936        // Use DAPI endpoint for coin-margined futures force orders
937        let url = format!("{}/forceOrders", self.urls().dapi_private);
938
939        let mut builder = self.signed_request(url);
940
941        // Add symbol parameter if provided
942        if let Some(sym) = symbol {
943            // Load markets to convert unified symbol to exchange symbol
944            self.load_markets(false).await?;
945            let market = self.base().market(sym).await?;
946            builder = builder.param("symbol", &market.id);
947        }
948
949        // Add auto close type filter if provided (LIQUIDATION or ADL)
950        if let Some(close_type) = auto_close_type {
951            builder = builder.param("autoCloseType", close_type);
952        }
953
954        let data = builder
955            .optional_param("startTime", since)
956            .optional_param("limit", limit)
957            .merge_json_params(params)
958            .execute()
959            .await?;
960
961        // Check for API errors in response
962        if let Some(code) = data.get("code") {
963            if let Some(msg) = data.get("msg") {
964                return Err(Error::exchange(
965                    code.to_string(),
966                    msg.as_str().unwrap_or("Unknown error").to_string(),
967                ));
968            }
969        }
970
971        Ok(data)
972    }
973}