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}