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 error;
13mod exchange_impl;
14pub mod parser;
15pub mod rest;
16pub mod ws;
17mod ws_exchange_impl;
18
19pub use auth::BitgetAuth;
20pub use builder::BitgetBuilder;
21pub use error::{BitgetErrorCode, is_error_response, parse_error};
22pub use parser::{
23 datetime_to_timestamp, parse_balance, parse_market, parse_ohlcv, parse_order,
24 parse_order_status, parse_orderbook, parse_ticker, parse_trade, timestamp_to_datetime,
25};
26
27#[derive(Debug)]
29pub struct Bitget {
30 base: BaseExchange,
32 options: BitgetOptions,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BitgetOptions {
52 pub product_type: String,
58 #[serde(default)]
68 pub default_type: DefaultType,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub default_sub_type: Option<DefaultSubType>,
78 pub recv_window: u64,
80 pub testnet: bool,
82}
83
84impl Default for BitgetOptions {
85 fn default() -> Self {
86 Self {
87 product_type: "spot".to_string(),
88 default_type: DefaultType::default(), default_sub_type: None,
90 recv_window: 5000,
91 testnet: false,
92 }
93 }
94}
95
96impl BitgetOptions {
97 pub fn effective_product_type(&self) -> &str {
123 match self.default_type {
124 DefaultType::Spot => "spot",
125 DefaultType::Margin => "spot", DefaultType::Swap | DefaultType::Futures => {
127 match self.default_sub_type.unwrap_or(DefaultSubType::Linear) {
128 DefaultSubType::Linear => "umcbl", DefaultSubType::Inverse => "dmcbl", }
131 }
132 DefaultType::Option => "spot", }
134 }
135}
136
137impl Bitget {
138 pub fn builder() -> BitgetBuilder {
156 BitgetBuilder::new()
157 }
158
159 pub fn new(config: ExchangeConfig) -> Result<Self> {
165 let base = BaseExchange::new(config)?;
166 let options = BitgetOptions::default();
167
168 Ok(Self { base, options })
169 }
170
171 pub fn new_with_options(config: ExchangeConfig, options: BitgetOptions) -> Result<Self> {
180 let base = BaseExchange::new(config)?;
181 Ok(Self { base, options })
182 }
183
184 pub fn base(&self) -> &BaseExchange {
186 &self.base
187 }
188
189 pub fn base_mut(&mut self) -> &mut BaseExchange {
191 &mut self.base
192 }
193
194 pub fn options(&self) -> &BitgetOptions {
196 &self.options
197 }
198
199 pub fn set_options(&mut self, options: BitgetOptions) {
201 self.options = options;
202 }
203
204 pub fn id(&self) -> &str {
206 "bitget"
207 }
208
209 pub fn name(&self) -> &str {
211 "Bitget"
212 }
213
214 pub fn version(&self) -> &str {
216 "v2"
217 }
218
219 pub fn certified(&self) -> bool {
221 false
222 }
223
224 pub fn pro(&self) -> bool {
226 true
227 }
228
229 pub fn rate_limit(&self) -> u32 {
231 20
232 }
233
234 pub fn is_sandbox(&self) -> bool {
258 self.base().config.sandbox || self.options.testnet
259 }
260
261 pub fn timeframes(&self) -> HashMap<String, String> {
263 let mut timeframes = HashMap::new();
264 timeframes.insert("1m".to_string(), "1m".to_string());
265 timeframes.insert("5m".to_string(), "5m".to_string());
266 timeframes.insert("15m".to_string(), "15m".to_string());
267 timeframes.insert("30m".to_string(), "30m".to_string());
268 timeframes.insert("1h".to_string(), "1H".to_string());
269 timeframes.insert("4h".to_string(), "4H".to_string());
270 timeframes.insert("6h".to_string(), "6H".to_string());
271 timeframes.insert("12h".to_string(), "12H".to_string());
272 timeframes.insert("1d".to_string(), "1D".to_string());
273 timeframes.insert("3d".to_string(), "3D".to_string());
274 timeframes.insert("1w".to_string(), "1W".to_string());
275 timeframes.insert("1M".to_string(), "1M".to_string());
276 timeframes
277 }
278
279 pub fn urls(&self) -> BitgetUrls {
289 if self.base().config.sandbox || self.options.testnet {
290 BitgetUrls::testnet()
291 } else {
292 BitgetUrls::production()
293 }
294 }
295
296 pub fn create_ws(&self) -> ws::BitgetWs {
315 let urls = self.urls();
316 ws::BitgetWs::new(urls.ws_public)
317 }
318}
319
320#[derive(Debug, Clone)]
322pub struct BitgetUrls {
323 pub rest: String,
325 pub ws_public: String,
327 pub ws_private: String,
329}
330
331impl BitgetUrls {
332 pub fn production() -> Self {
334 Self {
335 rest: "https://api.bitget.com".to_string(),
336 ws_public: "wss://ws.bitget.com/v2/ws/public".to_string(),
337 ws_private: "wss://ws.bitget.com/v2/ws/private".to_string(),
338 }
339 }
340
341 pub fn testnet() -> Self {
352 Self {
353 rest: "https://api-testnet.bitget.com".to_string(),
354 ws_public: "wss://ws-testnet.bitget.com/v2/ws/public".to_string(),
355 ws_private: "wss://ws-testnet.bitget.com/v2/ws/private".to_string(),
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_bitget_creation() {
366 let config = ExchangeConfig {
367 id: "bitget".to_string(),
368 name: "Bitget".to_string(),
369 ..Default::default()
370 };
371
372 let bitget = Bitget::new(config);
373 assert!(bitget.is_ok());
374
375 let bitget = bitget.unwrap();
376 assert_eq!(bitget.id(), "bitget");
377 assert_eq!(bitget.name(), "Bitget");
378 assert_eq!(bitget.version(), "v2");
379 assert!(!bitget.certified());
380 assert!(bitget.pro());
381 }
382
383 #[test]
384 fn test_timeframes() {
385 let config = ExchangeConfig::default();
386 let bitget = Bitget::new(config).unwrap();
387 let timeframes = bitget.timeframes();
388
389 assert!(timeframes.contains_key("1m"));
390 assert!(timeframes.contains_key("1h"));
391 assert!(timeframes.contains_key("1d"));
392 assert_eq!(timeframes.len(), 12);
393 }
394
395 #[test]
396 fn test_urls() {
397 let config = ExchangeConfig::default();
398 let bitget = Bitget::new(config).unwrap();
399 let urls = bitget.urls();
400
401 assert!(urls.rest.contains("api.bitget.com"));
402 assert!(urls.ws_public.contains("ws.bitget.com"));
403 }
404
405 #[test]
406 fn test_sandbox_urls() {
407 let config = ExchangeConfig {
408 sandbox: true,
409 ..Default::default()
410 };
411 let bitget = Bitget::new(config).unwrap();
412 let urls = bitget.urls();
413
414 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
416 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
417 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
418 }
419
420 #[test]
421 fn test_bitget_urls_testnet() {
422 let urls = BitgetUrls::testnet();
423 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
424 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
425 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
426 }
427
428 #[test]
429 fn test_sandbox_urls_with_testnet_option() {
430 let config = ExchangeConfig::default();
431 let options = BitgetOptions {
432 testnet: true,
433 ..Default::default()
434 };
435 let bitget = Bitget::new_with_options(config, options).unwrap();
436 let urls = bitget.urls();
437
438 assert_eq!(urls.rest, "https://api-testnet.bitget.com");
440 assert_eq!(urls.ws_public, "wss://ws-testnet.bitget.com/v2/ws/public");
441 assert_eq!(urls.ws_private, "wss://ws-testnet.bitget.com/v2/ws/private");
442 }
443
444 #[test]
445 fn test_is_sandbox_with_config_sandbox() {
446 let config = ExchangeConfig {
447 sandbox: true,
448 ..Default::default()
449 };
450 let bitget = Bitget::new(config).unwrap();
451 assert!(bitget.is_sandbox());
452 }
453
454 #[test]
455 fn test_is_sandbox_with_options_testnet() {
456 let config = ExchangeConfig::default();
457 let options = BitgetOptions {
458 testnet: true,
459 ..Default::default()
460 };
461 let bitget = Bitget::new_with_options(config, options).unwrap();
462 assert!(bitget.is_sandbox());
463 }
464
465 #[test]
466 fn test_is_sandbox_false_by_default() {
467 let config = ExchangeConfig::default();
468 let bitget = Bitget::new(config).unwrap();
469 assert!(!bitget.is_sandbox());
470 }
471
472 #[test]
473 fn test_default_options() {
474 let options = BitgetOptions::default();
475 assert_eq!(options.product_type, "spot");
476 assert_eq!(options.default_type, DefaultType::Spot);
477 assert_eq!(options.default_sub_type, None);
478 assert_eq!(options.recv_window, 5000);
479 assert!(!options.testnet);
480 }
481
482 #[test]
483 fn test_effective_product_type_spot() {
484 let mut options = BitgetOptions::default();
485 options.default_type = DefaultType::Spot;
486 assert_eq!(options.effective_product_type(), "spot");
487 }
488
489 #[test]
490 fn test_effective_product_type_swap_linear() {
491 let mut options = BitgetOptions::default();
492 options.default_type = DefaultType::Swap;
493 options.default_sub_type = Some(DefaultSubType::Linear);
494 assert_eq!(options.effective_product_type(), "umcbl");
495 }
496
497 #[test]
498 fn test_effective_product_type_swap_inverse() {
499 let mut options = BitgetOptions::default();
500 options.default_type = DefaultType::Swap;
501 options.default_sub_type = Some(DefaultSubType::Inverse);
502 assert_eq!(options.effective_product_type(), "dmcbl");
503 }
504
505 #[test]
506 fn test_effective_product_type_swap_default_sub_type() {
507 let mut options = BitgetOptions::default();
508 options.default_type = DefaultType::Swap;
509 assert_eq!(options.effective_product_type(), "umcbl");
511 }
512
513 #[test]
514 fn test_effective_product_type_futures_linear() {
515 let mut options = BitgetOptions::default();
516 options.default_type = DefaultType::Futures;
517 options.default_sub_type = Some(DefaultSubType::Linear);
518 assert_eq!(options.effective_product_type(), "umcbl");
519 }
520
521 #[test]
522 fn test_effective_product_type_futures_inverse() {
523 let mut options = BitgetOptions::default();
524 options.default_type = DefaultType::Futures;
525 options.default_sub_type = Some(DefaultSubType::Inverse);
526 assert_eq!(options.effective_product_type(), "dmcbl");
527 }
528
529 #[test]
530 fn test_effective_product_type_margin() {
531 let mut options = BitgetOptions::default();
532 options.default_type = DefaultType::Margin;
533 assert_eq!(options.effective_product_type(), "spot");
534 }
535
536 #[test]
537 fn test_effective_product_type_option() {
538 let mut options = BitgetOptions::default();
539 options.default_type = DefaultType::Option;
540 assert_eq!(options.effective_product_type(), "spot");
541 }
542}