1use ccxt_core::types::EndpointType;
6use ccxt_core::types::MarketType;
7use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
8use ccxt_core::{BaseExchange, ExchangeConfig, Result};
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::sync::RwLock;
12
13pub mod auth;
14pub mod builder;
15pub mod constants;
16pub mod endpoint_router;
17pub mod error;
18mod exchange_impl;
19pub mod options;
20pub mod parser;
21pub mod rate_limiter;
22pub mod rest;
23pub mod signed_request;
24pub mod signing_strategy;
25pub mod symbol;
26pub mod time_sync;
27pub mod urls;
28pub mod ws;
29mod ws_exchange_impl;
30
31pub use builder::BinanceBuilder;
32pub use endpoint_router::BinanceEndpointRouter;
33pub use error::BinanceApiError;
34pub use options::BinanceOptions;
35pub use signed_request::{HttpMethod, SignedRequestBuilder};
36pub use time_sync::{TimeSyncConfig, TimeSyncManager};
37pub use urls::BinanceUrls;
38
39use rate_limiter::WeightRateLimiter;
40
41#[derive(Debug, Clone)]
43pub struct Binance {
44 base: BaseExchange,
46 options: BinanceOptions,
48 time_sync: Arc<TimeSyncManager>,
50 #[deprecated(note = "Use connection_manager instead")]
52 ws_connection: Arc<RwLock<Option<ws::BinanceWs>>>,
53 pub connection_manager: Arc<ws::BinanceConnectionManager>,
55 rate_limiter: Arc<WeightRateLimiter>,
57}
58
59#[allow(deprecated)]
60impl Binance {
61 pub fn builder() -> BinanceBuilder {
78 BinanceBuilder::new()
79 }
80
81 pub fn new(config: ExchangeConfig) -> Result<Self> {
104 let base = BaseExchange::new(config)?;
105 let options = BinanceOptions::default();
106 let time_sync = Arc::new(TimeSyncManager::new());
107
108 let mut urls = if base.config.sandbox {
110 BinanceUrls::testnet()
111 } else {
112 BinanceUrls::production()
113 };
114 if let Some(ws_url) = base.config.url_overrides.get("ws") {
116 urls.ws.clone_from(ws_url);
117 }
118 if let Some(ws_fapi_url) = base.config.url_overrides.get("wsFapi") {
119 urls.ws_fapi.clone_from(ws_fapi_url);
120 }
121 if let Some(ws_dapi_url) = base.config.url_overrides.get("wsDapi") {
122 urls.ws_dapi.clone_from(ws_dapi_url);
123 }
124 if let Some(ws_eapi_url) = base.config.url_overrides.get("wsEapi") {
125 urls.ws_eapi.clone_from(ws_eapi_url);
126 }
127
128 let connection_manager =
129 Arc::new(ws::BinanceConnectionManager::new(urls, base.config.sandbox));
130 let rate_limiter = Arc::new(WeightRateLimiter::new());
131
132 Ok(Self {
133 base,
134 options,
135 time_sync,
136 ws_connection: Arc::new(RwLock::new(None)),
137 connection_manager,
138 rate_limiter,
139 })
140 }
141
142 pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
167 let base = BaseExchange::new(config)?;
168
169 let time_sync_config = TimeSyncConfig {
171 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
172 auto_sync: options.auto_time_sync,
173 max_offset_drift: options.recv_window as i64,
174 };
175 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
176
177 let mut urls = if base.config.sandbox {
179 BinanceUrls::testnet()
180 } else {
181 BinanceUrls::production()
182 };
183
184 if let Some(ws_url) = base.config.url_overrides.get("ws") {
186 urls.ws.clone_from(ws_url);
187 }
188 if let Some(ws_fapi_url) = base.config.url_overrides.get("wsFapi") {
189 urls.ws_fapi.clone_from(ws_fapi_url);
190 }
191 if let Some(ws_dapi_url) = base.config.url_overrides.get("wsDapi") {
192 urls.ws_dapi.clone_from(ws_dapi_url);
193 }
194 if let Some(ws_eapi_url) = base.config.url_overrides.get("wsEapi") {
195 urls.ws_eapi.clone_from(ws_eapi_url);
196 }
197
198 let connection_manager =
199 Arc::new(ws::BinanceConnectionManager::new(urls, base.config.sandbox));
200 let rate_limiter = Arc::new(WeightRateLimiter::new());
201
202 Ok(Self {
203 base,
204 options,
205 time_sync,
206 ws_connection: Arc::new(RwLock::new(None)),
207 connection_manager,
208 rate_limiter,
209 })
210 }
211
212 pub fn new_swap(config: ExchangeConfig) -> Result<Self> {
228 let base = BaseExchange::new(config)?;
229 let options = BinanceOptions {
230 default_type: DefaultType::Swap, ..Default::default()
232 };
233
234 let time_sync_config = TimeSyncConfig {
236 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
237 auto_sync: options.auto_time_sync,
238 max_offset_drift: options.recv_window as i64,
239 };
240 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
241
242 let mut urls = if base.config.sandbox {
244 BinanceUrls::testnet()
245 } else {
246 BinanceUrls::production()
247 };
248
249 if let Some(ws_fapi_url) = base.config.url_overrides.get("wsFapi") {
250 urls.ws_fapi.clone_from(ws_fapi_url);
251 }
252
253 let connection_manager =
254 Arc::new(ws::BinanceConnectionManager::new(urls, base.config.sandbox));
255 let rate_limiter = Arc::new(WeightRateLimiter::new());
256
257 Ok(Self {
258 base,
259 options,
260 time_sync,
261 ws_connection: Arc::new(RwLock::new(None)),
262 connection_manager,
263 rate_limiter,
264 })
265 }
266
267 pub fn base(&self) -> &BaseExchange {
269 &self.base
270 }
271
272 pub fn base_mut(&mut self) -> &mut BaseExchange {
274 &mut self.base
275 }
276
277 pub fn options(&self) -> &BinanceOptions {
279 &self.options
280 }
281
282 pub fn set_options(&mut self, options: BinanceOptions) {
284 self.options = options;
285 }
286
287 pub fn ws_connection(&self) -> &Arc<RwLock<Option<ws::BinanceWs>>> {
310 &self.ws_connection
311 }
312
313 pub fn time_sync(&self) -> &Arc<TimeSyncManager> {
329 &self.time_sync
330 }
331
332 pub fn rate_limiter(&self) -> &Arc<WeightRateLimiter> {
348 &self.rate_limiter
349 }
350
351 pub fn signed_request(&self, endpoint: impl Into<String>) -> SignedRequestBuilder<'_> {
386 SignedRequestBuilder::new(self, endpoint)
387 }
388
389 pub(crate) fn api_key_request(
394 &self,
395 endpoint: impl Into<String>,
396 ) -> signed_request::ApiKeyRequestBuilder<'_> {
397 signed_request::ApiKeyRequestBuilder::new(self, endpoint)
398 }
399
400 pub fn id(&self) -> &'static str {
402 "binance"
403 }
404
405 pub fn name(&self) -> &'static str {
407 "Binance"
408 }
409
410 pub fn version(&self) -> &'static str {
412 "v3"
413 }
414
415 pub fn certified(&self) -> bool {
417 true
418 }
419
420 pub fn pro(&self) -> bool {
422 true
423 }
424
425 pub fn rate_limit(&self) -> u32 {
427 50
428 }
429
430 pub fn is_sandbox(&self) -> bool {
454 self.base().config.sandbox || self.options.test
455 }
456
457 pub fn timeframes(&self) -> std::collections::HashMap<String, String> {
459 constants::timeframes()
460 }
461
462 pub fn urls(&self) -> BinanceUrls {
464 let mut urls = if self.base().config.sandbox {
465 BinanceUrls::testnet()
466 } else {
467 BinanceUrls::production()
468 };
469
470 if let Some(public_url) = self.base().config.url_overrides.get("public") {
472 urls.public.clone_from(public_url);
473 }
474 if let Some(private_url) = self.base().config.url_overrides.get("private") {
475 urls.private.clone_from(private_url);
476 }
477 if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
478 urls.fapi_public.clone_from(fapi_public_url);
479 }
480 if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
481 urls.fapi_private.clone_from(fapi_private_url);
482 }
483 if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
484 urls.dapi_public.clone_from(dapi_public_url);
485 }
486 if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
487 urls.dapi_private.clone_from(dapi_private_url);
488 }
489 if let Some(ws_url) = self.base().config.url_overrides.get("ws") {
491 urls.ws.clone_from(ws_url);
492 }
493 if let Some(ws_fapi_url) = self.base().config.url_overrides.get("wsFapi") {
494 urls.ws_fapi.clone_from(ws_fapi_url);
495 }
496 if let Some(ws_dapi_url) = self.base().config.url_overrides.get("wsDapi") {
497 urls.ws_dapi.clone_from(ws_dapi_url);
498 }
499 if let Some(ws_eapi_url) = self.base().config.url_overrides.get("wsEapi") {
500 urls.ws_eapi.clone_from(ws_eapi_url);
501 }
502
503 urls
504 }
505
506 pub fn get_ws_url(&self) -> String {
523 BinanceEndpointRouter::default_ws_endpoint(self)
524 }
525
526 pub fn get_rest_url_public(&self) -> String {
561 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Public)
562 }
563
564 pub fn get_rest_url_private(&self) -> String {
599 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Private)
600 }
601
602 pub fn is_contract_type(&self) -> bool {
610 self.options.default_type.is_contract()
611 }
612
613 pub fn is_inverse(&self) -> bool {
619 matches!(self.options.default_sub_type, Some(DefaultSubType::Inverse))
620 }
621
622 pub fn is_linear(&self) -> bool {
628 !self.is_inverse()
629 }
630
631 pub fn create_ws(&self) -> ws::BinanceWs {
657 let ws_url = self.get_ws_url();
658 ws::BinanceWs::new(ws_url)
659 }
660
661 pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
690 let ws_url = self.get_ws_url();
691 let market_type = MarketType::from(self.options.default_type);
692 ws::BinanceWs::new_with_auth(ws_url, self.clone(), market_type)
693 }
694
695 pub fn check_response(&self, response: serde_json::Value) -> Result<serde_json::Value> {
726 if let Some(api_error) = BinanceApiError::from_json(&response) {
727 return Err(api_error.into());
728 }
729 Ok(response)
730 }
731
732 pub(crate) async fn public_get(
740 &self,
741 url: &str,
742 headers: Option<reqwest::header::HeaderMap>,
743 ) -> Result<serde_json::Value> {
744 if let Some(wait) = self.rate_limiter().wait_duration() {
746 tokio::time::sleep(wait).await;
747 }
748
749 let data = self.base().http_client.get(url, headers).await?;
750
751 if let Some(resp_headers) = data.get("responseHeaders") {
753 let rate_info = rate_limiter::RateLimitInfo::from_headers(resp_headers);
754 if rate_info.has_data() {
755 self.rate_limiter().update(rate_info);
756 }
757 }
758
759 self.check_response(data)
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 #![allow(clippy::disallowed_methods)]
767 use super::*;
768
769 #[test]
770 fn test_binance_creation() {
771 let config = ExchangeConfig {
772 id: "binance".to_string(),
773 name: "Binance".to_string(),
774 ..Default::default()
775 };
776
777 let binance = Binance::new(config);
778 assert!(binance.is_ok());
779
780 let binance = binance.unwrap();
781 assert_eq!(binance.id(), "binance");
782 assert_eq!(binance.name(), "Binance");
783 assert_eq!(binance.version(), "v3");
784 assert!(binance.certified());
785 assert!(binance.pro());
786 }
787
788 #[test]
789 fn test_timeframes() {
790 let config = ExchangeConfig::default();
791 let binance = Binance::new(config).unwrap();
792 let timeframes = binance.timeframes();
793
794 assert!(timeframes.contains_key("1m"));
795 assert!(timeframes.contains_key("1h"));
796 assert!(timeframes.contains_key("1d"));
797 assert_eq!(timeframes.len(), 16);
798 }
799
800 #[test]
801 fn test_urls() {
802 let config = ExchangeConfig::default();
803 let binance = Binance::new(config).unwrap();
804 let urls = binance.urls();
805
806 assert!(urls.public.contains("api.binance.com"));
807 assert!(urls.ws.contains("stream.binance.com"));
808 }
809
810 #[test]
811 fn test_sandbox_urls() {
812 let config = ExchangeConfig {
813 sandbox: true,
814 ..Default::default()
815 };
816 let binance = Binance::new(config).unwrap();
817 let urls = binance.urls();
818
819 assert!(urls.public.contains("testnet"));
820 }
821
822 #[test]
823 fn test_is_sandbox_with_config_sandbox() {
824 let config = ExchangeConfig {
825 sandbox: true,
826 ..Default::default()
827 };
828 let binance = Binance::new(config).unwrap();
829 assert!(binance.is_sandbox());
830 }
831
832 #[test]
833 fn test_is_sandbox_with_options_test() {
834 let config = ExchangeConfig::default();
835 let options = BinanceOptions {
836 test: true,
837 ..Default::default()
838 };
839 let binance = Binance::new_with_options(config, options).unwrap();
840 assert!(binance.is_sandbox());
841 }
842
843 #[test]
844 fn test_is_sandbox_false_by_default() {
845 let config = ExchangeConfig::default();
846 let binance = Binance::new(config).unwrap();
847 assert!(!binance.is_sandbox());
848 }
849
850 #[test]
851 fn test_binance_options_default() {
852 let options = BinanceOptions::default();
853 assert_eq!(options.default_type, DefaultType::Spot);
854 assert_eq!(options.default_sub_type, None);
855 assert!(!options.adjust_for_time_difference);
856 assert_eq!(options.recv_window, 5000);
857 assert!(!options.test);
858 assert_eq!(options.time_sync_interval_secs, 30);
859 assert!(options.auto_time_sync);
860 }
861
862 #[test]
863 fn test_binance_options_with_default_type() {
864 let options = BinanceOptions {
865 default_type: DefaultType::Swap,
866 default_sub_type: Some(DefaultSubType::Linear),
867 ..Default::default()
868 };
869 assert_eq!(options.default_type, DefaultType::Swap);
870 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
871 }
872
873 #[test]
874 fn test_binance_options_serialization() {
875 let options = BinanceOptions {
876 default_type: DefaultType::Swap,
877 default_sub_type: Some(DefaultSubType::Linear),
878 ..Default::default()
879 };
880 let json = serde_json::to_string(&options).unwrap();
881 assert!(json.contains("\"default_type\":\"swap\""));
882 assert!(json.contains("\"default_sub_type\":\"linear\""));
883 assert!(json.contains("\"time_sync_interval_secs\":30"));
884 assert!(json.contains("\"auto_time_sync\":true"));
885 }
886
887 #[test]
888 fn test_binance_options_deserialization_with_enum() {
889 let json = r#"{
890 "adjust_for_time_difference": false,
891 "recv_window": 5000,
892 "default_type": "swap",
893 "default_sub_type": "linear",
894 "test": false,
895 "time_sync_interval_secs": 60,
896 "auto_time_sync": false
897 }"#;
898 let options: BinanceOptions = serde_json::from_str(json).unwrap();
899 assert_eq!(options.default_type, DefaultType::Swap);
900 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
901 assert_eq!(options.time_sync_interval_secs, 60);
902 assert!(!options.auto_time_sync);
903 }
904
905 #[test]
906 fn test_binance_options_deserialization_legacy_future() {
907 let json = r#"{
909 "adjust_for_time_difference": false,
910 "recv_window": 5000,
911 "default_type": "future",
912 "test": false
913 }"#;
914 let options: BinanceOptions = serde_json::from_str(json).unwrap();
915 assert_eq!(options.default_type, DefaultType::Swap);
916 assert_eq!(options.time_sync_interval_secs, 30);
918 assert!(options.auto_time_sync);
919 }
920
921 #[test]
922 fn test_binance_options_deserialization_legacy_delivery() {
923 let json = r#"{
925 "adjust_for_time_difference": false,
926 "recv_window": 5000,
927 "default_type": "delivery",
928 "test": false
929 }"#;
930 let options: BinanceOptions = serde_json::from_str(json).unwrap();
931 assert_eq!(options.default_type, DefaultType::Futures);
932 }
933
934 #[test]
935 fn test_binance_options_deserialization_without_sub_type() {
936 let json = r#"{
937 "adjust_for_time_difference": false,
938 "recv_window": 5000,
939 "default_type": "spot",
940 "test": false
941 }"#;
942 let options: BinanceOptions = serde_json::from_str(json).unwrap();
943 assert_eq!(options.default_type, DefaultType::Spot);
944 assert_eq!(options.default_sub_type, None);
945 }
946
947 #[test]
948 fn test_binance_options_deserialization_case_insensitive() {
949 let json = r#"{
951 "adjust_for_time_difference": false,
952 "recv_window": 5000,
953 "default_type": "SWAP",
954 "test": false
955 }"#;
956 let options: BinanceOptions = serde_json::from_str(json).unwrap();
957 assert_eq!(options.default_type, DefaultType::Swap);
958
959 let json = r#"{
961 "adjust_for_time_difference": false,
962 "recv_window": 5000,
963 "default_type": "FuTuReS",
964 "test": false
965 }"#;
966 let options: BinanceOptions = serde_json::from_str(json).unwrap();
967 assert_eq!(options.default_type, DefaultType::Futures);
968 }
969
970 #[test]
971 fn test_new_futures_uses_swap_type() {
972 let config = ExchangeConfig::default();
973 let binance = Binance::new_swap(config).unwrap();
974 assert_eq!(binance.options().default_type, DefaultType::Swap);
975 }
976
977 #[test]
978 fn test_get_ws_url_spot() {
979 let config = ExchangeConfig::default();
980 let options = BinanceOptions {
981 default_type: DefaultType::Spot,
982 ..Default::default()
983 };
984 let binance = Binance::new_with_options(config, options).unwrap();
985 let ws_url = binance.get_ws_url();
986 assert!(ws_url.contains("stream.binance.com"));
987 }
988
989 #[test]
990 fn test_get_ws_url_margin() {
991 let config = ExchangeConfig::default();
992 let options = BinanceOptions {
993 default_type: DefaultType::Margin,
994 ..Default::default()
995 };
996 let binance = Binance::new_with_options(config, options).unwrap();
997 let ws_url = binance.get_ws_url();
998 assert!(ws_url.contains("stream.binance.com"));
1000 }
1001
1002 #[test]
1003 fn test_get_ws_url_swap_linear() {
1004 let config = ExchangeConfig::default();
1005 let options = BinanceOptions {
1006 default_type: DefaultType::Swap,
1007 default_sub_type: Some(DefaultSubType::Linear),
1008 ..Default::default()
1009 };
1010 let binance = Binance::new_with_options(config, options).unwrap();
1011 let ws_url = binance.get_ws_url();
1012 assert!(ws_url.contains("fstream.binance.com"));
1013 }
1014
1015 #[test]
1016 fn test_get_ws_url_swap_inverse() {
1017 let config = ExchangeConfig::default();
1018 let options = BinanceOptions {
1019 default_type: DefaultType::Swap,
1020 default_sub_type: Some(DefaultSubType::Inverse),
1021 ..Default::default()
1022 };
1023 let binance = Binance::new_with_options(config, options).unwrap();
1024 let ws_url = binance.get_ws_url();
1025 assert!(ws_url.contains("dstream.binance.com"));
1026 }
1027
1028 #[test]
1029 fn test_get_ws_url_swap_default_sub_type() {
1030 let config = ExchangeConfig::default();
1032 let options = BinanceOptions {
1033 default_type: DefaultType::Swap,
1034 default_sub_type: None,
1035 ..Default::default()
1036 };
1037 let binance = Binance::new_with_options(config, options).unwrap();
1038 let ws_url = binance.get_ws_url();
1039 assert!(ws_url.contains("fstream.binance.com"));
1040 }
1041
1042 #[test]
1043 fn test_get_ws_url_futures_linear() {
1044 let config = ExchangeConfig::default();
1045 let options = BinanceOptions {
1046 default_type: DefaultType::Futures,
1047 default_sub_type: Some(DefaultSubType::Linear),
1048 ..Default::default()
1049 };
1050 let binance = Binance::new_with_options(config, options).unwrap();
1051 let ws_url = binance.get_ws_url();
1052 assert!(ws_url.contains("fstream.binance.com"));
1053 }
1054
1055 #[test]
1056 fn test_get_ws_url_futures_inverse() {
1057 let config = ExchangeConfig::default();
1058 let options = BinanceOptions {
1059 default_type: DefaultType::Futures,
1060 default_sub_type: Some(DefaultSubType::Inverse),
1061 ..Default::default()
1062 };
1063 let binance = Binance::new_with_options(config, options).unwrap();
1064 let ws_url = binance.get_ws_url();
1065 assert!(ws_url.contains("dstream.binance.com"));
1066 }
1067
1068 #[test]
1069 fn test_get_ws_url_option() {
1070 let config = ExchangeConfig::default();
1071 let options = BinanceOptions {
1072 default_type: DefaultType::Option,
1073 ..Default::default()
1074 };
1075 let binance = Binance::new_with_options(config, options).unwrap();
1076 let ws_url = binance.get_ws_url();
1077 assert!(ws_url.contains("nbstream.binance.com") || ws_url.contains("eoptions"));
1078 }
1079
1080 #[test]
1081 fn test_binance_urls_has_all_ws_endpoints() {
1082 let urls = BinanceUrls::production();
1083 assert!(!urls.ws.is_empty());
1084 assert!(!urls.ws_fapi.is_empty());
1085 assert!(!urls.ws_dapi.is_empty());
1086 assert!(!urls.ws_eapi.is_empty());
1087 }
1088
1089 #[test]
1090 fn test_binance_urls_testnet_has_all_ws_endpoints() {
1091 let urls = BinanceUrls::testnet();
1092 assert!(!urls.ws.is_empty());
1093 assert!(!urls.ws_fapi.is_empty());
1094 assert!(!urls.ws_dapi.is_empty());
1095 assert!(!urls.ws_eapi.is_empty());
1096 }
1097
1098 #[test]
1099 fn test_binance_urls_demo_has_all_ws_endpoints() {
1100 let urls = BinanceUrls::demo();
1101 assert!(!urls.ws.is_empty());
1102 assert!(!urls.ws_fapi.is_empty());
1103 assert!(!urls.ws_dapi.is_empty());
1104 assert!(!urls.ws_eapi.is_empty());
1105 }
1106
1107 #[test]
1108 fn test_get_rest_url_public_spot() {
1109 let config = ExchangeConfig::default();
1110 let options = BinanceOptions {
1111 default_type: DefaultType::Spot,
1112 ..Default::default()
1113 };
1114 let binance = Binance::new_with_options(config, options).unwrap();
1115 let url = binance.get_rest_url_public();
1116 assert!(url.contains("api.binance.com"));
1117 assert!(url.contains("/api/v3"));
1118 }
1119
1120 #[test]
1121 fn test_get_rest_url_public_margin() {
1122 let config = ExchangeConfig::default();
1123 let options = BinanceOptions {
1124 default_type: DefaultType::Margin,
1125 ..Default::default()
1126 };
1127 let binance = Binance::new_with_options(config, options).unwrap();
1128 let url = binance.get_rest_url_public();
1129 assert!(url.contains("api.binance.com"));
1130 assert!(url.contains("/sapi/"));
1131 }
1132
1133 #[test]
1134 fn test_get_rest_url_public_swap_linear() {
1135 let config = ExchangeConfig::default();
1136 let options = BinanceOptions {
1137 default_type: DefaultType::Swap,
1138 default_sub_type: Some(DefaultSubType::Linear),
1139 ..Default::default()
1140 };
1141 let binance = Binance::new_with_options(config, options).unwrap();
1142 let url = binance.get_rest_url_public();
1143 assert!(url.contains("fapi.binance.com"));
1144 }
1145
1146 #[test]
1147 fn test_get_rest_url_public_swap_inverse() {
1148 let config = ExchangeConfig::default();
1149 let options = BinanceOptions {
1150 default_type: DefaultType::Swap,
1151 default_sub_type: Some(DefaultSubType::Inverse),
1152 ..Default::default()
1153 };
1154 let binance = Binance::new_with_options(config, options).unwrap();
1155 let url = binance.get_rest_url_public();
1156 assert!(url.contains("dapi.binance.com"));
1157 }
1158
1159 #[test]
1160 fn test_get_rest_url_public_futures_linear() {
1161 let config = ExchangeConfig::default();
1162 let options = BinanceOptions {
1163 default_type: DefaultType::Futures,
1164 default_sub_type: Some(DefaultSubType::Linear),
1165 ..Default::default()
1166 };
1167 let binance = Binance::new_with_options(config, options).unwrap();
1168 let url = binance.get_rest_url_public();
1169 assert!(url.contains("fapi.binance.com"));
1170 }
1171
1172 #[test]
1173 fn test_get_rest_url_public_futures_inverse() {
1174 let config = ExchangeConfig::default();
1175 let options = BinanceOptions {
1176 default_type: DefaultType::Futures,
1177 default_sub_type: Some(DefaultSubType::Inverse),
1178 ..Default::default()
1179 };
1180 let binance = Binance::new_with_options(config, options).unwrap();
1181 let url = binance.get_rest_url_public();
1182 assert!(url.contains("dapi.binance.com"));
1183 }
1184
1185 #[test]
1186 fn test_get_rest_url_public_option() {
1187 let config = ExchangeConfig::default();
1188 let options = BinanceOptions {
1189 default_type: DefaultType::Option,
1190 ..Default::default()
1191 };
1192 let binance = Binance::new_with_options(config, options).unwrap();
1193 let url = binance.get_rest_url_public();
1194 assert!(url.contains("eapi.binance.com"));
1195 }
1196
1197 #[test]
1198 fn test_get_rest_url_private_swap_linear() {
1199 let config = ExchangeConfig::default();
1200 let options = BinanceOptions {
1201 default_type: DefaultType::Swap,
1202 default_sub_type: Some(DefaultSubType::Linear),
1203 ..Default::default()
1204 };
1205 let binance = Binance::new_with_options(config, options).unwrap();
1206 let url = binance.get_rest_url_private();
1207 assert!(url.contains("fapi.binance.com"));
1208 }
1209
1210 #[test]
1211 fn test_get_rest_url_private_swap_inverse() {
1212 let config = ExchangeConfig::default();
1213 let options = BinanceOptions {
1214 default_type: DefaultType::Swap,
1215 default_sub_type: Some(DefaultSubType::Inverse),
1216 ..Default::default()
1217 };
1218 let binance = Binance::new_with_options(config, options).unwrap();
1219 let url = binance.get_rest_url_private();
1220 assert!(url.contains("dapi.binance.com"));
1221 }
1222
1223 #[test]
1224 fn test_is_contract_type() {
1225 let config = ExchangeConfig::default();
1226
1227 let options = BinanceOptions {
1229 default_type: DefaultType::Spot,
1230 ..Default::default()
1231 };
1232 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1233 assert!(!binance.is_contract_type());
1234
1235 let options = BinanceOptions {
1237 default_type: DefaultType::Margin,
1238 ..Default::default()
1239 };
1240 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1241 assert!(!binance.is_contract_type());
1242
1243 let options = BinanceOptions {
1245 default_type: DefaultType::Swap,
1246 ..Default::default()
1247 };
1248 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1249 assert!(binance.is_contract_type());
1250
1251 let options = BinanceOptions {
1253 default_type: DefaultType::Futures,
1254 ..Default::default()
1255 };
1256 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1257 assert!(binance.is_contract_type());
1258
1259 let options = BinanceOptions {
1261 default_type: DefaultType::Option,
1262 ..Default::default()
1263 };
1264 let binance = Binance::new_with_options(config, options).unwrap();
1265 assert!(binance.is_contract_type());
1266 }
1267
1268 #[test]
1269 fn test_is_linear_and_is_inverse() {
1270 let config = ExchangeConfig::default();
1271
1272 let options = BinanceOptions {
1274 default_type: DefaultType::Swap,
1275 default_sub_type: None,
1276 ..Default::default()
1277 };
1278 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1279 assert!(binance.is_linear());
1280 assert!(!binance.is_inverse());
1281
1282 let options = BinanceOptions {
1284 default_type: DefaultType::Swap,
1285 default_sub_type: Some(DefaultSubType::Linear),
1286 ..Default::default()
1287 };
1288 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1289 assert!(binance.is_linear());
1290 assert!(!binance.is_inverse());
1291
1292 let options = BinanceOptions {
1294 default_type: DefaultType::Swap,
1295 default_sub_type: Some(DefaultSubType::Inverse),
1296 ..Default::default()
1297 };
1298 let binance = Binance::new_with_options(config, options).unwrap();
1299 assert!(!binance.is_linear());
1300 assert!(binance.is_inverse());
1301 }
1302
1303 #[test]
1308 fn test_sandbox_market_type_spot() {
1309 let config = ExchangeConfig {
1310 sandbox: true,
1311 ..Default::default()
1312 };
1313 let options = BinanceOptions {
1314 default_type: DefaultType::Spot,
1315 ..Default::default()
1316 };
1317 let binance = Binance::new_with_options(config, options).unwrap();
1318
1319 assert!(binance.is_sandbox());
1320 let url = binance.get_rest_url_public();
1321 assert!(
1322 url.contains("testnet.binance.vision"),
1323 "Spot sandbox URL should contain testnet.binance.vision, got: {}",
1324 url
1325 );
1326 assert!(
1327 url.contains("/api/v3"),
1328 "Spot sandbox URL should contain /api/v3, got: {}",
1329 url
1330 );
1331 }
1332
1333 #[test]
1334 fn test_sandbox_market_type_swap_linear() {
1335 let config = ExchangeConfig {
1336 sandbox: true,
1337 ..Default::default()
1338 };
1339 let options = BinanceOptions {
1340 default_type: DefaultType::Swap,
1341 default_sub_type: Some(DefaultSubType::Linear),
1342 ..Default::default()
1343 };
1344 let binance = Binance::new_with_options(config, options).unwrap();
1345
1346 assert!(binance.is_sandbox());
1347 let url = binance.get_rest_url_public();
1348 assert!(
1349 url.contains("testnet.binancefuture.com"),
1350 "Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1351 url
1352 );
1353 assert!(
1354 url.contains("/fapi/"),
1355 "Linear sandbox URL should contain /fapi/, got: {}",
1356 url
1357 );
1358 }
1359
1360 #[test]
1361 fn test_sandbox_market_type_swap_inverse() {
1362 let config = ExchangeConfig {
1363 sandbox: true,
1364 ..Default::default()
1365 };
1366 let options = BinanceOptions {
1367 default_type: DefaultType::Swap,
1368 default_sub_type: Some(DefaultSubType::Inverse),
1369 ..Default::default()
1370 };
1371 let binance = Binance::new_with_options(config, options).unwrap();
1372
1373 assert!(binance.is_sandbox());
1374 let url = binance.get_rest_url_public();
1375 assert!(
1376 url.contains("testnet.binancefuture.com"),
1377 "Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1378 url
1379 );
1380 assert!(
1381 url.contains("/dapi/"),
1382 "Inverse sandbox URL should contain /dapi/, got: {}",
1383 url
1384 );
1385 }
1386
1387 #[test]
1388 fn test_sandbox_market_type_option() {
1389 let config = ExchangeConfig {
1390 sandbox: true,
1391 ..Default::default()
1392 };
1393 let options = BinanceOptions {
1394 default_type: DefaultType::Option,
1395 ..Default::default()
1396 };
1397 let binance = Binance::new_with_options(config, options).unwrap();
1398
1399 assert!(binance.is_sandbox());
1400 let url = binance.get_rest_url_public();
1401 assert!(
1402 url.contains("testnet.binanceops.com"),
1403 "Option sandbox URL should contain testnet.binanceops.com, got: {}",
1404 url
1405 );
1406 assert!(
1407 url.contains("/eapi/"),
1408 "Option sandbox URL should contain /eapi/, got: {}",
1409 url
1410 );
1411 }
1412
1413 #[test]
1414 fn test_sandbox_market_type_futures_linear() {
1415 let config = ExchangeConfig {
1416 sandbox: true,
1417 ..Default::default()
1418 };
1419 let options = BinanceOptions {
1420 default_type: DefaultType::Futures,
1421 default_sub_type: Some(DefaultSubType::Linear),
1422 ..Default::default()
1423 };
1424 let binance = Binance::new_with_options(config, options).unwrap();
1425
1426 assert!(binance.is_sandbox());
1427 let url = binance.get_rest_url_public();
1428 assert!(
1429 url.contains("testnet.binancefuture.com"),
1430 "Futures Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1431 url
1432 );
1433 assert!(
1434 url.contains("/fapi/"),
1435 "Futures Linear sandbox URL should contain /fapi/, got: {}",
1436 url
1437 );
1438 }
1439
1440 #[test]
1441 fn test_sandbox_market_type_futures_inverse() {
1442 let config = ExchangeConfig {
1443 sandbox: true,
1444 ..Default::default()
1445 };
1446 let options = BinanceOptions {
1447 default_type: DefaultType::Futures,
1448 default_sub_type: Some(DefaultSubType::Inverse),
1449 ..Default::default()
1450 };
1451 let binance = Binance::new_with_options(config, options).unwrap();
1452
1453 assert!(binance.is_sandbox());
1454 let url = binance.get_rest_url_public();
1455 assert!(
1456 url.contains("testnet.binancefuture.com"),
1457 "Futures Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1458 url
1459 );
1460 assert!(
1461 url.contains("/dapi/"),
1462 "Futures Inverse sandbox URL should contain /dapi/, got: {}",
1463 url
1464 );
1465 }
1466
1467 #[test]
1468 fn test_sandbox_websocket_url_spot() {
1469 let config = ExchangeConfig {
1471 sandbox: true,
1472 ..Default::default()
1473 };
1474 let options = BinanceOptions {
1475 default_type: DefaultType::Spot,
1476 ..Default::default()
1477 };
1478 let binance = Binance::new_with_options(config, options).unwrap();
1479
1480 let urls = binance.urls();
1481 assert!(
1482 urls.ws.contains("testnet.binance.vision"),
1483 "Spot WS sandbox URL should contain testnet.binance.vision, got: {}",
1484 urls.ws
1485 );
1486 }
1487
1488 #[test]
1489 fn test_sandbox_websocket_url_fapi() {
1490 let config = ExchangeConfig {
1492 sandbox: true,
1493 ..Default::default()
1494 };
1495 let options = BinanceOptions {
1496 default_type: DefaultType::Swap,
1497 default_sub_type: Some(DefaultSubType::Linear),
1498 ..Default::default()
1499 };
1500 let binance = Binance::new_with_options(config, options).unwrap();
1501
1502 let urls = binance.urls();
1503 assert!(
1504 urls.ws_fapi.contains("binancefuture.com"),
1505 "FAPI WS sandbox URL should contain binancefuture.com, got: {}",
1506 urls.ws_fapi
1507 );
1508 }
1509
1510 #[test]
1511 fn test_sandbox_websocket_url_dapi() {
1512 let config = ExchangeConfig {
1514 sandbox: true,
1515 ..Default::default()
1516 };
1517 let options = BinanceOptions {
1518 default_type: DefaultType::Swap,
1519 default_sub_type: Some(DefaultSubType::Inverse),
1520 ..Default::default()
1521 };
1522 let binance = Binance::new_with_options(config, options).unwrap();
1523
1524 let urls = binance.urls();
1525 assert!(
1526 urls.ws_dapi.contains("dstream.binancefuture.com"),
1527 "DAPI WS sandbox URL should contain dstream.binancefuture.com, got: {}",
1528 urls.ws_dapi
1529 );
1530 }
1531
1532 #[test]
1533 fn test_sandbox_websocket_url_eapi() {
1534 let config = ExchangeConfig {
1536 sandbox: true,
1537 ..Default::default()
1538 };
1539 let options = BinanceOptions {
1540 default_type: DefaultType::Option,
1541 ..Default::default()
1542 };
1543 let binance = Binance::new_with_options(config, options).unwrap();
1544
1545 let urls = binance.urls();
1546 assert!(
1547 urls.ws_eapi.contains("testnet.binanceops.com"),
1548 "EAPI WS sandbox URL should contain testnet.binanceops.com, got: {}",
1549 urls.ws_eapi
1550 );
1551 }
1552
1553 #[test]
1558 fn test_check_response_success() {
1559 let config = ExchangeConfig::default();
1560 let binance = Binance::new(config).unwrap();
1561
1562 let response = serde_json::json!({
1564 "symbol": "BTCUSDT",
1565 "price": "50000.00"
1566 });
1567
1568 let result = binance.check_response(response.clone());
1569 assert!(result.is_ok());
1570 assert_eq!(result.unwrap(), response);
1571 }
1572
1573 #[test]
1574 fn test_check_response_with_binance_error() {
1575 let config = ExchangeConfig::default();
1576 let binance = Binance::new(config).unwrap();
1577
1578 let response = serde_json::json!({
1580 "code": -1121,
1581 "msg": "Invalid symbol."
1582 });
1583
1584 let result = binance.check_response(response);
1585 assert!(result.is_err());
1586 let err = result.unwrap_err();
1587 assert!(err.to_string().contains("Invalid symbol"));
1588 }
1589
1590 #[test]
1591 fn test_check_response_with_rate_limit_error() {
1592 let config = ExchangeConfig::default();
1593 let binance = Binance::new(config).unwrap();
1594
1595 let response = serde_json::json!({
1597 "code": -1003,
1598 "msg": "Too many requests; please use the websocket for live updates."
1599 });
1600
1601 let result = binance.check_response(response);
1602 assert!(result.is_err());
1603 let err = result.unwrap_err();
1604 assert!(matches!(err, ccxt_core::error::Error::RateLimit { .. }));
1605 }
1606
1607 #[test]
1608 fn test_check_response_with_auth_error() {
1609 let config = ExchangeConfig::default();
1610 let binance = Binance::new(config).unwrap();
1611
1612 let response = serde_json::json!({
1614 "code": -2015,
1615 "msg": "Invalid API-key, IP, or permissions for action."
1616 });
1617
1618 let result = binance.check_response(response);
1619 assert!(result.is_err());
1620 let err = result.unwrap_err();
1621 assert!(matches!(err, ccxt_core::error::Error::Authentication(_)));
1622 }
1623
1624 #[test]
1625 fn test_check_response_array_response() {
1626 let config = ExchangeConfig::default();
1627 let binance = Binance::new(config).unwrap();
1628
1629 let response = serde_json::json!([
1631 {"symbol": "BTCUSDT", "price": "50000.00"},
1632 {"symbol": "ETHUSDT", "price": "3000.00"}
1633 ]);
1634
1635 let result = binance.check_response(response.clone());
1636 assert!(result.is_ok());
1637 assert_eq!(result.unwrap(), response);
1638 }
1639}