ccxt_exchanges/binance/rest/futures/
funding.rs1use super::super::super::{Binance, parser};
4use ccxt_core::{
5 Error, ParseError, Result,
6 types::{FeeFundingRate, FeeFundingRateHistory, MarketType},
7};
8use serde_json::Value;
9use std::collections::{BTreeMap, HashMap};
10use std::fmt::Write;
11use tracing::warn;
12
13impl Binance {
14 pub async fn fetch_funding_rate(
16 &self,
17 symbol: &str,
18 params: Option<HashMap<String, String>>,
19 ) -> Result<FeeFundingRate> {
20 self.load_markets(false).await?;
21 let market = self.base().market(symbol).await?;
22
23 if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
24 return Err(Error::invalid_request(
25 "fetch_funding_rate() supports futures and swap markets only".to_string(),
26 ));
27 }
28
29 let mut request_params = BTreeMap::new();
30 request_params.insert("symbol".to_string(), market.id.clone());
31
32 if let Some(p) = params {
33 for (key, value) in p {
34 request_params.insert(key, value);
35 }
36 }
37
38 let url = if market.linear.unwrap_or(true) {
39 format!("{}/premiumIndex", self.urls().fapi_public)
40 } else {
41 format!("{}/premiumIndex", self.urls().dapi_public)
42 };
43
44 let mut request_url = format!("{}?", url);
45 for (key, value) in &request_params {
46 let _ = write!(request_url, "{}={}&", key, value);
47 }
48
49 let response = self.base().http_client.get(&request_url, None).await?;
50
51 let data = if market.linear.unwrap_or(true) {
52 &response
53 } else {
54 response
55 .as_array()
56 .and_then(|arr| arr.first())
57 .ok_or_else(|| {
58 Error::from(ParseError::invalid_format(
59 "data",
60 "COIN-M funding rate response should be an array with at least one element",
61 ))
62 })?
63 };
64
65 parser::parse_funding_rate(data, Some(&market))
66 }
67
68 pub async fn fetch_funding_rates(
70 &self,
71 symbols: Option<Vec<String>>,
72 params: Option<BTreeMap<String, String>>,
73 ) -> Result<BTreeMap<String, FeeFundingRate>> {
74 self.load_markets(false).await?;
75
76 let mut request_params = BTreeMap::new();
77
78 if let Some(p) = params {
79 for (key, value) in p {
80 request_params.insert(key, value);
81 }
82 }
83
84 let url = format!("{}/premiumIndex", self.urls().fapi_public);
85
86 let mut request_url = url.clone();
87 if !request_params.is_empty() {
88 request_url.push('?');
89 for (key, value) in &request_params {
90 let _ = write!(request_url, "{}={}&", key, value);
91 }
92 }
93
94 let response = self.base().http_client.get(&request_url, None).await?;
95
96 let mut rates = BTreeMap::new();
97
98 if let Some(rates_array) = response.as_array() {
99 for rate_data in rates_array {
100 if let Ok(symbol_id) = rate_data["symbol"]
101 .as_str()
102 .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
103 {
104 if let Ok(market) = self.base().market_by_id(symbol_id).await {
105 if let Some(ref syms) = symbols {
106 if !syms.contains(&market.symbol) {
107 continue;
108 }
109 }
110
111 if let Ok(rate) = parser::parse_funding_rate(rate_data, Some(&market)) {
112 rates.insert(market.symbol.clone(), rate);
113 }
114 }
115 }
116 }
117 } else if let Ok(symbol_id) = response["symbol"]
118 .as_str()
119 .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))
120 {
121 if let Ok(market) = self.base().market_by_id(symbol_id).await {
122 if let Ok(rate) = parser::parse_funding_rate(&response, Some(&market)) {
123 rates.insert(market.symbol.clone(), rate);
124 }
125 }
126 }
127
128 Ok(rates)
129 }
130
131 pub async fn fetch_funding_rate_history(
133 &self,
134 symbol: &str,
135 since: Option<i64>,
136 limit: Option<u32>,
137 params: Option<HashMap<String, String>>,
138 ) -> Result<Vec<FeeFundingRateHistory>> {
139 self.load_markets(false).await?;
140 let market = self.base().market(symbol).await?;
141
142 if market.market_type != MarketType::Futures && market.market_type != MarketType::Swap {
143 return Err(Error::invalid_request(
144 "fetch_funding_rate_history() supports futures and swap markets only".to_string(),
145 ));
146 }
147
148 let mut request_params = BTreeMap::new();
149 request_params.insert("symbol".to_string(), market.id.clone());
150
151 if let Some(s) = since {
152 request_params.insert("startTime".to_string(), s.to_string());
153 }
154
155 if let Some(l) = limit {
156 request_params.insert("limit".to_string(), l.to_string());
157 }
158
159 if let Some(p) = params {
160 for (key, value) in p {
161 request_params.insert(key, value);
162 }
163 }
164
165 let url = if market.linear.unwrap_or(true) {
166 format!("{}/fundingRate", self.urls().fapi_public)
167 } else {
168 format!("{}/fundingRate", self.urls().dapi_public)
169 };
170
171 let mut request_url = format!("{}?", url);
172 for (key, value) in &request_params {
173 let _ = write!(request_url, "{}={}&", key, value);
174 }
175
176 let response = self.base().http_client.get(&request_url, None).await?;
177
178 let history_array = response.as_array().ok_or_else(|| {
179 Error::from(ParseError::invalid_format(
180 "data",
181 "Expected array of funding rate history",
182 ))
183 })?;
184
185 let mut history = Vec::new();
186 for history_data in history_array {
187 match parser::parse_funding_rate_history(history_data, Some(&market)) {
188 Ok(record) => history.push(record),
189 Err(e) => {
190 warn!(error = %e, "Failed to parse funding rate history");
191 }
192 }
193 }
194
195 Ok(history)
196 }
197
198 pub async fn fetch_funding_history(
200 &self,
201 symbol: Option<&str>,
202 since: Option<i64>,
203 limit: Option<u32>,
204 params: Option<HashMap<String, String>>,
205 ) -> Result<Value> {
206 self.check_required_credentials()?;
207 self.load_markets(false).await?;
208
209 let mut request_params = BTreeMap::new();
210
211 if let Some(sym) = symbol {
212 let market = self.base().market(sym).await?;
213 request_params.insert("symbol".to_string(), market.id.clone());
214 }
215
216 if let Some(s) = since {
217 request_params.insert("startTime".to_string(), s.to_string());
218 }
219
220 if let Some(l) = limit {
221 request_params.insert("limit".to_string(), l.to_string());
222 }
223
224 if let Some(p) = params {
225 for (key, value) in p {
226 request_params.insert(key, value);
227 }
228 }
229
230 let timestamp = self.get_signing_timestamp().await?;
231 let auth = self.get_auth()?;
232 let signed_params =
233 auth.sign_with_timestamp(&request_params, timestamp, Some(self.options().recv_window))?;
234
235 let query_string: String = signed_params
236 .iter()
237 .map(|(k, v)| format!("{}={}", k, v))
238 .collect::<Vec<_>>()
239 .join("&");
240
241 let url = format!("{}/income?{}", self.urls().fapi_private, query_string);
242
243 let mut headers = reqwest::header::HeaderMap::new();
244 auth.add_auth_headers_reqwest(&mut headers);
245
246 let data = self.base().http_client.get(&url, Some(headers)).await?;
247
248 Ok(data)
249 }
250}