nautilus_binance/futures/http/
client.rs1use nautilus_core::nanos::UnixNanos;
22use nautilus_model::instruments::any::InstrumentAny;
23use serde::{Deserialize, Serialize};
24
25use crate::{
26 common::{
27 enums::{BinanceEnvironment, BinanceProductType},
28 parse::parse_usdm_instrument,
29 },
30 http::{
31 client::BinanceHttpClient,
32 error::{BinanceHttpError, BinanceHttpResult},
33 models::{
34 BinanceBookTicker, BinanceFuturesMarkPrice, BinanceFuturesTicker24hr,
35 BinanceFuturesUsdExchangeInfo, BinanceOrderBook,
36 },
37 query::{BinanceBookTickerParams, BinanceDepthParams, BinanceTicker24hrParams},
38 },
39};
40
41#[derive(Debug, Clone, Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct MarkPriceParams {
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub symbol: Option<String>,
48}
49
50#[derive(Debug, Deserialize)]
54#[serde(untagged)]
55enum MarkPriceResponse {
56 Single(BinanceFuturesMarkPrice),
58 Multiple(Vec<BinanceFuturesMarkPrice>),
60}
61
62impl From<MarkPriceResponse> for Vec<BinanceFuturesMarkPrice> {
63 fn from(response: MarkPriceResponse) -> Self {
64 match response {
65 MarkPriceResponse::Single(price) => vec![price],
66 MarkPriceResponse::Multiple(prices) => prices,
67 }
68 }
69}
70
71#[derive(Debug, Clone, Serialize)]
73#[serde(rename_all = "camelCase")]
74pub struct FundingRateParams {
75 pub symbol: String,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub start_time: Option<i64>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub end_time: Option<i64>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub limit: Option<u32>,
86}
87
88#[derive(Debug, Clone, Serialize)]
90#[serde(rename_all = "camelCase")]
91pub struct OpenInterestParams {
92 pub symbol: String,
94}
95
96#[derive(Debug, Clone, serde::Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct BinanceOpenInterest {
100 pub symbol: String,
102 pub open_interest: String,
104 pub time: i64,
106}
107
108#[derive(Debug, Clone, serde::Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct BinanceFundingRate {
112 pub symbol: String,
114 pub funding_rate: String,
116 pub funding_time: i64,
118 #[serde(default)]
120 pub mark_price: Option<String>,
121}
122
123#[derive(Debug, Clone)]
129#[cfg_attr(
130 feature = "python",
131 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance")
132)]
133pub struct BinanceFuturesHttpClient {
134 inner: BinanceHttpClient,
135 product_type: BinanceProductType,
136}
137
138impl BinanceFuturesHttpClient {
139 #[allow(clippy::too_many_arguments)]
147 pub fn new(
148 product_type: BinanceProductType,
149 environment: BinanceEnvironment,
150 api_key: Option<String>,
151 api_secret: Option<String>,
152 base_url_override: Option<String>,
153 recv_window: Option<u64>,
154 timeout_secs: Option<u64>,
155 proxy_url: Option<String>,
156 ) -> BinanceHttpResult<Self> {
157 match product_type {
158 BinanceProductType::UsdM | BinanceProductType::CoinM => {}
159 _ => {
160 return Err(BinanceHttpError::ValidationError(format!(
161 "BinanceFuturesHttpClient requires UsdM or CoinM product type, got {product_type:?}"
162 )));
163 }
164 }
165
166 let inner = BinanceHttpClient::new(
167 product_type,
168 environment,
169 api_key,
170 api_secret,
171 base_url_override,
172 recv_window,
173 timeout_secs,
174 proxy_url,
175 )?;
176
177 Ok(Self {
178 inner,
179 product_type,
180 })
181 }
182
183 #[must_use]
185 pub const fn product_type(&self) -> BinanceProductType {
186 self.product_type
187 }
188
189 #[must_use]
191 pub const fn inner(&self) -> &BinanceHttpClient {
192 &self.inner
193 }
194
195 pub async fn exchange_info(&self) -> BinanceHttpResult<()> {
201 self.inner.exchange_info().await
202 }
203
204 pub async fn instruments(&self) -> BinanceHttpResult<Vec<InstrumentAny>> {
213 let info: BinanceFuturesUsdExchangeInfo = self
214 .inner
215 .raw()
216 .get("exchangeInfo", None::<&()>, false, false)
217 .await?;
218
219 let ts_init = UnixNanos::default();
220 let mut instruments = Vec::with_capacity(info.symbols.len());
221
222 for symbol in &info.symbols {
223 match parse_usdm_instrument(symbol, ts_init, ts_init) {
224 Ok(instrument) => instruments.push(instrument),
225 Err(e) => {
226 tracing::debug!(
228 symbol = %symbol.symbol,
229 error = %e,
230 "Skipping symbol during instrument parsing"
231 );
232 }
233 }
234 }
235
236 tracing::info!(
237 count = instruments.len(),
238 "Loaded USD-M perpetual instruments"
239 );
240 Ok(instruments)
241 }
242
243 pub async fn ticker_24h(
249 &self,
250 params: &BinanceTicker24hrParams,
251 ) -> BinanceHttpResult<Vec<BinanceFuturesTicker24hr>> {
252 self.inner
253 .raw()
254 .get("ticker/24hr", Some(params), false, false)
255 .await
256 }
257
258 pub async fn book_ticker(
264 &self,
265 params: &BinanceBookTickerParams,
266 ) -> BinanceHttpResult<Vec<BinanceBookTicker>> {
267 self.inner.book_ticker(params).await
268 }
269
270 pub async fn depth(&self, params: &BinanceDepthParams) -> BinanceHttpResult<BinanceOrderBook> {
276 self.inner.depth(params).await
277 }
278
279 pub async fn mark_price(
287 &self,
288 params: &MarkPriceParams,
289 ) -> BinanceHttpResult<Vec<BinanceFuturesMarkPrice>> {
290 let response: MarkPriceResponse = self
291 .inner
292 .raw()
293 .get("premiumIndex", Some(params), false, false)
294 .await?;
295 Ok(response.into())
296 }
297
298 pub async fn funding_rate(
304 &self,
305 params: &FundingRateParams,
306 ) -> BinanceHttpResult<Vec<BinanceFundingRate>> {
307 self.inner
308 .raw()
309 .get("fundingRate", Some(params), false, false)
310 .await
311 }
312
313 pub async fn open_interest(
319 &self,
320 params: &OpenInterestParams,
321 ) -> BinanceHttpResult<BinanceOpenInterest> {
322 self.inner
323 .raw()
324 .get("openInterest", Some(params), false, false)
325 .await
326 }
327
328 pub async fn create_listen_key(&self) -> BinanceHttpResult<ListenKeyResponse> {
334 self.inner
335 .raw()
336 .post::<(), ListenKeyResponse>("listenKey", None, None, true, false)
337 .await
338 }
339
340 pub async fn keepalive_listen_key(&self, listen_key: &str) -> BinanceHttpResult<()> {
346 let params = ListenKeyParams {
347 listen_key: listen_key.to_string(),
348 };
349 let _: serde_json::Value = self
350 .inner
351 .raw()
352 .request_put("listenKey", Some(¶ms), true, false)
353 .await?;
354 Ok(())
355 }
356
357 pub async fn close_listen_key(&self, listen_key: &str) -> BinanceHttpResult<()> {
363 let params = ListenKeyParams {
364 listen_key: listen_key.to_string(),
365 };
366 let _: serde_json::Value = self
367 .inner
368 .raw()
369 .request_delete("listenKey", Some(¶ms), true, false)
370 .await?;
371 Ok(())
372 }
373}
374
375#[derive(Debug, Clone, serde::Deserialize)]
377#[serde(rename_all = "camelCase")]
378pub struct ListenKeyResponse {
379 pub listen_key: String,
381}
382
383#[derive(Debug, Clone, Serialize)]
385#[serde(rename_all = "camelCase")]
386struct ListenKeyParams {
387 listen_key: String,
388}