1use ccxt_core::types::EndpointType;
6use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
7use ccxt_core::{BaseExchange, ExchangeConfig, Result};
8use std::sync::Arc;
9use std::time::Duration;
10use tokio::sync::RwLock;
11
12pub mod auth;
13pub mod builder;
14pub mod constants;
15pub mod endpoint_router;
16pub mod error;
17mod exchange_impl;
18pub mod options;
19pub mod parser;
20pub mod rest;
21pub mod signed_request;
22pub mod signing_strategy;
23pub mod symbol;
24pub mod time_sync;
25pub mod urls;
26pub mod ws;
27mod ws_exchange_impl;
28
29pub use builder::BinanceBuilder;
30pub use endpoint_router::BinanceEndpointRouter;
31pub use options::BinanceOptions;
32pub use signed_request::{HttpMethod, SignedRequestBuilder};
33pub use time_sync::{TimeSyncConfig, TimeSyncManager};
34pub use urls::BinanceUrls;
35#[derive(Debug, Clone)]
37pub struct Binance {
38 base: BaseExchange,
40 options: BinanceOptions,
42 time_sync: Arc<TimeSyncManager>,
44 #[deprecated(note = "Use connection_manager instead")]
46 ws_connection: Arc<RwLock<Option<ws::BinanceWs>>>,
47 pub connection_manager: Arc<ws::BinanceConnectionManager>,
49}
50
51#[allow(deprecated)]
52impl Binance {
53 pub fn builder() -> BinanceBuilder {
70 BinanceBuilder::new()
71 }
72
73 pub fn new(config: ExchangeConfig) -> Result<Self> {
96 let base = BaseExchange::new(config)?;
97 let options = BinanceOptions::default();
98 let time_sync = Arc::new(TimeSyncManager::new());
99
100 let urls = if base.config.sandbox {
102 BinanceUrls::testnet()
103 } else {
104 BinanceUrls::production()
105 };
106 let ws_url = if let Some(url) = base.config.url_overrides.get("ws") {
108 url.clone()
109 } else {
110 urls.ws.clone()
111 };
112
113 let connection_manager = Arc::new(ws::BinanceConnectionManager::new(ws_url));
114
115 Ok(Self {
116 base,
117 options,
118 time_sync,
119 ws_connection: Arc::new(RwLock::new(None)),
120 connection_manager,
121 })
122 }
123
124 pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
149 let base = BaseExchange::new(config)?;
150
151 let time_sync_config = TimeSyncConfig {
153 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
154 auto_sync: options.auto_time_sync,
155 max_offset_drift: options.recv_window as i64,
156 };
157 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
158
159 let mut urls = if base.config.sandbox {
161 BinanceUrls::testnet()
162 } else {
163 BinanceUrls::production()
164 };
165
166 if let Some(ws_url) = base.config.url_overrides.get("ws") {
168 urls.ws.clone_from(ws_url);
169 }
170 if let Some(ws_fapi_url) = base.config.url_overrides.get("wsFapi") {
171 urls.ws_fapi.clone_from(ws_fapi_url);
172 }
173 if let Some(ws_dapi_url) = base.config.url_overrides.get("wsDapi") {
174 urls.ws_dapi.clone_from(ws_dapi_url);
175 }
176 if let Some(ws_eapi_url) = base.config.url_overrides.get("wsEapi") {
177 urls.ws_eapi.clone_from(ws_eapi_url);
178 }
179
180 let ws_url = match options.default_type {
181 DefaultType::Swap | DefaultType::Futures => match options.default_sub_type {
182 Some(DefaultSubType::Inverse) => urls.ws_dapi.clone(),
183 _ => urls.ws_fapi.clone(),
184 },
185 DefaultType::Option => urls.ws_eapi.clone(),
186 _ => urls.ws.clone(),
187 };
188
189 let connection_manager = Arc::new(ws::BinanceConnectionManager::new(ws_url));
190
191 Ok(Self {
192 base,
193 options,
194 time_sync,
195 ws_connection: Arc::new(RwLock::new(None)),
196 connection_manager,
197 })
198 }
199
200 pub fn new_swap(config: ExchangeConfig) -> Result<Self> {
216 let base = BaseExchange::new(config)?;
217 let options = BinanceOptions {
218 default_type: DefaultType::Swap, ..Default::default()
220 };
221
222 let time_sync_config = TimeSyncConfig {
224 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
225 auto_sync: options.auto_time_sync,
226 max_offset_drift: options.recv_window as i64,
227 };
228 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
229
230 let mut urls = if base.config.sandbox {
232 BinanceUrls::testnet()
233 } else {
234 BinanceUrls::production()
235 };
236
237 if let Some(ws_fapi_url) = base.config.url_overrides.get("wsFapi") {
238 urls.ws_fapi.clone_from(ws_fapi_url);
239 }
240
241 let ws_url = urls.ws_fapi.clone();
243
244 let connection_manager = Arc::new(ws::BinanceConnectionManager::new(ws_url));
245
246 Ok(Self {
247 base,
248 options,
249 time_sync,
250 ws_connection: Arc::new(RwLock::new(None)),
251 connection_manager,
252 })
253 }
254
255 pub fn base(&self) -> &BaseExchange {
257 &self.base
258 }
259
260 pub fn base_mut(&mut self) -> &mut BaseExchange {
262 &mut self.base
263 }
264
265 pub fn options(&self) -> &BinanceOptions {
267 &self.options
268 }
269
270 pub fn set_options(&mut self, options: BinanceOptions) {
272 self.options = options;
273 }
274
275 pub fn ws_connection(&self) -> &Arc<RwLock<Option<ws::BinanceWs>>> {
298 &self.ws_connection
299 }
300
301 pub fn time_sync(&self) -> &Arc<TimeSyncManager> {
317 &self.time_sync
318 }
319
320 pub fn signed_request(&self, endpoint: impl Into<String>) -> SignedRequestBuilder<'_> {
355 SignedRequestBuilder::new(self, endpoint)
356 }
357
358 pub fn id(&self) -> &'static str {
360 "binance"
361 }
362
363 pub fn name(&self) -> &'static str {
365 "Binance"
366 }
367
368 pub fn version(&self) -> &'static str {
370 "v3"
371 }
372
373 pub fn certified(&self) -> bool {
375 true
376 }
377
378 pub fn pro(&self) -> bool {
380 true
381 }
382
383 pub fn rate_limit(&self) -> u32 {
385 50
386 }
387
388 pub fn is_sandbox(&self) -> bool {
412 self.base().config.sandbox || self.options.test
413 }
414
415 pub fn timeframes(&self) -> std::collections::HashMap<String, String> {
417 constants::timeframes()
418 }
419
420 pub fn urls(&self) -> BinanceUrls {
422 let mut urls = if self.base().config.sandbox {
423 BinanceUrls::testnet()
424 } else {
425 BinanceUrls::production()
426 };
427
428 if let Some(public_url) = self.base().config.url_overrides.get("public") {
430 urls.public.clone_from(public_url);
431 }
432 if let Some(private_url) = self.base().config.url_overrides.get("private") {
433 urls.private.clone_from(private_url);
434 }
435 if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
436 urls.fapi_public.clone_from(fapi_public_url);
437 }
438 if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
439 urls.fapi_private.clone_from(fapi_private_url);
440 }
441 if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
442 urls.dapi_public.clone_from(dapi_public_url);
443 }
444 if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
445 urls.dapi_private.clone_from(dapi_private_url);
446 }
447 if let Some(ws_url) = self.base().config.url_overrides.get("ws") {
449 urls.ws.clone_from(ws_url);
450 }
451 if let Some(ws_fapi_url) = self.base().config.url_overrides.get("wsFapi") {
452 urls.ws_fapi.clone_from(ws_fapi_url);
453 }
454 if let Some(ws_dapi_url) = self.base().config.url_overrides.get("wsDapi") {
455 urls.ws_dapi.clone_from(ws_dapi_url);
456 }
457 if let Some(ws_eapi_url) = self.base().config.url_overrides.get("wsEapi") {
458 urls.ws_eapi.clone_from(ws_eapi_url);
459 }
460
461 urls
462 }
463
464 pub fn get_ws_url(&self) -> String {
481 BinanceEndpointRouter::default_ws_endpoint(self)
482 }
483
484 pub fn get_rest_url_public(&self) -> String {
519 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Public)
520 }
521
522 pub fn get_rest_url_private(&self) -> String {
557 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Private)
558 }
559
560 pub fn is_contract_type(&self) -> bool {
568 self.options.default_type.is_contract()
569 }
570
571 pub fn is_inverse(&self) -> bool {
577 matches!(self.options.default_sub_type, Some(DefaultSubType::Inverse))
578 }
579
580 pub fn is_linear(&self) -> bool {
586 !self.is_inverse()
587 }
588
589 pub fn create_ws(&self) -> ws::BinanceWs {
615 let ws_url = self.get_ws_url();
616 ws::BinanceWs::new(ws_url)
617 }
618
619 pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
648 let ws_url = self.get_ws_url();
649 ws::BinanceWs::new_with_auth(ws_url, self.clone())
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 #![allow(clippy::disallowed_methods)]
656 use super::*;
657
658 #[test]
659 fn test_binance_creation() {
660 let config = ExchangeConfig {
661 id: "binance".to_string(),
662 name: "Binance".to_string(),
663 ..Default::default()
664 };
665
666 let binance = Binance::new(config);
667 assert!(binance.is_ok());
668
669 let binance = binance.unwrap();
670 assert_eq!(binance.id(), "binance");
671 assert_eq!(binance.name(), "Binance");
672 assert_eq!(binance.version(), "v3");
673 assert!(binance.certified());
674 assert!(binance.pro());
675 }
676
677 #[test]
678 fn test_timeframes() {
679 let config = ExchangeConfig::default();
680 let binance = Binance::new(config).unwrap();
681 let timeframes = binance.timeframes();
682
683 assert!(timeframes.contains_key("1m"));
684 assert!(timeframes.contains_key("1h"));
685 assert!(timeframes.contains_key("1d"));
686 assert_eq!(timeframes.len(), 16);
687 }
688
689 #[test]
690 fn test_urls() {
691 let config = ExchangeConfig::default();
692 let binance = Binance::new(config).unwrap();
693 let urls = binance.urls();
694
695 assert!(urls.public.contains("api.binance.com"));
696 assert!(urls.ws.contains("stream.binance.com"));
697 }
698
699 #[test]
700 fn test_sandbox_urls() {
701 let config = ExchangeConfig {
702 sandbox: true,
703 ..Default::default()
704 };
705 let binance = Binance::new(config).unwrap();
706 let urls = binance.urls();
707
708 assert!(urls.public.contains("testnet"));
709 }
710
711 #[test]
712 fn test_is_sandbox_with_config_sandbox() {
713 let config = ExchangeConfig {
714 sandbox: true,
715 ..Default::default()
716 };
717 let binance = Binance::new(config).unwrap();
718 assert!(binance.is_sandbox());
719 }
720
721 #[test]
722 fn test_is_sandbox_with_options_test() {
723 let config = ExchangeConfig::default();
724 let options = BinanceOptions {
725 test: true,
726 ..Default::default()
727 };
728 let binance = Binance::new_with_options(config, options).unwrap();
729 assert!(binance.is_sandbox());
730 }
731
732 #[test]
733 fn test_is_sandbox_false_by_default() {
734 let config = ExchangeConfig::default();
735 let binance = Binance::new(config).unwrap();
736 assert!(!binance.is_sandbox());
737 }
738
739 #[test]
740 fn test_binance_options_default() {
741 let options = BinanceOptions::default();
742 assert_eq!(options.default_type, DefaultType::Spot);
743 assert_eq!(options.default_sub_type, None);
744 assert!(!options.adjust_for_time_difference);
745 assert_eq!(options.recv_window, 5000);
746 assert!(!options.test);
747 assert_eq!(options.time_sync_interval_secs, 30);
748 assert!(options.auto_time_sync);
749 }
750
751 #[test]
752 fn test_binance_options_with_default_type() {
753 let options = BinanceOptions {
754 default_type: DefaultType::Swap,
755 default_sub_type: Some(DefaultSubType::Linear),
756 ..Default::default()
757 };
758 assert_eq!(options.default_type, DefaultType::Swap);
759 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
760 }
761
762 #[test]
763 fn test_binance_options_serialization() {
764 let options = BinanceOptions {
765 default_type: DefaultType::Swap,
766 default_sub_type: Some(DefaultSubType::Linear),
767 ..Default::default()
768 };
769 let json = serde_json::to_string(&options).unwrap();
770 assert!(json.contains("\"default_type\":\"swap\""));
771 assert!(json.contains("\"default_sub_type\":\"linear\""));
772 assert!(json.contains("\"time_sync_interval_secs\":30"));
773 assert!(json.contains("\"auto_time_sync\":true"));
774 }
775
776 #[test]
777 fn test_binance_options_deserialization_with_enum() {
778 let json = r#"{
779 "adjust_for_time_difference": false,
780 "recv_window": 5000,
781 "default_type": "swap",
782 "default_sub_type": "linear",
783 "test": false,
784 "time_sync_interval_secs": 60,
785 "auto_time_sync": false
786 }"#;
787 let options: BinanceOptions = serde_json::from_str(json).unwrap();
788 assert_eq!(options.default_type, DefaultType::Swap);
789 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
790 assert_eq!(options.time_sync_interval_secs, 60);
791 assert!(!options.auto_time_sync);
792 }
793
794 #[test]
795 fn test_binance_options_deserialization_legacy_future() {
796 let json = r#"{
798 "adjust_for_time_difference": false,
799 "recv_window": 5000,
800 "default_type": "future",
801 "test": false
802 }"#;
803 let options: BinanceOptions = serde_json::from_str(json).unwrap();
804 assert_eq!(options.default_type, DefaultType::Swap);
805 assert_eq!(options.time_sync_interval_secs, 30);
807 assert!(options.auto_time_sync);
808 }
809
810 #[test]
811 fn test_binance_options_deserialization_legacy_delivery() {
812 let json = r#"{
814 "adjust_for_time_difference": false,
815 "recv_window": 5000,
816 "default_type": "delivery",
817 "test": false
818 }"#;
819 let options: BinanceOptions = serde_json::from_str(json).unwrap();
820 assert_eq!(options.default_type, DefaultType::Futures);
821 }
822
823 #[test]
824 fn test_binance_options_deserialization_without_sub_type() {
825 let json = r#"{
826 "adjust_for_time_difference": false,
827 "recv_window": 5000,
828 "default_type": "spot",
829 "test": false
830 }"#;
831 let options: BinanceOptions = serde_json::from_str(json).unwrap();
832 assert_eq!(options.default_type, DefaultType::Spot);
833 assert_eq!(options.default_sub_type, None);
834 }
835
836 #[test]
837 fn test_binance_options_deserialization_case_insensitive() {
838 let json = r#"{
840 "adjust_for_time_difference": false,
841 "recv_window": 5000,
842 "default_type": "SWAP",
843 "test": false
844 }"#;
845 let options: BinanceOptions = serde_json::from_str(json).unwrap();
846 assert_eq!(options.default_type, DefaultType::Swap);
847
848 let json = r#"{
850 "adjust_for_time_difference": false,
851 "recv_window": 5000,
852 "default_type": "FuTuReS",
853 "test": false
854 }"#;
855 let options: BinanceOptions = serde_json::from_str(json).unwrap();
856 assert_eq!(options.default_type, DefaultType::Futures);
857 }
858
859 #[test]
860 fn test_new_futures_uses_swap_type() {
861 let config = ExchangeConfig::default();
862 let binance = Binance::new_swap(config).unwrap();
863 assert_eq!(binance.options().default_type, DefaultType::Swap);
864 }
865
866 #[test]
867 fn test_get_ws_url_spot() {
868 let config = ExchangeConfig::default();
869 let options = BinanceOptions {
870 default_type: DefaultType::Spot,
871 ..Default::default()
872 };
873 let binance = Binance::new_with_options(config, options).unwrap();
874 let ws_url = binance.get_ws_url();
875 assert!(ws_url.contains("stream.binance.com"));
876 }
877
878 #[test]
879 fn test_get_ws_url_margin() {
880 let config = ExchangeConfig::default();
881 let options = BinanceOptions {
882 default_type: DefaultType::Margin,
883 ..Default::default()
884 };
885 let binance = Binance::new_with_options(config, options).unwrap();
886 let ws_url = binance.get_ws_url();
887 assert!(ws_url.contains("stream.binance.com"));
889 }
890
891 #[test]
892 fn test_get_ws_url_swap_linear() {
893 let config = ExchangeConfig::default();
894 let options = BinanceOptions {
895 default_type: DefaultType::Swap,
896 default_sub_type: Some(DefaultSubType::Linear),
897 ..Default::default()
898 };
899 let binance = Binance::new_with_options(config, options).unwrap();
900 let ws_url = binance.get_ws_url();
901 assert!(ws_url.contains("fstream.binance.com"));
902 }
903
904 #[test]
905 fn test_get_ws_url_swap_inverse() {
906 let config = ExchangeConfig::default();
907 let options = BinanceOptions {
908 default_type: DefaultType::Swap,
909 default_sub_type: Some(DefaultSubType::Inverse),
910 ..Default::default()
911 };
912 let binance = Binance::new_with_options(config, options).unwrap();
913 let ws_url = binance.get_ws_url();
914 assert!(ws_url.contains("dstream.binance.com"));
915 }
916
917 #[test]
918 fn test_get_ws_url_swap_default_sub_type() {
919 let config = ExchangeConfig::default();
921 let options = BinanceOptions {
922 default_type: DefaultType::Swap,
923 default_sub_type: None,
924 ..Default::default()
925 };
926 let binance = Binance::new_with_options(config, options).unwrap();
927 let ws_url = binance.get_ws_url();
928 assert!(ws_url.contains("fstream.binance.com"));
929 }
930
931 #[test]
932 fn test_get_ws_url_futures_linear() {
933 let config = ExchangeConfig::default();
934 let options = BinanceOptions {
935 default_type: DefaultType::Futures,
936 default_sub_type: Some(DefaultSubType::Linear),
937 ..Default::default()
938 };
939 let binance = Binance::new_with_options(config, options).unwrap();
940 let ws_url = binance.get_ws_url();
941 assert!(ws_url.contains("fstream.binance.com"));
942 }
943
944 #[test]
945 fn test_get_ws_url_futures_inverse() {
946 let config = ExchangeConfig::default();
947 let options = BinanceOptions {
948 default_type: DefaultType::Futures,
949 default_sub_type: Some(DefaultSubType::Inverse),
950 ..Default::default()
951 };
952 let binance = Binance::new_with_options(config, options).unwrap();
953 let ws_url = binance.get_ws_url();
954 assert!(ws_url.contains("dstream.binance.com"));
955 }
956
957 #[test]
958 fn test_get_ws_url_option() {
959 let config = ExchangeConfig::default();
960 let options = BinanceOptions {
961 default_type: DefaultType::Option,
962 ..Default::default()
963 };
964 let binance = Binance::new_with_options(config, options).unwrap();
965 let ws_url = binance.get_ws_url();
966 assert!(ws_url.contains("nbstream.binance.com") || ws_url.contains("eoptions"));
967 }
968
969 #[test]
970 fn test_binance_urls_has_all_ws_endpoints() {
971 let urls = BinanceUrls::production();
972 assert!(!urls.ws.is_empty());
973 assert!(!urls.ws_fapi.is_empty());
974 assert!(!urls.ws_dapi.is_empty());
975 assert!(!urls.ws_eapi.is_empty());
976 }
977
978 #[test]
979 fn test_binance_urls_testnet_has_all_ws_endpoints() {
980 let urls = BinanceUrls::testnet();
981 assert!(!urls.ws.is_empty());
982 assert!(!urls.ws_fapi.is_empty());
983 assert!(!urls.ws_dapi.is_empty());
984 assert!(!urls.ws_eapi.is_empty());
985 }
986
987 #[test]
988 fn test_binance_urls_demo_has_all_ws_endpoints() {
989 let urls = BinanceUrls::demo();
990 assert!(!urls.ws.is_empty());
991 assert!(!urls.ws_fapi.is_empty());
992 assert!(!urls.ws_dapi.is_empty());
993 assert!(!urls.ws_eapi.is_empty());
994 }
995
996 #[test]
997 fn test_get_rest_url_public_spot() {
998 let config = ExchangeConfig::default();
999 let options = BinanceOptions {
1000 default_type: DefaultType::Spot,
1001 ..Default::default()
1002 };
1003 let binance = Binance::new_with_options(config, options).unwrap();
1004 let url = binance.get_rest_url_public();
1005 assert!(url.contains("api.binance.com"));
1006 assert!(url.contains("/api/v3"));
1007 }
1008
1009 #[test]
1010 fn test_get_rest_url_public_margin() {
1011 let config = ExchangeConfig::default();
1012 let options = BinanceOptions {
1013 default_type: DefaultType::Margin,
1014 ..Default::default()
1015 };
1016 let binance = Binance::new_with_options(config, options).unwrap();
1017 let url = binance.get_rest_url_public();
1018 assert!(url.contains("api.binance.com"));
1019 assert!(url.contains("/sapi/"));
1020 }
1021
1022 #[test]
1023 fn test_get_rest_url_public_swap_linear() {
1024 let config = ExchangeConfig::default();
1025 let options = BinanceOptions {
1026 default_type: DefaultType::Swap,
1027 default_sub_type: Some(DefaultSubType::Linear),
1028 ..Default::default()
1029 };
1030 let binance = Binance::new_with_options(config, options).unwrap();
1031 let url = binance.get_rest_url_public();
1032 assert!(url.contains("fapi.binance.com"));
1033 }
1034
1035 #[test]
1036 fn test_get_rest_url_public_swap_inverse() {
1037 let config = ExchangeConfig::default();
1038 let options = BinanceOptions {
1039 default_type: DefaultType::Swap,
1040 default_sub_type: Some(DefaultSubType::Inverse),
1041 ..Default::default()
1042 };
1043 let binance = Binance::new_with_options(config, options).unwrap();
1044 let url = binance.get_rest_url_public();
1045 assert!(url.contains("dapi.binance.com"));
1046 }
1047
1048 #[test]
1049 fn test_get_rest_url_public_futures_linear() {
1050 let config = ExchangeConfig::default();
1051 let options = BinanceOptions {
1052 default_type: DefaultType::Futures,
1053 default_sub_type: Some(DefaultSubType::Linear),
1054 ..Default::default()
1055 };
1056 let binance = Binance::new_with_options(config, options).unwrap();
1057 let url = binance.get_rest_url_public();
1058 assert!(url.contains("fapi.binance.com"));
1059 }
1060
1061 #[test]
1062 fn test_get_rest_url_public_futures_inverse() {
1063 let config = ExchangeConfig::default();
1064 let options = BinanceOptions {
1065 default_type: DefaultType::Futures,
1066 default_sub_type: Some(DefaultSubType::Inverse),
1067 ..Default::default()
1068 };
1069 let binance = Binance::new_with_options(config, options).unwrap();
1070 let url = binance.get_rest_url_public();
1071 assert!(url.contains("dapi.binance.com"));
1072 }
1073
1074 #[test]
1075 fn test_get_rest_url_public_option() {
1076 let config = ExchangeConfig::default();
1077 let options = BinanceOptions {
1078 default_type: DefaultType::Option,
1079 ..Default::default()
1080 };
1081 let binance = Binance::new_with_options(config, options).unwrap();
1082 let url = binance.get_rest_url_public();
1083 assert!(url.contains("eapi.binance.com"));
1084 }
1085
1086 #[test]
1087 fn test_get_rest_url_private_swap_linear() {
1088 let config = ExchangeConfig::default();
1089 let options = BinanceOptions {
1090 default_type: DefaultType::Swap,
1091 default_sub_type: Some(DefaultSubType::Linear),
1092 ..Default::default()
1093 };
1094 let binance = Binance::new_with_options(config, options).unwrap();
1095 let url = binance.get_rest_url_private();
1096 assert!(url.contains("fapi.binance.com"));
1097 }
1098
1099 #[test]
1100 fn test_get_rest_url_private_swap_inverse() {
1101 let config = ExchangeConfig::default();
1102 let options = BinanceOptions {
1103 default_type: DefaultType::Swap,
1104 default_sub_type: Some(DefaultSubType::Inverse),
1105 ..Default::default()
1106 };
1107 let binance = Binance::new_with_options(config, options).unwrap();
1108 let url = binance.get_rest_url_private();
1109 assert!(url.contains("dapi.binance.com"));
1110 }
1111
1112 #[test]
1113 fn test_is_contract_type() {
1114 let config = ExchangeConfig::default();
1115
1116 let options = BinanceOptions {
1118 default_type: DefaultType::Spot,
1119 ..Default::default()
1120 };
1121 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1122 assert!(!binance.is_contract_type());
1123
1124 let options = BinanceOptions {
1126 default_type: DefaultType::Margin,
1127 ..Default::default()
1128 };
1129 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1130 assert!(!binance.is_contract_type());
1131
1132 let options = BinanceOptions {
1134 default_type: DefaultType::Swap,
1135 ..Default::default()
1136 };
1137 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1138 assert!(binance.is_contract_type());
1139
1140 let options = BinanceOptions {
1142 default_type: DefaultType::Futures,
1143 ..Default::default()
1144 };
1145 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1146 assert!(binance.is_contract_type());
1147
1148 let options = BinanceOptions {
1150 default_type: DefaultType::Option,
1151 ..Default::default()
1152 };
1153 let binance = Binance::new_with_options(config, options).unwrap();
1154 assert!(binance.is_contract_type());
1155 }
1156
1157 #[test]
1158 fn test_is_linear_and_is_inverse() {
1159 let config = ExchangeConfig::default();
1160
1161 let options = BinanceOptions {
1163 default_type: DefaultType::Swap,
1164 default_sub_type: None,
1165 ..Default::default()
1166 };
1167 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1168 assert!(binance.is_linear());
1169 assert!(!binance.is_inverse());
1170
1171 let options = BinanceOptions {
1173 default_type: DefaultType::Swap,
1174 default_sub_type: Some(DefaultSubType::Linear),
1175 ..Default::default()
1176 };
1177 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1178 assert!(binance.is_linear());
1179 assert!(!binance.is_inverse());
1180
1181 let options = BinanceOptions {
1183 default_type: DefaultType::Swap,
1184 default_sub_type: Some(DefaultSubType::Inverse),
1185 ..Default::default()
1186 };
1187 let binance = Binance::new_with_options(config, options).unwrap();
1188 assert!(!binance.is_linear());
1189 assert!(binance.is_inverse());
1190 }
1191
1192 #[test]
1197 fn test_sandbox_market_type_spot() {
1198 let config = ExchangeConfig {
1199 sandbox: true,
1200 ..Default::default()
1201 };
1202 let options = BinanceOptions {
1203 default_type: DefaultType::Spot,
1204 ..Default::default()
1205 };
1206 let binance = Binance::new_with_options(config, options).unwrap();
1207
1208 assert!(binance.is_sandbox());
1209 let url = binance.get_rest_url_public();
1210 assert!(
1211 url.contains("testnet.binance.vision"),
1212 "Spot sandbox URL should contain testnet.binance.vision, got: {}",
1213 url
1214 );
1215 assert!(
1216 url.contains("/api/v3"),
1217 "Spot sandbox URL should contain /api/v3, got: {}",
1218 url
1219 );
1220 }
1221
1222 #[test]
1223 fn test_sandbox_market_type_swap_linear() {
1224 let config = ExchangeConfig {
1225 sandbox: true,
1226 ..Default::default()
1227 };
1228 let options = BinanceOptions {
1229 default_type: DefaultType::Swap,
1230 default_sub_type: Some(DefaultSubType::Linear),
1231 ..Default::default()
1232 };
1233 let binance = Binance::new_with_options(config, options).unwrap();
1234
1235 assert!(binance.is_sandbox());
1236 let url = binance.get_rest_url_public();
1237 assert!(
1238 url.contains("testnet.binancefuture.com"),
1239 "Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1240 url
1241 );
1242 assert!(
1243 url.contains("/fapi/"),
1244 "Linear sandbox URL should contain /fapi/, got: {}",
1245 url
1246 );
1247 }
1248
1249 #[test]
1250 fn test_sandbox_market_type_swap_inverse() {
1251 let config = ExchangeConfig {
1252 sandbox: true,
1253 ..Default::default()
1254 };
1255 let options = BinanceOptions {
1256 default_type: DefaultType::Swap,
1257 default_sub_type: Some(DefaultSubType::Inverse),
1258 ..Default::default()
1259 };
1260 let binance = Binance::new_with_options(config, options).unwrap();
1261
1262 assert!(binance.is_sandbox());
1263 let url = binance.get_rest_url_public();
1264 assert!(
1265 url.contains("testnet.binancefuture.com"),
1266 "Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1267 url
1268 );
1269 assert!(
1270 url.contains("/dapi/"),
1271 "Inverse sandbox URL should contain /dapi/, got: {}",
1272 url
1273 );
1274 }
1275
1276 #[test]
1277 fn test_sandbox_market_type_option() {
1278 let config = ExchangeConfig {
1279 sandbox: true,
1280 ..Default::default()
1281 };
1282 let options = BinanceOptions {
1283 default_type: DefaultType::Option,
1284 ..Default::default()
1285 };
1286 let binance = Binance::new_with_options(config, options).unwrap();
1287
1288 assert!(binance.is_sandbox());
1289 let url = binance.get_rest_url_public();
1290 assert!(
1291 url.contains("testnet.binanceops.com"),
1292 "Option sandbox URL should contain testnet.binanceops.com, got: {}",
1293 url
1294 );
1295 assert!(
1296 url.contains("/eapi/"),
1297 "Option sandbox URL should contain /eapi/, got: {}",
1298 url
1299 );
1300 }
1301
1302 #[test]
1303 fn test_sandbox_market_type_futures_linear() {
1304 let config = ExchangeConfig {
1305 sandbox: true,
1306 ..Default::default()
1307 };
1308 let options = BinanceOptions {
1309 default_type: DefaultType::Futures,
1310 default_sub_type: Some(DefaultSubType::Linear),
1311 ..Default::default()
1312 };
1313 let binance = Binance::new_with_options(config, options).unwrap();
1314
1315 assert!(binance.is_sandbox());
1316 let url = binance.get_rest_url_public();
1317 assert!(
1318 url.contains("testnet.binancefuture.com"),
1319 "Futures Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1320 url
1321 );
1322 assert!(
1323 url.contains("/fapi/"),
1324 "Futures Linear sandbox URL should contain /fapi/, got: {}",
1325 url
1326 );
1327 }
1328
1329 #[test]
1330 fn test_sandbox_market_type_futures_inverse() {
1331 let config = ExchangeConfig {
1332 sandbox: true,
1333 ..Default::default()
1334 };
1335 let options = BinanceOptions {
1336 default_type: DefaultType::Futures,
1337 default_sub_type: Some(DefaultSubType::Inverse),
1338 ..Default::default()
1339 };
1340 let binance = Binance::new_with_options(config, options).unwrap();
1341
1342 assert!(binance.is_sandbox());
1343 let url = binance.get_rest_url_public();
1344 assert!(
1345 url.contains("testnet.binancefuture.com"),
1346 "Futures Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1347 url
1348 );
1349 assert!(
1350 url.contains("/dapi/"),
1351 "Futures Inverse sandbox URL should contain /dapi/, got: {}",
1352 url
1353 );
1354 }
1355
1356 #[test]
1357 fn test_sandbox_websocket_url_spot() {
1358 let config = ExchangeConfig {
1360 sandbox: true,
1361 ..Default::default()
1362 };
1363 let options = BinanceOptions {
1364 default_type: DefaultType::Spot,
1365 ..Default::default()
1366 };
1367 let binance = Binance::new_with_options(config, options).unwrap();
1368
1369 let urls = binance.urls();
1370 assert!(
1371 urls.ws.contains("testnet.binance.vision"),
1372 "Spot WS sandbox URL should contain testnet.binance.vision, got: {}",
1373 urls.ws
1374 );
1375 }
1376
1377 #[test]
1378 fn test_sandbox_websocket_url_fapi() {
1379 let config = ExchangeConfig {
1381 sandbox: true,
1382 ..Default::default()
1383 };
1384 let options = BinanceOptions {
1385 default_type: DefaultType::Swap,
1386 default_sub_type: Some(DefaultSubType::Linear),
1387 ..Default::default()
1388 };
1389 let binance = Binance::new_with_options(config, options).unwrap();
1390
1391 let urls = binance.urls();
1392 assert!(
1393 urls.ws_fapi.contains("binancefuture.com"),
1394 "FAPI WS sandbox URL should contain binancefuture.com, got: {}",
1395 urls.ws_fapi
1396 );
1397 }
1398
1399 #[test]
1400 fn test_sandbox_websocket_url_dapi() {
1401 let config = ExchangeConfig {
1403 sandbox: true,
1404 ..Default::default()
1405 };
1406 let options = BinanceOptions {
1407 default_type: DefaultType::Swap,
1408 default_sub_type: Some(DefaultSubType::Inverse),
1409 ..Default::default()
1410 };
1411 let binance = Binance::new_with_options(config, options).unwrap();
1412
1413 let urls = binance.urls();
1414 assert!(
1415 urls.ws_dapi.contains("dstream.binancefuture.com"),
1416 "DAPI WS sandbox URL should contain dstream.binancefuture.com, got: {}",
1417 urls.ws_dapi
1418 );
1419 }
1420
1421 #[test]
1422 fn test_sandbox_websocket_url_eapi() {
1423 let config = ExchangeConfig {
1425 sandbox: true,
1426 ..Default::default()
1427 };
1428 let options = BinanceOptions {
1429 default_type: DefaultType::Option,
1430 ..Default::default()
1431 };
1432 let binance = Binance::new_with_options(config, options).unwrap();
1433
1434 let urls = binance.urls();
1435 assert!(
1436 urls.ws_eapi.contains("testnet.binanceops.com"),
1437 "EAPI WS sandbox URL should contain testnet.binanceops.com, got: {}",
1438 urls.ws_eapi
1439 );
1440 }
1441}