1use std::{collections::HashMap, num::NonZeroU32, sync::Arc, time::Duration};
19
20use chrono::{DateTime, Utc};
21use dashmap::DashMap;
22use nautilus_core::{consts::NAUTILUS_USER_AGENT, nanos::UnixNanos};
23use nautilus_model::{
24 data::{Bar, BarType, TradeTick},
25 enums::{AggregationSource, AggressorSide, BarAggregation, OrderSide, OrderType, TimeInForce},
26 events::AccountState,
27 identifiers::{AccountId, ClientOrderId, InstrumentId, TradeId, VenueOrderId},
28 instruments::any::InstrumentAny,
29 reports::{FillReport, OrderStatusReport},
30 types::{Price, Quantity},
31};
32use nautilus_network::{
33 http::{HttpClient, HttpResponse, Method},
34 ratelimiter::quota::Quota,
35};
36use serde::{Deserialize, Serialize, de::DeserializeOwned};
37use ustr::Ustr;
38
39use super::{
40 error::{BinanceFuturesHttpError, BinanceFuturesHttpResult},
41 models::{
42 BatchOrderResult, BinanceBookTicker, BinanceCancelAllOrdersResponse, BinanceFundingRate,
43 BinanceFuturesAccountInfo, BinanceFuturesAlgoOrder, BinanceFuturesAlgoOrderCancelResponse,
44 BinanceFuturesCoinExchangeInfo, BinanceFuturesCoinSymbol, BinanceFuturesKline,
45 BinanceFuturesMarkPrice, BinanceFuturesOrder, BinanceFuturesTicker24hr,
46 BinanceFuturesTrade, BinanceFuturesUsdExchangeInfo, BinanceFuturesUsdSymbol,
47 BinanceHedgeModeResponse, BinanceLeverageResponse, BinanceOpenInterest, BinanceOrderBook,
48 BinancePositionRisk, BinancePriceTicker, BinanceServerTime, BinanceUserTrade,
49 ListenKeyResponse,
50 },
51 query::{
52 BatchCancelItem, BatchModifyItem, BatchOrderItem, BinanceAlgoOrderQueryParams,
53 BinanceAllAlgoOrdersParams, BinanceAllOrdersParams, BinanceBookTickerParams,
54 BinanceCancelAllAlgoOrdersParams, BinanceCancelAllOrdersParams, BinanceCancelOrderParams,
55 BinanceDepthParams, BinanceFundingRateParams, BinanceKlinesParams, BinanceMarkPriceParams,
56 BinanceModifyOrderParams, BinanceNewAlgoOrderParams, BinanceNewOrderParams,
57 BinanceOpenAlgoOrdersParams, BinanceOpenInterestParams, BinanceOpenOrdersParams,
58 BinanceOrderQueryParams, BinancePositionRiskParams, BinanceSetLeverageParams,
59 BinanceSetMarginTypeParams, BinanceTicker24hrParams, BinanceTradesParams,
60 BinanceUserTradesParams, ListenKeyParams,
61 },
62};
63use crate::common::{
64 consts::{
65 BINANCE_DAPI_PATH, BINANCE_DAPI_RATE_LIMITS, BINANCE_FAPI_PATH, BINANCE_FAPI_RATE_LIMITS,
66 BinanceRateLimitQuota,
67 },
68 credential::Credential,
69 enums::{
70 BinanceAlgoType, BinanceEnvironment, BinanceFuturesOrderType, BinancePositionSide,
71 BinanceProductType, BinanceRateLimitInterval, BinanceRateLimitType, BinanceSide,
72 BinanceTimeInForce,
73 },
74 models::BinanceErrorResponse,
75 parse::{parse_coinm_instrument, parse_usdm_instrument},
76 symbol::{format_binance_symbol, format_instrument_id},
77 urls::get_http_base_url,
78};
79
80const BINANCE_GLOBAL_RATE_KEY: &str = "binance:global";
81const BINANCE_ORDERS_RATE_KEY: &str = "binance:orders";
82
83#[derive(Debug, Clone)]
85pub struct BinanceRawFuturesHttpClient {
86 client: HttpClient,
87 base_url: String,
88 api_path: &'static str,
89 credential: Option<Credential>,
90 recv_window: Option<u64>,
91 order_rate_keys: Vec<String>,
92}
93
94impl BinanceRawFuturesHttpClient {
95 #[must_use]
97 pub fn http_client(&self) -> &HttpClient {
98 &self.client
99 }
100
101 #[allow(clippy::too_many_arguments)]
107 pub fn new(
108 product_type: BinanceProductType,
109 environment: BinanceEnvironment,
110 api_key: Option<String>,
111 api_secret: Option<String>,
112 base_url_override: Option<String>,
113 recv_window: Option<u64>,
114 timeout_secs: Option<u64>,
115 proxy_url: Option<String>,
116 ) -> BinanceFuturesHttpResult<Self> {
117 let RateLimitConfig {
118 default_quota,
119 keyed_quotas,
120 order_keys,
121 } = Self::rate_limit_config(product_type);
122
123 let credential = match (api_key, api_secret) {
124 (Some(key), Some(secret)) => Some(Credential::new(key, secret)),
125 (None, None) => None,
126 _ => return Err(BinanceFuturesHttpError::MissingCredentials),
127 };
128
129 let base_url = base_url_override
130 .unwrap_or_else(|| get_http_base_url(product_type, environment).to_string());
131
132 let api_path = Self::resolve_api_path(product_type);
133 let headers = Self::default_headers(&credential);
134
135 let client = HttpClient::new(
136 headers,
137 vec!["X-MBX-APIKEY".to_string()],
138 keyed_quotas,
139 default_quota,
140 timeout_secs,
141 proxy_url,
142 )?;
143
144 Ok(Self {
145 client,
146 base_url,
147 api_path,
148 credential,
149 recv_window,
150 order_rate_keys: order_keys,
151 })
152 }
153
154 pub async fn get<P, T>(
160 &self,
161 path: &str,
162 params: Option<&P>,
163 signed: bool,
164 use_order_quota: bool,
165 ) -> BinanceFuturesHttpResult<T>
166 where
167 P: Serialize + ?Sized,
168 T: DeserializeOwned,
169 {
170 self.request(Method::GET, path, params, signed, use_order_quota, None)
171 .await
172 }
173
174 pub async fn post<P, T>(
180 &self,
181 path: &str,
182 params: Option<&P>,
183 body: Option<Vec<u8>>,
184 signed: bool,
185 use_order_quota: bool,
186 ) -> BinanceFuturesHttpResult<T>
187 where
188 P: Serialize + ?Sized,
189 T: DeserializeOwned,
190 {
191 self.request(Method::POST, path, params, signed, use_order_quota, body)
192 .await
193 }
194
195 pub async fn request_put<P, T>(
201 &self,
202 path: &str,
203 params: Option<&P>,
204 signed: bool,
205 use_order_quota: bool,
206 ) -> BinanceFuturesHttpResult<T>
207 where
208 P: Serialize + ?Sized,
209 T: DeserializeOwned,
210 {
211 self.request(Method::PUT, path, params, signed, use_order_quota, None)
212 .await
213 }
214
215 pub async fn request_delete<P, T>(
221 &self,
222 path: &str,
223 params: Option<&P>,
224 signed: bool,
225 use_order_quota: bool,
226 ) -> BinanceFuturesHttpResult<T>
227 where
228 P: Serialize + ?Sized,
229 T: DeserializeOwned,
230 {
231 self.request(Method::DELETE, path, params, signed, use_order_quota, None)
232 .await
233 }
234
235 pub async fn batch_request<T: Serialize>(
241 &self,
242 path: &str,
243 items: &[T],
244 use_order_quota: bool,
245 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
246 self.batch_request_method(Method::POST, path, items, use_order_quota)
247 .await
248 }
249
250 pub async fn batch_request_delete<T: Serialize>(
256 &self,
257 path: &str,
258 items: &[T],
259 use_order_quota: bool,
260 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
261 self.batch_request_method(Method::DELETE, path, items, use_order_quota)
262 .await
263 }
264
265 pub async fn batch_request_put<T: Serialize>(
271 &self,
272 path: &str,
273 items: &[T],
274 use_order_quota: bool,
275 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
276 self.batch_request_method(Method::PUT, path, items, use_order_quota)
277 .await
278 }
279
280 async fn batch_request_method<T: Serialize>(
281 &self,
282 method: Method,
283 path: &str,
284 items: &[T],
285 use_order_quota: bool,
286 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
287 let cred = self
288 .credential
289 .as_ref()
290 .ok_or(BinanceFuturesHttpError::MissingCredentials)?;
291
292 let batch_json = serde_json::to_string(items)
293 .map_err(|e| BinanceFuturesHttpError::ValidationError(e.to_string()))?;
294
295 let encoded_batch = Self::percent_encode(&batch_json);
296 let timestamp = Utc::now().timestamp_millis();
297 let mut query = format!("batchOrders={encoded_batch}×tamp={timestamp}");
298
299 if let Some(recv_window) = self.recv_window {
300 query.push_str(&format!("&recvWindow={recv_window}"));
301 }
302
303 let signature = cred.sign(&query);
304 query.push_str(&format!("&signature={signature}"));
305
306 let url = self.build_url(path, &query);
307
308 let mut headers = HashMap::new();
309 headers.insert("X-MBX-APIKEY".to_string(), cred.api_key().to_string());
310
311 let keys = self.rate_limit_keys(use_order_quota);
312
313 let response = self
314 .client
315 .request(
316 method,
317 url,
318 None::<&HashMap<String, Vec<String>>>,
319 Some(headers),
320 None,
321 None,
322 Some(keys),
323 )
324 .await?;
325
326 if !response.status.is_success() {
327 return self.parse_error_response(response);
328 }
329
330 serde_json::from_slice(&response.body)
331 .map_err(|e| BinanceFuturesHttpError::JsonError(e.to_string()))
332 }
333
334 fn percent_encode(input: &str) -> String {
336 let mut result = String::with_capacity(input.len() * 3);
337 for byte in input.bytes() {
338 match byte {
339 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
340 result.push(byte as char);
341 }
342 _ => {
343 result.push('%');
344 result.push_str(&format!("{byte:02X}"));
345 }
346 }
347 }
348 result
349 }
350
351 async fn request<P, T>(
352 &self,
353 method: Method,
354 path: &str,
355 params: Option<&P>,
356 signed: bool,
357 use_order_quota: bool,
358 body: Option<Vec<u8>>,
359 ) -> BinanceFuturesHttpResult<T>
360 where
361 P: Serialize + ?Sized,
362 T: DeserializeOwned,
363 {
364 let mut query = params
365 .map(serde_urlencoded::to_string)
366 .transpose()
367 .map_err(|e| BinanceFuturesHttpError::ValidationError(e.to_string()))?
368 .unwrap_or_default();
369
370 let mut headers = HashMap::new();
371 if signed {
372 let cred = self
373 .credential
374 .as_ref()
375 .ok_or(BinanceFuturesHttpError::MissingCredentials)?;
376
377 if !query.is_empty() {
378 query.push('&');
379 }
380
381 let timestamp = Utc::now().timestamp_millis();
382 query.push_str(&format!("timestamp={timestamp}"));
383
384 if let Some(recv_window) = self.recv_window {
385 query.push_str(&format!("&recvWindow={recv_window}"));
386 }
387
388 let signature = cred.sign(&query);
389 query.push_str(&format!("&signature={signature}"));
390 headers.insert("X-MBX-APIKEY".to_string(), cred.api_key().to_string());
391 }
392
393 let url = self.build_url(path, &query);
394 let keys = self.rate_limit_keys(use_order_quota);
395
396 let response = self
397 .client
398 .request(
399 method,
400 url,
401 None::<&HashMap<String, Vec<String>>>,
402 Some(headers),
403 body,
404 None,
405 Some(keys),
406 )
407 .await?;
408
409 if !response.status.is_success() {
410 return self.parse_error_response(response);
411 }
412
413 serde_json::from_slice::<T>(&response.body)
414 .map_err(|e| BinanceFuturesHttpError::JsonError(e.to_string()))
415 }
416
417 fn build_url(&self, path: &str, query: &str) -> String {
418 let url_path = if path.starts_with("/fapi/") || path.starts_with("/dapi/") {
420 path.to_string()
421 } else if path.starts_with('/') {
422 format!("{}{}", self.api_path, path)
423 } else {
424 format!("{}/{}", self.api_path, path)
425 };
426
427 let mut url = format!("{}{}", self.base_url, url_path);
428 if !query.is_empty() {
429 url.push('?');
430 url.push_str(query);
431 }
432 url
433 }
434
435 fn rate_limit_keys(&self, use_orders: bool) -> Vec<String> {
436 if use_orders {
437 let mut keys = Vec::with_capacity(1 + self.order_rate_keys.len());
438 keys.push(BINANCE_GLOBAL_RATE_KEY.to_string());
439 keys.extend(self.order_rate_keys.iter().cloned());
440 keys
441 } else {
442 vec![BINANCE_GLOBAL_RATE_KEY.to_string()]
443 }
444 }
445
446 fn parse_error_response<T>(&self, response: HttpResponse) -> BinanceFuturesHttpResult<T> {
447 let status = response.status.as_u16();
448 let body = String::from_utf8_lossy(&response.body).to_string();
449
450 if let Ok(err) = serde_json::from_str::<BinanceErrorResponse>(&body) {
451 return Err(BinanceFuturesHttpError::BinanceError {
452 code: err.code,
453 message: err.msg,
454 });
455 }
456
457 Err(BinanceFuturesHttpError::UnexpectedStatus { status, body })
458 }
459
460 fn default_headers(credential: &Option<Credential>) -> HashMap<String, String> {
461 let mut headers = HashMap::new();
462 headers.insert("User-Agent".to_string(), NAUTILUS_USER_AGENT.to_string());
463 if let Some(cred) = credential {
464 headers.insert("X-MBX-APIKEY".to_string(), cred.api_key().to_string());
465 }
466 headers
467 }
468
469 fn resolve_api_path(product_type: BinanceProductType) -> &'static str {
470 match product_type {
471 BinanceProductType::UsdM => BINANCE_FAPI_PATH,
472 BinanceProductType::CoinM => BINANCE_DAPI_PATH,
473 _ => BINANCE_FAPI_PATH, }
475 }
476
477 fn rate_limit_config(product_type: BinanceProductType) -> RateLimitConfig {
478 let quotas = match product_type {
479 BinanceProductType::UsdM => BINANCE_FAPI_RATE_LIMITS,
480 BinanceProductType::CoinM => BINANCE_DAPI_RATE_LIMITS,
481 _ => BINANCE_FAPI_RATE_LIMITS,
482 };
483
484 let mut keyed = Vec::new();
485 let mut order_keys = Vec::new();
486 let mut default = None;
487
488 for quota in quotas {
489 if let Some(q) = Self::quota_from(quota) {
490 match quota.rate_limit_type {
491 BinanceRateLimitType::RequestWeight if default.is_none() => {
492 default = Some(q);
493 }
494 BinanceRateLimitType::Orders => {
495 let key = format!("{}:{:?}", BINANCE_ORDERS_RATE_KEY, quota.interval);
496 order_keys.push(key.clone());
497 keyed.push((key, q));
498 }
499 _ => {}
500 }
501 }
502 }
503
504 let default_quota =
505 default.unwrap_or_else(|| Quota::per_second(NonZeroU32::new(10).unwrap()));
506
507 keyed.push((BINANCE_GLOBAL_RATE_KEY.to_string(), default_quota));
508
509 RateLimitConfig {
510 default_quota: Some(default_quota),
511 keyed_quotas: keyed,
512 order_keys,
513 }
514 }
515
516 fn quota_from(quota: &BinanceRateLimitQuota) -> Option<Quota> {
517 let burst = NonZeroU32::new(quota.limit)?;
518 match quota.interval {
519 BinanceRateLimitInterval::Second => Some(Quota::per_second(burst)),
520 BinanceRateLimitInterval::Minute => Some(Quota::per_minute(burst)),
521 BinanceRateLimitInterval::Day => {
522 Quota::with_period(Duration::from_secs(86_400)).map(|q| q.allow_burst(burst))
523 }
524 }
525 }
526
527 pub async fn ticker_24h(
533 &self,
534 params: &BinanceTicker24hrParams,
535 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesTicker24hr>> {
536 self.get("ticker/24hr", Some(params), false, false).await
537 }
538
539 pub async fn book_ticker(
545 &self,
546 params: &BinanceBookTickerParams,
547 ) -> BinanceFuturesHttpResult<Vec<BinanceBookTicker>> {
548 self.get("ticker/bookTicker", Some(params), false, false)
549 .await
550 }
551
552 pub async fn price_ticker(
558 &self,
559 symbol: Option<&str>,
560 ) -> BinanceFuturesHttpResult<Vec<BinancePriceTicker>> {
561 #[derive(Serialize)]
562 struct Params<'a> {
563 #[serde(skip_serializing_if = "Option::is_none")]
564 symbol: Option<&'a str>,
565 }
566 self.get("ticker/price", Some(&Params { symbol }), false, false)
567 .await
568 }
569
570 pub async fn depth(
576 &self,
577 params: &BinanceDepthParams,
578 ) -> BinanceFuturesHttpResult<BinanceOrderBook> {
579 self.get("depth", Some(params), false, false).await
580 }
581
582 pub async fn mark_price(
588 &self,
589 params: &BinanceMarkPriceParams,
590 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesMarkPrice>> {
591 let response: MarkPriceResponse =
592 self.get("premiumIndex", Some(params), false, false).await?;
593 Ok(response.into())
594 }
595
596 pub async fn funding_rate(
602 &self,
603 params: &BinanceFundingRateParams,
604 ) -> BinanceFuturesHttpResult<Vec<BinanceFundingRate>> {
605 self.get("fundingRate", Some(params), false, false).await
606 }
607
608 pub async fn open_interest(
614 &self,
615 params: &BinanceOpenInterestParams,
616 ) -> BinanceFuturesHttpResult<BinanceOpenInterest> {
617 self.get("openInterest", Some(params), false, false).await
618 }
619
620 pub async fn trades(
626 &self,
627 params: &BinanceTradesParams,
628 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesTrade>> {
629 self.get("trades", Some(params), false, false).await
630 }
631
632 pub async fn klines(
638 &self,
639 params: &BinanceKlinesParams,
640 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesKline>> {
641 self.get("klines", Some(params), false, false).await
642 }
643
644 pub async fn set_leverage(
650 &self,
651 params: &BinanceSetLeverageParams,
652 ) -> BinanceFuturesHttpResult<BinanceLeverageResponse> {
653 self.post("leverage", Some(params), None, true, false).await
654 }
655
656 pub async fn set_margin_type(
662 &self,
663 params: &BinanceSetMarginTypeParams,
664 ) -> BinanceFuturesHttpResult<serde_json::Value> {
665 self.post("marginType", Some(params), None, true, false)
666 .await
667 }
668
669 pub async fn query_hedge_mode(&self) -> BinanceFuturesHttpResult<BinanceHedgeModeResponse> {
675 self.get::<(), _>("positionSide/dual", None, true, false)
676 .await
677 }
678
679 pub async fn create_listen_key(&self) -> BinanceFuturesHttpResult<ListenKeyResponse> {
685 self.post::<(), ListenKeyResponse>("listenKey", None, None, true, false)
686 .await
687 }
688
689 pub async fn keepalive_listen_key(&self, listen_key: &str) -> BinanceFuturesHttpResult<()> {
695 let params = ListenKeyParams {
696 listen_key: listen_key.to_string(),
697 };
698 let _: serde_json::Value = self
699 .request_put("listenKey", Some(¶ms), true, false)
700 .await?;
701 Ok(())
702 }
703
704 pub async fn close_listen_key(&self, listen_key: &str) -> BinanceFuturesHttpResult<()> {
710 let params = ListenKeyParams {
711 listen_key: listen_key.to_string(),
712 };
713 let _: serde_json::Value = self
714 .request_delete("listenKey", Some(¶ms), true, false)
715 .await?;
716 Ok(())
717 }
718
719 pub async fn query_account(&self) -> BinanceFuturesHttpResult<BinanceFuturesAccountInfo> {
725 let path = if self.api_path.starts_with("/fapi") {
727 "/fapi/v2/account"
728 } else {
729 "/dapi/v1/account"
730 };
731 self.get::<(), _>(path, None, true, false).await
732 }
733
734 pub async fn query_positions(
740 &self,
741 params: &BinancePositionRiskParams,
742 ) -> BinanceFuturesHttpResult<Vec<BinancePositionRisk>> {
743 let path = if self.api_path.starts_with("/fapi") {
745 "/fapi/v2/positionRisk"
746 } else {
747 "/dapi/v1/positionRisk"
748 };
749 self.get(path, Some(params), true, false).await
750 }
751
752 pub async fn query_user_trades(
758 &self,
759 params: &BinanceUserTradesParams,
760 ) -> BinanceFuturesHttpResult<Vec<BinanceUserTrade>> {
761 self.get("userTrades", Some(params), true, false).await
762 }
763
764 pub async fn query_order(
770 &self,
771 params: &BinanceOrderQueryParams,
772 ) -> BinanceFuturesHttpResult<BinanceFuturesOrder> {
773 self.get("order", Some(params), true, false).await
774 }
775
776 pub async fn query_open_orders(
782 &self,
783 params: &BinanceOpenOrdersParams,
784 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesOrder>> {
785 self.get("openOrders", Some(params), true, false).await
786 }
787
788 pub async fn query_all_orders(
794 &self,
795 params: &BinanceAllOrdersParams,
796 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesOrder>> {
797 self.get("allOrders", Some(params), true, false).await
798 }
799
800 pub async fn submit_order(
806 &self,
807 params: &BinanceNewOrderParams,
808 ) -> BinanceFuturesHttpResult<BinanceFuturesOrder> {
809 self.post("order", Some(params), None, true, true).await
810 }
811
812 pub async fn submit_order_list(
818 &self,
819 orders: &[BatchOrderItem],
820 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
821 if orders.is_empty() {
822 return Ok(Vec::new());
823 }
824
825 if orders.len() > 5 {
826 return Err(BinanceFuturesHttpError::ValidationError(
827 "Batch order limit is 5 orders maximum".to_string(),
828 ));
829 }
830
831 self.batch_request("batchOrders", orders, true).await
832 }
833
834 pub async fn modify_order(
840 &self,
841 params: &BinanceModifyOrderParams,
842 ) -> BinanceFuturesHttpResult<BinanceFuturesOrder> {
843 self.request_put("order", Some(params), true, true).await
844 }
845
846 pub async fn batch_modify_orders(
852 &self,
853 modifies: &[BatchModifyItem],
854 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
855 if modifies.is_empty() {
856 return Ok(Vec::new());
857 }
858
859 if modifies.len() > 5 {
860 return Err(BinanceFuturesHttpError::ValidationError(
861 "Batch modify limit is 5 orders maximum".to_string(),
862 ));
863 }
864
865 self.batch_request_put("batchOrders", modifies, true).await
866 }
867
868 pub async fn cancel_order(
874 &self,
875 params: &BinanceCancelOrderParams,
876 ) -> BinanceFuturesHttpResult<BinanceFuturesOrder> {
877 self.request_delete("order", Some(params), true, true).await
878 }
879
880 pub async fn cancel_all_orders(
886 &self,
887 params: &BinanceCancelAllOrdersParams,
888 ) -> BinanceFuturesHttpResult<BinanceCancelAllOrdersResponse> {
889 self.request_delete("allOpenOrders", Some(params), true, true)
890 .await
891 }
892
893 pub async fn batch_cancel_orders(
899 &self,
900 cancels: &[BatchCancelItem],
901 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
902 if cancels.is_empty() {
903 return Ok(Vec::new());
904 }
905
906 if cancels.len() > 5 {
907 return Err(BinanceFuturesHttpError::ValidationError(
908 "Batch cancel limit is 5 orders maximum".to_string(),
909 ));
910 }
911
912 self.batch_request_delete("batchOrders", cancels, true)
913 .await
914 }
915
916 pub async fn submit_algo_order(
925 &self,
926 params: &BinanceNewAlgoOrderParams,
927 ) -> BinanceFuturesHttpResult<BinanceFuturesAlgoOrder> {
928 self.post("algoOrder", Some(params), None, true, true).await
929 }
930
931 pub async fn cancel_algo_order(
939 &self,
940 params: &BinanceAlgoOrderQueryParams,
941 ) -> BinanceFuturesHttpResult<BinanceFuturesAlgoOrderCancelResponse> {
942 self.request_delete("algoOrder", Some(params), true, true)
943 .await
944 }
945
946 pub async fn query_algo_order(
954 &self,
955 params: &BinanceAlgoOrderQueryParams,
956 ) -> BinanceFuturesHttpResult<BinanceFuturesAlgoOrder> {
957 self.get("algoOrder", Some(params), true, false).await
958 }
959
960 pub async fn query_open_algo_orders(
966 &self,
967 params: &BinanceOpenAlgoOrdersParams,
968 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesAlgoOrder>> {
969 self.get("openAlgoOrders", Some(params), true, false).await
970 }
971
972 pub async fn query_all_algo_orders(
978 &self,
979 params: &BinanceAllAlgoOrdersParams,
980 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesAlgoOrder>> {
981 self.get("allAlgoOrders", Some(params), true, false).await
982 }
983
984 pub async fn cancel_all_algo_orders(
990 &self,
991 params: &BinanceCancelAllAlgoOrdersParams,
992 ) -> BinanceFuturesHttpResult<BinanceCancelAllOrdersResponse> {
993 self.request_delete("algoOpenOrders", Some(params), true, true)
994 .await
995 }
996}
997
998#[derive(Debug, Deserialize)]
1000#[serde(untagged)]
1001enum MarkPriceResponse {
1002 Single(BinanceFuturesMarkPrice),
1003 Multiple(Vec<BinanceFuturesMarkPrice>),
1004}
1005
1006impl From<MarkPriceResponse> for Vec<BinanceFuturesMarkPrice> {
1007 fn from(response: MarkPriceResponse) -> Self {
1008 match response {
1009 MarkPriceResponse::Single(price) => vec![price],
1010 MarkPriceResponse::Multiple(prices) => prices,
1011 }
1012 }
1013}
1014
1015struct RateLimitConfig {
1016 default_quota: Option<Quota>,
1017 keyed_quotas: Vec<(String, Quota)>,
1018 order_keys: Vec<String>,
1019}
1020
1021#[derive(Clone, Debug)]
1023pub enum BinanceFuturesInstrument {
1024 UsdM(BinanceFuturesUsdSymbol),
1026 CoinM(BinanceFuturesCoinSymbol),
1028}
1029
1030impl BinanceFuturesInstrument {
1031 #[must_use]
1033 pub const fn symbol(&self) -> Ustr {
1034 match self {
1035 Self::UsdM(s) => s.symbol,
1036 Self::CoinM(s) => s.symbol,
1037 }
1038 }
1039
1040 #[must_use]
1042 pub const fn price_precision(&self) -> i32 {
1043 match self {
1044 Self::UsdM(s) => s.price_precision,
1045 Self::CoinM(s) => s.price_precision,
1046 }
1047 }
1048
1049 #[must_use]
1051 pub const fn quantity_precision(&self) -> i32 {
1052 match self {
1053 Self::UsdM(s) => s.quantity_precision,
1054 Self::CoinM(s) => s.quantity_precision,
1055 }
1056 }
1057
1058 #[must_use]
1060 pub fn id(&self) -> InstrumentId {
1061 match self {
1062 Self::UsdM(s) => format_instrument_id(&s.symbol, BinanceProductType::UsdM),
1063 Self::CoinM(s) => format_instrument_id(&s.symbol, BinanceProductType::CoinM),
1064 }
1065 }
1066}
1067
1068#[derive(Debug, Clone)]
1070#[cfg_attr(
1071 feature = "python",
1072 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.binance")
1073)]
1074pub struct BinanceFuturesHttpClient {
1075 raw: BinanceRawFuturesHttpClient,
1076 product_type: BinanceProductType,
1077 instruments: Arc<DashMap<Ustr, BinanceFuturesInstrument>>,
1078}
1079
1080impl BinanceFuturesHttpClient {
1081 #[allow(clippy::too_many_arguments)]
1087 pub fn new(
1088 product_type: BinanceProductType,
1089 environment: BinanceEnvironment,
1090 api_key: Option<String>,
1091 api_secret: Option<String>,
1092 base_url_override: Option<String>,
1093 recv_window: Option<u64>,
1094 timeout_secs: Option<u64>,
1095 proxy_url: Option<String>,
1096 ) -> BinanceFuturesHttpResult<Self> {
1097 match product_type {
1098 BinanceProductType::UsdM | BinanceProductType::CoinM => {}
1099 _ => {
1100 return Err(BinanceFuturesHttpError::ValidationError(format!(
1101 "BinanceFuturesHttpClient requires UsdM or CoinM product type, was {product_type:?}"
1102 )));
1103 }
1104 }
1105
1106 let raw = BinanceRawFuturesHttpClient::new(
1107 product_type,
1108 environment,
1109 api_key,
1110 api_secret,
1111 base_url_override,
1112 recv_window,
1113 timeout_secs,
1114 proxy_url,
1115 )?;
1116
1117 Ok(Self {
1118 raw,
1119 product_type,
1120 instruments: Arc::new(DashMap::new()),
1121 })
1122 }
1123
1124 #[must_use]
1126 pub const fn product_type(&self) -> BinanceProductType {
1127 self.product_type
1128 }
1129
1130 #[must_use]
1132 pub const fn raw(&self) -> &BinanceRawFuturesHttpClient {
1133 &self.raw
1134 }
1135
1136 #[must_use]
1138 pub fn instruments_cache(&self) -> Arc<DashMap<Ustr, BinanceFuturesInstrument>> {
1139 Arc::clone(&self.instruments)
1140 }
1141
1142 pub async fn server_time(&self) -> BinanceFuturesHttpResult<BinanceServerTime> {
1148 self.raw
1149 .get::<_, BinanceServerTime>("time", None::<&()>, false, false)
1150 .await
1151 }
1152
1153 pub async fn set_leverage(
1159 &self,
1160 params: &BinanceSetLeverageParams,
1161 ) -> BinanceFuturesHttpResult<BinanceLeverageResponse> {
1162 self.raw.set_leverage(params).await
1163 }
1164
1165 pub async fn set_margin_type(
1171 &self,
1172 params: &BinanceSetMarginTypeParams,
1173 ) -> BinanceFuturesHttpResult<serde_json::Value> {
1174 self.raw.set_margin_type(params).await
1175 }
1176
1177 pub async fn query_hedge_mode(&self) -> BinanceFuturesHttpResult<BinanceHedgeModeResponse> {
1183 self.raw.query_hedge_mode().await
1184 }
1185
1186 pub async fn create_listen_key(&self) -> BinanceFuturesHttpResult<ListenKeyResponse> {
1192 self.raw.create_listen_key().await
1193 }
1194
1195 pub async fn keepalive_listen_key(&self, listen_key: &str) -> BinanceFuturesHttpResult<()> {
1201 self.raw.keepalive_listen_key(listen_key).await
1202 }
1203
1204 pub async fn close_listen_key(&self, listen_key: &str) -> BinanceFuturesHttpResult<()> {
1210 self.raw.close_listen_key(listen_key).await
1211 }
1212
1213 pub async fn exchange_info(&self) -> BinanceFuturesHttpResult<()> {
1219 match self.product_type {
1220 BinanceProductType::UsdM => {
1221 let info: BinanceFuturesUsdExchangeInfo = self
1222 .raw
1223 .get("exchangeInfo", None::<&()>, false, false)
1224 .await?;
1225 for symbol in info.symbols {
1226 self.instruments
1227 .insert(symbol.symbol, BinanceFuturesInstrument::UsdM(symbol));
1228 }
1229 }
1230 BinanceProductType::CoinM => {
1231 let info: BinanceFuturesCoinExchangeInfo = self
1232 .raw
1233 .get("exchangeInfo", None::<&()>, false, false)
1234 .await?;
1235 for symbol in info.symbols {
1236 self.instruments
1237 .insert(symbol.symbol, BinanceFuturesInstrument::CoinM(symbol));
1238 }
1239 }
1240 _ => {
1241 return Err(BinanceFuturesHttpError::ValidationError(
1242 "Invalid product type for futures".to_string(),
1243 ));
1244 }
1245 }
1246
1247 Ok(())
1248 }
1249
1250 pub async fn request_instruments(&self) -> BinanceFuturesHttpResult<Vec<InstrumentAny>> {
1256 let ts_init = UnixNanos::default();
1257
1258 let instruments = match self.product_type {
1259 BinanceProductType::UsdM => {
1260 let info: BinanceFuturesUsdExchangeInfo = self
1261 .raw
1262 .get("exchangeInfo", None::<&()>, false, false)
1263 .await?;
1264
1265 let mut instruments = Vec::with_capacity(info.symbols.len());
1266
1267 for symbol in info.symbols {
1268 self.instruments.insert(
1270 symbol.symbol,
1271 BinanceFuturesInstrument::UsdM(symbol.clone()),
1272 );
1273
1274 match parse_usdm_instrument(&symbol, ts_init, ts_init) {
1275 Ok(instrument) => instruments.push(instrument),
1276 Err(e) => {
1277 log::debug!(
1278 "Skipping symbol during instrument parsing: symbol={}, error={e}",
1279 symbol.symbol
1280 );
1281 }
1282 }
1283 }
1284
1285 log::info!(
1286 "Loaded USD-M perpetual instruments: count={}",
1287 instruments.len()
1288 );
1289 instruments
1290 }
1291 BinanceProductType::CoinM => {
1292 let info: BinanceFuturesCoinExchangeInfo = self
1293 .raw
1294 .get("exchangeInfo", None::<&()>, false, false)
1295 .await?;
1296
1297 let mut instruments = Vec::with_capacity(info.symbols.len());
1298 for symbol in info.symbols {
1299 self.instruments.insert(
1301 symbol.symbol,
1302 BinanceFuturesInstrument::CoinM(symbol.clone()),
1303 );
1304
1305 match parse_coinm_instrument(&symbol, ts_init, ts_init) {
1306 Ok(instrument) => instruments.push(instrument),
1307 Err(e) => {
1308 log::debug!(
1309 "Skipping symbol during instrument parsing: symbol={}, error={e}",
1310 symbol.symbol
1311 );
1312 }
1313 }
1314 }
1315
1316 log::info!(
1317 "Loaded COIN-M perpetual instruments: count={}",
1318 instruments.len()
1319 );
1320 instruments
1321 }
1322 _ => {
1323 return Err(BinanceFuturesHttpError::ValidationError(
1324 "Invalid product type for futures".to_string(),
1325 ));
1326 }
1327 };
1328
1329 Ok(instruments)
1330 }
1331
1332 pub async fn ticker_24h(
1338 &self,
1339 params: &BinanceTicker24hrParams,
1340 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesTicker24hr>> {
1341 self.raw.ticker_24h(params).await
1342 }
1343
1344 pub async fn book_ticker(
1350 &self,
1351 params: &BinanceBookTickerParams,
1352 ) -> BinanceFuturesHttpResult<Vec<BinanceBookTicker>> {
1353 self.raw.book_ticker(params).await
1354 }
1355
1356 pub async fn price_ticker(
1362 &self,
1363 symbol: Option<&str>,
1364 ) -> BinanceFuturesHttpResult<Vec<BinancePriceTicker>> {
1365 self.raw.price_ticker(symbol).await
1366 }
1367
1368 pub async fn depth(
1374 &self,
1375 params: &BinanceDepthParams,
1376 ) -> BinanceFuturesHttpResult<BinanceOrderBook> {
1377 self.raw.depth(params).await
1378 }
1379
1380 pub async fn mark_price(
1386 &self,
1387 params: &BinanceMarkPriceParams,
1388 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesMarkPrice>> {
1389 self.raw.mark_price(params).await
1390 }
1391
1392 pub async fn funding_rate(
1398 &self,
1399 params: &BinanceFundingRateParams,
1400 ) -> BinanceFuturesHttpResult<Vec<BinanceFundingRate>> {
1401 self.raw.funding_rate(params).await
1402 }
1403
1404 pub async fn open_interest(
1410 &self,
1411 params: &BinanceOpenInterestParams,
1412 ) -> BinanceFuturesHttpResult<BinanceOpenInterest> {
1413 self.raw.open_interest(params).await
1414 }
1415
1416 pub async fn query_order(
1422 &self,
1423 params: &BinanceOrderQueryParams,
1424 ) -> BinanceFuturesHttpResult<BinanceFuturesOrder> {
1425 self.raw.query_order(params).await
1426 }
1427
1428 pub async fn query_open_orders(
1434 &self,
1435 params: &BinanceOpenOrdersParams,
1436 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesOrder>> {
1437 self.raw.query_open_orders(params).await
1438 }
1439
1440 pub async fn query_all_orders(
1446 &self,
1447 params: &BinanceAllOrdersParams,
1448 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesOrder>> {
1449 self.raw.query_all_orders(params).await
1450 }
1451
1452 pub async fn query_account(&self) -> BinanceFuturesHttpResult<BinanceFuturesAccountInfo> {
1458 self.raw.query_account().await
1459 }
1460
1461 pub async fn query_positions(
1467 &self,
1468 params: &BinancePositionRiskParams,
1469 ) -> BinanceFuturesHttpResult<Vec<BinancePositionRisk>> {
1470 self.raw.query_positions(params).await
1471 }
1472
1473 pub async fn query_user_trades(
1479 &self,
1480 params: &BinanceUserTradesParams,
1481 ) -> BinanceFuturesHttpResult<Vec<BinanceUserTrade>> {
1482 self.raw.query_user_trades(params).await
1483 }
1484
1485 #[allow(clippy::too_many_arguments)]
1495 pub async fn submit_order(
1496 &self,
1497 account_id: AccountId,
1498 instrument_id: InstrumentId,
1499 client_order_id: ClientOrderId,
1500 order_side: OrderSide,
1501 order_type: OrderType,
1502 quantity: Quantity,
1503 time_in_force: TimeInForce,
1504 price: Option<Price>,
1505 trigger_price: Option<Price>,
1506 reduce_only: bool,
1507 position_side: Option<BinancePositionSide>,
1508 ) -> anyhow::Result<OrderStatusReport> {
1509 let symbol = format_binance_symbol(&instrument_id);
1510 let size_precision = self.get_size_precision(&symbol)?;
1511
1512 let binance_side = BinanceSide::try_from(order_side)?;
1513 let binance_order_type = order_type_to_binance_futures(order_type)?;
1514 let binance_tif = BinanceTimeInForce::try_from(time_in_force)?;
1515
1516 let requires_trigger_price = matches!(
1517 order_type,
1518 OrderType::StopMarket
1519 | OrderType::StopLimit
1520 | OrderType::TrailingStopMarket
1521 | OrderType::MarketIfTouched
1522 | OrderType::LimitIfTouched
1523 );
1524 if requires_trigger_price && trigger_price.is_none() {
1525 anyhow::bail!("Order type {order_type:?} requires a trigger price");
1526 }
1527
1528 let requires_time_in_force = matches!(
1530 order_type,
1531 OrderType::Limit | OrderType::StopLimit | OrderType::LimitIfTouched
1532 );
1533
1534 let qty_str = quantity.to_string();
1535 let price_str = price.map(|p| p.to_string());
1536 let stop_price_str = trigger_price.map(|p| p.to_string());
1537 let client_id_str = client_order_id.to_string();
1538
1539 let params = BinanceNewOrderParams {
1540 symbol,
1541 side: binance_side,
1542 order_type: binance_order_type,
1543 time_in_force: if requires_time_in_force {
1544 Some(binance_tif)
1545 } else {
1546 None
1547 },
1548 quantity: Some(qty_str),
1549 price: price_str,
1550 new_client_order_id: Some(client_id_str),
1551 stop_price: stop_price_str,
1552 reduce_only: if reduce_only { Some(true) } else { None },
1553 position_side,
1554 close_position: None,
1555 activation_price: None,
1556 callback_rate: None,
1557 working_type: None,
1558 price_protect: None,
1559 new_order_resp_type: None,
1560 good_till_date: None,
1561 recv_window: None,
1562 price_match: None,
1563 self_trade_prevention_mode: None,
1564 };
1565
1566 let order = self.raw.submit_order(¶ms).await?;
1567 order.to_order_status_report(account_id, instrument_id, size_precision)
1568 }
1569
1570 #[allow(clippy::too_many_arguments)]
1583 pub async fn submit_algo_order(
1584 &self,
1585 account_id: AccountId,
1586 instrument_id: InstrumentId,
1587 client_order_id: ClientOrderId,
1588 order_side: OrderSide,
1589 order_type: OrderType,
1590 quantity: Quantity,
1591 time_in_force: TimeInForce,
1592 price: Option<Price>,
1593 trigger_price: Option<Price>,
1594 reduce_only: bool,
1595 position_side: Option<BinancePositionSide>,
1596 ) -> anyhow::Result<OrderStatusReport> {
1597 let symbol = format_binance_symbol(&instrument_id);
1598 let size_precision = self.get_size_precision(&symbol)?;
1599
1600 let binance_side = BinanceSide::try_from(order_side)?;
1601 let binance_order_type = order_type_to_binance_futures(order_type)?;
1602 let binance_tif = BinanceTimeInForce::try_from(time_in_force)?;
1603
1604 anyhow::ensure!(
1605 trigger_price.is_some(),
1606 "Algo order type {order_type:?} requires a trigger price"
1607 );
1608
1609 let requires_time_in_force =
1611 matches!(order_type, OrderType::StopLimit | OrderType::LimitIfTouched);
1612
1613 let qty_str = quantity.to_string();
1614 let price_str = price.map(|p| p.to_string());
1615 let trigger_price_str = trigger_price.map(|p| p.to_string());
1616 let client_id_str = client_order_id.to_string();
1617
1618 let params = BinanceNewAlgoOrderParams {
1619 symbol,
1620 side: binance_side,
1621 order_type: binance_order_type,
1622 algo_type: BinanceAlgoType::Conditional,
1623 position_side,
1624 quantity: Some(qty_str),
1625 price: price_str,
1626 trigger_price: trigger_price_str,
1627 time_in_force: if requires_time_in_force {
1628 Some(binance_tif)
1629 } else {
1630 None
1631 },
1632 working_type: None,
1633 close_position: None,
1634 price_protect: None,
1635 reduce_only: if reduce_only { Some(true) } else { None },
1636 activation_price: None,
1637 callback_rate: None,
1638 client_algo_id: Some(client_id_str),
1639 good_till_date: None,
1640 recv_window: None,
1641 };
1642
1643 let order = self.raw.submit_algo_order(¶ms).await?;
1644 order.to_order_status_report(account_id, instrument_id, size_precision)
1645 }
1646
1647 pub async fn submit_order_list(
1656 &self,
1657 orders: &[BatchOrderItem],
1658 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
1659 self.raw.submit_order_list(orders).await
1660 }
1661
1662 #[allow(clippy::too_many_arguments)]
1673 pub async fn modify_order(
1674 &self,
1675 account_id: AccountId,
1676 instrument_id: InstrumentId,
1677 venue_order_id: Option<VenueOrderId>,
1678 client_order_id: Option<ClientOrderId>,
1679 order_side: OrderSide,
1680 quantity: Quantity,
1681 price: Price,
1682 ) -> anyhow::Result<OrderStatusReport> {
1683 anyhow::ensure!(
1684 venue_order_id.is_some() || client_order_id.is_some(),
1685 "Either venue_order_id or client_order_id must be provided"
1686 );
1687
1688 let symbol = format_binance_symbol(&instrument_id);
1689 let size_precision = self.get_size_precision(&symbol)?;
1690
1691 let binance_side = BinanceSide::try_from(order_side)?;
1692
1693 let order_id = venue_order_id
1694 .map(|id| id.inner().parse::<i64>())
1695 .transpose()
1696 .map_err(|_| anyhow::anyhow!("Invalid venue order ID"))?;
1697
1698 let params = BinanceModifyOrderParams {
1699 symbol,
1700 order_id,
1701 orig_client_order_id: client_order_id.map(|id| id.to_string()),
1702 side: binance_side,
1703 quantity: quantity.to_string(),
1704 price: price.to_string(),
1705 recv_window: None,
1706 };
1707
1708 let order = self.raw.modify_order(¶ms).await?;
1709 order.to_order_status_report(account_id, instrument_id, size_precision)
1710 }
1711
1712 pub async fn batch_modify_orders(
1721 &self,
1722 modifies: &[BatchModifyItem],
1723 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
1724 self.raw.batch_modify_orders(modifies).await
1725 }
1726
1727 pub async fn cancel_order(
1737 &self,
1738 instrument_id: InstrumentId,
1739 venue_order_id: Option<VenueOrderId>,
1740 client_order_id: Option<ClientOrderId>,
1741 ) -> anyhow::Result<VenueOrderId> {
1742 anyhow::ensure!(
1743 venue_order_id.is_some() || client_order_id.is_some(),
1744 "Either venue_order_id or client_order_id must be provided"
1745 );
1746
1747 let symbol = format_binance_symbol(&instrument_id);
1748
1749 let order_id = venue_order_id
1750 .map(|id| id.inner().parse::<i64>())
1751 .transpose()
1752 .map_err(|_| anyhow::anyhow!("Invalid venue order ID"))?;
1753
1754 let params = BinanceCancelOrderParams {
1755 symbol,
1756 order_id,
1757 orig_client_order_id: client_order_id.map(|id| id.to_string()),
1758 recv_window: None,
1759 };
1760
1761 let order = self.raw.cancel_order(¶ms).await?;
1762 Ok(VenueOrderId::new(order.order_id.to_string()))
1763 }
1764
1765 pub async fn cancel_algo_order(&self, client_order_id: ClientOrderId) -> anyhow::Result<()> {
1774 let params = BinanceAlgoOrderQueryParams {
1775 algo_id: None,
1776 client_algo_id: Some(client_order_id.to_string()),
1777 recv_window: None,
1778 };
1779
1780 let response = self.raw.cancel_algo_order(¶ms).await?;
1781 if response.code == 200 {
1782 Ok(())
1783 } else {
1784 anyhow::bail!(
1785 "Cancel algo order failed: code={}, msg={}",
1786 response.code,
1787 response.msg
1788 )
1789 }
1790 }
1791
1792 pub async fn cancel_all_orders(
1798 &self,
1799 instrument_id: InstrumentId,
1800 ) -> anyhow::Result<Vec<VenueOrderId>> {
1801 let symbol = format_binance_symbol(&instrument_id);
1802
1803 let params = BinanceCancelAllOrdersParams {
1804 symbol,
1805 recv_window: None,
1806 };
1807
1808 let response = self.raw.cancel_all_orders(¶ms).await?;
1809 if response.code == 200 {
1810 Ok(vec![])
1811 } else {
1812 anyhow::bail!("Cancel all orders failed: {}", response.msg);
1813 }
1814 }
1815
1816 pub async fn cancel_all_algo_orders(&self, instrument_id: InstrumentId) -> anyhow::Result<()> {
1822 let symbol = format_binance_symbol(&instrument_id);
1823
1824 let params = BinanceCancelAllAlgoOrdersParams {
1825 symbol,
1826 recv_window: None,
1827 };
1828
1829 let response = self.raw.cancel_all_algo_orders(¶ms).await?;
1830 if response.code == 200 {
1831 Ok(())
1832 } else {
1833 anyhow::bail!("Cancel all algo orders failed: {}", response.msg);
1834 }
1835 }
1836
1837 pub async fn batch_cancel_orders(
1846 &self,
1847 cancels: &[BatchCancelItem],
1848 ) -> BinanceFuturesHttpResult<Vec<BatchOrderResult>> {
1849 self.raw.batch_cancel_orders(cancels).await
1850 }
1851
1852 pub async fn query_open_algo_orders(
1860 &self,
1861 instrument_id: Option<InstrumentId>,
1862 ) -> BinanceFuturesHttpResult<Vec<BinanceFuturesAlgoOrder>> {
1863 let symbol = instrument_id.map(|id| format_binance_symbol(&id));
1864
1865 let params = BinanceOpenAlgoOrdersParams {
1866 symbol,
1867 recv_window: None,
1868 };
1869
1870 self.raw.query_open_algo_orders(¶ms).await
1871 }
1872
1873 pub async fn query_algo_order(
1879 &self,
1880 client_order_id: ClientOrderId,
1881 ) -> BinanceFuturesHttpResult<BinanceFuturesAlgoOrder> {
1882 let params = BinanceAlgoOrderQueryParams {
1883 algo_id: None,
1884 client_algo_id: Some(client_order_id.to_string()),
1885 recv_window: None,
1886 };
1887
1888 self.raw.query_algo_order(¶ms).await
1889 }
1890
1891 fn get_size_precision(&self, symbol: &str) -> anyhow::Result<u8> {
1893 let instrument = self
1894 .instruments
1895 .get(&Ustr::from(symbol))
1896 .ok_or_else(|| anyhow::anyhow!("Instrument not found in cache: {symbol}"))?;
1897
1898 let precision = match instrument.value() {
1899 BinanceFuturesInstrument::UsdM(s) => s.quantity_precision,
1900 BinanceFuturesInstrument::CoinM(s) => s.quantity_precision,
1901 };
1902
1903 Ok(precision as u8)
1904 }
1905
1906 fn get_price_precision(&self, symbol: &str) -> anyhow::Result<u8> {
1908 let instrument = self
1909 .instruments
1910 .get(&Ustr::from(symbol))
1911 .ok_or_else(|| anyhow::anyhow!("Instrument not found in cache: {symbol}"))?;
1912
1913 let precision = match instrument.value() {
1914 BinanceFuturesInstrument::UsdM(s) => s.price_precision,
1915 BinanceFuturesInstrument::CoinM(s) => s.price_precision,
1916 };
1917
1918 Ok(precision as u8)
1919 }
1920
1921 pub async fn request_account_state(
1927 &self,
1928 account_id: AccountId,
1929 ) -> anyhow::Result<AccountState> {
1930 let ts_init = UnixNanos::default();
1931 let account_info = self.raw.query_account().await?;
1932 account_info.to_account_state(account_id, ts_init)
1933 }
1934
1935 pub async fn request_order_status_report(
1943 &self,
1944 account_id: AccountId,
1945 instrument_id: InstrumentId,
1946 venue_order_id: Option<VenueOrderId>,
1947 client_order_id: Option<ClientOrderId>,
1948 ) -> anyhow::Result<OrderStatusReport> {
1949 anyhow::ensure!(
1950 venue_order_id.is_some() || client_order_id.is_some(),
1951 "Either venue_order_id or client_order_id must be provided"
1952 );
1953
1954 let symbol = format_binance_symbol(&instrument_id);
1955 let size_precision = self.get_size_precision(&symbol)?;
1956
1957 let order_id = venue_order_id
1958 .map(|id| id.inner().parse::<i64>())
1959 .transpose()
1960 .map_err(|_| anyhow::anyhow!("Invalid venue order ID"))?;
1961
1962 let orig_client_order_id = client_order_id.map(|id| id.to_string());
1963
1964 let params = BinanceOrderQueryParams {
1965 symbol,
1966 order_id,
1967 orig_client_order_id,
1968 recv_window: None,
1969 };
1970
1971 let order = self.raw.query_order(¶ms).await?;
1972 order.to_order_status_report(account_id, instrument_id, size_precision)
1973 }
1974
1975 pub async fn request_order_status_reports(
1983 &self,
1984 account_id: AccountId,
1985 instrument_id: Option<InstrumentId>,
1986 open_only: bool,
1987 ) -> anyhow::Result<Vec<OrderStatusReport>> {
1988 let symbol = instrument_id.map(|id| format_binance_symbol(&id));
1989
1990 let orders = if open_only {
1991 let params = BinanceOpenOrdersParams {
1992 symbol: symbol.clone(),
1993 recv_window: None,
1994 };
1995 self.raw.query_open_orders(¶ms).await?
1996 } else {
1997 let symbol = symbol.ok_or_else(|| {
1999 anyhow::anyhow!("instrument_id is required for historical orders")
2000 })?;
2001 let params = BinanceAllOrdersParams {
2002 symbol,
2003 order_id: None,
2004 start_time: None,
2005 end_time: None,
2006 limit: None,
2007 recv_window: None,
2008 };
2009 self.raw.query_all_orders(¶ms).await?
2010 };
2011
2012 let mut reports = Vec::with_capacity(orders.len());
2013
2014 for order in orders {
2015 let order_instrument_id = instrument_id.unwrap_or_else(|| {
2016 let suffix = self.product_type.suffix();
2018 InstrumentId::from(format!("{}{}.BINANCE", order.symbol, suffix))
2019 });
2020
2021 let size_precision = self.get_size_precision(&order.symbol).unwrap_or(8); match order.to_order_status_report(account_id, order_instrument_id, size_precision) {
2024 Ok(report) => reports.push(report),
2025 Err(e) => {
2026 log::warn!("Failed to parse order status report: {e}");
2027 }
2028 }
2029 }
2030
2031 Ok(reports)
2032 }
2033
2034 pub async fn request_fill_reports(
2040 &self,
2041 account_id: AccountId,
2042 instrument_id: InstrumentId,
2043 venue_order_id: Option<VenueOrderId>,
2044 start: Option<i64>,
2045 end: Option<i64>,
2046 limit: Option<u32>,
2047 ) -> anyhow::Result<Vec<FillReport>> {
2048 let symbol = format_binance_symbol(&instrument_id);
2049 let size_precision = self.get_size_precision(&symbol)?;
2050 let price_precision = self.get_price_precision(&symbol)?;
2051
2052 let order_id = venue_order_id
2053 .map(|id| id.inner().parse::<i64>())
2054 .transpose()
2055 .map_err(|_| anyhow::anyhow!("Invalid venue order ID"))?;
2056
2057 let params = BinanceUserTradesParams {
2058 symbol,
2059 order_id,
2060 start_time: start,
2061 end_time: end,
2062 from_id: None,
2063 limit,
2064 recv_window: None,
2065 };
2066
2067 let trades = self.raw.query_user_trades(¶ms).await?;
2068
2069 let mut reports = Vec::with_capacity(trades.len());
2070
2071 for trade in trades {
2072 match trade.to_fill_report(account_id, instrument_id, price_precision, size_precision) {
2073 Ok(report) => reports.push(report),
2074 Err(e) => {
2075 log::warn!("Failed to parse fill report: {e}");
2076 }
2077 }
2078 }
2079
2080 Ok(reports)
2081 }
2082
2083 pub async fn request_trades(
2089 &self,
2090 instrument_id: InstrumentId,
2091 limit: Option<u32>,
2092 ) -> anyhow::Result<Vec<TradeTick>> {
2093 let symbol = format_binance_symbol(&instrument_id);
2094 let size_precision = self.get_size_precision(&symbol)?;
2095 let price_precision = self.get_price_precision(&symbol)?;
2096
2097 let params = BinanceTradesParams { symbol, limit };
2098
2099 let trades = self.raw.trades(¶ms).await?;
2100 let ts_init = UnixNanos::default();
2101
2102 let mut result = Vec::with_capacity(trades.len());
2103 for trade in trades {
2104 let price: f64 = trade.price.parse().unwrap_or(0.0);
2105 let size: f64 = trade.qty.parse().unwrap_or(0.0);
2106 let ts_event = UnixNanos::from((trade.time * 1_000_000) as u64);
2107
2108 let aggressor_side = if trade.is_buyer_maker {
2109 AggressorSide::Seller
2110 } else {
2111 AggressorSide::Buyer
2112 };
2113
2114 let tick = TradeTick::new(
2115 instrument_id,
2116 Price::new(price, price_precision),
2117 Quantity::new(size, size_precision),
2118 aggressor_side,
2119 TradeId::new(trade.id.to_string()),
2120 ts_event,
2121 ts_init,
2122 );
2123 result.push(tick);
2124 }
2125
2126 Ok(result)
2127 }
2128
2129 pub async fn request_bars(
2136 &self,
2137 bar_type: BarType,
2138 start: Option<DateTime<Utc>>,
2139 end: Option<DateTime<Utc>>,
2140 limit: Option<u32>,
2141 ) -> anyhow::Result<Vec<Bar>> {
2142 anyhow::ensure!(
2143 bar_type.aggregation_source() == AggregationSource::External,
2144 "Only EXTERNAL aggregation is supported"
2145 );
2146
2147 let spec = bar_type.spec();
2148 let step = spec.step.get();
2149 let interval = match spec.aggregation {
2150 BarAggregation::Second => {
2151 anyhow::bail!("Binance Futures does not support second-level kline intervals")
2152 }
2153 BarAggregation::Minute => format!("{step}m"),
2154 BarAggregation::Hour => format!("{step}h"),
2155 BarAggregation::Day => format!("{step}d"),
2156 BarAggregation::Week => format!("{step}w"),
2157 BarAggregation::Month => format!("{step}M"),
2158 a => anyhow::bail!("Binance Futures does not support {a:?} aggregation"),
2159 };
2160
2161 let symbol = format_binance_symbol(&bar_type.instrument_id());
2162 let price_precision = self.get_price_precision(&symbol)?;
2163 let size_precision = self.get_size_precision(&symbol)?;
2164
2165 let params = BinanceKlinesParams {
2166 symbol,
2167 interval,
2168 start_time: start.map(|dt| dt.timestamp_millis()),
2169 end_time: end.map(|dt| dt.timestamp_millis()),
2170 limit,
2171 };
2172
2173 let klines = self.raw.klines(¶ms).await?;
2174 let ts_init = UnixNanos::default();
2175
2176 let mut result = Vec::with_capacity(klines.len());
2177 for kline in klines {
2178 let open: f64 = kline.open.parse().unwrap_or(0.0);
2179 let high: f64 = kline.high.parse().unwrap_or(0.0);
2180 let low: f64 = kline.low.parse().unwrap_or(0.0);
2181 let close: f64 = kline.close.parse().unwrap_or(0.0);
2182 let volume: f64 = kline.volume.parse().unwrap_or(0.0);
2183
2184 let ts_event = UnixNanos::from((kline.close_time * 1_000_000) as u64);
2186
2187 let bar = Bar::new(
2188 bar_type,
2189 Price::new(open, price_precision),
2190 Price::new(high, price_precision),
2191 Price::new(low, price_precision),
2192 Price::new(close, price_precision),
2193 Quantity::new(volume, size_precision),
2194 ts_event,
2195 ts_init,
2196 );
2197 result.push(bar);
2198 }
2199
2200 Ok(result)
2201 }
2202}
2203
2204#[must_use]
2209pub fn is_algo_order_type(order_type: OrderType) -> bool {
2210 matches!(
2211 order_type,
2212 OrderType::StopMarket
2213 | OrderType::StopLimit
2214 | OrderType::MarketIfTouched
2215 | OrderType::LimitIfTouched
2216 | OrderType::TrailingStopMarket
2217 )
2218}
2219
2220fn order_type_to_binance_futures(order_type: OrderType) -> anyhow::Result<BinanceFuturesOrderType> {
2222 match order_type {
2223 OrderType::Market => Ok(BinanceFuturesOrderType::Market),
2224 OrderType::Limit => Ok(BinanceFuturesOrderType::Limit),
2225 OrderType::StopMarket => Ok(BinanceFuturesOrderType::StopMarket),
2226 OrderType::StopLimit => Ok(BinanceFuturesOrderType::Stop),
2227 OrderType::MarketIfTouched => Ok(BinanceFuturesOrderType::TakeProfitMarket),
2228 OrderType::LimitIfTouched => Ok(BinanceFuturesOrderType::TakeProfit),
2229 OrderType::TrailingStopMarket => Ok(BinanceFuturesOrderType::TrailingStopMarket),
2230 _ => anyhow::bail!("Unsupported order type for Binance Futures: {order_type:?}"),
2231 }
2232}
2233
2234#[cfg(test)]
2235mod tests {
2236 use nautilus_network::http::{HttpStatus, StatusCode};
2237 use rstest::rstest;
2238 use tokio_util::bytes::Bytes;
2239
2240 use super::*;
2241
2242 #[rstest]
2243 fn test_rate_limit_config_usdm_has_request_weight_and_orders() {
2244 let config = BinanceRawFuturesHttpClient::rate_limit_config(BinanceProductType::UsdM);
2245
2246 assert!(config.default_quota.is_some());
2247 assert_eq!(config.order_keys.len(), 2);
2248 assert!(config.order_keys.iter().any(|k| k.contains("Second")));
2249 assert!(config.order_keys.iter().any(|k| k.contains("Minute")));
2250 }
2251
2252 #[rstest]
2253 fn test_rate_limit_config_coinm_has_request_weight_and_orders() {
2254 let config = BinanceRawFuturesHttpClient::rate_limit_config(BinanceProductType::CoinM);
2255
2256 assert!(config.default_quota.is_some());
2257 assert_eq!(config.order_keys.len(), 2);
2258 }
2259
2260 #[rstest]
2261 fn test_create_client_rejects_spot_product_type() {
2262 let result = BinanceFuturesHttpClient::new(
2263 BinanceProductType::Spot,
2264 BinanceEnvironment::Mainnet,
2265 None,
2266 None,
2267 None,
2268 None,
2269 None,
2270 None,
2271 );
2272
2273 assert!(result.is_err());
2274 }
2275
2276 fn create_test_raw_client() -> BinanceRawFuturesHttpClient {
2277 BinanceRawFuturesHttpClient::new(
2278 BinanceProductType::UsdM,
2279 BinanceEnvironment::Mainnet,
2280 None,
2281 None,
2282 None,
2283 None,
2284 None,
2285 None,
2286 )
2287 .expect("Failed to create test client")
2288 }
2289
2290 #[rstest]
2291 fn test_parse_error_response_binance_error() {
2292 let client = create_test_raw_client();
2293 let response = HttpResponse {
2294 status: HttpStatus::new(StatusCode::BAD_REQUEST),
2295 headers: HashMap::new(),
2296 body: Bytes::from(r#"{"code":-1121,"msg":"Invalid symbol."}"#),
2297 };
2298
2299 let result: BinanceFuturesHttpResult<()> = client.parse_error_response(response);
2300
2301 match result {
2302 Err(BinanceFuturesHttpError::BinanceError { code, message }) => {
2303 assert_eq!(code, -1121);
2304 assert_eq!(message, "Invalid symbol.");
2305 }
2306 other => panic!("Expected BinanceError, was {other:?}"),
2307 }
2308 }
2309}