ccxt_exchanges/binance/rest/futures/
funding.rs

1//! Funding rate operations for Binance futures.
2
3use 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    /// Fetch current funding rate for a trading pair.
15    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    /// Fetch current funding rates for multiple trading pairs.
69    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    /// Fetch funding rate history for a trading pair.
132    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    /// Fetch funding payment history for a trading pair.
199    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}