ccxt_exchanges/binance/rest/futures/
margin.rs1use super::super::super::{Binance, signed_request::HttpMethod};
4use ccxt_core::{Error, ParseError, Result, types::MarketType};
5use reqwest::header::HeaderMap;
6use rust_decimal::Decimal;
7use serde_json::Value;
8use std::collections::{BTreeMap, HashMap};
9
10impl Binance {
11 pub async fn set_margin_mode(
13 &self,
14 symbol: &str,
15 margin_mode: &str,
16 params: Option<HashMap<String, String>>,
17 ) -> Result<HashMap<String, Value>> {
18 let margin_type = match margin_mode.to_uppercase().as_str() {
19 "ISOLATED" | "ISOLATED_MARGIN" => "ISOLATED",
20 "CROSS" | "CROSSED" | "CROSS_MARGIN" => "CROSSED",
21 _ => {
22 return Err(Error::invalid_request(format!(
23 "Invalid margin mode: {}. Must be 'isolated' or 'cross'",
24 margin_mode
25 )));
26 }
27 };
28
29 self.load_markets(false).await?;
30 let market = self.base().market(symbol).await?;
31
32 if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
33 return Err(Error::invalid_request(
34 "set_margin_mode() supports futures and swap markets only".to_string(),
35 ));
36 }
37
38 let url = if market.linear.unwrap_or(true) {
39 format!("{}/marginType", self.urls().fapi_private)
40 } else if market.inverse.unwrap_or(false) {
41 format!("{}/marginType", self.urls().dapi_private)
42 } else {
43 return Err(Error::invalid_request(
44 "Unknown futures market type".to_string(),
45 ));
46 };
47
48 let mut builder = self
49 .signed_request(url)
50 .method(HttpMethod::Post)
51 .param("symbol", &market.id)
52 .param("marginType", margin_type);
53
54 if let Some(p) = params {
55 let params_map: BTreeMap<String, String> = p.into_iter().collect();
56 builder = builder.params(params_map);
57 }
58
59 let response = builder.execute().await?;
60
61 let result: HashMap<String, Value> = serde_json::from_value(response).map_err(|e| {
62 Error::from(ParseError::invalid_format(
63 "data",
64 format!("Failed to parse response: {}", e),
65 ))
66 })?;
67
68 Ok(result)
69 }
70
71 pub async fn set_position_mode(&self, dual_side: bool, params: Option<Value>) -> Result<Value> {
73 self.check_required_credentials()?;
74
75 let use_dapi = params
76 .as_ref()
77 .and_then(|p| p.get("type"))
78 .and_then(serde_json::Value::as_str)
79 .is_some_and(|t| t == "delivery" || t == "coin_m");
80
81 let mut request_params = BTreeMap::new();
82 request_params.insert("dualSidePosition".to_string(), dual_side.to_string());
83
84 if let Some(params) = params {
85 if let Some(obj) = params.as_object() {
86 for (key, value) in obj {
87 if key == "type" {
88 continue;
89 }
90 if let Some(v) = value.as_str() {
91 request_params.insert(key.clone(), v.to_string());
92 } else if let Some(v) = value.as_bool() {
93 request_params.insert(key.clone(), v.to_string());
94 } else if let Some(v) = value.as_i64() {
95 request_params.insert(key.clone(), v.to_string());
96 }
97 }
98 }
99 }
100
101 let timestamp = self.get_signing_timestamp().await?;
102 let auth = self.get_auth()?;
103 let signed_params =
104 auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
105
106 let url = if use_dapi {
107 format!("{}/positionSide/dual", self.urls().dapi_private)
108 } else {
109 format!("{}/positionSide/dual", self.urls().fapi_private)
110 };
111
112 let mut headers = HeaderMap::new();
113 auth.add_auth_headers_reqwest(&mut headers);
114
115 let body = serde_json::to_value(&signed_params).map_err(|e| {
116 Error::from(ParseError::invalid_format(
117 "data",
118 format!("Failed to serialize params: {}", e),
119 ))
120 })?;
121
122 let data = self
123 .base()
124 .http_client
125 .post(&url, Some(headers), Some(body))
126 .await?;
127
128 Ok(data)
129 }
130
131 pub async fn fetch_position_mode(&self, params: Option<Value>) -> Result<bool> {
133 self.check_required_credentials()?;
134
135 let use_dapi = params
136 .as_ref()
137 .and_then(|p| p.get("type"))
138 .and_then(serde_json::Value::as_str)
139 .is_some_and(|t| t == "delivery" || t == "coin_m");
140
141 let mut request_params = BTreeMap::new();
142
143 if let Some(params) = params {
144 if let Some(obj) = params.as_object() {
145 for (key, value) in obj {
146 if key == "type" {
147 continue;
148 }
149 if let Some(v) = value.as_str() {
150 request_params.insert(key.clone(), v.to_string());
151 }
152 }
153 }
154 }
155
156 let timestamp = self.get_signing_timestamp().await?;
157 let auth = self.get_auth()?;
158 let signed_params =
159 auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
160
161 let query_string = signed_params
162 .iter()
163 .map(|(k, v)| format!("{}={}", k, v))
164 .collect::<Vec<_>>()
165 .join("&");
166
167 let url = if use_dapi {
168 format!(
169 "{}/positionSide/dual?{}",
170 self.urls().dapi_private,
171 query_string
172 )
173 } else {
174 format!(
175 "{}/positionSide/dual?{}",
176 self.urls().fapi_private,
177 query_string
178 )
179 };
180
181 let mut headers = HeaderMap::new();
182 auth.add_auth_headers_reqwest(&mut headers);
183
184 let data = self.base().http_client.get(&url, Some(headers)).await?;
185
186 if let Some(dual_side) = data.get("dualSidePosition") {
187 if let Some(value) = dual_side.as_bool() {
188 return Ok(value);
189 }
190 if let Some(value_str) = dual_side.as_str() {
191 return Ok(value_str.to_lowercase() == "true");
192 }
193 }
194
195 Err(Error::from(ParseError::invalid_format(
196 "data",
197 "Failed to parse position mode response",
198 )))
199 }
200
201 pub async fn modify_isolated_position_margin(
203 &self,
204 symbol: &str,
205 amount: Decimal,
206 params: Option<Value>,
207 ) -> Result<Value> {
208 self.check_required_credentials()?;
209
210 let market = self.base().market(symbol).await?;
211 let mut request_params = BTreeMap::new();
212 request_params.insert("symbol".to_string(), market.id.clone());
213 request_params.insert("amount".to_string(), amount.abs().to_string());
214 request_params.insert(
215 "type".to_string(),
216 if amount > Decimal::ZERO {
217 "1".to_string()
218 } else {
219 "2".to_string()
220 },
221 );
222
223 if let Some(params) = params {
224 if let Some(obj) = params.as_object() {
225 for (key, value) in obj {
226 if let Some(v) = value.as_str() {
227 request_params.insert(key.clone(), v.to_string());
228 }
229 }
230 }
231 }
232
233 let timestamp = self.get_signing_timestamp().await?;
234 let auth = self.get_auth()?;
235 let signed_params =
236 auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
237
238 let url = if market.linear.unwrap_or(true) {
239 format!("{}/positionMargin", self.urls().fapi_private)
240 } else {
241 format!("{}/positionMargin", self.urls().dapi_private)
242 };
243
244 let mut headers = HeaderMap::new();
245 auth.add_auth_headers_reqwest(&mut headers);
246
247 let body = serde_json::to_value(&signed_params).map_err(|e| {
248 Error::from(ParseError::invalid_format(
249 "data",
250 format!("Failed to serialize params: {}", e),
251 ))
252 })?;
253
254 let data = self
255 .base()
256 .http_client
257 .post(&url, Some(headers), Some(body))
258 .await?;
259
260 Ok(data)
261 }
262}