1use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
6use ccxt_core::{BaseExchange, ExchangeConfig, Result};
7use serde::{Deserialize, Deserializer, Serialize};
8use std::collections::HashMap;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::time::Duration;
12
13pub mod auth;
14pub mod builder;
15pub mod error;
16mod exchange_impl;
17pub mod parser;
18pub mod rest;
19pub mod signed_request;
20pub mod symbol;
21pub mod time_sync;
22pub mod ws;
23mod ws_exchange_impl;
24
25pub use builder::BinanceBuilder;
26pub use signed_request::{HttpMethod, SignedRequestBuilder};
27pub use time_sync::{TimeSyncConfig, TimeSyncManager};
28
29#[derive(Debug)]
31pub struct Binance {
32 base: BaseExchange,
34 options: BinanceOptions,
36 time_sync: Arc<TimeSyncManager>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct BinanceOptions {
56 pub adjust_for_time_difference: bool,
58 pub recv_window: u64,
60 #[serde(deserialize_with = "deserialize_default_type")]
65 pub default_type: DefaultType,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub default_sub_type: Option<DefaultSubType>,
75 pub test: bool,
77 #[serde(default = "default_sync_interval")]
82 pub time_sync_interval_secs: u64,
83 #[serde(default = "default_auto_sync")]
89 pub auto_time_sync: bool,
90}
91
92fn default_sync_interval() -> u64 {
93 30
94}
95
96fn default_auto_sync() -> bool {
97 true
98}
99
100fn deserialize_default_type<'de, D>(deserializer: D) -> std::result::Result<DefaultType, D::Error>
105where
106 D: Deserializer<'de>,
107{
108 use serde::de::Error;
109
110 #[derive(Deserialize)]
113 #[serde(untagged)]
114 enum StringOrDefaultType {
115 String(String),
116 DefaultType(DefaultType),
117 }
118
119 match StringOrDefaultType::deserialize(deserializer)? {
120 StringOrDefaultType::String(s) => {
121 let lowercase = s.to_lowercase();
123 let normalized = match lowercase.as_str() {
124 "future" => "swap", "delivery" => "futures", _ => lowercase.as_str(),
127 };
128 DefaultType::from_str(normalized).map_err(|e| D::Error::custom(e.to_string()))
129 }
130 StringOrDefaultType::DefaultType(dt) => Ok(dt),
131 }
132}
133
134impl Default for BinanceOptions {
135 fn default() -> Self {
136 Self {
137 adjust_for_time_difference: false,
138 recv_window: 5000,
139 default_type: DefaultType::default(), default_sub_type: None,
141 test: false,
142 time_sync_interval_secs: default_sync_interval(),
143 auto_time_sync: default_auto_sync(),
144 }
145 }
146}
147
148impl Binance {
149 pub fn builder() -> BinanceBuilder {
166 BinanceBuilder::new()
167 }
168
169 pub fn new(config: ExchangeConfig) -> Result<Self> {
192 let base = BaseExchange::new(config)?;
193 let options = BinanceOptions::default();
194 let time_sync = Arc::new(TimeSyncManager::new());
195
196 Ok(Self {
197 base,
198 options,
199 time_sync,
200 })
201 }
202
203 pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
228 let base = BaseExchange::new(config)?;
229
230 let time_sync_config = TimeSyncConfig {
232 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
233 auto_sync: options.auto_time_sync,
234 max_offset_drift: options.recv_window as i64,
235 };
236 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
237
238 Ok(Self {
239 base,
240 options,
241 time_sync,
242 })
243 }
244
245 pub fn new_swap(config: ExchangeConfig) -> Result<Self> {
261 let base = BaseExchange::new(config)?;
262 let mut options = BinanceOptions::default();
263 options.default_type = DefaultType::Swap; let time_sync_config = TimeSyncConfig {
267 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
268 auto_sync: options.auto_time_sync,
269 max_offset_drift: options.recv_window as i64,
270 };
271 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
272
273 Ok(Self {
274 base,
275 options,
276 time_sync,
277 })
278 }
279
280 pub fn base(&self) -> &BaseExchange {
282 &self.base
283 }
284
285 pub fn base_mut(&mut self) -> &mut BaseExchange {
287 &mut self.base
288 }
289
290 pub fn options(&self) -> &BinanceOptions {
292 &self.options
293 }
294
295 pub fn set_options(&mut self, options: BinanceOptions) {
297 self.options = options;
298 }
299
300 pub fn time_sync(&self) -> &Arc<TimeSyncManager> {
316 &self.time_sync
317 }
318
319 pub fn signed_request(&self, endpoint: impl Into<String>) -> SignedRequestBuilder<'_> {
354 SignedRequestBuilder::new(self, endpoint)
355 }
356
357 pub fn id(&self) -> &str {
359 "binance"
360 }
361
362 pub fn name(&self) -> &str {
364 "Binance"
365 }
366
367 pub fn version(&self) -> &str {
369 "v3"
370 }
371
372 pub fn certified(&self) -> bool {
374 true
375 }
376
377 pub fn pro(&self) -> bool {
379 true
380 }
381
382 pub fn rate_limit(&self) -> u32 {
384 50
385 }
386
387 pub fn is_sandbox(&self) -> bool {
411 self.base().config.sandbox || self.options.test
412 }
413
414 pub fn timeframes(&self) -> HashMap<String, String> {
416 let mut timeframes = HashMap::new();
417 timeframes.insert("1s".to_string(), "1s".to_string());
418 timeframes.insert("1m".to_string(), "1m".to_string());
419 timeframes.insert("3m".to_string(), "3m".to_string());
420 timeframes.insert("5m".to_string(), "5m".to_string());
421 timeframes.insert("15m".to_string(), "15m".to_string());
422 timeframes.insert("30m".to_string(), "30m".to_string());
423 timeframes.insert("1h".to_string(), "1h".to_string());
424 timeframes.insert("2h".to_string(), "2h".to_string());
425 timeframes.insert("4h".to_string(), "4h".to_string());
426 timeframes.insert("6h".to_string(), "6h".to_string());
427 timeframes.insert("8h".to_string(), "8h".to_string());
428 timeframes.insert("12h".to_string(), "12h".to_string());
429 timeframes.insert("1d".to_string(), "1d".to_string());
430 timeframes.insert("3d".to_string(), "3d".to_string());
431 timeframes.insert("1w".to_string(), "1w".to_string());
432 timeframes.insert("1M".to_string(), "1M".to_string());
433 timeframes
434 }
435
436 pub fn urls(&self) -> BinanceUrls {
438 let mut urls = if self.base().config.sandbox {
439 BinanceUrls::testnet()
440 } else {
441 BinanceUrls::production()
442 };
443
444 if let Some(public_url) = self.base().config.url_overrides.get("public") {
446 urls.public = public_url.clone();
447 }
448 if let Some(private_url) = self.base().config.url_overrides.get("private") {
449 urls.private = private_url.clone();
450 }
451 if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
452 urls.fapi_public = fapi_public_url.clone();
453 }
454 if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
455 urls.fapi_private = fapi_private_url.clone();
456 }
457 if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
458 urls.dapi_public = dapi_public_url.clone();
459 }
460 if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
461 urls.dapi_private = dapi_private_url.clone();
462 }
463 if let Some(ws_url) = self.base().config.url_overrides.get("ws") {
465 urls.ws = ws_url.clone();
466 }
467 if let Some(ws_fapi_url) = self.base().config.url_overrides.get("wsFapi") {
468 urls.ws_fapi = ws_fapi_url.clone();
469 }
470 if let Some(ws_dapi_url) = self.base().config.url_overrides.get("wsDapi") {
471 urls.ws_dapi = ws_dapi_url.clone();
472 }
473 if let Some(ws_eapi_url) = self.base().config.url_overrides.get("wsEapi") {
474 urls.ws_eapi = ws_eapi_url.clone();
475 }
476
477 urls
478 }
479
480 fn get_ws_url(&self) -> String {
492 let urls = self.urls();
493 match self.options.default_type {
494 DefaultType::Swap | DefaultType::Futures => {
495 match self.options.default_sub_type {
497 Some(DefaultSubType::Inverse) => urls.ws_dapi,
498 _ => urls.ws_fapi, }
500 }
501 DefaultType::Option => urls.ws_eapi,
502 _ => urls.ws, }
504 }
505
506 pub fn get_rest_url_public(&self) -> String {
536 let urls = self.urls();
537 match self.options.default_type {
538 DefaultType::Spot => urls.public.clone(),
539 DefaultType::Margin => urls.sapi.clone(),
540 DefaultType::Swap | DefaultType::Futures => {
541 match self.options.default_sub_type {
542 Some(DefaultSubType::Inverse) => urls.dapi_public.clone(),
543 _ => urls.fapi_public.clone(), }
545 }
546 DefaultType::Option => urls.eapi_public.clone(),
547 }
548 }
549
550 pub fn get_rest_url_private(&self) -> String {
580 let urls = self.urls();
581 match self.options.default_type {
582 DefaultType::Spot => urls.private.clone(),
583 DefaultType::Margin => urls.sapi.clone(),
584 DefaultType::Swap | DefaultType::Futures => {
585 match self.options.default_sub_type {
586 Some(DefaultSubType::Inverse) => urls.dapi_private.clone(),
587 _ => urls.fapi_private.clone(), }
589 }
590 DefaultType::Option => urls.eapi_private.clone(),
591 }
592 }
593
594 pub fn is_contract_type(&self) -> bool {
602 self.options.default_type.is_contract()
603 }
604
605 pub fn is_inverse(&self) -> bool {
611 matches!(self.options.default_sub_type, Some(DefaultSubType::Inverse))
612 }
613
614 pub fn is_linear(&self) -> bool {
620 !self.is_inverse()
621 }
622
623 pub fn create_ws(&self) -> ws::BinanceWs {
649 let ws_url = self.get_ws_url();
650 ws::BinanceWs::new(ws_url)
651 }
652
653 pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
682 let ws_url = self.get_ws_url();
683 ws::BinanceWs::new_with_auth(ws_url, self.clone())
684 }
685}
686
687#[derive(Debug, Clone)]
689pub struct BinanceUrls {
690 pub public: String,
692 pub private: String,
694 pub sapi: String,
696 pub sapi_v2: String,
698 pub fapi: String,
700 pub fapi_public: String,
702 pub fapi_private: String,
704 pub dapi: String,
706 pub dapi_public: String,
708 pub dapi_private: String,
710 pub eapi: String,
712 pub eapi_public: String,
714 pub eapi_private: String,
716 pub papi: String,
718 pub ws: String,
720 pub ws_fapi: String,
722 pub ws_dapi: String,
724 pub ws_eapi: String,
726}
727
728impl BinanceUrls {
729 pub fn production() -> Self {
731 Self {
732 public: "https://api.binance.com/api/v3".to_string(),
733 private: "https://api.binance.com/api/v3".to_string(),
734 sapi: "https://api.binance.com/sapi/v1".to_string(),
735 sapi_v2: "https://api.binance.com/sapi/v2".to_string(),
736 fapi: "https://fapi.binance.com/fapi/v1".to_string(),
737 fapi_public: "https://fapi.binance.com/fapi/v1".to_string(),
738 fapi_private: "https://fapi.binance.com/fapi/v1".to_string(),
739 dapi: "https://dapi.binance.com/dapi/v1".to_string(),
740 dapi_public: "https://dapi.binance.com/dapi/v1".to_string(),
741 dapi_private: "https://dapi.binance.com/dapi/v1".to_string(),
742 eapi: "https://eapi.binance.com/eapi/v1".to_string(),
743 eapi_public: "https://eapi.binance.com/eapi/v1".to_string(),
744 eapi_private: "https://eapi.binance.com/eapi/v1".to_string(),
745 papi: "https://papi.binance.com/papi/v1".to_string(),
746 ws: "wss://stream.binance.com:9443/ws".to_string(),
747 ws_fapi: "wss://fstream.binance.com/ws".to_string(),
748 ws_dapi: "wss://dstream.binance.com/ws".to_string(),
749 ws_eapi: "wss://nbstream.binance.com/eoptions/ws".to_string(),
750 }
751 }
752
753 pub fn testnet() -> Self {
755 Self {
756 public: "https://testnet.binance.vision/api/v3".to_string(),
757 private: "https://testnet.binance.vision/api/v3".to_string(),
758 sapi: "https://testnet.binance.vision/sapi/v1".to_string(),
759 sapi_v2: "https://testnet.binance.vision/sapi/v2".to_string(),
760 fapi: "https://testnet.binancefuture.com/fapi/v1".to_string(),
761 fapi_public: "https://testnet.binancefuture.com/fapi/v1".to_string(),
762 fapi_private: "https://testnet.binancefuture.com/fapi/v1".to_string(),
763 dapi: "https://testnet.binancefuture.com/dapi/v1".to_string(),
764 dapi_public: "https://testnet.binancefuture.com/dapi/v1".to_string(),
765 dapi_private: "https://testnet.binancefuture.com/dapi/v1".to_string(),
766 eapi: "https://testnet.binanceops.com/eapi/v1".to_string(),
767 eapi_public: "https://testnet.binanceops.com/eapi/v1".to_string(),
768 eapi_private: "https://testnet.binanceops.com/eapi/v1".to_string(),
769 papi: "https://testnet.binance.vision/papi/v1".to_string(),
770 ws: "wss://testnet.binance.vision/ws".to_string(),
771 ws_fapi: "wss://stream.binancefuture.com/ws".to_string(),
772 ws_dapi: "wss://dstream.binancefuture.com/ws".to_string(),
773 ws_eapi: "wss://testnet.binanceops.com/ws-api/v3".to_string(),
774 }
775 }
776
777 pub fn demo() -> Self {
779 Self {
780 public: "https://demo-api.binance.com/api/v3".to_string(),
781 private: "https://demo-api.binance.com/api/v3".to_string(),
782 sapi: "https://demo-api.binance.com/sapi/v1".to_string(),
783 sapi_v2: "https://demo-api.binance.com/sapi/v2".to_string(),
784 fapi: "https://demo-fapi.binance.com/fapi/v1".to_string(),
785 fapi_public: "https://demo-fapi.binance.com/fapi/v1".to_string(),
786 fapi_private: "https://demo-fapi.binance.com/fapi/v1".to_string(),
787 dapi: "https://demo-dapi.binance.com/dapi/v1".to_string(),
788 dapi_public: "https://demo-dapi.binance.com/dapi/v1".to_string(),
789 dapi_private: "https://demo-dapi.binance.com/dapi/v1".to_string(),
790 eapi: "https://demo-eapi.binance.com/eapi/v1".to_string(),
791 eapi_public: "https://demo-eapi.binance.com/eapi/v1".to_string(),
792 eapi_private: "https://demo-eapi.binance.com/eapi/v1".to_string(),
793 papi: "https://demo-papi.binance.com/papi/v1".to_string(),
794 ws: "wss://demo-stream.binance.com/ws".to_string(),
795 ws_fapi: "wss://demo-fstream.binance.com/ws".to_string(),
796 ws_dapi: "wss://demo-dstream.binance.com/ws".to_string(),
797 ws_eapi: "wss://demo-nbstream.binance.com/eoptions/ws".to_string(),
798 }
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805
806 #[test]
807 fn test_binance_creation() {
808 let config = ExchangeConfig {
809 id: "binance".to_string(),
810 name: "Binance".to_string(),
811 ..Default::default()
812 };
813
814 let binance = Binance::new(config);
815 assert!(binance.is_ok());
816
817 let binance = binance.unwrap();
818 assert_eq!(binance.id(), "binance");
819 assert_eq!(binance.name(), "Binance");
820 assert_eq!(binance.version(), "v3");
821 assert!(binance.certified());
822 assert!(binance.pro());
823 }
824
825 #[test]
826 fn test_timeframes() {
827 let config = ExchangeConfig::default();
828 let binance = Binance::new(config).unwrap();
829 let timeframes = binance.timeframes();
830
831 assert!(timeframes.contains_key("1m"));
832 assert!(timeframes.contains_key("1h"));
833 assert!(timeframes.contains_key("1d"));
834 assert_eq!(timeframes.len(), 16);
835 }
836
837 #[test]
838 fn test_urls() {
839 let config = ExchangeConfig::default();
840 let binance = Binance::new(config).unwrap();
841 let urls = binance.urls();
842
843 assert!(urls.public.contains("api.binance.com"));
844 assert!(urls.ws.contains("stream.binance.com"));
845 }
846
847 #[test]
848 fn test_sandbox_urls() {
849 let config = ExchangeConfig {
850 sandbox: true,
851 ..Default::default()
852 };
853 let binance = Binance::new(config).unwrap();
854 let urls = binance.urls();
855
856 assert!(urls.public.contains("testnet"));
857 }
858
859 #[test]
860 fn test_is_sandbox_with_config_sandbox() {
861 let config = ExchangeConfig {
862 sandbox: true,
863 ..Default::default()
864 };
865 let binance = Binance::new(config).unwrap();
866 assert!(binance.is_sandbox());
867 }
868
869 #[test]
870 fn test_is_sandbox_with_options_test() {
871 let config = ExchangeConfig::default();
872 let options = BinanceOptions {
873 test: true,
874 ..Default::default()
875 };
876 let binance = Binance::new_with_options(config, options).unwrap();
877 assert!(binance.is_sandbox());
878 }
879
880 #[test]
881 fn test_is_sandbox_false_by_default() {
882 let config = ExchangeConfig::default();
883 let binance = Binance::new(config).unwrap();
884 assert!(!binance.is_sandbox());
885 }
886
887 #[test]
888 fn test_binance_options_default() {
889 let options = BinanceOptions::default();
890 assert_eq!(options.default_type, DefaultType::Spot);
891 assert_eq!(options.default_sub_type, None);
892 assert!(!options.adjust_for_time_difference);
893 assert_eq!(options.recv_window, 5000);
894 assert!(!options.test);
895 assert_eq!(options.time_sync_interval_secs, 30);
896 assert!(options.auto_time_sync);
897 }
898
899 #[test]
900 fn test_binance_options_with_default_type() {
901 let options = BinanceOptions {
902 default_type: DefaultType::Swap,
903 default_sub_type: Some(DefaultSubType::Linear),
904 ..Default::default()
905 };
906 assert_eq!(options.default_type, DefaultType::Swap);
907 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
908 }
909
910 #[test]
911 fn test_binance_options_serialization() {
912 let options = BinanceOptions {
913 default_type: DefaultType::Swap,
914 default_sub_type: Some(DefaultSubType::Linear),
915 ..Default::default()
916 };
917 let json = serde_json::to_string(&options).unwrap();
918 assert!(json.contains("\"default_type\":\"swap\""));
919 assert!(json.contains("\"default_sub_type\":\"linear\""));
920 assert!(json.contains("\"time_sync_interval_secs\":30"));
921 assert!(json.contains("\"auto_time_sync\":true"));
922 }
923
924 #[test]
925 fn test_binance_options_deserialization_with_enum() {
926 let json = r#"{
927 "adjust_for_time_difference": false,
928 "recv_window": 5000,
929 "default_type": "swap",
930 "default_sub_type": "linear",
931 "test": false,
932 "time_sync_interval_secs": 60,
933 "auto_time_sync": false
934 }"#;
935 let options: BinanceOptions = serde_json::from_str(json).unwrap();
936 assert_eq!(options.default_type, DefaultType::Swap);
937 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
938 assert_eq!(options.time_sync_interval_secs, 60);
939 assert!(!options.auto_time_sync);
940 }
941
942 #[test]
943 fn test_binance_options_deserialization_legacy_future() {
944 let json = r#"{
946 "adjust_for_time_difference": false,
947 "recv_window": 5000,
948 "default_type": "future",
949 "test": false
950 }"#;
951 let options: BinanceOptions = serde_json::from_str(json).unwrap();
952 assert_eq!(options.default_type, DefaultType::Swap);
953 assert_eq!(options.time_sync_interval_secs, 30);
955 assert!(options.auto_time_sync);
956 }
957
958 #[test]
959 fn test_binance_options_deserialization_legacy_delivery() {
960 let json = r#"{
962 "adjust_for_time_difference": false,
963 "recv_window": 5000,
964 "default_type": "delivery",
965 "test": false
966 }"#;
967 let options: BinanceOptions = serde_json::from_str(json).unwrap();
968 assert_eq!(options.default_type, DefaultType::Futures);
969 }
970
971 #[test]
972 fn test_binance_options_deserialization_without_sub_type() {
973 let json = r#"{
974 "adjust_for_time_difference": false,
975 "recv_window": 5000,
976 "default_type": "spot",
977 "test": false
978 }"#;
979 let options: BinanceOptions = serde_json::from_str(json).unwrap();
980 assert_eq!(options.default_type, DefaultType::Spot);
981 assert_eq!(options.default_sub_type, None);
982 }
983
984 #[test]
985 fn test_binance_options_deserialization_case_insensitive() {
986 let json = r#"{
988 "adjust_for_time_difference": false,
989 "recv_window": 5000,
990 "default_type": "SWAP",
991 "test": false
992 }"#;
993 let options: BinanceOptions = serde_json::from_str(json).unwrap();
994 assert_eq!(options.default_type, DefaultType::Swap);
995
996 let json = r#"{
998 "adjust_for_time_difference": false,
999 "recv_window": 5000,
1000 "default_type": "FuTuReS",
1001 "test": false
1002 }"#;
1003 let options: BinanceOptions = serde_json::from_str(json).unwrap();
1004 assert_eq!(options.default_type, DefaultType::Futures);
1005 }
1006
1007 #[test]
1008 fn test_new_futures_uses_swap_type() {
1009 let config = ExchangeConfig::default();
1010 let binance = Binance::new_swap(config).unwrap();
1011 assert_eq!(binance.options().default_type, DefaultType::Swap);
1012 }
1013
1014 #[test]
1015 fn test_get_ws_url_spot() {
1016 let config = ExchangeConfig::default();
1017 let options = BinanceOptions {
1018 default_type: DefaultType::Spot,
1019 ..Default::default()
1020 };
1021 let binance = Binance::new_with_options(config, options).unwrap();
1022 let ws_url = binance.get_ws_url();
1023 assert!(ws_url.contains("stream.binance.com"));
1024 }
1025
1026 #[test]
1027 fn test_get_ws_url_margin() {
1028 let config = ExchangeConfig::default();
1029 let options = BinanceOptions {
1030 default_type: DefaultType::Margin,
1031 ..Default::default()
1032 };
1033 let binance = Binance::new_with_options(config, options).unwrap();
1034 let ws_url = binance.get_ws_url();
1035 assert!(ws_url.contains("stream.binance.com"));
1037 }
1038
1039 #[test]
1040 fn test_get_ws_url_swap_linear() {
1041 let config = ExchangeConfig::default();
1042 let options = BinanceOptions {
1043 default_type: DefaultType::Swap,
1044 default_sub_type: Some(DefaultSubType::Linear),
1045 ..Default::default()
1046 };
1047 let binance = Binance::new_with_options(config, options).unwrap();
1048 let ws_url = binance.get_ws_url();
1049 assert!(ws_url.contains("fstream.binance.com"));
1050 }
1051
1052 #[test]
1053 fn test_get_ws_url_swap_inverse() {
1054 let config = ExchangeConfig::default();
1055 let options = BinanceOptions {
1056 default_type: DefaultType::Swap,
1057 default_sub_type: Some(DefaultSubType::Inverse),
1058 ..Default::default()
1059 };
1060 let binance = Binance::new_with_options(config, options).unwrap();
1061 let ws_url = binance.get_ws_url();
1062 assert!(ws_url.contains("dstream.binance.com"));
1063 }
1064
1065 #[test]
1066 fn test_get_ws_url_swap_default_sub_type() {
1067 let config = ExchangeConfig::default();
1069 let options = BinanceOptions {
1070 default_type: DefaultType::Swap,
1071 default_sub_type: None,
1072 ..Default::default()
1073 };
1074 let binance = Binance::new_with_options(config, options).unwrap();
1075 let ws_url = binance.get_ws_url();
1076 assert!(ws_url.contains("fstream.binance.com"));
1077 }
1078
1079 #[test]
1080 fn test_get_ws_url_futures_linear() {
1081 let config = ExchangeConfig::default();
1082 let options = BinanceOptions {
1083 default_type: DefaultType::Futures,
1084 default_sub_type: Some(DefaultSubType::Linear),
1085 ..Default::default()
1086 };
1087 let binance = Binance::new_with_options(config, options).unwrap();
1088 let ws_url = binance.get_ws_url();
1089 assert!(ws_url.contains("fstream.binance.com"));
1090 }
1091
1092 #[test]
1093 fn test_get_ws_url_futures_inverse() {
1094 let config = ExchangeConfig::default();
1095 let options = BinanceOptions {
1096 default_type: DefaultType::Futures,
1097 default_sub_type: Some(DefaultSubType::Inverse),
1098 ..Default::default()
1099 };
1100 let binance = Binance::new_with_options(config, options).unwrap();
1101 let ws_url = binance.get_ws_url();
1102 assert!(ws_url.contains("dstream.binance.com"));
1103 }
1104
1105 #[test]
1106 fn test_get_ws_url_option() {
1107 let config = ExchangeConfig::default();
1108 let options = BinanceOptions {
1109 default_type: DefaultType::Option,
1110 ..Default::default()
1111 };
1112 let binance = Binance::new_with_options(config, options).unwrap();
1113 let ws_url = binance.get_ws_url();
1114 assert!(ws_url.contains("nbstream.binance.com") || ws_url.contains("eoptions"));
1115 }
1116
1117 #[test]
1118 fn test_binance_urls_has_all_ws_endpoints() {
1119 let urls = BinanceUrls::production();
1120 assert!(!urls.ws.is_empty());
1121 assert!(!urls.ws_fapi.is_empty());
1122 assert!(!urls.ws_dapi.is_empty());
1123 assert!(!urls.ws_eapi.is_empty());
1124 }
1125
1126 #[test]
1127 fn test_binance_urls_testnet_has_all_ws_endpoints() {
1128 let urls = BinanceUrls::testnet();
1129 assert!(!urls.ws.is_empty());
1130 assert!(!urls.ws_fapi.is_empty());
1131 assert!(!urls.ws_dapi.is_empty());
1132 assert!(!urls.ws_eapi.is_empty());
1133 }
1134
1135 #[test]
1136 fn test_binance_urls_demo_has_all_ws_endpoints() {
1137 let urls = BinanceUrls::demo();
1138 assert!(!urls.ws.is_empty());
1139 assert!(!urls.ws_fapi.is_empty());
1140 assert!(!urls.ws_dapi.is_empty());
1141 assert!(!urls.ws_eapi.is_empty());
1142 }
1143
1144 #[test]
1145 fn test_get_rest_url_public_spot() {
1146 let config = ExchangeConfig::default();
1147 let options = BinanceOptions {
1148 default_type: DefaultType::Spot,
1149 ..Default::default()
1150 };
1151 let binance = Binance::new_with_options(config, options).unwrap();
1152 let url = binance.get_rest_url_public();
1153 assert!(url.contains("api.binance.com"));
1154 assert!(url.contains("/api/v3"));
1155 }
1156
1157 #[test]
1158 fn test_get_rest_url_public_margin() {
1159 let config = ExchangeConfig::default();
1160 let options = BinanceOptions {
1161 default_type: DefaultType::Margin,
1162 ..Default::default()
1163 };
1164 let binance = Binance::new_with_options(config, options).unwrap();
1165 let url = binance.get_rest_url_public();
1166 assert!(url.contains("api.binance.com"));
1167 assert!(url.contains("/sapi/"));
1168 }
1169
1170 #[test]
1171 fn test_get_rest_url_public_swap_linear() {
1172 let config = ExchangeConfig::default();
1173 let options = BinanceOptions {
1174 default_type: DefaultType::Swap,
1175 default_sub_type: Some(DefaultSubType::Linear),
1176 ..Default::default()
1177 };
1178 let binance = Binance::new_with_options(config, options).unwrap();
1179 let url = binance.get_rest_url_public();
1180 assert!(url.contains("fapi.binance.com"));
1181 }
1182
1183 #[test]
1184 fn test_get_rest_url_public_swap_inverse() {
1185 let config = ExchangeConfig::default();
1186 let options = BinanceOptions {
1187 default_type: DefaultType::Swap,
1188 default_sub_type: Some(DefaultSubType::Inverse),
1189 ..Default::default()
1190 };
1191 let binance = Binance::new_with_options(config, options).unwrap();
1192 let url = binance.get_rest_url_public();
1193 assert!(url.contains("dapi.binance.com"));
1194 }
1195
1196 #[test]
1197 fn test_get_rest_url_public_futures_linear() {
1198 let config = ExchangeConfig::default();
1199 let options = BinanceOptions {
1200 default_type: DefaultType::Futures,
1201 default_sub_type: Some(DefaultSubType::Linear),
1202 ..Default::default()
1203 };
1204 let binance = Binance::new_with_options(config, options).unwrap();
1205 let url = binance.get_rest_url_public();
1206 assert!(url.contains("fapi.binance.com"));
1207 }
1208
1209 #[test]
1210 fn test_get_rest_url_public_futures_inverse() {
1211 let config = ExchangeConfig::default();
1212 let options = BinanceOptions {
1213 default_type: DefaultType::Futures,
1214 default_sub_type: Some(DefaultSubType::Inverse),
1215 ..Default::default()
1216 };
1217 let binance = Binance::new_with_options(config, options).unwrap();
1218 let url = binance.get_rest_url_public();
1219 assert!(url.contains("dapi.binance.com"));
1220 }
1221
1222 #[test]
1223 fn test_get_rest_url_public_option() {
1224 let config = ExchangeConfig::default();
1225 let options = BinanceOptions {
1226 default_type: DefaultType::Option,
1227 ..Default::default()
1228 };
1229 let binance = Binance::new_with_options(config, options).unwrap();
1230 let url = binance.get_rest_url_public();
1231 assert!(url.contains("eapi.binance.com"));
1232 }
1233
1234 #[test]
1235 fn test_get_rest_url_private_swap_linear() {
1236 let config = ExchangeConfig::default();
1237 let options = BinanceOptions {
1238 default_type: DefaultType::Swap,
1239 default_sub_type: Some(DefaultSubType::Linear),
1240 ..Default::default()
1241 };
1242 let binance = Binance::new_with_options(config, options).unwrap();
1243 let url = binance.get_rest_url_private();
1244 assert!(url.contains("fapi.binance.com"));
1245 }
1246
1247 #[test]
1248 fn test_get_rest_url_private_swap_inverse() {
1249 let config = ExchangeConfig::default();
1250 let options = BinanceOptions {
1251 default_type: DefaultType::Swap,
1252 default_sub_type: Some(DefaultSubType::Inverse),
1253 ..Default::default()
1254 };
1255 let binance = Binance::new_with_options(config, options).unwrap();
1256 let url = binance.get_rest_url_private();
1257 assert!(url.contains("dapi.binance.com"));
1258 }
1259
1260 #[test]
1261 fn test_is_contract_type() {
1262 let config = ExchangeConfig::default();
1263
1264 let options = BinanceOptions {
1266 default_type: DefaultType::Spot,
1267 ..Default::default()
1268 };
1269 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1270 assert!(!binance.is_contract_type());
1271
1272 let options = BinanceOptions {
1274 default_type: DefaultType::Margin,
1275 ..Default::default()
1276 };
1277 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1278 assert!(!binance.is_contract_type());
1279
1280 let options = BinanceOptions {
1282 default_type: DefaultType::Swap,
1283 ..Default::default()
1284 };
1285 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1286 assert!(binance.is_contract_type());
1287
1288 let options = BinanceOptions {
1290 default_type: DefaultType::Futures,
1291 ..Default::default()
1292 };
1293 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1294 assert!(binance.is_contract_type());
1295
1296 let options = BinanceOptions {
1298 default_type: DefaultType::Option,
1299 ..Default::default()
1300 };
1301 let binance = Binance::new_with_options(config, options).unwrap();
1302 assert!(binance.is_contract_type());
1303 }
1304
1305 #[test]
1306 fn test_is_linear_and_is_inverse() {
1307 let config = ExchangeConfig::default();
1308
1309 let options = BinanceOptions {
1311 default_type: DefaultType::Swap,
1312 default_sub_type: None,
1313 ..Default::default()
1314 };
1315 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1316 assert!(binance.is_linear());
1317 assert!(!binance.is_inverse());
1318
1319 let options = BinanceOptions {
1321 default_type: DefaultType::Swap,
1322 default_sub_type: Some(DefaultSubType::Linear),
1323 ..Default::default()
1324 };
1325 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1326 assert!(binance.is_linear());
1327 assert!(!binance.is_inverse());
1328
1329 let options = BinanceOptions {
1331 default_type: DefaultType::Swap,
1332 default_sub_type: Some(DefaultSubType::Inverse),
1333 ..Default::default()
1334 };
1335 let binance = Binance::new_with_options(config, options).unwrap();
1336 assert!(!binance.is_linear());
1337 assert!(binance.is_inverse());
1338 }
1339
1340 #[test]
1345 fn test_sandbox_market_type_spot() {
1346 let config = ExchangeConfig {
1347 sandbox: true,
1348 ..Default::default()
1349 };
1350 let options = BinanceOptions {
1351 default_type: DefaultType::Spot,
1352 ..Default::default()
1353 };
1354 let binance = Binance::new_with_options(config, options).unwrap();
1355
1356 assert!(binance.is_sandbox());
1357 let url = binance.get_rest_url_public();
1358 assert!(
1359 url.contains("testnet.binance.vision"),
1360 "Spot sandbox URL should contain testnet.binance.vision, got: {}",
1361 url
1362 );
1363 assert!(
1364 url.contains("/api/v3"),
1365 "Spot sandbox URL should contain /api/v3, got: {}",
1366 url
1367 );
1368 }
1369
1370 #[test]
1371 fn test_sandbox_market_type_swap_linear() {
1372 let config = ExchangeConfig {
1373 sandbox: true,
1374 ..Default::default()
1375 };
1376 let options = BinanceOptions {
1377 default_type: DefaultType::Swap,
1378 default_sub_type: Some(DefaultSubType::Linear),
1379 ..Default::default()
1380 };
1381 let binance = Binance::new_with_options(config, options).unwrap();
1382
1383 assert!(binance.is_sandbox());
1384 let url = binance.get_rest_url_public();
1385 assert!(
1386 url.contains("testnet.binancefuture.com"),
1387 "Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1388 url
1389 );
1390 assert!(
1391 url.contains("/fapi/"),
1392 "Linear sandbox URL should contain /fapi/, got: {}",
1393 url
1394 );
1395 }
1396
1397 #[test]
1398 fn test_sandbox_market_type_swap_inverse() {
1399 let config = ExchangeConfig {
1400 sandbox: true,
1401 ..Default::default()
1402 };
1403 let options = BinanceOptions {
1404 default_type: DefaultType::Swap,
1405 default_sub_type: Some(DefaultSubType::Inverse),
1406 ..Default::default()
1407 };
1408 let binance = Binance::new_with_options(config, options).unwrap();
1409
1410 assert!(binance.is_sandbox());
1411 let url = binance.get_rest_url_public();
1412 assert!(
1413 url.contains("testnet.binancefuture.com"),
1414 "Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1415 url
1416 );
1417 assert!(
1418 url.contains("/dapi/"),
1419 "Inverse sandbox URL should contain /dapi/, got: {}",
1420 url
1421 );
1422 }
1423
1424 #[test]
1425 fn test_sandbox_market_type_option() {
1426 let config = ExchangeConfig {
1427 sandbox: true,
1428 ..Default::default()
1429 };
1430 let options = BinanceOptions {
1431 default_type: DefaultType::Option,
1432 ..Default::default()
1433 };
1434 let binance = Binance::new_with_options(config, options).unwrap();
1435
1436 assert!(binance.is_sandbox());
1437 let url = binance.get_rest_url_public();
1438 assert!(
1439 url.contains("testnet.binanceops.com"),
1440 "Option sandbox URL should contain testnet.binanceops.com, got: {}",
1441 url
1442 );
1443 assert!(
1444 url.contains("/eapi/"),
1445 "Option sandbox URL should contain /eapi/, got: {}",
1446 url
1447 );
1448 }
1449
1450 #[test]
1451 fn test_sandbox_market_type_futures_linear() {
1452 let config = ExchangeConfig {
1453 sandbox: true,
1454 ..Default::default()
1455 };
1456 let options = BinanceOptions {
1457 default_type: DefaultType::Futures,
1458 default_sub_type: Some(DefaultSubType::Linear),
1459 ..Default::default()
1460 };
1461 let binance = Binance::new_with_options(config, options).unwrap();
1462
1463 assert!(binance.is_sandbox());
1464 let url = binance.get_rest_url_public();
1465 assert!(
1466 url.contains("testnet.binancefuture.com"),
1467 "Futures Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1468 url
1469 );
1470 assert!(
1471 url.contains("/fapi/"),
1472 "Futures Linear sandbox URL should contain /fapi/, got: {}",
1473 url
1474 );
1475 }
1476
1477 #[test]
1478 fn test_sandbox_market_type_futures_inverse() {
1479 let config = ExchangeConfig {
1480 sandbox: true,
1481 ..Default::default()
1482 };
1483 let options = BinanceOptions {
1484 default_type: DefaultType::Futures,
1485 default_sub_type: Some(DefaultSubType::Inverse),
1486 ..Default::default()
1487 };
1488 let binance = Binance::new_with_options(config, options).unwrap();
1489
1490 assert!(binance.is_sandbox());
1491 let url = binance.get_rest_url_public();
1492 assert!(
1493 url.contains("testnet.binancefuture.com"),
1494 "Futures Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1495 url
1496 );
1497 assert!(
1498 url.contains("/dapi/"),
1499 "Futures Inverse sandbox URL should contain /dapi/, got: {}",
1500 url
1501 );
1502 }
1503
1504 #[test]
1505 fn test_sandbox_websocket_url_spot() {
1506 let config = ExchangeConfig {
1508 sandbox: true,
1509 ..Default::default()
1510 };
1511 let options = BinanceOptions {
1512 default_type: DefaultType::Spot,
1513 ..Default::default()
1514 };
1515 let binance = Binance::new_with_options(config, options).unwrap();
1516
1517 let urls = binance.urls();
1518 assert!(
1519 urls.ws.contains("testnet.binance.vision"),
1520 "Spot WS sandbox URL should contain testnet.binance.vision, got: {}",
1521 urls.ws
1522 );
1523 }
1524
1525 #[test]
1526 fn test_sandbox_websocket_url_fapi() {
1527 let config = ExchangeConfig {
1529 sandbox: true,
1530 ..Default::default()
1531 };
1532 let options = BinanceOptions {
1533 default_type: DefaultType::Swap,
1534 default_sub_type: Some(DefaultSubType::Linear),
1535 ..Default::default()
1536 };
1537 let binance = Binance::new_with_options(config, options).unwrap();
1538
1539 let urls = binance.urls();
1540 assert!(
1541 urls.ws_fapi.contains("binancefuture.com"),
1542 "FAPI WS sandbox URL should contain binancefuture.com, got: {}",
1543 urls.ws_fapi
1544 );
1545 }
1546
1547 #[test]
1548 fn test_sandbox_websocket_url_dapi() {
1549 let config = ExchangeConfig {
1551 sandbox: true,
1552 ..Default::default()
1553 };
1554 let options = BinanceOptions {
1555 default_type: DefaultType::Swap,
1556 default_sub_type: Some(DefaultSubType::Inverse),
1557 ..Default::default()
1558 };
1559 let binance = Binance::new_with_options(config, options).unwrap();
1560
1561 let urls = binance.urls();
1562 assert!(
1563 urls.ws_dapi.contains("dstream.binancefuture.com"),
1564 "DAPI WS sandbox URL should contain dstream.binancefuture.com, got: {}",
1565 urls.ws_dapi
1566 );
1567 }
1568
1569 #[test]
1570 fn test_sandbox_websocket_url_eapi() {
1571 let config = ExchangeConfig {
1573 sandbox: true,
1574 ..Default::default()
1575 };
1576 let options = BinanceOptions {
1577 default_type: DefaultType::Option,
1578 ..Default::default()
1579 };
1580 let binance = Binance::new_with_options(config, options).unwrap();
1581
1582 let urls = binance.urls();
1583 assert!(
1584 urls.ws_eapi.contains("testnet.binanceops.com"),
1585 "EAPI WS sandbox URL should contain testnet.binanceops.com, got: {}",
1586 urls.ws_eapi
1587 );
1588 }
1589}