Skip to main content

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