Skip to main content

ccxt_exchanges/binance/rest/futures/
margin.rs

1//! Margin operations for Binance futures.
2
3use super::super::super::{Binance, signed_request::HttpMethod};
4use ccxt_core::{Error, ParseError, Result, types::MarketType};
5use rust_decimal::Decimal;
6use serde_json::Value;
7use std::collections::{BTreeMap, HashMap};
8
9impl Binance {
10    /// Set margin mode for a trading pair.
11    pub async fn set_margin_mode(
12        &self,
13        symbol: &str,
14        margin_mode: &str,
15        params: Option<HashMap<String, String>>,
16    ) -> Result<HashMap<String, Value>> {
17        let margin_type = match margin_mode.to_uppercase().as_str() {
18            "ISOLATED" | "ISOLATED_MARGIN" => "ISOLATED",
19            "CROSS" | "CROSSED" | "CROSS_MARGIN" => "CROSSED",
20            _ => {
21                return Err(Error::invalid_request(format!(
22                    "Invalid margin mode: {}. Must be 'isolated' or 'cross'",
23                    margin_mode
24                )));
25            }
26        };
27
28        self.load_markets(false).await?;
29        let market = self.base().market(symbol).await?;
30
31        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
32            return Err(Error::invalid_request(
33                "set_margin_mode() supports futures and swap markets only".to_string(),
34            ));
35        }
36
37        let url = if market.linear.unwrap_or(true) {
38            format!("{}/marginType", self.urls().fapi_private)
39        } else if market.inverse.unwrap_or(false) {
40            format!("{}/marginType", self.urls().dapi_private)
41        } else {
42            return Err(Error::invalid_request(
43                "Unknown futures market type".to_string(),
44            ));
45        };
46
47        let mut builder = self
48            .signed_request(url)
49            .method(HttpMethod::Post)
50            .param("symbol", &market.id)
51            .param("marginType", margin_type);
52
53        if let Some(p) = params {
54            let params_map: BTreeMap<String, String> = p.into_iter().collect();
55            builder = builder.params(params_map);
56        }
57
58        let response = builder.execute().await?;
59
60        let result: HashMap<String, Value> = serde_json::from_value(response).map_err(|e| {
61            Error::from(ParseError::invalid_format(
62                "data",
63                format!("Failed to parse response: {}", e),
64            ))
65        })?;
66
67        Ok(result)
68    }
69
70    /// Set position mode (hedge mode or one-way mode).
71    pub async fn set_position_mode(&self, dual_side: bool, params: Option<Value>) -> Result<Value> {
72        let use_dapi = params
73            .as_ref()
74            .and_then(|p| p.get("type"))
75            .and_then(serde_json::Value::as_str)
76            .is_some_and(|t| t == "delivery" || t == "coin_m");
77
78        let url = if use_dapi {
79            format!("{}/positionSide/dual", self.urls().dapi_private)
80        } else {
81            format!("{}/positionSide/dual", self.urls().fapi_private)
82        };
83
84        let builder = self
85            .signed_request(url)
86            .method(HttpMethod::Post)
87            .param("dualSidePosition", dual_side)
88            .merge_json_params(params);
89
90        let data = builder.execute().await?;
91        Ok(data)
92    }
93
94    /// Fetch current position mode.
95    pub async fn fetch_position_mode(&self, params: Option<Value>) -> Result<bool> {
96        let use_dapi = params
97            .as_ref()
98            .and_then(|p| p.get("type"))
99            .and_then(serde_json::Value::as_str)
100            .is_some_and(|t| t == "delivery" || t == "coin_m");
101
102        let url = if use_dapi {
103            format!("{}/positionSide/dual", self.urls().dapi_private)
104        } else {
105            format!("{}/positionSide/dual", self.urls().fapi_private)
106        };
107
108        let builder = self.signed_request(url).merge_json_params(params);
109
110        let data = builder.execute().await?;
111
112        if let Some(dual_side) = data.get("dualSidePosition") {
113            if let Some(value) = dual_side.as_bool() {
114                return Ok(value);
115            }
116            if let Some(value_str) = dual_side.as_str() {
117                return Ok(value_str.to_lowercase() == "true");
118            }
119        }
120
121        Err(Error::from(ParseError::invalid_format(
122            "data",
123            "Failed to parse position mode response",
124        )))
125    }
126
127    /// Modify isolated position margin.
128    pub async fn modify_isolated_position_margin(
129        &self,
130        symbol: &str,
131        amount: Decimal,
132        params: Option<Value>,
133    ) -> Result<Value> {
134        let market = self.base().market(symbol).await?;
135
136        let url = if market.linear.unwrap_or(true) {
137            format!("{}/positionMargin", self.urls().fapi_private)
138        } else {
139            format!("{}/positionMargin", self.urls().dapi_private)
140        };
141
142        let margin_type = if amount > Decimal::ZERO { "1" } else { "2" };
143
144        let builder = self
145            .signed_request(url)
146            .method(HttpMethod::Post)
147            .param("symbol", &market.id)
148            .param("amount", amount.abs())
149            .param("type", margin_type)
150            .merge_json_params(params);
151
152        let data = builder.execute().await?;
153        Ok(data)
154    }
155}