1use ccxt_core::types::EndpointType;
6use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
7use ccxt_core::{BaseExchange, ExchangeConfig, Result};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::collections::HashMap;
10use std::str::FromStr;
11use std::sync::Arc;
12use std::time::Duration;
13
14pub mod auth;
15pub mod builder;
16pub mod constants;
17pub mod endpoint_router;
18pub mod error;
19mod exchange_impl;
20pub mod parser;
21pub mod rest;
22pub mod signed_request;
23pub mod symbol;
24pub mod time_sync;
25pub mod ws;
26mod ws_exchange_impl;
27
28pub use builder::BinanceBuilder;
29pub use endpoint_router::BinanceEndpointRouter;
30pub use signed_request::{HttpMethod, SignedRequestBuilder};
31pub use time_sync::{TimeSyncConfig, TimeSyncManager};
32#[derive(Debug)]
34pub struct Binance {
35 base: BaseExchange,
37 options: BinanceOptions,
39 time_sync: Arc<TimeSyncManager>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BinanceOptions {
59 pub adjust_for_time_difference: bool,
61 pub recv_window: u64,
63 #[serde(deserialize_with = "deserialize_default_type")]
68 pub default_type: DefaultType,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub default_sub_type: Option<DefaultSubType>,
78 pub test: bool,
80 #[serde(default = "default_sync_interval")]
85 pub time_sync_interval_secs: u64,
86 #[serde(default = "default_auto_sync")]
92 pub auto_time_sync: bool,
93 #[serde(default = "default_rate_limit")]
97 pub rate_limit: u32,
98}
99
100fn default_sync_interval() -> u64 {
101 30
102}
103
104fn default_auto_sync() -> bool {
105 true
106}
107
108fn default_rate_limit() -> u32 {
109 50
110}
111
112fn deserialize_default_type<'de, D>(deserializer: D) -> std::result::Result<DefaultType, D::Error>
117where
118 D: Deserializer<'de>,
119{
120 use serde::de::Error;
121
122 #[derive(Deserialize)]
125 #[serde(untagged)]
126 enum StringOrDefaultType {
127 String(String),
128 DefaultType(DefaultType),
129 }
130
131 match StringOrDefaultType::deserialize(deserializer)? {
132 StringOrDefaultType::String(s) => {
133 let lowercase = s.to_lowercase();
135 let normalized = match lowercase.as_str() {
136 "future" => "swap", "delivery" => "futures", _ => lowercase.as_str(),
139 };
140 DefaultType::from_str(normalized).map_err(|e| D::Error::custom(e.to_string()))
141 }
142 StringOrDefaultType::DefaultType(dt) => Ok(dt),
143 }
144}
145
146impl Default for BinanceOptions {
147 fn default() -> Self {
148 Self {
149 adjust_for_time_difference: false,
150 recv_window: 5000,
151 default_type: DefaultType::default(), default_sub_type: None,
153 test: false,
154 time_sync_interval_secs: default_sync_interval(),
155 auto_time_sync: default_auto_sync(),
156 rate_limit: default_rate_limit(),
157 }
158 }
159}
160
161impl Binance {
162 pub fn builder() -> BinanceBuilder {
179 BinanceBuilder::new()
180 }
181
182 pub fn new(config: ExchangeConfig) -> Result<Self> {
205 let base = BaseExchange::new(config)?;
206 let options = BinanceOptions::default();
207 let time_sync = Arc::new(TimeSyncManager::new());
208
209 Ok(Self {
210 base,
211 options,
212 time_sync,
213 })
214 }
215
216 pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
241 let base = BaseExchange::new(config)?;
242
243 let time_sync_config = TimeSyncConfig {
245 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
246 auto_sync: options.auto_time_sync,
247 max_offset_drift: options.recv_window as i64,
248 };
249 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
250
251 Ok(Self {
252 base,
253 options,
254 time_sync,
255 })
256 }
257
258 pub fn new_swap(config: ExchangeConfig) -> Result<Self> {
274 let base = BaseExchange::new(config)?;
275 let options = BinanceOptions {
276 default_type: DefaultType::Swap, ..Default::default()
278 };
279
280 let time_sync_config = TimeSyncConfig {
282 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
283 auto_sync: options.auto_time_sync,
284 max_offset_drift: options.recv_window as i64,
285 };
286 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
287
288 Ok(Self {
289 base,
290 options,
291 time_sync,
292 })
293 }
294
295 pub fn base(&self) -> &BaseExchange {
297 &self.base
298 }
299
300 pub fn base_mut(&mut self) -> &mut BaseExchange {
302 &mut self.base
303 }
304
305 pub fn options(&self) -> &BinanceOptions {
307 &self.options
308 }
309
310 pub fn set_options(&mut self, options: BinanceOptions) {
312 self.options = options;
313 }
314
315 pub fn time_sync(&self) -> &Arc<TimeSyncManager> {
331 &self.time_sync
332 }
333
334 pub fn signed_request(&self, endpoint: impl Into<String>) -> SignedRequestBuilder<'_> {
369 SignedRequestBuilder::new(self, endpoint)
370 }
371
372 pub fn id(&self) -> &'static str {
374 "binance"
375 }
376
377 pub fn name(&self) -> &'static str {
379 "Binance"
380 }
381
382 pub fn version(&self) -> &'static str {
384 "v3"
385 }
386
387 pub fn certified(&self) -> bool {
389 true
390 }
391
392 pub fn pro(&self) -> bool {
394 true
395 }
396
397 pub fn rate_limit(&self) -> u32 {
399 50
400 }
401
402 pub fn is_sandbox(&self) -> bool {
426 self.base().config.sandbox || self.options.test
427 }
428
429 pub fn timeframes(&self) -> HashMap<String, String> {
431 let mut timeframes = HashMap::new();
432 timeframes.insert("1s".to_string(), "1s".to_string());
433 timeframes.insert("1m".to_string(), "1m".to_string());
434 timeframes.insert("3m".to_string(), "3m".to_string());
435 timeframes.insert("5m".to_string(), "5m".to_string());
436 timeframes.insert("15m".to_string(), "15m".to_string());
437 timeframes.insert("30m".to_string(), "30m".to_string());
438 timeframes.insert("1h".to_string(), "1h".to_string());
439 timeframes.insert("2h".to_string(), "2h".to_string());
440 timeframes.insert("4h".to_string(), "4h".to_string());
441 timeframes.insert("6h".to_string(), "6h".to_string());
442 timeframes.insert("8h".to_string(), "8h".to_string());
443 timeframes.insert("12h".to_string(), "12h".to_string());
444 timeframes.insert("1d".to_string(), "1d".to_string());
445 timeframes.insert("3d".to_string(), "3d".to_string());
446 timeframes.insert("1w".to_string(), "1w".to_string());
447 timeframes.insert("1M".to_string(), "1M".to_string());
448 timeframes
449 }
450
451 pub fn urls(&self) -> BinanceUrls {
453 let mut urls = if self.base().config.sandbox {
454 BinanceUrls::testnet()
455 } else {
456 BinanceUrls::production()
457 };
458
459 if let Some(public_url) = self.base().config.url_overrides.get("public") {
461 urls.public.clone_from(public_url);
462 }
463 if let Some(private_url) = self.base().config.url_overrides.get("private") {
464 urls.private.clone_from(private_url);
465 }
466 if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
467 urls.fapi_public.clone_from(fapi_public_url);
468 }
469 if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
470 urls.fapi_private.clone_from(fapi_private_url);
471 }
472 if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
473 urls.dapi_public.clone_from(dapi_public_url);
474 }
475 if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
476 urls.dapi_private.clone_from(dapi_private_url);
477 }
478 if let Some(ws_url) = self.base().config.url_overrides.get("ws") {
480 urls.ws.clone_from(ws_url);
481 }
482 if let Some(ws_fapi_url) = self.base().config.url_overrides.get("wsFapi") {
483 urls.ws_fapi.clone_from(ws_fapi_url);
484 }
485 if let Some(ws_dapi_url) = self.base().config.url_overrides.get("wsDapi") {
486 urls.ws_dapi.clone_from(ws_dapi_url);
487 }
488 if let Some(ws_eapi_url) = self.base().config.url_overrides.get("wsEapi") {
489 urls.ws_eapi.clone_from(ws_eapi_url);
490 }
491
492 urls
493 }
494
495 pub fn get_ws_url(&self) -> String {
512 BinanceEndpointRouter::default_ws_endpoint(self)
513 }
514
515 pub fn get_rest_url_public(&self) -> String {
550 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Public)
551 }
552
553 pub fn get_rest_url_private(&self) -> String {
588 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Private)
589 }
590
591 pub fn is_contract_type(&self) -> bool {
599 self.options.default_type.is_contract()
600 }
601
602 pub fn is_inverse(&self) -> bool {
608 matches!(self.options.default_sub_type, Some(DefaultSubType::Inverse))
609 }
610
611 pub fn is_linear(&self) -> bool {
617 !self.is_inverse()
618 }
619
620 pub fn create_ws(&self) -> ws::BinanceWs {
646 let ws_url = self.get_ws_url();
647 ws::BinanceWs::new(ws_url)
648 }
649
650 pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
679 let ws_url = self.get_ws_url();
680 ws::BinanceWs::new_with_auth(ws_url, self.clone())
681 }
682}
683
684#[derive(Debug, Clone)]
686pub struct BinanceUrls {
687 pub public: String,
689 pub private: String,
691 pub sapi: String,
693 pub sapi_v2: String,
695 pub fapi: String,
697 pub fapi_public: String,
699 pub fapi_private: String,
701 pub dapi: String,
703 pub dapi_public: String,
705 pub dapi_private: String,
707 pub eapi: String,
709 pub eapi_public: String,
711 pub eapi_private: String,
713 pub papi: String,
715 pub ws: String,
717 pub ws_fapi: String,
719 pub ws_dapi: String,
721 pub ws_eapi: String,
723}
724
725impl BinanceUrls {
726 pub fn production() -> Self {
728 Self {
729 public: "https://api.binance.com/api/v3".to_string(),
730 private: "https://api.binance.com/api/v3".to_string(),
731 sapi: "https://api.binance.com/sapi/v1".to_string(),
732 sapi_v2: "https://api.binance.com/sapi/v2".to_string(),
733 fapi: "https://fapi.binance.com/fapi/v1".to_string(),
734 fapi_public: "https://fapi.binance.com/fapi/v1".to_string(),
735 fapi_private: "https://fapi.binance.com/fapi/v1".to_string(),
736 dapi: "https://dapi.binance.com/dapi/v1".to_string(),
737 dapi_public: "https://dapi.binance.com/dapi/v1".to_string(),
738 dapi_private: "https://dapi.binance.com/dapi/v1".to_string(),
739 eapi: "https://eapi.binance.com/eapi/v1".to_string(),
740 eapi_public: "https://eapi.binance.com/eapi/v1".to_string(),
741 eapi_private: "https://eapi.binance.com/eapi/v1".to_string(),
742 papi: "https://papi.binance.com/papi/v1".to_string(),
743 ws: "wss://stream.binance.com:9443/ws".to_string(),
744 ws_fapi: "wss://fstream.binance.com/ws".to_string(),
745 ws_dapi: "wss://dstream.binance.com/ws".to_string(),
746 ws_eapi: "wss://nbstream.binance.com/eoptions/ws".to_string(),
747 }
748 }
749
750 pub fn testnet() -> Self {
752 Self {
753 public: "https://testnet.binance.vision/api/v3".to_string(),
754 private: "https://testnet.binance.vision/api/v3".to_string(),
755 sapi: "https://testnet.binance.vision/sapi/v1".to_string(),
756 sapi_v2: "https://testnet.binance.vision/sapi/v2".to_string(),
757 fapi: "https://testnet.binancefuture.com/fapi/v1".to_string(),
758 fapi_public: "https://testnet.binancefuture.com/fapi/v1".to_string(),
759 fapi_private: "https://testnet.binancefuture.com/fapi/v1".to_string(),
760 dapi: "https://testnet.binancefuture.com/dapi/v1".to_string(),
761 dapi_public: "https://testnet.binancefuture.com/dapi/v1".to_string(),
762 dapi_private: "https://testnet.binancefuture.com/dapi/v1".to_string(),
763 eapi: "https://testnet.binanceops.com/eapi/v1".to_string(),
764 eapi_public: "https://testnet.binanceops.com/eapi/v1".to_string(),
765 eapi_private: "https://testnet.binanceops.com/eapi/v1".to_string(),
766 papi: "https://testnet.binance.vision/papi/v1".to_string(),
767 ws: "wss://testnet.binance.vision/ws".to_string(),
768 ws_fapi: "wss://stream.binancefuture.com/ws".to_string(),
769 ws_dapi: "wss://dstream.binancefuture.com/ws".to_string(),
770 ws_eapi: "wss://testnet.binanceops.com/ws-api/v3".to_string(),
771 }
772 }
773
774 pub fn demo() -> Self {
776 Self {
777 public: "https://demo-api.binance.com/api/v3".to_string(),
778 private: "https://demo-api.binance.com/api/v3".to_string(),
779 sapi: "https://demo-api.binance.com/sapi/v1".to_string(),
780 sapi_v2: "https://demo-api.binance.com/sapi/v2".to_string(),
781 fapi: "https://demo-fapi.binance.com/fapi/v1".to_string(),
782 fapi_public: "https://demo-fapi.binance.com/fapi/v1".to_string(),
783 fapi_private: "https://demo-fapi.binance.com/fapi/v1".to_string(),
784 dapi: "https://demo-dapi.binance.com/dapi/v1".to_string(),
785 dapi_public: "https://demo-dapi.binance.com/dapi/v1".to_string(),
786 dapi_private: "https://demo-dapi.binance.com/dapi/v1".to_string(),
787 eapi: "https://demo-eapi.binance.com/eapi/v1".to_string(),
788 eapi_public: "https://demo-eapi.binance.com/eapi/v1".to_string(),
789 eapi_private: "https://demo-eapi.binance.com/eapi/v1".to_string(),
790 papi: "https://demo-papi.binance.com/papi/v1".to_string(),
791 ws: "wss://demo-stream.binance.com/ws".to_string(),
792 ws_fapi: "wss://demo-fstream.binance.com/ws".to_string(),
793 ws_dapi: "wss://demo-dstream.binance.com/ws".to_string(),
794 ws_eapi: "wss://demo-nbstream.binance.com/eoptions/ws".to_string(),
795 }
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use super::*;
802
803 #[test]
804 fn test_binance_creation() {
805 let config = ExchangeConfig {
806 id: "binance".to_string(),
807 name: "Binance".to_string(),
808 ..Default::default()
809 };
810
811 let binance = Binance::new(config);
812 assert!(binance.is_ok());
813
814 let binance = binance.unwrap();
815 assert_eq!(binance.id(), "binance");
816 assert_eq!(binance.name(), "Binance");
817 assert_eq!(binance.version(), "v3");
818 assert!(binance.certified());
819 assert!(binance.pro());
820 }
821
822 #[test]
823 fn test_timeframes() {
824 let config = ExchangeConfig::default();
825 let binance = Binance::new(config).unwrap();
826 let timeframes = binance.timeframes();
827
828 assert!(timeframes.contains_key("1m"));
829 assert!(timeframes.contains_key("1h"));
830 assert!(timeframes.contains_key("1d"));
831 assert_eq!(timeframes.len(), 16);
832 }
833
834 #[test]
835 fn test_urls() {
836 let config = ExchangeConfig::default();
837 let binance = Binance::new(config).unwrap();
838 let urls = binance.urls();
839
840 assert!(urls.public.contains("api.binance.com"));
841 assert!(urls.ws.contains("stream.binance.com"));
842 }
843
844 #[test]
845 fn test_sandbox_urls() {
846 let config = ExchangeConfig {
847 sandbox: true,
848 ..Default::default()
849 };
850 let binance = Binance::new(config).unwrap();
851 let urls = binance.urls();
852
853 assert!(urls.public.contains("testnet"));
854 }
855
856 #[test]
857 fn test_is_sandbox_with_config_sandbox() {
858 let config = ExchangeConfig {
859 sandbox: true,
860 ..Default::default()
861 };
862 let binance = Binance::new(config).unwrap();
863 assert!(binance.is_sandbox());
864 }
865
866 #[test]
867 fn test_is_sandbox_with_options_test() {
868 let config = ExchangeConfig::default();
869 let options = BinanceOptions {
870 test: true,
871 ..Default::default()
872 };
873 let binance = Binance::new_with_options(config, options).unwrap();
874 assert!(binance.is_sandbox());
875 }
876
877 #[test]
878 fn test_is_sandbox_false_by_default() {
879 let config = ExchangeConfig::default();
880 let binance = Binance::new(config).unwrap();
881 assert!(!binance.is_sandbox());
882 }
883
884 #[test]
885 fn test_binance_options_default() {
886 let options = BinanceOptions::default();
887 assert_eq!(options.default_type, DefaultType::Spot);
888 assert_eq!(options.default_sub_type, None);
889 assert!(!options.adjust_for_time_difference);
890 assert_eq!(options.recv_window, 5000);
891 assert!(!options.test);
892 assert_eq!(options.time_sync_interval_secs, 30);
893 assert!(options.auto_time_sync);
894 }
895
896 #[test]
897 fn test_binance_options_with_default_type() {
898 let options = BinanceOptions {
899 default_type: DefaultType::Swap,
900 default_sub_type: Some(DefaultSubType::Linear),
901 ..Default::default()
902 };
903 assert_eq!(options.default_type, DefaultType::Swap);
904 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
905 }
906
907 #[test]
908 fn test_binance_options_serialization() {
909 let options = BinanceOptions {
910 default_type: DefaultType::Swap,
911 default_sub_type: Some(DefaultSubType::Linear),
912 ..Default::default()
913 };
914 let json = serde_json::to_string(&options).unwrap();
915 assert!(json.contains("\"default_type\":\"swap\""));
916 assert!(json.contains("\"default_sub_type\":\"linear\""));
917 assert!(json.contains("\"time_sync_interval_secs\":30"));
918 assert!(json.contains("\"auto_time_sync\":true"));
919 }
920
921 #[test]
922 fn test_binance_options_deserialization_with_enum() {
923 let json = r#"{
924 "adjust_for_time_difference": false,
925 "recv_window": 5000,
926 "default_type": "swap",
927 "default_sub_type": "linear",
928 "test": false,
929 "time_sync_interval_secs": 60,
930 "auto_time_sync": false
931 }"#;
932 let options: BinanceOptions = serde_json::from_str(json).unwrap();
933 assert_eq!(options.default_type, DefaultType::Swap);
934 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
935 assert_eq!(options.time_sync_interval_secs, 60);
936 assert!(!options.auto_time_sync);
937 }
938
939 #[test]
940 fn test_binance_options_deserialization_legacy_future() {
941 let json = r#"{
943 "adjust_for_time_difference": false,
944 "recv_window": 5000,
945 "default_type": "future",
946 "test": false
947 }"#;
948 let options: BinanceOptions = serde_json::from_str(json).unwrap();
949 assert_eq!(options.default_type, DefaultType::Swap);
950 assert_eq!(options.time_sync_interval_secs, 30);
952 assert!(options.auto_time_sync);
953 }
954
955 #[test]
956 fn test_binance_options_deserialization_legacy_delivery() {
957 let json = r#"{
959 "adjust_for_time_difference": false,
960 "recv_window": 5000,
961 "default_type": "delivery",
962 "test": false
963 }"#;
964 let options: BinanceOptions = serde_json::from_str(json).unwrap();
965 assert_eq!(options.default_type, DefaultType::Futures);
966 }
967
968 #[test]
969 fn test_binance_options_deserialization_without_sub_type() {
970 let json = r#"{
971 "adjust_for_time_difference": false,
972 "recv_window": 5000,
973 "default_type": "spot",
974 "test": false
975 }"#;
976 let options: BinanceOptions = serde_json::from_str(json).unwrap();
977 assert_eq!(options.default_type, DefaultType::Spot);
978 assert_eq!(options.default_sub_type, None);
979 }
980
981 #[test]
982 fn test_binance_options_deserialization_case_insensitive() {
983 let json = r#"{
985 "adjust_for_time_difference": false,
986 "recv_window": 5000,
987 "default_type": "SWAP",
988 "test": false
989 }"#;
990 let options: BinanceOptions = serde_json::from_str(json).unwrap();
991 assert_eq!(options.default_type, DefaultType::Swap);
992
993 let json = r#"{
995 "adjust_for_time_difference": false,
996 "recv_window": 5000,
997 "default_type": "FuTuReS",
998 "test": false
999 }"#;
1000 let options: BinanceOptions = serde_json::from_str(json).unwrap();
1001 assert_eq!(options.default_type, DefaultType::Futures);
1002 }
1003
1004 #[test]
1005 fn test_new_futures_uses_swap_type() {
1006 let config = ExchangeConfig::default();
1007 let binance = Binance::new_swap(config).unwrap();
1008 assert_eq!(binance.options().default_type, DefaultType::Swap);
1009 }
1010
1011 #[test]
1012 fn test_get_ws_url_spot() {
1013 let config = ExchangeConfig::default();
1014 let options = BinanceOptions {
1015 default_type: DefaultType::Spot,
1016 ..Default::default()
1017 };
1018 let binance = Binance::new_with_options(config, options).unwrap();
1019 let ws_url = binance.get_ws_url();
1020 assert!(ws_url.contains("stream.binance.com"));
1021 }
1022
1023 #[test]
1024 fn test_get_ws_url_margin() {
1025 let config = ExchangeConfig::default();
1026 let options = BinanceOptions {
1027 default_type: DefaultType::Margin,
1028 ..Default::default()
1029 };
1030 let binance = Binance::new_with_options(config, options).unwrap();
1031 let ws_url = binance.get_ws_url();
1032 assert!(ws_url.contains("stream.binance.com"));
1034 }
1035
1036 #[test]
1037 fn test_get_ws_url_swap_linear() {
1038 let config = ExchangeConfig::default();
1039 let options = BinanceOptions {
1040 default_type: DefaultType::Swap,
1041 default_sub_type: Some(DefaultSubType::Linear),
1042 ..Default::default()
1043 };
1044 let binance = Binance::new_with_options(config, options).unwrap();
1045 let ws_url = binance.get_ws_url();
1046 assert!(ws_url.contains("fstream.binance.com"));
1047 }
1048
1049 #[test]
1050 fn test_get_ws_url_swap_inverse() {
1051 let config = ExchangeConfig::default();
1052 let options = BinanceOptions {
1053 default_type: DefaultType::Swap,
1054 default_sub_type: Some(DefaultSubType::Inverse),
1055 ..Default::default()
1056 };
1057 let binance = Binance::new_with_options(config, options).unwrap();
1058 let ws_url = binance.get_ws_url();
1059 assert!(ws_url.contains("dstream.binance.com"));
1060 }
1061
1062 #[test]
1063 fn test_get_ws_url_swap_default_sub_type() {
1064 let config = ExchangeConfig::default();
1066 let options = BinanceOptions {
1067 default_type: DefaultType::Swap,
1068 default_sub_type: None,
1069 ..Default::default()
1070 };
1071 let binance = Binance::new_with_options(config, options).unwrap();
1072 let ws_url = binance.get_ws_url();
1073 assert!(ws_url.contains("fstream.binance.com"));
1074 }
1075
1076 #[test]
1077 fn test_get_ws_url_futures_linear() {
1078 let config = ExchangeConfig::default();
1079 let options = BinanceOptions {
1080 default_type: DefaultType::Futures,
1081 default_sub_type: Some(DefaultSubType::Linear),
1082 ..Default::default()
1083 };
1084 let binance = Binance::new_with_options(config, options).unwrap();
1085 let ws_url = binance.get_ws_url();
1086 assert!(ws_url.contains("fstream.binance.com"));
1087 }
1088
1089 #[test]
1090 fn test_get_ws_url_futures_inverse() {
1091 let config = ExchangeConfig::default();
1092 let options = BinanceOptions {
1093 default_type: DefaultType::Futures,
1094 default_sub_type: Some(DefaultSubType::Inverse),
1095 ..Default::default()
1096 };
1097 let binance = Binance::new_with_options(config, options).unwrap();
1098 let ws_url = binance.get_ws_url();
1099 assert!(ws_url.contains("dstream.binance.com"));
1100 }
1101
1102 #[test]
1103 fn test_get_ws_url_option() {
1104 let config = ExchangeConfig::default();
1105 let options = BinanceOptions {
1106 default_type: DefaultType::Option,
1107 ..Default::default()
1108 };
1109 let binance = Binance::new_with_options(config, options).unwrap();
1110 let ws_url = binance.get_ws_url();
1111 assert!(ws_url.contains("nbstream.binance.com") || ws_url.contains("eoptions"));
1112 }
1113
1114 #[test]
1115 fn test_binance_urls_has_all_ws_endpoints() {
1116 let urls = BinanceUrls::production();
1117 assert!(!urls.ws.is_empty());
1118 assert!(!urls.ws_fapi.is_empty());
1119 assert!(!urls.ws_dapi.is_empty());
1120 assert!(!urls.ws_eapi.is_empty());
1121 }
1122
1123 #[test]
1124 fn test_binance_urls_testnet_has_all_ws_endpoints() {
1125 let urls = BinanceUrls::testnet();
1126 assert!(!urls.ws.is_empty());
1127 assert!(!urls.ws_fapi.is_empty());
1128 assert!(!urls.ws_dapi.is_empty());
1129 assert!(!urls.ws_eapi.is_empty());
1130 }
1131
1132 #[test]
1133 fn test_binance_urls_demo_has_all_ws_endpoints() {
1134 let urls = BinanceUrls::demo();
1135 assert!(!urls.ws.is_empty());
1136 assert!(!urls.ws_fapi.is_empty());
1137 assert!(!urls.ws_dapi.is_empty());
1138 assert!(!urls.ws_eapi.is_empty());
1139 }
1140
1141 #[test]
1142 fn test_get_rest_url_public_spot() {
1143 let config = ExchangeConfig::default();
1144 let options = BinanceOptions {
1145 default_type: DefaultType::Spot,
1146 ..Default::default()
1147 };
1148 let binance = Binance::new_with_options(config, options).unwrap();
1149 let url = binance.get_rest_url_public();
1150 assert!(url.contains("api.binance.com"));
1151 assert!(url.contains("/api/v3"));
1152 }
1153
1154 #[test]
1155 fn test_get_rest_url_public_margin() {
1156 let config = ExchangeConfig::default();
1157 let options = BinanceOptions {
1158 default_type: DefaultType::Margin,
1159 ..Default::default()
1160 };
1161 let binance = Binance::new_with_options(config, options).unwrap();
1162 let url = binance.get_rest_url_public();
1163 assert!(url.contains("api.binance.com"));
1164 assert!(url.contains("/sapi/"));
1165 }
1166
1167 #[test]
1168 fn test_get_rest_url_public_swap_linear() {
1169 let config = ExchangeConfig::default();
1170 let options = BinanceOptions {
1171 default_type: DefaultType::Swap,
1172 default_sub_type: Some(DefaultSubType::Linear),
1173 ..Default::default()
1174 };
1175 let binance = Binance::new_with_options(config, options).unwrap();
1176 let url = binance.get_rest_url_public();
1177 assert!(url.contains("fapi.binance.com"));
1178 }
1179
1180 #[test]
1181 fn test_get_rest_url_public_swap_inverse() {
1182 let config = ExchangeConfig::default();
1183 let options = BinanceOptions {
1184 default_type: DefaultType::Swap,
1185 default_sub_type: Some(DefaultSubType::Inverse),
1186 ..Default::default()
1187 };
1188 let binance = Binance::new_with_options(config, options).unwrap();
1189 let url = binance.get_rest_url_public();
1190 assert!(url.contains("dapi.binance.com"));
1191 }
1192
1193 #[test]
1194 fn test_get_rest_url_public_futures_linear() {
1195 let config = ExchangeConfig::default();
1196 let options = BinanceOptions {
1197 default_type: DefaultType::Futures,
1198 default_sub_type: Some(DefaultSubType::Linear),
1199 ..Default::default()
1200 };
1201 let binance = Binance::new_with_options(config, options).unwrap();
1202 let url = binance.get_rest_url_public();
1203 assert!(url.contains("fapi.binance.com"));
1204 }
1205
1206 #[test]
1207 fn test_get_rest_url_public_futures_inverse() {
1208 let config = ExchangeConfig::default();
1209 let options = BinanceOptions {
1210 default_type: DefaultType::Futures,
1211 default_sub_type: Some(DefaultSubType::Inverse),
1212 ..Default::default()
1213 };
1214 let binance = Binance::new_with_options(config, options).unwrap();
1215 let url = binance.get_rest_url_public();
1216 assert!(url.contains("dapi.binance.com"));
1217 }
1218
1219 #[test]
1220 fn test_get_rest_url_public_option() {
1221 let config = ExchangeConfig::default();
1222 let options = BinanceOptions {
1223 default_type: DefaultType::Option,
1224 ..Default::default()
1225 };
1226 let binance = Binance::new_with_options(config, options).unwrap();
1227 let url = binance.get_rest_url_public();
1228 assert!(url.contains("eapi.binance.com"));
1229 }
1230
1231 #[test]
1232 fn test_get_rest_url_private_swap_linear() {
1233 let config = ExchangeConfig::default();
1234 let options = BinanceOptions {
1235 default_type: DefaultType::Swap,
1236 default_sub_type: Some(DefaultSubType::Linear),
1237 ..Default::default()
1238 };
1239 let binance = Binance::new_with_options(config, options).unwrap();
1240 let url = binance.get_rest_url_private();
1241 assert!(url.contains("fapi.binance.com"));
1242 }
1243
1244 #[test]
1245 fn test_get_rest_url_private_swap_inverse() {
1246 let config = ExchangeConfig::default();
1247 let options = BinanceOptions {
1248 default_type: DefaultType::Swap,
1249 default_sub_type: Some(DefaultSubType::Inverse),
1250 ..Default::default()
1251 };
1252 let binance = Binance::new_with_options(config, options).unwrap();
1253 let url = binance.get_rest_url_private();
1254 assert!(url.contains("dapi.binance.com"));
1255 }
1256
1257 #[test]
1258 fn test_is_contract_type() {
1259 let config = ExchangeConfig::default();
1260
1261 let options = BinanceOptions {
1263 default_type: DefaultType::Spot,
1264 ..Default::default()
1265 };
1266 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1267 assert!(!binance.is_contract_type());
1268
1269 let options = BinanceOptions {
1271 default_type: DefaultType::Margin,
1272 ..Default::default()
1273 };
1274 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1275 assert!(!binance.is_contract_type());
1276
1277 let options = BinanceOptions {
1279 default_type: DefaultType::Swap,
1280 ..Default::default()
1281 };
1282 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1283 assert!(binance.is_contract_type());
1284
1285 let options = BinanceOptions {
1287 default_type: DefaultType::Futures,
1288 ..Default::default()
1289 };
1290 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1291 assert!(binance.is_contract_type());
1292
1293 let options = BinanceOptions {
1295 default_type: DefaultType::Option,
1296 ..Default::default()
1297 };
1298 let binance = Binance::new_with_options(config, options).unwrap();
1299 assert!(binance.is_contract_type());
1300 }
1301
1302 #[test]
1303 fn test_is_linear_and_is_inverse() {
1304 let config = ExchangeConfig::default();
1305
1306 let options = BinanceOptions {
1308 default_type: DefaultType::Swap,
1309 default_sub_type: None,
1310 ..Default::default()
1311 };
1312 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1313 assert!(binance.is_linear());
1314 assert!(!binance.is_inverse());
1315
1316 let options = BinanceOptions {
1318 default_type: DefaultType::Swap,
1319 default_sub_type: Some(DefaultSubType::Linear),
1320 ..Default::default()
1321 };
1322 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1323 assert!(binance.is_linear());
1324 assert!(!binance.is_inverse());
1325
1326 let options = BinanceOptions {
1328 default_type: DefaultType::Swap,
1329 default_sub_type: Some(DefaultSubType::Inverse),
1330 ..Default::default()
1331 };
1332 let binance = Binance::new_with_options(config, options).unwrap();
1333 assert!(!binance.is_linear());
1334 assert!(binance.is_inverse());
1335 }
1336
1337 #[test]
1342 fn test_sandbox_market_type_spot() {
1343 let config = ExchangeConfig {
1344 sandbox: true,
1345 ..Default::default()
1346 };
1347 let options = BinanceOptions {
1348 default_type: DefaultType::Spot,
1349 ..Default::default()
1350 };
1351 let binance = Binance::new_with_options(config, options).unwrap();
1352
1353 assert!(binance.is_sandbox());
1354 let url = binance.get_rest_url_public();
1355 assert!(
1356 url.contains("testnet.binance.vision"),
1357 "Spot sandbox URL should contain testnet.binance.vision, got: {}",
1358 url
1359 );
1360 assert!(
1361 url.contains("/api/v3"),
1362 "Spot sandbox URL should contain /api/v3, got: {}",
1363 url
1364 );
1365 }
1366
1367 #[test]
1368 fn test_sandbox_market_type_swap_linear() {
1369 let config = ExchangeConfig {
1370 sandbox: true,
1371 ..Default::default()
1372 };
1373 let options = BinanceOptions {
1374 default_type: DefaultType::Swap,
1375 default_sub_type: Some(DefaultSubType::Linear),
1376 ..Default::default()
1377 };
1378 let binance = Binance::new_with_options(config, options).unwrap();
1379
1380 assert!(binance.is_sandbox());
1381 let url = binance.get_rest_url_public();
1382 assert!(
1383 url.contains("testnet.binancefuture.com"),
1384 "Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1385 url
1386 );
1387 assert!(
1388 url.contains("/fapi/"),
1389 "Linear sandbox URL should contain /fapi/, got: {}",
1390 url
1391 );
1392 }
1393
1394 #[test]
1395 fn test_sandbox_market_type_swap_inverse() {
1396 let config = ExchangeConfig {
1397 sandbox: true,
1398 ..Default::default()
1399 };
1400 let options = BinanceOptions {
1401 default_type: DefaultType::Swap,
1402 default_sub_type: Some(DefaultSubType::Inverse),
1403 ..Default::default()
1404 };
1405 let binance = Binance::new_with_options(config, options).unwrap();
1406
1407 assert!(binance.is_sandbox());
1408 let url = binance.get_rest_url_public();
1409 assert!(
1410 url.contains("testnet.binancefuture.com"),
1411 "Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1412 url
1413 );
1414 assert!(
1415 url.contains("/dapi/"),
1416 "Inverse sandbox URL should contain /dapi/, got: {}",
1417 url
1418 );
1419 }
1420
1421 #[test]
1422 fn test_sandbox_market_type_option() {
1423 let config = ExchangeConfig {
1424 sandbox: true,
1425 ..Default::default()
1426 };
1427 let options = BinanceOptions {
1428 default_type: DefaultType::Option,
1429 ..Default::default()
1430 };
1431 let binance = Binance::new_with_options(config, options).unwrap();
1432
1433 assert!(binance.is_sandbox());
1434 let url = binance.get_rest_url_public();
1435 assert!(
1436 url.contains("testnet.binanceops.com"),
1437 "Option sandbox URL should contain testnet.binanceops.com, got: {}",
1438 url
1439 );
1440 assert!(
1441 url.contains("/eapi/"),
1442 "Option sandbox URL should contain /eapi/, got: {}",
1443 url
1444 );
1445 }
1446
1447 #[test]
1448 fn test_sandbox_market_type_futures_linear() {
1449 let config = ExchangeConfig {
1450 sandbox: true,
1451 ..Default::default()
1452 };
1453 let options = BinanceOptions {
1454 default_type: DefaultType::Futures,
1455 default_sub_type: Some(DefaultSubType::Linear),
1456 ..Default::default()
1457 };
1458 let binance = Binance::new_with_options(config, options).unwrap();
1459
1460 assert!(binance.is_sandbox());
1461 let url = binance.get_rest_url_public();
1462 assert!(
1463 url.contains("testnet.binancefuture.com"),
1464 "Futures Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1465 url
1466 );
1467 assert!(
1468 url.contains("/fapi/"),
1469 "Futures Linear sandbox URL should contain /fapi/, got: {}",
1470 url
1471 );
1472 }
1473
1474 #[test]
1475 fn test_sandbox_market_type_futures_inverse() {
1476 let config = ExchangeConfig {
1477 sandbox: true,
1478 ..Default::default()
1479 };
1480 let options = BinanceOptions {
1481 default_type: DefaultType::Futures,
1482 default_sub_type: Some(DefaultSubType::Inverse),
1483 ..Default::default()
1484 };
1485 let binance = Binance::new_with_options(config, options).unwrap();
1486
1487 assert!(binance.is_sandbox());
1488 let url = binance.get_rest_url_public();
1489 assert!(
1490 url.contains("testnet.binancefuture.com"),
1491 "Futures Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1492 url
1493 );
1494 assert!(
1495 url.contains("/dapi/"),
1496 "Futures Inverse sandbox URL should contain /dapi/, got: {}",
1497 url
1498 );
1499 }
1500
1501 #[test]
1502 fn test_sandbox_websocket_url_spot() {
1503 let config = ExchangeConfig {
1505 sandbox: true,
1506 ..Default::default()
1507 };
1508 let options = BinanceOptions {
1509 default_type: DefaultType::Spot,
1510 ..Default::default()
1511 };
1512 let binance = Binance::new_with_options(config, options).unwrap();
1513
1514 let urls = binance.urls();
1515 assert!(
1516 urls.ws.contains("testnet.binance.vision"),
1517 "Spot WS sandbox URL should contain testnet.binance.vision, got: {}",
1518 urls.ws
1519 );
1520 }
1521
1522 #[test]
1523 fn test_sandbox_websocket_url_fapi() {
1524 let config = ExchangeConfig {
1526 sandbox: true,
1527 ..Default::default()
1528 };
1529 let options = BinanceOptions {
1530 default_type: DefaultType::Swap,
1531 default_sub_type: Some(DefaultSubType::Linear),
1532 ..Default::default()
1533 };
1534 let binance = Binance::new_with_options(config, options).unwrap();
1535
1536 let urls = binance.urls();
1537 assert!(
1538 urls.ws_fapi.contains("binancefuture.com"),
1539 "FAPI WS sandbox URL should contain binancefuture.com, got: {}",
1540 urls.ws_fapi
1541 );
1542 }
1543
1544 #[test]
1545 fn test_sandbox_websocket_url_dapi() {
1546 let config = ExchangeConfig {
1548 sandbox: true,
1549 ..Default::default()
1550 };
1551 let options = BinanceOptions {
1552 default_type: DefaultType::Swap,
1553 default_sub_type: Some(DefaultSubType::Inverse),
1554 ..Default::default()
1555 };
1556 let binance = Binance::new_with_options(config, options).unwrap();
1557
1558 let urls = binance.urls();
1559 assert!(
1560 urls.ws_dapi.contains("dstream.binancefuture.com"),
1561 "DAPI WS sandbox URL should contain dstream.binancefuture.com, got: {}",
1562 urls.ws_dapi
1563 );
1564 }
1565
1566 #[test]
1567 fn test_sandbox_websocket_url_eapi() {
1568 let config = ExchangeConfig {
1570 sandbox: true,
1571 ..Default::default()
1572 };
1573 let options = BinanceOptions {
1574 default_type: DefaultType::Option,
1575 ..Default::default()
1576 };
1577 let binance = Binance::new_with_options(config, options).unwrap();
1578
1579 let urls = binance.urls();
1580 assert!(
1581 urls.ws_eapi.contains("testnet.binanceops.com"),
1582 "EAPI WS sandbox URL should contain testnet.binanceops.com, got: {}",
1583 urls.ws_eapi
1584 );
1585 }
1586}