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)]
37pub struct Binance {
38 base: BaseExchange,
40 options: BinanceOptions,
42 time_sync: Arc<TimeSyncManager>,
44 ws_connection: Arc<RwLock<Option<ws::BinanceWs>>>,
46}
47
48impl Binance {
49 pub fn builder() -> BinanceBuilder {
66 BinanceBuilder::new()
67 }
68
69 pub fn new(config: ExchangeConfig) -> Result<Self> {
92 let base = BaseExchange::new(config)?;
93 let options = BinanceOptions::default();
94 let time_sync = Arc::new(TimeSyncManager::new());
95
96 Ok(Self {
97 base,
98 options,
99 time_sync,
100 ws_connection: Arc::new(RwLock::new(None)),
101 })
102 }
103
104 pub fn new_with_options(config: ExchangeConfig, options: BinanceOptions) -> Result<Self> {
129 let base = BaseExchange::new(config)?;
130
131 let time_sync_config = TimeSyncConfig {
133 sync_interval: Duration::from_secs(options.time_sync_interval_secs),
134 auto_sync: options.auto_time_sync,
135 max_offset_drift: options.recv_window as i64,
136 };
137 let time_sync = Arc::new(TimeSyncManager::with_config(time_sync_config));
138
139 Ok(Self {
140 base,
141 options,
142 time_sync,
143 ws_connection: Arc::new(RwLock::new(None)),
144 })
145 }
146
147 pub fn new_swap(config: ExchangeConfig) -> Result<Self> {
163 let base = BaseExchange::new(config)?;
164 let options = BinanceOptions {
165 default_type: DefaultType::Swap, ..Default::default()
167 };
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 Ok(Self {
178 base,
179 options,
180 time_sync,
181 ws_connection: Arc::new(RwLock::new(None)),
182 })
183 }
184
185 pub fn base(&self) -> &BaseExchange {
187 &self.base
188 }
189
190 pub fn base_mut(&mut self) -> &mut BaseExchange {
192 &mut self.base
193 }
194
195 pub fn options(&self) -> &BinanceOptions {
197 &self.options
198 }
199
200 pub fn set_options(&mut self, options: BinanceOptions) {
202 self.options = options;
203 }
204
205 pub fn ws_connection(&self) -> &Arc<RwLock<Option<ws::BinanceWs>>> {
228 &self.ws_connection
229 }
230
231 pub fn time_sync(&self) -> &Arc<TimeSyncManager> {
247 &self.time_sync
248 }
249
250 pub fn signed_request(&self, endpoint: impl Into<String>) -> SignedRequestBuilder<'_> {
285 SignedRequestBuilder::new(self, endpoint)
286 }
287
288 pub fn id(&self) -> &'static str {
290 "binance"
291 }
292
293 pub fn name(&self) -> &'static str {
295 "Binance"
296 }
297
298 pub fn version(&self) -> &'static str {
300 "v3"
301 }
302
303 pub fn certified(&self) -> bool {
305 true
306 }
307
308 pub fn pro(&self) -> bool {
310 true
311 }
312
313 pub fn rate_limit(&self) -> u32 {
315 50
316 }
317
318 pub fn is_sandbox(&self) -> bool {
342 self.base().config.sandbox || self.options.test
343 }
344
345 pub fn timeframes(&self) -> std::collections::HashMap<String, String> {
347 constants::timeframes()
348 }
349
350 pub fn urls(&self) -> BinanceUrls {
352 let mut urls = if self.base().config.sandbox {
353 BinanceUrls::testnet()
354 } else {
355 BinanceUrls::production()
356 };
357
358 if let Some(public_url) = self.base().config.url_overrides.get("public") {
360 urls.public.clone_from(public_url);
361 }
362 if let Some(private_url) = self.base().config.url_overrides.get("private") {
363 urls.private.clone_from(private_url);
364 }
365 if let Some(fapi_public_url) = self.base().config.url_overrides.get("fapiPublic") {
366 urls.fapi_public.clone_from(fapi_public_url);
367 }
368 if let Some(fapi_private_url) = self.base().config.url_overrides.get("fapiPrivate") {
369 urls.fapi_private.clone_from(fapi_private_url);
370 }
371 if let Some(dapi_public_url) = self.base().config.url_overrides.get("dapiPublic") {
372 urls.dapi_public.clone_from(dapi_public_url);
373 }
374 if let Some(dapi_private_url) = self.base().config.url_overrides.get("dapiPrivate") {
375 urls.dapi_private.clone_from(dapi_private_url);
376 }
377 if let Some(ws_url) = self.base().config.url_overrides.get("ws") {
379 urls.ws.clone_from(ws_url);
380 }
381 if let Some(ws_fapi_url) = self.base().config.url_overrides.get("wsFapi") {
382 urls.ws_fapi.clone_from(ws_fapi_url);
383 }
384 if let Some(ws_dapi_url) = self.base().config.url_overrides.get("wsDapi") {
385 urls.ws_dapi.clone_from(ws_dapi_url);
386 }
387 if let Some(ws_eapi_url) = self.base().config.url_overrides.get("wsEapi") {
388 urls.ws_eapi.clone_from(ws_eapi_url);
389 }
390
391 urls
392 }
393
394 pub fn get_ws_url(&self) -> String {
411 BinanceEndpointRouter::default_ws_endpoint(self)
412 }
413
414 pub fn get_rest_url_public(&self) -> String {
449 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Public)
450 }
451
452 pub fn get_rest_url_private(&self) -> String {
487 BinanceEndpointRouter::default_rest_endpoint(self, EndpointType::Private)
488 }
489
490 pub fn is_contract_type(&self) -> bool {
498 self.options.default_type.is_contract()
499 }
500
501 pub fn is_inverse(&self) -> bool {
507 matches!(self.options.default_sub_type, Some(DefaultSubType::Inverse))
508 }
509
510 pub fn is_linear(&self) -> bool {
516 !self.is_inverse()
517 }
518
519 pub fn create_ws(&self) -> ws::BinanceWs {
545 let ws_url = self.get_ws_url();
546 ws::BinanceWs::new(ws_url)
547 }
548
549 pub fn create_authenticated_ws(self: &std::sync::Arc<Self>) -> ws::BinanceWs {
578 let ws_url = self.get_ws_url();
579 ws::BinanceWs::new_with_auth(ws_url, self.clone())
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn test_binance_creation() {
589 let config = ExchangeConfig {
590 id: "binance".to_string(),
591 name: "Binance".to_string(),
592 ..Default::default()
593 };
594
595 let binance = Binance::new(config);
596 assert!(binance.is_ok());
597
598 let binance = binance.unwrap();
599 assert_eq!(binance.id(), "binance");
600 assert_eq!(binance.name(), "Binance");
601 assert_eq!(binance.version(), "v3");
602 assert!(binance.certified());
603 assert!(binance.pro());
604 }
605
606 #[test]
607 fn test_timeframes() {
608 let config = ExchangeConfig::default();
609 let binance = Binance::new(config).unwrap();
610 let timeframes = binance.timeframes();
611
612 assert!(timeframes.contains_key("1m"));
613 assert!(timeframes.contains_key("1h"));
614 assert!(timeframes.contains_key("1d"));
615 assert_eq!(timeframes.len(), 16);
616 }
617
618 #[test]
619 fn test_urls() {
620 let config = ExchangeConfig::default();
621 let binance = Binance::new(config).unwrap();
622 let urls = binance.urls();
623
624 assert!(urls.public.contains("api.binance.com"));
625 assert!(urls.ws.contains("stream.binance.com"));
626 }
627
628 #[test]
629 fn test_sandbox_urls() {
630 let config = ExchangeConfig {
631 sandbox: true,
632 ..Default::default()
633 };
634 let binance = Binance::new(config).unwrap();
635 let urls = binance.urls();
636
637 assert!(urls.public.contains("testnet"));
638 }
639
640 #[test]
641 fn test_is_sandbox_with_config_sandbox() {
642 let config = ExchangeConfig {
643 sandbox: true,
644 ..Default::default()
645 };
646 let binance = Binance::new(config).unwrap();
647 assert!(binance.is_sandbox());
648 }
649
650 #[test]
651 fn test_is_sandbox_with_options_test() {
652 let config = ExchangeConfig::default();
653 let options = BinanceOptions {
654 test: true,
655 ..Default::default()
656 };
657 let binance = Binance::new_with_options(config, options).unwrap();
658 assert!(binance.is_sandbox());
659 }
660
661 #[test]
662 fn test_is_sandbox_false_by_default() {
663 let config = ExchangeConfig::default();
664 let binance = Binance::new(config).unwrap();
665 assert!(!binance.is_sandbox());
666 }
667
668 #[test]
669 fn test_binance_options_default() {
670 let options = BinanceOptions::default();
671 assert_eq!(options.default_type, DefaultType::Spot);
672 assert_eq!(options.default_sub_type, None);
673 assert!(!options.adjust_for_time_difference);
674 assert_eq!(options.recv_window, 5000);
675 assert!(!options.test);
676 assert_eq!(options.time_sync_interval_secs, 30);
677 assert!(options.auto_time_sync);
678 }
679
680 #[test]
681 fn test_binance_options_with_default_type() {
682 let options = BinanceOptions {
683 default_type: DefaultType::Swap,
684 default_sub_type: Some(DefaultSubType::Linear),
685 ..Default::default()
686 };
687 assert_eq!(options.default_type, DefaultType::Swap);
688 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
689 }
690
691 #[test]
692 fn test_binance_options_serialization() {
693 let options = BinanceOptions {
694 default_type: DefaultType::Swap,
695 default_sub_type: Some(DefaultSubType::Linear),
696 ..Default::default()
697 };
698 let json = serde_json::to_string(&options).unwrap();
699 assert!(json.contains("\"default_type\":\"swap\""));
700 assert!(json.contains("\"default_sub_type\":\"linear\""));
701 assert!(json.contains("\"time_sync_interval_secs\":30"));
702 assert!(json.contains("\"auto_time_sync\":true"));
703 }
704
705 #[test]
706 fn test_binance_options_deserialization_with_enum() {
707 let json = r#"{
708 "adjust_for_time_difference": false,
709 "recv_window": 5000,
710 "default_type": "swap",
711 "default_sub_type": "linear",
712 "test": false,
713 "time_sync_interval_secs": 60,
714 "auto_time_sync": false
715 }"#;
716 let options: BinanceOptions = serde_json::from_str(json).unwrap();
717 assert_eq!(options.default_type, DefaultType::Swap);
718 assert_eq!(options.default_sub_type, Some(DefaultSubType::Linear));
719 assert_eq!(options.time_sync_interval_secs, 60);
720 assert!(!options.auto_time_sync);
721 }
722
723 #[test]
724 fn test_binance_options_deserialization_legacy_future() {
725 let json = r#"{
727 "adjust_for_time_difference": false,
728 "recv_window": 5000,
729 "default_type": "future",
730 "test": false
731 }"#;
732 let options: BinanceOptions = serde_json::from_str(json).unwrap();
733 assert_eq!(options.default_type, DefaultType::Swap);
734 assert_eq!(options.time_sync_interval_secs, 30);
736 assert!(options.auto_time_sync);
737 }
738
739 #[test]
740 fn test_binance_options_deserialization_legacy_delivery() {
741 let json = r#"{
743 "adjust_for_time_difference": false,
744 "recv_window": 5000,
745 "default_type": "delivery",
746 "test": false
747 }"#;
748 let options: BinanceOptions = serde_json::from_str(json).unwrap();
749 assert_eq!(options.default_type, DefaultType::Futures);
750 }
751
752 #[test]
753 fn test_binance_options_deserialization_without_sub_type() {
754 let json = r#"{
755 "adjust_for_time_difference": false,
756 "recv_window": 5000,
757 "default_type": "spot",
758 "test": false
759 }"#;
760 let options: BinanceOptions = serde_json::from_str(json).unwrap();
761 assert_eq!(options.default_type, DefaultType::Spot);
762 assert_eq!(options.default_sub_type, None);
763 }
764
765 #[test]
766 fn test_binance_options_deserialization_case_insensitive() {
767 let json = r#"{
769 "adjust_for_time_difference": false,
770 "recv_window": 5000,
771 "default_type": "SWAP",
772 "test": false
773 }"#;
774 let options: BinanceOptions = serde_json::from_str(json).unwrap();
775 assert_eq!(options.default_type, DefaultType::Swap);
776
777 let json = r#"{
779 "adjust_for_time_difference": false,
780 "recv_window": 5000,
781 "default_type": "FuTuReS",
782 "test": false
783 }"#;
784 let options: BinanceOptions = serde_json::from_str(json).unwrap();
785 assert_eq!(options.default_type, DefaultType::Futures);
786 }
787
788 #[test]
789 fn test_new_futures_uses_swap_type() {
790 let config = ExchangeConfig::default();
791 let binance = Binance::new_swap(config).unwrap();
792 assert_eq!(binance.options().default_type, DefaultType::Swap);
793 }
794
795 #[test]
796 fn test_get_ws_url_spot() {
797 let config = ExchangeConfig::default();
798 let options = BinanceOptions {
799 default_type: DefaultType::Spot,
800 ..Default::default()
801 };
802 let binance = Binance::new_with_options(config, options).unwrap();
803 let ws_url = binance.get_ws_url();
804 assert!(ws_url.contains("stream.binance.com"));
805 }
806
807 #[test]
808 fn test_get_ws_url_margin() {
809 let config = ExchangeConfig::default();
810 let options = BinanceOptions {
811 default_type: DefaultType::Margin,
812 ..Default::default()
813 };
814 let binance = Binance::new_with_options(config, options).unwrap();
815 let ws_url = binance.get_ws_url();
816 assert!(ws_url.contains("stream.binance.com"));
818 }
819
820 #[test]
821 fn test_get_ws_url_swap_linear() {
822 let config = ExchangeConfig::default();
823 let options = BinanceOptions {
824 default_type: DefaultType::Swap,
825 default_sub_type: Some(DefaultSubType::Linear),
826 ..Default::default()
827 };
828 let binance = Binance::new_with_options(config, options).unwrap();
829 let ws_url = binance.get_ws_url();
830 assert!(ws_url.contains("fstream.binance.com"));
831 }
832
833 #[test]
834 fn test_get_ws_url_swap_inverse() {
835 let config = ExchangeConfig::default();
836 let options = BinanceOptions {
837 default_type: DefaultType::Swap,
838 default_sub_type: Some(DefaultSubType::Inverse),
839 ..Default::default()
840 };
841 let binance = Binance::new_with_options(config, options).unwrap();
842 let ws_url = binance.get_ws_url();
843 assert!(ws_url.contains("dstream.binance.com"));
844 }
845
846 #[test]
847 fn test_get_ws_url_swap_default_sub_type() {
848 let config = ExchangeConfig::default();
850 let options = BinanceOptions {
851 default_type: DefaultType::Swap,
852 default_sub_type: None,
853 ..Default::default()
854 };
855 let binance = Binance::new_with_options(config, options).unwrap();
856 let ws_url = binance.get_ws_url();
857 assert!(ws_url.contains("fstream.binance.com"));
858 }
859
860 #[test]
861 fn test_get_ws_url_futures_linear() {
862 let config = ExchangeConfig::default();
863 let options = BinanceOptions {
864 default_type: DefaultType::Futures,
865 default_sub_type: Some(DefaultSubType::Linear),
866 ..Default::default()
867 };
868 let binance = Binance::new_with_options(config, options).unwrap();
869 let ws_url = binance.get_ws_url();
870 assert!(ws_url.contains("fstream.binance.com"));
871 }
872
873 #[test]
874 fn test_get_ws_url_futures_inverse() {
875 let config = ExchangeConfig::default();
876 let options = BinanceOptions {
877 default_type: DefaultType::Futures,
878 default_sub_type: Some(DefaultSubType::Inverse),
879 ..Default::default()
880 };
881 let binance = Binance::new_with_options(config, options).unwrap();
882 let ws_url = binance.get_ws_url();
883 assert!(ws_url.contains("dstream.binance.com"));
884 }
885
886 #[test]
887 fn test_get_ws_url_option() {
888 let config = ExchangeConfig::default();
889 let options = BinanceOptions {
890 default_type: DefaultType::Option,
891 ..Default::default()
892 };
893 let binance = Binance::new_with_options(config, options).unwrap();
894 let ws_url = binance.get_ws_url();
895 assert!(ws_url.contains("nbstream.binance.com") || ws_url.contains("eoptions"));
896 }
897
898 #[test]
899 fn test_binance_urls_has_all_ws_endpoints() {
900 let urls = BinanceUrls::production();
901 assert!(!urls.ws.is_empty());
902 assert!(!urls.ws_fapi.is_empty());
903 assert!(!urls.ws_dapi.is_empty());
904 assert!(!urls.ws_eapi.is_empty());
905 }
906
907 #[test]
908 fn test_binance_urls_testnet_has_all_ws_endpoints() {
909 let urls = BinanceUrls::testnet();
910 assert!(!urls.ws.is_empty());
911 assert!(!urls.ws_fapi.is_empty());
912 assert!(!urls.ws_dapi.is_empty());
913 assert!(!urls.ws_eapi.is_empty());
914 }
915
916 #[test]
917 fn test_binance_urls_demo_has_all_ws_endpoints() {
918 let urls = BinanceUrls::demo();
919 assert!(!urls.ws.is_empty());
920 assert!(!urls.ws_fapi.is_empty());
921 assert!(!urls.ws_dapi.is_empty());
922 assert!(!urls.ws_eapi.is_empty());
923 }
924
925 #[test]
926 fn test_get_rest_url_public_spot() {
927 let config = ExchangeConfig::default();
928 let options = BinanceOptions {
929 default_type: DefaultType::Spot,
930 ..Default::default()
931 };
932 let binance = Binance::new_with_options(config, options).unwrap();
933 let url = binance.get_rest_url_public();
934 assert!(url.contains("api.binance.com"));
935 assert!(url.contains("/api/v3"));
936 }
937
938 #[test]
939 fn test_get_rest_url_public_margin() {
940 let config = ExchangeConfig::default();
941 let options = BinanceOptions {
942 default_type: DefaultType::Margin,
943 ..Default::default()
944 };
945 let binance = Binance::new_with_options(config, options).unwrap();
946 let url = binance.get_rest_url_public();
947 assert!(url.contains("api.binance.com"));
948 assert!(url.contains("/sapi/"));
949 }
950
951 #[test]
952 fn test_get_rest_url_public_swap_linear() {
953 let config = ExchangeConfig::default();
954 let options = BinanceOptions {
955 default_type: DefaultType::Swap,
956 default_sub_type: Some(DefaultSubType::Linear),
957 ..Default::default()
958 };
959 let binance = Binance::new_with_options(config, options).unwrap();
960 let url = binance.get_rest_url_public();
961 assert!(url.contains("fapi.binance.com"));
962 }
963
964 #[test]
965 fn test_get_rest_url_public_swap_inverse() {
966 let config = ExchangeConfig::default();
967 let options = BinanceOptions {
968 default_type: DefaultType::Swap,
969 default_sub_type: Some(DefaultSubType::Inverse),
970 ..Default::default()
971 };
972 let binance = Binance::new_with_options(config, options).unwrap();
973 let url = binance.get_rest_url_public();
974 assert!(url.contains("dapi.binance.com"));
975 }
976
977 #[test]
978 fn test_get_rest_url_public_futures_linear() {
979 let config = ExchangeConfig::default();
980 let options = BinanceOptions {
981 default_type: DefaultType::Futures,
982 default_sub_type: Some(DefaultSubType::Linear),
983 ..Default::default()
984 };
985 let binance = Binance::new_with_options(config, options).unwrap();
986 let url = binance.get_rest_url_public();
987 assert!(url.contains("fapi.binance.com"));
988 }
989
990 #[test]
991 fn test_get_rest_url_public_futures_inverse() {
992 let config = ExchangeConfig::default();
993 let options = BinanceOptions {
994 default_type: DefaultType::Futures,
995 default_sub_type: Some(DefaultSubType::Inverse),
996 ..Default::default()
997 };
998 let binance = Binance::new_with_options(config, options).unwrap();
999 let url = binance.get_rest_url_public();
1000 assert!(url.contains("dapi.binance.com"));
1001 }
1002
1003 #[test]
1004 fn test_get_rest_url_public_option() {
1005 let config = ExchangeConfig::default();
1006 let options = BinanceOptions {
1007 default_type: DefaultType::Option,
1008 ..Default::default()
1009 };
1010 let binance = Binance::new_with_options(config, options).unwrap();
1011 let url = binance.get_rest_url_public();
1012 assert!(url.contains("eapi.binance.com"));
1013 }
1014
1015 #[test]
1016 fn test_get_rest_url_private_swap_linear() {
1017 let config = ExchangeConfig::default();
1018 let options = BinanceOptions {
1019 default_type: DefaultType::Swap,
1020 default_sub_type: Some(DefaultSubType::Linear),
1021 ..Default::default()
1022 };
1023 let binance = Binance::new_with_options(config, options).unwrap();
1024 let url = binance.get_rest_url_private();
1025 assert!(url.contains("fapi.binance.com"));
1026 }
1027
1028 #[test]
1029 fn test_get_rest_url_private_swap_inverse() {
1030 let config = ExchangeConfig::default();
1031 let options = BinanceOptions {
1032 default_type: DefaultType::Swap,
1033 default_sub_type: Some(DefaultSubType::Inverse),
1034 ..Default::default()
1035 };
1036 let binance = Binance::new_with_options(config, options).unwrap();
1037 let url = binance.get_rest_url_private();
1038 assert!(url.contains("dapi.binance.com"));
1039 }
1040
1041 #[test]
1042 fn test_is_contract_type() {
1043 let config = ExchangeConfig::default();
1044
1045 let options = BinanceOptions {
1047 default_type: DefaultType::Spot,
1048 ..Default::default()
1049 };
1050 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1051 assert!(!binance.is_contract_type());
1052
1053 let options = BinanceOptions {
1055 default_type: DefaultType::Margin,
1056 ..Default::default()
1057 };
1058 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1059 assert!(!binance.is_contract_type());
1060
1061 let options = BinanceOptions {
1063 default_type: DefaultType::Swap,
1064 ..Default::default()
1065 };
1066 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1067 assert!(binance.is_contract_type());
1068
1069 let options = BinanceOptions {
1071 default_type: DefaultType::Futures,
1072 ..Default::default()
1073 };
1074 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1075 assert!(binance.is_contract_type());
1076
1077 let options = BinanceOptions {
1079 default_type: DefaultType::Option,
1080 ..Default::default()
1081 };
1082 let binance = Binance::new_with_options(config, options).unwrap();
1083 assert!(binance.is_contract_type());
1084 }
1085
1086 #[test]
1087 fn test_is_linear_and_is_inverse() {
1088 let config = ExchangeConfig::default();
1089
1090 let options = BinanceOptions {
1092 default_type: DefaultType::Swap,
1093 default_sub_type: None,
1094 ..Default::default()
1095 };
1096 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1097 assert!(binance.is_linear());
1098 assert!(!binance.is_inverse());
1099
1100 let options = BinanceOptions {
1102 default_type: DefaultType::Swap,
1103 default_sub_type: Some(DefaultSubType::Linear),
1104 ..Default::default()
1105 };
1106 let binance = Binance::new_with_options(config.clone(), options).unwrap();
1107 assert!(binance.is_linear());
1108 assert!(!binance.is_inverse());
1109
1110 let options = BinanceOptions {
1112 default_type: DefaultType::Swap,
1113 default_sub_type: Some(DefaultSubType::Inverse),
1114 ..Default::default()
1115 };
1116 let binance = Binance::new_with_options(config, options).unwrap();
1117 assert!(!binance.is_linear());
1118 assert!(binance.is_inverse());
1119 }
1120
1121 #[test]
1126 fn test_sandbox_market_type_spot() {
1127 let config = ExchangeConfig {
1128 sandbox: true,
1129 ..Default::default()
1130 };
1131 let options = BinanceOptions {
1132 default_type: DefaultType::Spot,
1133 ..Default::default()
1134 };
1135 let binance = Binance::new_with_options(config, options).unwrap();
1136
1137 assert!(binance.is_sandbox());
1138 let url = binance.get_rest_url_public();
1139 assert!(
1140 url.contains("testnet.binance.vision"),
1141 "Spot sandbox URL should contain testnet.binance.vision, got: {}",
1142 url
1143 );
1144 assert!(
1145 url.contains("/api/v3"),
1146 "Spot sandbox URL should contain /api/v3, got: {}",
1147 url
1148 );
1149 }
1150
1151 #[test]
1152 fn test_sandbox_market_type_swap_linear() {
1153 let config = ExchangeConfig {
1154 sandbox: true,
1155 ..Default::default()
1156 };
1157 let options = BinanceOptions {
1158 default_type: DefaultType::Swap,
1159 default_sub_type: Some(DefaultSubType::Linear),
1160 ..Default::default()
1161 };
1162 let binance = Binance::new_with_options(config, options).unwrap();
1163
1164 assert!(binance.is_sandbox());
1165 let url = binance.get_rest_url_public();
1166 assert!(
1167 url.contains("testnet.binancefuture.com"),
1168 "Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1169 url
1170 );
1171 assert!(
1172 url.contains("/fapi/"),
1173 "Linear sandbox URL should contain /fapi/, got: {}",
1174 url
1175 );
1176 }
1177
1178 #[test]
1179 fn test_sandbox_market_type_swap_inverse() {
1180 let config = ExchangeConfig {
1181 sandbox: true,
1182 ..Default::default()
1183 };
1184 let options = BinanceOptions {
1185 default_type: DefaultType::Swap,
1186 default_sub_type: Some(DefaultSubType::Inverse),
1187 ..Default::default()
1188 };
1189 let binance = Binance::new_with_options(config, options).unwrap();
1190
1191 assert!(binance.is_sandbox());
1192 let url = binance.get_rest_url_public();
1193 assert!(
1194 url.contains("testnet.binancefuture.com"),
1195 "Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1196 url
1197 );
1198 assert!(
1199 url.contains("/dapi/"),
1200 "Inverse sandbox URL should contain /dapi/, got: {}",
1201 url
1202 );
1203 }
1204
1205 #[test]
1206 fn test_sandbox_market_type_option() {
1207 let config = ExchangeConfig {
1208 sandbox: true,
1209 ..Default::default()
1210 };
1211 let options = BinanceOptions {
1212 default_type: DefaultType::Option,
1213 ..Default::default()
1214 };
1215 let binance = Binance::new_with_options(config, options).unwrap();
1216
1217 assert!(binance.is_sandbox());
1218 let url = binance.get_rest_url_public();
1219 assert!(
1220 url.contains("testnet.binanceops.com"),
1221 "Option sandbox URL should contain testnet.binanceops.com, got: {}",
1222 url
1223 );
1224 assert!(
1225 url.contains("/eapi/"),
1226 "Option sandbox URL should contain /eapi/, got: {}",
1227 url
1228 );
1229 }
1230
1231 #[test]
1232 fn test_sandbox_market_type_futures_linear() {
1233 let config = ExchangeConfig {
1234 sandbox: true,
1235 ..Default::default()
1236 };
1237 let options = BinanceOptions {
1238 default_type: DefaultType::Futures,
1239 default_sub_type: Some(DefaultSubType::Linear),
1240 ..Default::default()
1241 };
1242 let binance = Binance::new_with_options(config, options).unwrap();
1243
1244 assert!(binance.is_sandbox());
1245 let url = binance.get_rest_url_public();
1246 assert!(
1247 url.contains("testnet.binancefuture.com"),
1248 "Futures Linear sandbox URL should contain testnet.binancefuture.com, got: {}",
1249 url
1250 );
1251 assert!(
1252 url.contains("/fapi/"),
1253 "Futures Linear sandbox URL should contain /fapi/, got: {}",
1254 url
1255 );
1256 }
1257
1258 #[test]
1259 fn test_sandbox_market_type_futures_inverse() {
1260 let config = ExchangeConfig {
1261 sandbox: true,
1262 ..Default::default()
1263 };
1264 let options = BinanceOptions {
1265 default_type: DefaultType::Futures,
1266 default_sub_type: Some(DefaultSubType::Inverse),
1267 ..Default::default()
1268 };
1269 let binance = Binance::new_with_options(config, options).unwrap();
1270
1271 assert!(binance.is_sandbox());
1272 let url = binance.get_rest_url_public();
1273 assert!(
1274 url.contains("testnet.binancefuture.com"),
1275 "Futures Inverse sandbox URL should contain testnet.binancefuture.com, got: {}",
1276 url
1277 );
1278 assert!(
1279 url.contains("/dapi/"),
1280 "Futures Inverse sandbox URL should contain /dapi/, got: {}",
1281 url
1282 );
1283 }
1284
1285 #[test]
1286 fn test_sandbox_websocket_url_spot() {
1287 let config = ExchangeConfig {
1289 sandbox: true,
1290 ..Default::default()
1291 };
1292 let options = BinanceOptions {
1293 default_type: DefaultType::Spot,
1294 ..Default::default()
1295 };
1296 let binance = Binance::new_with_options(config, options).unwrap();
1297
1298 let urls = binance.urls();
1299 assert!(
1300 urls.ws.contains("testnet.binance.vision"),
1301 "Spot WS sandbox URL should contain testnet.binance.vision, got: {}",
1302 urls.ws
1303 );
1304 }
1305
1306 #[test]
1307 fn test_sandbox_websocket_url_fapi() {
1308 let config = ExchangeConfig {
1310 sandbox: true,
1311 ..Default::default()
1312 };
1313 let options = BinanceOptions {
1314 default_type: DefaultType::Swap,
1315 default_sub_type: Some(DefaultSubType::Linear),
1316 ..Default::default()
1317 };
1318 let binance = Binance::new_with_options(config, options).unwrap();
1319
1320 let urls = binance.urls();
1321 assert!(
1322 urls.ws_fapi.contains("binancefuture.com"),
1323 "FAPI WS sandbox URL should contain binancefuture.com, got: {}",
1324 urls.ws_fapi
1325 );
1326 }
1327
1328 #[test]
1329 fn test_sandbox_websocket_url_dapi() {
1330 let config = ExchangeConfig {
1332 sandbox: true,
1333 ..Default::default()
1334 };
1335 let options = BinanceOptions {
1336 default_type: DefaultType::Swap,
1337 default_sub_type: Some(DefaultSubType::Inverse),
1338 ..Default::default()
1339 };
1340 let binance = Binance::new_with_options(config, options).unwrap();
1341
1342 let urls = binance.urls();
1343 assert!(
1344 urls.ws_dapi.contains("dstream.binancefuture.com"),
1345 "DAPI WS sandbox URL should contain dstream.binancefuture.com, got: {}",
1346 urls.ws_dapi
1347 );
1348 }
1349
1350 #[test]
1351 fn test_sandbox_websocket_url_eapi() {
1352 let config = ExchangeConfig {
1354 sandbox: true,
1355 ..Default::default()
1356 };
1357 let options = BinanceOptions {
1358 default_type: DefaultType::Option,
1359 ..Default::default()
1360 };
1361 let binance = Binance::new_with_options(config, options).unwrap();
1362
1363 let urls = binance.urls();
1364 assert!(
1365 urls.ws_eapi.contains("testnet.binanceops.com"),
1366 "EAPI WS sandbox URL should contain testnet.binanceops.com, got: {}",
1367 urls.ws_eapi
1368 );
1369 }
1370}