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}