1use async_trait::async_trait;
6use ccxt_core::{
7 Result,
8 exchange::{Exchange, ExchangeCapabilities},
9 types::{
10 Amount, Balance, Market, Ohlcv, Order, OrderBook, OrderSide, OrderType, Price, Ticker,
11 Timeframe, Trade,
12 },
13};
14use rust_decimal::Decimal;
15use std::collections::HashMap;
16
17use super::Bitget;
18
19#[async_trait]
20impl Exchange for Bitget {
21 fn id(&self) -> &str {
24 "bitget"
25 }
26
27 fn name(&self) -> &str {
28 "Bitget"
29 }
30
31 fn version(&self) -> &'static str {
32 "v2"
33 }
34
35 fn certified(&self) -> bool {
36 false
37 }
38
39 fn has_websocket(&self) -> bool {
40 true
41 }
42
43 fn capabilities(&self) -> ExchangeCapabilities {
44 ExchangeCapabilities {
45 fetch_markets: true,
47 fetch_currencies: false,
48 fetch_ticker: true,
49 fetch_tickers: true,
50 fetch_order_book: true,
51 fetch_trades: true,
52 fetch_ohlcv: true,
53 fetch_status: false,
54 fetch_time: false,
55
56 create_order: true,
58 create_market_order: true,
59 create_limit_order: true,
60 cancel_order: true,
61 cancel_all_orders: false, edit_order: false,
63 fetch_order: true,
64 fetch_orders: false,
65 fetch_open_orders: true,
66 fetch_closed_orders: true,
67 fetch_canceled_orders: false,
68
69 fetch_balance: true,
71 fetch_my_trades: true,
72 fetch_deposits: false,
73 fetch_withdrawals: false,
74 fetch_transactions: false,
75 fetch_ledger: false,
76
77 fetch_deposit_address: false,
79 create_deposit_address: false,
80 withdraw: false,
81 transfer: false,
82
83 fetch_borrow_rate: false,
85 fetch_borrow_rates: false,
86 fetch_funding_rate: false,
87 fetch_funding_rates: false,
88 fetch_positions: false,
89 set_leverage: false,
90 set_margin_mode: false,
91
92 websocket: true,
94 watch_ticker: true,
95 watch_tickers: false,
96 watch_order_book: true,
97 watch_trades: true,
98 watch_ohlcv: false,
99 watch_balance: false,
100 watch_orders: false,
101 watch_my_trades: false,
102 }
103 }
104
105 fn timeframes(&self) -> Vec<Timeframe> {
106 vec![
107 Timeframe::M1,
108 Timeframe::M5,
109 Timeframe::M15,
110 Timeframe::M30,
111 Timeframe::H1,
112 Timeframe::H4,
113 Timeframe::H6,
114 Timeframe::H12,
115 Timeframe::D1,
116 Timeframe::D3,
117 Timeframe::W1,
118 Timeframe::Mon1,
119 ]
120 }
121
122 fn rate_limit(&self) -> f64 {
123 20.0
124 }
125
126 async fn fetch_markets(&self) -> Result<Vec<Market>> {
129 Bitget::fetch_markets(self).await
130 }
131
132 async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>> {
133 Bitget::load_markets(self, reload).await
134 }
135
136 async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
137 Bitget::fetch_ticker(self, symbol).await
138 }
139
140 async fn fetch_tickers(&self, symbols: Option<&[String]>) -> Result<Vec<Ticker>> {
141 let symbols_vec = symbols.map(|s| s.to_vec());
142 Bitget::fetch_tickers(self, symbols_vec).await
143 }
144
145 async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
146 Bitget::fetch_order_book(self, symbol, limit).await
147 }
148
149 async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
150 Bitget::fetch_trades(self, symbol, limit).await
151 }
152
153 async fn fetch_ohlcv(
154 &self,
155 symbol: &str,
156 timeframe: Timeframe,
157 since: Option<i64>,
158 limit: Option<u32>,
159 ) -> Result<Vec<Ohlcv>> {
160 let timeframe_str = timeframe.to_string();
161 let ohlcv_data = Bitget::fetch_ohlcv(self, symbol, &timeframe_str, since, limit).await?;
162
163 Ok(ohlcv_data
165 .into_iter()
166 .map(|o| Ohlcv {
167 timestamp: o.timestamp,
168 open: Price::from(Decimal::try_from(o.open).unwrap_or_default()),
169 high: Price::from(Decimal::try_from(o.high).unwrap_or_default()),
170 low: Price::from(Decimal::try_from(o.low).unwrap_or_default()),
171 close: Price::from(Decimal::try_from(o.close).unwrap_or_default()),
172 volume: Amount::from(Decimal::try_from(o.volume).unwrap_or_default()),
173 })
174 .collect())
175 }
176
177 async fn create_order(
180 &self,
181 symbol: &str,
182 order_type: OrderType,
183 side: OrderSide,
184 amount: Decimal,
185 price: Option<Decimal>,
186 ) -> Result<Order> {
187 let amount_f64 = amount.to_string().parse::<f64>().unwrap_or(0.0);
188 let price_f64 = price.map(|p| p.to_string().parse::<f64>().unwrap_or(0.0));
189
190 Bitget::create_order(self, symbol, order_type, side, amount_f64, price_f64).await
191 }
192
193 async fn cancel_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
194 let symbol_str = symbol.ok_or_else(|| {
195 ccxt_core::Error::invalid_request("Symbol is required for cancel_order on Bitget")
196 })?;
197 Bitget::cancel_order(self, id, symbol_str).await
198 }
199
200 async fn cancel_all_orders(&self, _symbol: Option<&str>) -> Result<Vec<Order>> {
201 Err(ccxt_core::Error::not_implemented("cancel_all_orders"))
202 }
203
204 async fn fetch_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
205 let symbol_str = symbol.ok_or_else(|| {
206 ccxt_core::Error::invalid_request("Symbol is required for fetch_order on Bitget")
207 })?;
208 Bitget::fetch_order(self, id, symbol_str).await
209 }
210
211 async fn fetch_open_orders(
212 &self,
213 symbol: Option<&str>,
214 since: Option<i64>,
215 limit: Option<u32>,
216 ) -> Result<Vec<Order>> {
217 Bitget::fetch_open_orders(self, symbol, since, limit).await
218 }
219
220 async fn fetch_closed_orders(
221 &self,
222 symbol: Option<&str>,
223 since: Option<i64>,
224 limit: Option<u32>,
225 ) -> Result<Vec<Order>> {
226 Bitget::fetch_closed_orders(self, symbol, since, limit).await
227 }
228
229 async fn fetch_balance(&self) -> Result<Balance> {
232 Bitget::fetch_balance(self).await
233 }
234
235 async fn fetch_my_trades(
236 &self,
237 symbol: Option<&str>,
238 since: Option<i64>,
239 limit: Option<u32>,
240 ) -> Result<Vec<Trade>> {
241 let symbol_str = symbol.ok_or_else(|| {
242 ccxt_core::Error::invalid_request("Symbol is required for fetch_my_trades on Bitget")
243 })?;
244 Bitget::fetch_my_trades(self, symbol_str, since, limit).await
245 }
246
247 async fn market(&self, symbol: &str) -> Result<Market> {
250 let cache = self.base().market_cache.read().await;
251
252 if !cache.loaded {
253 return Err(ccxt_core::Error::exchange(
254 "-1",
255 "Markets not loaded. Call load_markets() first.",
256 ));
257 }
258
259 cache
260 .markets
261 .get(symbol)
262 .cloned()
263 .ok_or_else(|| ccxt_core::Error::bad_symbol(format!("Market {} not found", symbol)))
264 }
265
266 async fn markets(&self) -> HashMap<String, Market> {
267 let cache = self.base().market_cache.read().await;
268 cache.markets.clone()
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use ccxt_core::ExchangeConfig;
276
277 #[test]
278 fn test_bitget_exchange_trait_metadata() {
279 let config = ExchangeConfig::default();
280 let bitget = Bitget::new(config).unwrap();
281
282 let exchange: &dyn Exchange = &bitget;
284
285 assert_eq!(exchange.id(), "bitget");
286 assert_eq!(exchange.name(), "Bitget");
287 assert_eq!(exchange.version(), "v2");
288 assert!(!exchange.certified());
289 assert!(exchange.has_websocket());
290 }
291
292 #[test]
293 fn test_bitget_exchange_trait_capabilities() {
294 let config = ExchangeConfig::default();
295 let bitget = Bitget::new(config).unwrap();
296
297 let exchange: &dyn Exchange = &bitget;
298 let caps = exchange.capabilities();
299
300 assert!(caps.fetch_markets);
302 assert!(caps.fetch_ticker);
303 assert!(caps.fetch_tickers);
304 assert!(caps.fetch_order_book);
305 assert!(caps.fetch_trades);
306 assert!(caps.fetch_ohlcv);
307
308 assert!(caps.create_order);
310 assert!(caps.cancel_order);
311 assert!(caps.fetch_order);
312 assert!(caps.fetch_open_orders);
313 assert!(caps.fetch_closed_orders);
314 assert!(caps.fetch_balance);
315 assert!(caps.fetch_my_trades);
316
317 assert!(caps.websocket);
319 assert!(caps.watch_ticker);
320 assert!(caps.watch_order_book);
321 assert!(caps.watch_trades);
322
323 assert!(!caps.edit_order);
325 assert!(!caps.cancel_all_orders);
326 assert!(!caps.fetch_currencies);
327 }
328
329 #[test]
330 fn test_bitget_exchange_trait_timeframes() {
331 let config = ExchangeConfig::default();
332 let bitget = Bitget::new(config).unwrap();
333
334 let exchange: &dyn Exchange = &bitget;
335 let timeframes = exchange.timeframes();
336
337 assert!(!timeframes.is_empty());
338 assert!(timeframes.contains(&Timeframe::M1));
339 assert!(timeframes.contains(&Timeframe::M5));
340 assert!(timeframes.contains(&Timeframe::M15));
341 assert!(timeframes.contains(&Timeframe::H1));
342 assert!(timeframes.contains(&Timeframe::H4));
343 assert!(timeframes.contains(&Timeframe::D1));
344 assert!(timeframes.contains(&Timeframe::W1));
345 assert!(timeframes.contains(&Timeframe::Mon1));
346 }
347
348 #[test]
349 fn test_bitget_exchange_trait_rate_limit() {
350 let config = ExchangeConfig::default();
351 let bitget = Bitget::new(config).unwrap();
352
353 let exchange: &dyn Exchange = &bitget;
354 assert!((exchange.rate_limit() - 20.0).abs() < f64::EPSILON);
355 }
356
357 #[test]
358 fn test_bitget_exchange_trait_object_safety() {
359 let config = ExchangeConfig::default();
360 let bitget = Bitget::new(config).unwrap();
361
362 let exchange: Box<dyn Exchange> = Box::new(bitget);
364
365 assert_eq!(exchange.id(), "bitget");
366 assert_eq!(exchange.name(), "Bitget");
367 assert!((exchange.rate_limit() - 20.0).abs() < f64::EPSILON);
368 }
369
370 #[test]
371 fn test_bitget_exchange_trait_polymorphic_usage() {
372 let config = ExchangeConfig::default();
373 let bitget = Bitget::new(config).unwrap();
374
375 fn check_exchange_metadata(exchange: &dyn Exchange) -> (&str, &str, bool) {
377 (exchange.id(), exchange.name(), exchange.has_websocket())
378 }
379
380 let (id, name, has_ws) = check_exchange_metadata(&bitget);
381 assert_eq!(id, "bitget");
382 assert_eq!(name, "Bitget");
383 assert!(has_ws);
384 }
385
386 #[test]
387 fn test_bitget_capabilities_has_method() {
388 let config = ExchangeConfig::default();
389 let bitget = Bitget::new(config).unwrap();
390
391 let exchange: &dyn Exchange = &bitget;
392 let caps = exchange.capabilities();
393
394 assert!(caps.has("fetchMarkets"));
396 assert!(caps.has("fetchTicker"));
397 assert!(caps.has("fetchTickers"));
398 assert!(caps.has("fetchOrderBook"));
399 assert!(caps.has("fetchTrades"));
400 assert!(caps.has("fetchOHLCV"));
401 assert!(caps.has("createOrder"));
402 assert!(caps.has("cancelOrder"));
403 assert!(caps.has("fetchOrder"));
404 assert!(caps.has("fetchOpenOrders"));
405 assert!(caps.has("fetchClosedOrders"));
406 assert!(caps.has("fetchBalance"));
407 assert!(caps.has("fetchMyTrades"));
408 assert!(caps.has("websocket"));
409
410 assert!(!caps.has("editOrder"));
412 assert!(!caps.has("cancelAllOrders"));
413 assert!(!caps.has("fetchCurrencies"));
414 assert!(!caps.has("unknownCapability"));
415 }
416}