Skip to main content

ccxt_exchanges/binance/rest/futures/
leverage.rs

1//! Leverage operations for Binance futures.
2
3use super::super::super::{Binance, parser, signed_request::HttpMethod};
4use ccxt_core::{
5    Error, Result,
6    types::{LeverageTier, MarketType},
7};
8use serde_json::Value;
9use std::collections::{BTreeMap, HashMap};
10
11impl Binance {
12    /// Fetch leverage settings for multiple trading pairs.
13    pub async fn fetch_leverages(
14        &self,
15        symbols: Option<Vec<String>>,
16        params: Option<Value>,
17    ) -> Result<BTreeMap<String, ccxt_core::types::Leverage>> {
18        self.load_markets(false).await?;
19
20        let mut params_map = if let Some(p) = params {
21            serde_json::from_value::<BTreeMap<String, String>>(p).unwrap_or_default()
22        } else {
23            BTreeMap::new()
24        };
25
26        let market_type = params_map
27            .remove("type")
28            .or_else(|| params_map.remove("marketType"))
29            .unwrap_or_else(|| "future".to_string());
30
31        let sub_type = params_map
32            .remove("subType")
33            .unwrap_or_else(|| "linear".to_string());
34
35        let is_portfolio_margin = params_map
36            .remove("portfolioMargin")
37            .and_then(|v| v.parse::<bool>().ok())
38            .unwrap_or(false);
39
40        let url = if market_type == "future" && sub_type == "linear" {
41            if is_portfolio_margin {
42                format!(
43                    "{}/account",
44                    self.urls().fapi_private.replace("/fapi/v1", "/papi/v1/um")
45                )
46            } else {
47                format!("{}/symbolConfig", self.urls().fapi_private)
48            }
49        } else if market_type == "future" && sub_type == "inverse" {
50            if is_portfolio_margin {
51                format!(
52                    "{}/account",
53                    self.urls().dapi_private.replace("/dapi/v1", "/papi/v1/cm")
54                )
55            } else {
56                format!("{}/account", self.urls().dapi_private)
57            }
58        } else {
59            return Err(Error::invalid_request(
60                "fetchLeverages() supports linear and inverse contracts only",
61            ));
62        };
63
64        let response = self
65            .signed_request(url)
66            .params(params_map)
67            .execute()
68            .await?;
69
70        let leverages_data = if let Some(positions) = response.get("positions") {
71            positions.as_array().cloned().unwrap_or_default()
72        } else if response.is_array() {
73            response.as_array().cloned().unwrap_or_default()
74        } else {
75            vec![]
76        };
77
78        let mut leverages = BTreeMap::new();
79
80        for item in leverages_data {
81            if let Ok(leverage) = parser::parse_leverage(&item, None) {
82                if let Some(ref filter_symbols) = symbols {
83                    if filter_symbols.contains(&leverage.symbol) {
84                        leverages.insert(leverage.symbol.clone(), leverage);
85                    }
86                } else {
87                    leverages.insert(leverage.symbol.clone(), leverage);
88                }
89            }
90        }
91
92        Ok(leverages)
93    }
94
95    /// Fetch leverage settings for a single trading pair.
96    pub async fn fetch_leverage(
97        &self,
98        symbol: &str,
99        params: Option<Value>,
100    ) -> Result<ccxt_core::types::Leverage> {
101        let symbols = Some(vec![symbol.to_string()]);
102        let leverages = self.fetch_leverages(symbols, params).await?;
103
104        leverages.get(symbol).cloned().ok_or_else(|| {
105            Error::exchange("404", format!("Leverage not found for symbol: {}", symbol))
106        })
107    }
108
109    /// Set leverage multiplier for a trading pair.
110    pub async fn set_leverage(
111        &self,
112        symbol: &str,
113        leverage: i64,
114        params: Option<HashMap<String, String>>,
115    ) -> Result<HashMap<String, Value>> {
116        if !(1..=125).contains(&leverage) {
117            return Err(Error::invalid_request(
118                "Leverage must be between 1 and 125".to_string(),
119            ));
120        }
121
122        self.load_markets(false).await?;
123        let market = self.base().market(symbol).await?;
124
125        if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
126            return Err(Error::invalid_request(
127                "set_leverage() supports futures and swap markets only".to_string(),
128            ));
129        }
130
131        let url = if market.linear.unwrap_or(true) {
132            format!("{}/leverage", self.urls().fapi_private)
133        } else if market.inverse.unwrap_or(false) {
134            format!("{}/leverage", self.urls().dapi_private)
135        } else {
136            return Err(Error::invalid_request(
137                "Unknown futures market type".to_string(),
138            ));
139        };
140
141        let mut builder = self
142            .signed_request(url)
143            .method(HttpMethod::Post)
144            .param("symbol", &market.id)
145            .param("leverage", leverage);
146
147        if let Some(p) = params {
148            let params_map: BTreeMap<String, String> = p.into_iter().collect();
149            builder = builder.params(params_map);
150        }
151
152        let response = builder.execute().await?;
153
154        let result: HashMap<String, Value> = serde_json::from_value(response).map_err(|e| {
155            Error::from(ccxt_core::ParseError::invalid_format(
156                "data",
157                format!("Failed to parse response: {}", e),
158            ))
159        })?;
160
161        Ok(result)
162    }
163
164    /// Fetch leverage bracket information.
165    pub async fn fetch_leverage_bracket(
166        &self,
167        symbol: Option<&str>,
168        params: Option<Value>,
169    ) -> Result<Value> {
170        let market_id = if let Some(sym) = symbol {
171            let market = self.base().market(sym).await?;
172            Some(market.id.clone())
173        } else {
174            None
175        };
176
177        let url = format!("{}/leverageBracket", self.urls().fapi_private);
178
179        self.signed_request(url)
180            .optional_param("symbol", market_id)
181            .merge_json_params(params)
182            .execute()
183            .await
184    }
185
186    /// Fetch leverage tier information for trading pairs.
187    pub async fn fetch_leverage_tiers(
188        &self,
189        symbols: Option<Vec<String>>,
190        params: Option<HashMap<String, String>>,
191    ) -> Result<BTreeMap<String, Vec<LeverageTier>>> {
192        self.load_markets(false).await?;
193
194        let is_portfolio_margin = params
195            .as_ref()
196            .and_then(|p| p.get("portfolioMargin"))
197            .is_some_and(|v| v == "true");
198
199        let (market, market_id) = if let Some(syms) = &symbols {
200            if let Some(first_symbol) = syms.first() {
201                let m = self.base().market(first_symbol).await?;
202                let id = m.id.clone();
203                (Some(m), Some(id))
204            } else {
205                (None, None)
206            }
207        } else {
208            (None, None)
209        };
210
211        let url = if let Some(ref m) = market {
212            if is_portfolio_margin {
213                if m.is_linear() {
214                    format!("{}/um/leverageBracket", self.urls().papi)
215                } else {
216                    format!("{}/cm/leverageBracket", self.urls().papi)
217                }
218            } else if m.is_linear() {
219                format!("{}/leverageBracket", self.urls().fapi_private)
220            } else {
221                format!("{}/v2/leverageBracket", self.urls().dapi_private)
222            }
223        } else {
224            format!("{}/leverageBracket", self.urls().fapi_private)
225        };
226
227        let filtered_params: Option<BTreeMap<String, String>> = params.map(|p| {
228            p.into_iter()
229                .filter(|(k, _)| k != "portfolioMargin")
230                .collect()
231        });
232
233        let response = self
234            .signed_request(url)
235            .optional_param("symbol", market_id)
236            .params(filtered_params.unwrap_or_default())
237            .execute()
238            .await?;
239
240        let mut tiers_map: BTreeMap<String, Vec<LeverageTier>> = BTreeMap::new();
241
242        if let Some(symbols_array) = response.as_array() {
243            for symbol_data in symbols_array {
244                if let (Some(symbol_id), Some(brackets)) = (
245                    symbol_data["symbol"].as_str(),
246                    symbol_data["brackets"].as_array(),
247                ) {
248                    if let Ok(market) = self.base().market_by_id(symbol_id).await {
249                        let mut tier_list = Vec::new();
250                        for bracket in brackets {
251                            if let Ok(tier) = parser::parse_leverage_tier(bracket, &market) {
252                                tier_list.push(tier);
253                            }
254                        }
255
256                        if !tier_list.is_empty() {
257                            if let Some(ref filter_symbols) = symbols {
258                                if filter_symbols.contains(&market.symbol) {
259                                    tiers_map.insert(market.symbol.clone(), tier_list);
260                                }
261                            } else {
262                                tiers_map.insert(market.symbol.clone(), tier_list);
263                            }
264                        }
265                    }
266                }
267            }
268        }
269
270        Ok(tiers_map)
271    }
272}