ccxt_exchanges/binance/rest/futures/
leverage.rs1use 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 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 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 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 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 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}