1use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
6use ccxt_core::{BaseExchange, ExchangeConfig, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10pub mod auth;
11pub mod builder;
12pub mod endpoint_router;
13pub mod error;
14mod exchange_impl;
15mod margin_impl;
16pub mod parser;
17pub mod rest;
18pub mod signed_request;
19pub mod symbol;
20pub mod ws;
21mod ws_exchange_impl;
22
23pub use auth::BitgetAuth;
24pub use builder::BitgetBuilder;
25pub use endpoint_router::BitgetEndpointRouter;
26pub use error::{BitgetErrorCode, is_error_response, parse_error};
27pub use parser::{
28 datetime_to_timestamp, parse_balance, parse_market, parse_ohlcv, parse_order,
29 parse_order_status, parse_orderbook, parse_ticker, parse_trade, timestamp_to_datetime,
30};
31
32#[derive(Debug)]
34pub struct Bitget {
35 base: BaseExchange,
37 options: BitgetOptions,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct BitgetOptions {
57 pub product_type: String,
63 #[serde(default)]
73 pub default_type: DefaultType,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub default_sub_type: Option<DefaultSubType>,
83 pub recv_window: u64,
85 pub testnet: bool,
87}
88
89impl Default for BitgetOptions {
90 fn default() -> Self {
91 Self {
92 product_type: "spot".to_string(),
93 default_type: DefaultType::default(), default_sub_type: None,
95 recv_window: 5000,
96 testnet: false,
97 }
98 }
99}
100
101impl BitgetOptions {
102 pub fn effective_product_type(&self) -> &str {
128 match self.default_type {
129 DefaultType::Swap | DefaultType::Futures => {
130 match self.default_sub_type.unwrap_or(DefaultSubType::Linear) {
131 DefaultSubType::Linear => "umcbl", DefaultSubType::Inverse => "dmcbl", }
134 }
135 _ => "spot", }
137 }
138}
139
140impl Bitget {
141 pub fn builder() -> BitgetBuilder {
159 BitgetBuilder::new()
160 }
161
162 pub fn new(config: ExchangeConfig) -> Result<Self> {
168 let base = BaseExchange::new(config)?;
169 let options = BitgetOptions::default();
170
171 Ok(Self { base, options })
172 }
173
174 pub fn new_with_options(config: ExchangeConfig, options: BitgetOptions) -> Result<Self> {
183 let base = BaseExchange::new(config)?;
184 Ok(Self { base, options })
185 }
186
187 pub fn base(&self) -> &BaseExchange {
189 &self.base
190 }
191
192 pub fn base_mut(&mut self) -> &mut BaseExchange {
194 &mut self.base
195 }
196
197 pub fn options(&self) -> &BitgetOptions {
199 &self.options
200 }
201
202 pub fn set_options(&mut self, options: BitgetOptions) {
204 self.options = options;
205 }
206
207 pub fn id(&self) -> &'static str {
209 "bitget"
210 }
211
212 pub fn name(&self) -> &'static str {
214 "Bitget"
215 }
216
217 pub fn version(&self) -> &'static str {
219 "v2"
220 }
221
222 pub fn certified(&self) -> bool {
224 false
225 }
226
227 pub fn pro(&self) -> bool {
229 true
230 }
231
232 pub fn rate_limit(&self) -> u32 {
234 20
235 }
236
237 pub fn is_sandbox(&self) -> bool {
261 self.base().config.sandbox || self.options.testnet
262 }
263
264 pub fn timeframes(&self) -> HashMap<String, String> {
266 let mut timeframes = HashMap::new();
267 timeframes.insert("1m".to_string(), "1m".to_string());
268 timeframes.insert("5m".to_string(), "5m".to_string());
269 timeframes.insert("15m".to_string(), "15m".to_string());
270 timeframes.insert("30m".to_string(), "30m".to_string());
271 timeframes.insert("1h".to_string(), "1H".to_string());
272 timeframes.insert("4h".to_string(), "4H".to_string());
273 timeframes.insert("6h".to_string(), "6H".to_string());
274 timeframes.insert("12h".to_string(), "12H".to_string());
275 timeframes.insert("1d".to_string(), "1D".to_string());
276 timeframes.insert("3d".to_string(), "3D".to_string());
277 timeframes.insert("1w".to_string(), "1W".to_string());
278 timeframes.insert("1M".to_string(), "1M".to_string());
279 timeframes
280 }
281
282 pub fn urls(&self) -> BitgetUrls {
292 if self.base().config.sandbox || self.options.testnet {
293 BitgetUrls::testnet()
294 } else {
295 BitgetUrls::production()
296 }
297 }
298
299 pub fn create_ws(&self) -> ws::BitgetWs {
318 let urls = self.urls();
319 ws::BitgetWs::new(urls.ws_public)
320 }
321
322 pub fn create_private_ws(&self) -> ws::BitgetWs {
328 let urls = self.urls();
329 ws::BitgetWs::new(urls.ws_private)
330 }
331
332 pub fn signed_request(
359 &self,
360 endpoint: impl Into<String>,
361 ) -> signed_request::BitgetSignedRequestBuilder<'_> {
362 signed_request::BitgetSignedRequestBuilder::new(self, endpoint)
363 }
364}
365
366#[derive(Debug, Clone)]
368pub struct BitgetUrls {
369 pub rest: String,
371 pub ws_public: String,
373 pub ws_private: String,
375}
376
377impl BitgetUrls {
378 pub fn production() -> Self {
380 Self {
381 rest: "https://api.bitget.com".to_string(),
382 ws_public: "wss://ws.bitget.com/v2/ws/public".to_string(),
383 ws_private: "wss://ws.bitget.com/v2/ws/private".to_string(),
384 }
385 }
386
387 pub fn testnet() -> Self {
398 Self {
399 rest: "https://api-testnet.bitget.com".to_string(),
400 ws_public: "wss://ws-testnet.bitget.com/v2/ws/public".to_string(),
401 ws_private: "wss://ws-testnet.bitget.com/v2/ws/private".to_string(),
402 }
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_bitget_creation() {
412 let config = ExchangeConfig {
413 id: "bitget".to_string(),
414 name: "Bitget".to_string(),
415 ..Default::default()
416 };
417
418 let bitget = Bitget::new(config);
419 assert!(bitget.is_ok());
420
421 let bitget = bitget.unwrap();
422 assert_eq!(bitget.id(), "bitget");
423 assert_eq!(bitget.name(), "Bitget");
424 assert_eq!(bitget.version(), "v2");
425 assert!(!bitget.certified());
426 assert!(bitget.pro());
427 }
428
429 #[test]
430 fn test_timeframes() {
431 let config = ExchangeConfig::default();
432 let bitget = Bitget::new(config).unwrap();
433 let timeframes = bitget.timeframes();
434
435 assert!(timeframes.contains_key("1m"));
436 assert!(timeframes.contains_key("1h"));
437 assert!(timeframes.contains_key("1d"));
438 assert_eq!(timeframes.len(), 12);
439 }
440
441 #[test]
442 fn test_urls() {
443 let config = ExchangeConfig::default();
444 let bitget = Bitget::new(config).unwrap();
445 let urls = bitget.urls();
446
447 assert!(urls.rest.contains("api.bitget.com"));
448 assert!(urls.ws_public.contains("ws.bitget.com"));
449 }
450
451 #[test]
452 fn test_sandbox_urls() {
453 let config = ExchangeConfig {
454 sandbox: true,
455 ..Default::default()
456 };
457 let bitget = Bitget::new(config).unwrap();
458 let urls = bitget.urls();
459
460 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
462 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
463 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
464 }
465
466 #[test]
467 fn test_bitget_urls_testnet() {
468 let urls = BitgetUrls::testnet();
469 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
470 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
471 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
472 }
473
474 #[test]
475 fn test_sandbox_urls_with_testnet_option() {
476 let config = ExchangeConfig::default();
477 let options = BitgetOptions {
478 testnet: true,
479 ..Default::default()
480 };
481 let bitget = Bitget::new_with_options(config, options).unwrap();
482 let urls = bitget.urls();
483
484 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
486 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
487 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
488 }
489
490 #[test]
491 fn test_is_sandbox_with_config_sandbox() {
492 let config = ExchangeConfig {
493 sandbox: true,
494 ..Default::default()
495 };
496 let bitget = Bitget::new(config).unwrap();
497 assert!(bitget.is_sandbox());
498 }
499
500 #[test]
501 fn test_is_sandbox_with_options_testnet() {
502 let config = ExchangeConfig::default();
503 let options = BitgetOptions {
504 testnet: true,
505 ..Default::default()
506 };
507 let bitget = Bitget::new_with_options(config, options).unwrap();
508 assert!(bitget.is_sandbox());
509 }
510
511 #[test]
512 fn test_is_sandbox_false_by_default() {
513 let config = ExchangeConfig::default();
514 let bitget = Bitget::new(config).unwrap();
515 assert!(!bitget.is_sandbox());
516 }
517
518 #[test]
519 fn test_default_options() {
520 let options = BitgetOptions::default();
521 assert_eq!(options.product_type, "spot");
522 assert_eq!(options.default_type, DefaultType::Spot);
523 assert_eq!(options.default_sub_type, None);
524 assert_eq!(options.recv_window, 5000);
525 assert!(!options.testnet);
526 }
527
528 #[test]
529 fn test_effective_product_type_spot() {
530 let mut options = BitgetOptions::default();
531 options.default_type = DefaultType::Spot;
532 assert_eq!(options.effective_product_type(), "spot");
533 }
534
535 #[test]
536 fn test_effective_product_type_swap_linear() {
537 let mut options = BitgetOptions::default();
538 options.default_type = DefaultType::Swap;
539 options.default_sub_type = Some(DefaultSubType::Linear);
540 assert_eq!(options.effective_product_type(), "umcbl");
541 }
542
543 #[test]
544 fn test_effective_product_type_swap_inverse() {
545 let mut options = BitgetOptions::default();
546 options.default_type = DefaultType::Swap;
547 options.default_sub_type = Some(DefaultSubType::Inverse);
548 assert_eq!(options.effective_product_type(), "dmcbl");
549 }
550
551 #[test]
552 fn test_effective_product_type_swap_default_sub_type() {
553 let mut options = BitgetOptions::default();
554 options.default_type = DefaultType::Swap;
555 assert_eq!(options.effective_product_type(), "umcbl");
557 }
558
559 #[test]
560 fn test_effective_product_type_futures_linear() {
561 let mut options = BitgetOptions::default();
562 options.default_type = DefaultType::Futures;
563 options.default_sub_type = Some(DefaultSubType::Linear);
564 assert_eq!(options.effective_product_type(), "umcbl");
565 }
566
567 #[test]
568 fn test_effective_product_type_futures_inverse() {
569 let mut options = BitgetOptions::default();
570 options.default_type = DefaultType::Futures;
571 options.default_sub_type = Some(DefaultSubType::Inverse);
572 assert_eq!(options.effective_product_type(), "dmcbl");
573 }
574
575 #[test]
576 fn test_effective_product_type_margin() {
577 let mut options = BitgetOptions::default();
578 options.default_type = DefaultType::Margin;
579 assert_eq!(options.effective_product_type(), "spot");
580 }
581
582 #[test]
583 fn test_effective_product_type_option() {
584 let mut options = BitgetOptions::default();
585 options.default_type = DefaultType::Option;
586 assert_eq!(options.effective_product_type(), "spot");
587 }
588}