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;
15pub mod parser;
16pub mod rest;
17pub mod signed_request;
18pub mod ws;
19mod ws_exchange_impl;
20
21pub use auth::BitgetAuth;
22pub use builder::BitgetBuilder;
23pub use endpoint_router::BitgetEndpointRouter;
24pub use error::{BitgetErrorCode, is_error_response, parse_error};
25pub use parser::{
26 datetime_to_timestamp, parse_balance, parse_market, parse_ohlcv, parse_order,
27 parse_order_status, parse_orderbook, parse_ticker, parse_trade, timestamp_to_datetime,
28};
29
30#[derive(Debug)]
32pub struct Bitget {
33 base: BaseExchange,
35 options: BitgetOptions,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct BitgetOptions {
55 pub product_type: String,
61 #[serde(default)]
71 pub default_type: DefaultType,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub default_sub_type: Option<DefaultSubType>,
81 pub recv_window: u64,
83 pub testnet: bool,
85}
86
87impl Default for BitgetOptions {
88 fn default() -> Self {
89 Self {
90 product_type: "spot".to_string(),
91 default_type: DefaultType::default(), default_sub_type: None,
93 recv_window: 5000,
94 testnet: false,
95 }
96 }
97}
98
99impl BitgetOptions {
100 pub fn effective_product_type(&self) -> &str {
126 match self.default_type {
127 DefaultType::Swap | DefaultType::Futures => {
128 match self.default_sub_type.unwrap_or(DefaultSubType::Linear) {
129 DefaultSubType::Linear => "umcbl", DefaultSubType::Inverse => "dmcbl", }
132 }
133 _ => "spot", }
135 }
136}
137
138impl Bitget {
139 pub fn builder() -> BitgetBuilder {
157 BitgetBuilder::new()
158 }
159
160 pub fn new(config: ExchangeConfig) -> Result<Self> {
166 let base = BaseExchange::new(config)?;
167 let options = BitgetOptions::default();
168
169 Ok(Self { base, options })
170 }
171
172 pub fn new_with_options(config: ExchangeConfig, options: BitgetOptions) -> Result<Self> {
181 let base = BaseExchange::new(config)?;
182 Ok(Self { base, options })
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) -> &BitgetOptions {
197 &self.options
198 }
199
200 pub fn set_options(&mut self, options: BitgetOptions) {
202 self.options = options;
203 }
204
205 pub fn id(&self) -> &'static str {
207 "bitget"
208 }
209
210 pub fn name(&self) -> &'static str {
212 "Bitget"
213 }
214
215 pub fn version(&self) -> &'static str {
217 "v2"
218 }
219
220 pub fn certified(&self) -> bool {
222 false
223 }
224
225 pub fn pro(&self) -> bool {
227 true
228 }
229
230 pub fn rate_limit(&self) -> u32 {
232 20
233 }
234
235 pub fn is_sandbox(&self) -> bool {
259 self.base().config.sandbox || self.options.testnet
260 }
261
262 pub fn timeframes(&self) -> HashMap<String, String> {
264 let mut timeframes = HashMap::new();
265 timeframes.insert("1m".to_string(), "1m".to_string());
266 timeframes.insert("5m".to_string(), "5m".to_string());
267 timeframes.insert("15m".to_string(), "15m".to_string());
268 timeframes.insert("30m".to_string(), "30m".to_string());
269 timeframes.insert("1h".to_string(), "1H".to_string());
270 timeframes.insert("4h".to_string(), "4H".to_string());
271 timeframes.insert("6h".to_string(), "6H".to_string());
272 timeframes.insert("12h".to_string(), "12H".to_string());
273 timeframes.insert("1d".to_string(), "1D".to_string());
274 timeframes.insert("3d".to_string(), "3D".to_string());
275 timeframes.insert("1w".to_string(), "1W".to_string());
276 timeframes.insert("1M".to_string(), "1M".to_string());
277 timeframes
278 }
279
280 pub fn urls(&self) -> BitgetUrls {
290 if self.base().config.sandbox || self.options.testnet {
291 BitgetUrls::testnet()
292 } else {
293 BitgetUrls::production()
294 }
295 }
296
297 pub fn create_ws(&self) -> ws::BitgetWs {
316 let urls = self.urls();
317 ws::BitgetWs::new(urls.ws_public)
318 }
319
320 pub fn signed_request(
347 &self,
348 endpoint: impl Into<String>,
349 ) -> signed_request::BitgetSignedRequestBuilder<'_> {
350 signed_request::BitgetSignedRequestBuilder::new(self, endpoint)
351 }
352}
353
354#[derive(Debug, Clone)]
356pub struct BitgetUrls {
357 pub rest: String,
359 pub ws_public: String,
361 pub ws_private: String,
363}
364
365impl BitgetUrls {
366 pub fn production() -> Self {
368 Self {
369 rest: "https://api.bitget.com".to_string(),
370 ws_public: "wss://ws.bitget.com/v2/ws/public".to_string(),
371 ws_private: "wss://ws.bitget.com/v2/ws/private".to_string(),
372 }
373 }
374
375 pub fn testnet() -> Self {
386 Self {
387 rest: "https://api-testnet.bitget.com".to_string(),
388 ws_public: "wss://ws-testnet.bitget.com/v2/ws/public".to_string(),
389 ws_private: "wss://ws-testnet.bitget.com/v2/ws/private".to_string(),
390 }
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_bitget_creation() {
400 let config = ExchangeConfig {
401 id: "bitget".to_string(),
402 name: "Bitget".to_string(),
403 ..Default::default()
404 };
405
406 let bitget = Bitget::new(config);
407 assert!(bitget.is_ok());
408
409 let bitget = bitget.unwrap();
410 assert_eq!(bitget.id(), "bitget");
411 assert_eq!(bitget.name(), "Bitget");
412 assert_eq!(bitget.version(), "v2");
413 assert!(!bitget.certified());
414 assert!(bitget.pro());
415 }
416
417 #[test]
418 fn test_timeframes() {
419 let config = ExchangeConfig::default();
420 let bitget = Bitget::new(config).unwrap();
421 let timeframes = bitget.timeframes();
422
423 assert!(timeframes.contains_key("1m"));
424 assert!(timeframes.contains_key("1h"));
425 assert!(timeframes.contains_key("1d"));
426 assert_eq!(timeframes.len(), 12);
427 }
428
429 #[test]
430 fn test_urls() {
431 let config = ExchangeConfig::default();
432 let bitget = Bitget::new(config).unwrap();
433 let urls = bitget.urls();
434
435 assert!(urls.rest.contains("api.bitget.com"));
436 assert!(urls.ws_public.contains("ws.bitget.com"));
437 }
438
439 #[test]
440 fn test_sandbox_urls() {
441 let config = ExchangeConfig {
442 sandbox: true,
443 ..Default::default()
444 };
445 let bitget = Bitget::new(config).unwrap();
446 let urls = bitget.urls();
447
448 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
450 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
451 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
452 }
453
454 #[test]
455 fn test_bitget_urls_testnet() {
456 let urls = BitgetUrls::testnet();
457 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
458 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
459 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
460 }
461
462 #[test]
463 fn test_sandbox_urls_with_testnet_option() {
464 let config = ExchangeConfig::default();
465 let options = BitgetOptions {
466 testnet: true,
467 ..Default::default()
468 };
469 let bitget = Bitget::new_with_options(config, options).unwrap();
470 let urls = bitget.urls();
471
472 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
474 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
475 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
476 }
477
478 #[test]
479 fn test_is_sandbox_with_config_sandbox() {
480 let config = ExchangeConfig {
481 sandbox: true,
482 ..Default::default()
483 };
484 let bitget = Bitget::new(config).unwrap();
485 assert!(bitget.is_sandbox());
486 }
487
488 #[test]
489 fn test_is_sandbox_with_options_testnet() {
490 let config = ExchangeConfig::default();
491 let options = BitgetOptions {
492 testnet: true,
493 ..Default::default()
494 };
495 let bitget = Bitget::new_with_options(config, options).unwrap();
496 assert!(bitget.is_sandbox());
497 }
498
499 #[test]
500 fn test_is_sandbox_false_by_default() {
501 let config = ExchangeConfig::default();
502 let bitget = Bitget::new(config).unwrap();
503 assert!(!bitget.is_sandbox());
504 }
505
506 #[test]
507 fn test_default_options() {
508 let options = BitgetOptions::default();
509 assert_eq!(options.product_type, "spot");
510 assert_eq!(options.default_type, DefaultType::Spot);
511 assert_eq!(options.default_sub_type, None);
512 assert_eq!(options.recv_window, 5000);
513 assert!(!options.testnet);
514 }
515
516 #[test]
517 fn test_effective_product_type_spot() {
518 let mut options = BitgetOptions::default();
519 options.default_type = DefaultType::Spot;
520 assert_eq!(options.effective_product_type(), "spot");
521 }
522
523 #[test]
524 fn test_effective_product_type_swap_linear() {
525 let mut options = BitgetOptions::default();
526 options.default_type = DefaultType::Swap;
527 options.default_sub_type = Some(DefaultSubType::Linear);
528 assert_eq!(options.effective_product_type(), "umcbl");
529 }
530
531 #[test]
532 fn test_effective_product_type_swap_inverse() {
533 let mut options = BitgetOptions::default();
534 options.default_type = DefaultType::Swap;
535 options.default_sub_type = Some(DefaultSubType::Inverse);
536 assert_eq!(options.effective_product_type(), "dmcbl");
537 }
538
539 #[test]
540 fn test_effective_product_type_swap_default_sub_type() {
541 let mut options = BitgetOptions::default();
542 options.default_type = DefaultType::Swap;
543 assert_eq!(options.effective_product_type(), "umcbl");
545 }
546
547 #[test]
548 fn test_effective_product_type_futures_linear() {
549 let mut options = BitgetOptions::default();
550 options.default_type = DefaultType::Futures;
551 options.default_sub_type = Some(DefaultSubType::Linear);
552 assert_eq!(options.effective_product_type(), "umcbl");
553 }
554
555 #[test]
556 fn test_effective_product_type_futures_inverse() {
557 let mut options = BitgetOptions::default();
558 options.default_type = DefaultType::Futures;
559 options.default_sub_type = Some(DefaultSubType::Inverse);
560 assert_eq!(options.effective_product_type(), "dmcbl");
561 }
562
563 #[test]
564 fn test_effective_product_type_margin() {
565 let mut options = BitgetOptions::default();
566 options.default_type = DefaultType::Margin;
567 assert_eq!(options.effective_product_type(), "spot");
568 }
569
570 #[test]
571 fn test_effective_product_type_option() {
572 let mut options = BitgetOptions::default();
573 options.default_type = DefaultType::Option;
574 assert_eq!(options.effective_product_type(), "spot");
575 }
576}