ccxt_exchanges/binance/rest/
margin.rs

1//! Binance margin trading operations.
2//!
3//! This module contains all margin trading methods including borrowing, repaying,
4//! and margin-specific account operations.
5
6use super::super::signed_request::HttpMethod;
7use super::super::{Binance, parser};
8use ccxt_core::{
9    Error, ParseError, Result,
10    types::{MarginAdjustment, MarginLoan, MarginRepay},
11};
12use rust_decimal::Decimal;
13
14impl Binance {
15    // ==================== Borrow Methods ====================
16
17    /// Borrow funds in cross margin mode.
18    ///
19    /// # Arguments
20    ///
21    /// * `currency` - Currency code (e.g., "USDT", "BTC").
22    /// * `amount` - Borrow amount.
23    ///
24    /// # Returns
25    ///
26    /// Returns a [`MarginLoan`] record with transaction details.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if authentication fails or the API request fails.
31    ///
32    /// # Example
33    ///
34    /// ```no_run
35    /// # use ccxt_exchanges::binance::Binance;
36    /// # use ccxt_core::ExchangeConfig;
37    /// # async fn example() -> ccxt_core::Result<()> {
38    /// let mut config = ExchangeConfig::default();
39    /// config.api_key = Some("your_api_key".to_string());
40    /// config.secret = Some("your_secret".to_string());
41    /// let binance = Binance::new(config)?;
42    /// let loan = binance.borrow_cross_margin("USDT", 100.0).await?;
43    /// println!("Loan ID: {}", loan.id);
44    /// # Ok(())
45    /// # }
46    /// ```
47    pub async fn borrow_cross_margin(&self, currency: &str, amount: f64) -> Result<MarginLoan> {
48        let url = format!("{}/sapi/v1/margin/loan", self.urls().sapi);
49
50        let data = self
51            .signed_request(url)
52            .method(HttpMethod::Post)
53            .param("asset", currency)
54            .param("amount", amount)
55            .execute()
56            .await?;
57
58        parser::parse_margin_loan(&data)
59    }
60
61    /// Borrow funds in isolated margin mode.
62    ///
63    /// # Arguments
64    ///
65    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
66    /// * `currency` - Currency code to borrow.
67    /// * `amount` - Borrow amount.
68    ///
69    /// # Returns
70    ///
71    /// Returns a [`MarginLoan`] record with transaction details.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if authentication fails or the API request fails.
76    ///
77    /// # Example
78    ///
79    /// ```no_run
80    /// # use ccxt_exchanges::binance::Binance;
81    /// # use ccxt_core::ExchangeConfig;
82    /// # async fn example() -> ccxt_core::Result<()> {
83    /// let mut config = ExchangeConfig::default();
84    /// config.api_key = Some("your_api_key".to_string());
85    /// config.secret = Some("your_secret".to_string());
86    /// let binance = Binance::new(config)?;
87    /// let loan = binance.borrow_isolated_margin("BTC/USDT", "USDT", 100.0).await?;
88    /// println!("Loan ID: {}", loan.id);
89    /// # Ok(())
90    /// # }
91    /// ```
92    pub async fn borrow_isolated_margin(
93        &self,
94        symbol: &str,
95        currency: &str,
96        amount: f64,
97    ) -> Result<MarginLoan> {
98        self.load_markets(false).await?;
99        let market = self.base().market(symbol).await?;
100
101        let url = format!("{}/sapi/v1/margin/loan", self.urls().sapi);
102
103        let data = self
104            .signed_request(url)
105            .method(HttpMethod::Post)
106            .param("asset", currency)
107            .param("amount", amount)
108            .param("symbol", &market.id)
109            .param("isIsolated", "TRUE")
110            .execute()
111            .await?;
112
113        parser::parse_margin_loan(&data)
114    }
115
116    // ==================== Repay Methods ====================
117
118    /// Repay borrowed funds in cross margin mode.
119    ///
120    /// # Arguments
121    ///
122    /// * `currency` - Currency code (e.g., "USDT", "BTC").
123    /// * `amount` - Repayment amount.
124    ///
125    /// # Returns
126    ///
127    /// Returns a [`MarginRepay`] record with transaction details.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if authentication fails or the API request fails.
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// # use ccxt_exchanges::binance::Binance;
137    /// # use ccxt_core::ExchangeConfig;
138    /// # async fn example() -> ccxt_core::Result<()> {
139    /// let mut config = ExchangeConfig::default();
140    /// config.api_key = Some("your_api_key".to_string());
141    /// config.secret = Some("your_secret".to_string());
142    /// let binance = Binance::new(config)?;
143    /// let repay = binance.repay_cross_margin("USDT", 100.0).await?;
144    /// println!("Repay ID: {}", repay.id);
145    /// # Ok(())
146    /// # }
147    /// ```
148    pub async fn repay_cross_margin(&self, currency: &str, amount: f64) -> Result<MarginRepay> {
149        let url = format!("{}/sapi/v1/margin/repay", self.urls().sapi);
150
151        let data = self
152            .signed_request(url)
153            .method(HttpMethod::Post)
154            .param("asset", currency)
155            .param("amount", amount)
156            .execute()
157            .await?;
158
159        let loan = parser::parse_margin_loan(&data)?;
160
161        Ok(MarginRepay {
162            id: loan.id,
163            currency: loan.currency,
164            amount: loan.amount,
165            symbol: loan.symbol,
166            timestamp: loan.timestamp,
167            datetime: loan.datetime,
168            status: loan.status,
169            is_isolated: loan.is_isolated,
170            info: loan.info,
171        })
172    }
173
174    /// Repay borrowed funds in isolated margin mode.
175    ///
176    /// # Arguments
177    ///
178    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
179    /// * `currency` - Currency code to repay.
180    /// * `amount` - Repayment amount.
181    ///
182    /// # Returns
183    ///
184    /// Returns a [`MarginRepay`] record with transaction details.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if authentication fails or the API request fails.
189    ///
190    /// # Example
191    ///
192    /// ```no_run
193    /// # use ccxt_exchanges::binance::Binance;
194    /// # use ccxt_core::ExchangeConfig;
195    /// # use rust_decimal_macros::dec;
196    /// # async fn example() -> ccxt_core::Result<()> {
197    /// let mut config = ExchangeConfig::default();
198    /// config.api_key = Some("your_api_key".to_string());
199    /// config.secret = Some("your_secret".to_string());
200    /// let binance = Binance::new(config)?;
201    /// let repay = binance.repay_isolated_margin("BTC/USDT", "USDT", dec!(100)).await?;
202    /// println!("Repay ID: {}", repay.id);
203    /// # Ok(())
204    /// # }
205    /// ```
206    pub async fn repay_isolated_margin(
207        &self,
208        symbol: &str,
209        currency: &str,
210        amount: Decimal,
211    ) -> Result<MarginRepay> {
212        self.load_markets(false).await?;
213        let market = self.base().market(symbol).await?;
214
215        let url = format!("{}/sapi/v1/margin/repay", self.urls().sapi);
216
217        let data = self
218            .signed_request(url)
219            .method(HttpMethod::Post)
220            .param("asset", currency)
221            .param("amount", amount)
222            .param("symbol", &market.id)
223            .param("isIsolated", "TRUE")
224            .execute()
225            .await?;
226
227        let loan = parser::parse_margin_loan(&data)?;
228
229        Ok(MarginRepay {
230            id: loan.id,
231            currency: loan.currency,
232            amount: loan.amount,
233            symbol: loan.symbol,
234            timestamp: loan.timestamp,
235            datetime: loan.datetime,
236            status: loan.status,
237            is_isolated: loan.is_isolated,
238            info: loan.info,
239        })
240    }
241
242    // ==================== Margin Info Methods ====================
243
244    /// Fetch margin adjustment history.
245    ///
246    /// Retrieves liquidation records and margin adjustment history.
247    ///
248    /// # Arguments
249    ///
250    /// * `symbol` - Optional trading pair symbol (required for isolated margin).
251    /// * `since` - Optional start timestamp in milliseconds.
252    /// * `limit` - Optional maximum number of records to return.
253    ///
254    /// # Returns
255    ///
256    /// Returns a vector of [`MarginAdjustment`] records.
257    ///
258    /// # Errors
259    ///
260    /// Returns an error if authentication fails or the API request fails.
261    pub async fn fetch_margin_adjustment_history(
262        &self,
263        symbol: Option<&str>,
264        since: Option<i64>,
265        limit: Option<i64>,
266    ) -> Result<Vec<MarginAdjustment>> {
267        let market_id = if let Some(sym) = symbol {
268            self.load_markets(false).await?;
269            let market = self.base().market(sym).await?;
270            Some(market.id.clone())
271        } else {
272            None
273        };
274
275        let url = format!("{}/sapi/v1/margin/forceLiquidationRec", self.urls().sapi);
276
277        let data = self
278            .signed_request(url)
279            .optional_param("symbol", market_id.as_ref())
280            .optional_param("isolatedSymbol", market_id.as_ref())
281            .optional_param("startTime", since)
282            .optional_param("size", limit)
283            .execute()
284            .await?;
285
286        let rows = data["rows"].as_array().ok_or_else(|| {
287            Error::from(ParseError::invalid_format(
288                "data",
289                "Expected rows array in response",
290            ))
291        })?;
292
293        let mut adjustments = Vec::new();
294        for row in rows {
295            if let Ok(adjustment) = parser::parse_margin_adjustment(row) {
296                adjustments.push(adjustment);
297            }
298        }
299
300        Ok(adjustments)
301    }
302
303    /// Fetch maximum borrowable amount for cross margin.
304    ///
305    /// # Arguments
306    ///
307    /// * `currency` - Currency code (e.g., "USDT", "BTC").
308    ///
309    /// # Returns
310    ///
311    /// Returns the maximum borrowable amount as a `Decimal`.
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if authentication fails or the API request fails.
316    pub async fn fetch_cross_margin_max_borrowable(&self, currency: &str) -> Result<Decimal> {
317        let url = format!("{}/sapi/v1/margin/maxBorrowable", self.urls().sapi);
318
319        let data = self
320            .signed_request(url)
321            .param("asset", currency)
322            .execute()
323            .await?;
324
325        let amount_str = data["amount"]
326            .as_str()
327            .ok_or_else(|| Error::from(ParseError::missing_field("amount")))?;
328
329        amount_str.parse::<Decimal>().map_err(|e| {
330            Error::from(ParseError::invalid_format(
331                "amount",
332                format!("Failed to parse amount: {}", e),
333            ))
334        })
335    }
336
337    /// Fetch maximum borrowable amount for isolated margin.
338    ///
339    /// # Arguments
340    ///
341    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT").
342    /// * `currency` - Currency code to check.
343    ///
344    /// # Returns
345    ///
346    /// Returns the maximum borrowable amount as a `Decimal`.
347    ///
348    /// # Errors
349    ///
350    /// Returns an error if authentication fails or the API request fails.
351    pub async fn fetch_isolated_margin_max_borrowable(
352        &self,
353        symbol: &str,
354        currency: &str,
355    ) -> Result<Decimal> {
356        self.load_markets(false).await?;
357        let market = self.base().market(symbol).await?;
358
359        let url = format!("{}/sapi/v1/margin/maxBorrowable", self.urls().sapi);
360
361        let data = self
362            .signed_request(url)
363            .param("asset", currency)
364            .param("isolatedSymbol", &market.id)
365            .execute()
366            .await?;
367
368        let amount_str = data["amount"]
369            .as_str()
370            .ok_or_else(|| Error::from(ParseError::missing_field("amount")))?;
371
372        amount_str.parse::<Decimal>().map_err(|e| {
373            Error::from(ParseError::invalid_format(
374                "amount",
375                format!("Failed to parse amount: {}", e),
376            ))
377        })
378    }
379
380    /// Fetch maximum transferable amount.
381    ///
382    /// # Arguments
383    ///
384    /// * `currency` - Currency code (e.g., "USDT", "BTC").
385    ///
386    /// # Returns
387    ///
388    /// Returns the maximum transferable amount as a `Decimal`.
389    ///
390    /// # Errors
391    ///
392    /// Returns an error if authentication fails or the API request fails.
393    pub async fn fetch_max_transferable(&self, currency: &str) -> Result<Decimal> {
394        let url = format!("{}/sapi/v1/margin/maxTransferable", self.urls().sapi);
395
396        let data = self
397            .signed_request(url)
398            .param("asset", currency)
399            .execute()
400            .await?;
401
402        let amount_str = data["amount"]
403            .as_str()
404            .ok_or_else(|| Error::from(ParseError::missing_field("amount")))?;
405
406        amount_str.parse::<Decimal>().map_err(|e| {
407            Error::from(ParseError::invalid_format(
408                "amount",
409                format!("Failed to parse amount: {}", e),
410            ))
411        })
412    }
413}