1use async_trait::async_trait;
6use ccxt_core::{
7 Result,
8 exchange::{Capability, 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;
16use std::sync::Arc;
17
18use super::Bitget;
19
20#[async_trait]
21impl Exchange for Bitget {
22 fn id(&self) -> &'static str {
25 "bitget"
26 }
27
28 fn name(&self) -> &'static str {
29 "Bitget"
30 }
31
32 fn version(&self) -> &'static str {
33 "v2"
34 }
35
36 fn certified(&self) -> bool {
37 false
38 }
39
40 fn has_websocket(&self) -> bool {
41 true
42 }
43
44 fn capabilities(&self) -> ExchangeCapabilities {
45 ExchangeCapabilities::builder()
51 .market_data()
52 .trading()
53 .account()
54 .without_capability(Capability::FetchCurrencies)
56 .without_capability(Capability::FetchStatus)
57 .without_capability(Capability::FetchTime)
58 .without_capability(Capability::CancelAllOrders)
60 .without_capability(Capability::EditOrder)
61 .without_capability(Capability::FetchOrders)
62 .without_capability(Capability::FetchCanceledOrders)
63 .without_capability(Capability::FetchDeposits)
65 .without_capability(Capability::FetchWithdrawals)
66 .without_capability(Capability::FetchTransactions)
67 .without_capability(Capability::FetchLedger)
68 .capability(Capability::Websocket)
70 .capability(Capability::WatchTicker)
71 .capability(Capability::WatchOrderBook)
72 .capability(Capability::WatchTrades)
73 .build()
74 }
75
76 fn timeframes(&self) -> Vec<Timeframe> {
77 vec![
78 Timeframe::M1,
79 Timeframe::M5,
80 Timeframe::M15,
81 Timeframe::M30,
82 Timeframe::H1,
83 Timeframe::H4,
84 Timeframe::H6,
85 Timeframe::H12,
86 Timeframe::D1,
87 Timeframe::D3,
88 Timeframe::W1,
89 Timeframe::Mon1,
90 ]
91 }
92
93 fn rate_limit(&self) -> u32 {
94 20
95 }
96
97 async fn fetch_markets(&self) -> Result<Vec<Market>> {
100 let arc_markets = Bitget::fetch_markets(self).await?;
101 Ok(arc_markets.values().map(|v| (**v).clone()).collect())
102 }
103
104 async fn load_markets(&self, reload: bool) -> Result<Arc<HashMap<String, Arc<Market>>>> {
105 Bitget::load_markets(self, reload).await
106 }
107
108 async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
109 Bitget::fetch_ticker(self, symbol).await
110 }
111
112 async fn fetch_tickers(&self, symbols: Option<&[String]>) -> Result<Vec<Ticker>> {
113 let symbols_vec = symbols.map(<[String]>::to_vec);
114 Bitget::fetch_tickers(self, symbols_vec).await
115 }
116
117 async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
118 Bitget::fetch_order_book(self, symbol, limit).await
119 }
120
121 async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
122 Bitget::fetch_trades(self, symbol, limit).await
123 }
124
125 async fn fetch_ohlcv(
126 &self,
127 symbol: &str,
128 timeframe: Timeframe,
129 since: Option<i64>,
130 limit: Option<u32>,
131 ) -> Result<Vec<Ohlcv>> {
132 let timeframe_str = timeframe.to_string();
133 #[allow(deprecated)]
134 let ohlcv_data = Bitget::fetch_ohlcv(self, symbol, &timeframe_str, since, limit).await?;
135
136 ohlcv_data
138 .into_iter()
139 .map(|o| -> ccxt_core::Result<Ohlcv> {
140 Ok(Ohlcv {
141 timestamp: o.timestamp,
142 open: Price(Decimal::try_from(o.open).map_err(|e| {
143 ccxt_core::Error::from(ccxt_core::ParseError::invalid_value(
144 "OHLCV open",
145 format!("{e}"),
146 ))
147 })?),
148 high: Price(Decimal::try_from(o.high).map_err(|e| {
149 ccxt_core::Error::from(ccxt_core::ParseError::invalid_value(
150 "OHLCV high",
151 format!("{e}"),
152 ))
153 })?),
154 low: Price(Decimal::try_from(o.low).map_err(|e| {
155 ccxt_core::Error::from(ccxt_core::ParseError::invalid_value(
156 "OHLCV low",
157 format!("{e}"),
158 ))
159 })?),
160 close: Price(Decimal::try_from(o.close).map_err(|e| {
161 ccxt_core::Error::from(ccxt_core::ParseError::invalid_value(
162 "OHLCV close",
163 format!("{e}"),
164 ))
165 })?),
166 volume: Amount(Decimal::try_from(o.volume).map_err(|e| {
167 ccxt_core::Error::from(ccxt_core::ParseError::invalid_value(
168 "OHLCV volume",
169 format!("{e}"),
170 ))
171 })?),
172 })
173 })
174 .collect::<ccxt_core::Result<Vec<Ohlcv>>>()
175 .map_err(|e| e.context("Failed to convert Bitget OHLCV data"))
176 }
177
178 async fn create_order(
181 &self,
182 symbol: &str,
183 order_type: OrderType,
184 side: OrderSide,
185 amount: Amount,
186 price: Option<Price>,
187 ) -> Result<Order> {
188 #[allow(deprecated)]
190 Bitget::create_order(self, symbol, order_type, side, amount, price).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<Arc<Market>> {
250 let cache = self.base().market_cache.read().await;
251
252 if !cache.is_loaded() {
253 return Err(ccxt_core::Error::exchange(
254 "-1",
255 "Markets not loaded. Call load_markets() first.",
256 ));
257 }
258
259 cache
260 .get_market(symbol)
261 .ok_or_else(|| ccxt_core::Error::bad_symbol(format!("Market {} not found", symbol)))
262 }
263
264 async fn markets(&self) -> Arc<HashMap<String, Arc<Market>>> {
265 let cache = self.base().market_cache.read().await;
266 cache.markets()
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 #![allow(clippy::disallowed_methods)]
273 use super::*;
274 use ccxt_core::ExchangeConfig;
275
276 #[test]
277 fn test_bitget_exchange_trait_metadata() {
278 let config = ExchangeConfig::default();
279 let bitget = Bitget::new(config).unwrap();
280
281 let exchange: &dyn Exchange = &bitget;
283
284 assert_eq!(exchange.id(), "bitget");
285 assert_eq!(exchange.name(), "Bitget");
286 assert_eq!(exchange.version(), "v2");
287 assert!(!exchange.certified());
288 assert!(exchange.has_websocket());
289 }
290
291 #[test]
292 fn test_bitget_exchange_trait_capabilities() {
293 let config = ExchangeConfig::default();
294 let bitget = Bitget::new(config).unwrap();
295
296 let exchange: &dyn Exchange = &bitget;
297 let caps = exchange.capabilities();
298
299 assert!(caps.fetch_markets());
301 assert!(caps.fetch_ticker());
302 assert!(caps.fetch_tickers());
303 assert!(caps.fetch_order_book());
304 assert!(caps.fetch_trades());
305 assert!(caps.fetch_ohlcv());
306
307 assert!(caps.create_order());
309 assert!(caps.cancel_order());
310 assert!(caps.fetch_order());
311 assert!(caps.fetch_open_orders());
312 assert!(caps.fetch_closed_orders());
313 assert!(caps.fetch_balance());
314 assert!(caps.fetch_my_trades());
315
316 assert!(caps.websocket());
318 assert!(caps.watch_ticker());
319 assert!(caps.watch_order_book());
320 assert!(caps.watch_trades());
321
322 assert!(!caps.edit_order());
324 assert!(!caps.cancel_all_orders());
325 assert!(!caps.fetch_currencies());
326 }
327
328 #[test]
329 fn test_bitget_exchange_trait_timeframes() {
330 let config = ExchangeConfig::default();
331 let bitget = Bitget::new(config).unwrap();
332
333 let exchange: &dyn Exchange = &bitget;
334 let timeframes = exchange.timeframes();
335
336 assert!(!timeframes.is_empty());
337 assert!(timeframes.contains(&Timeframe::M1));
338 assert!(timeframes.contains(&Timeframe::M5));
339 assert!(timeframes.contains(&Timeframe::M15));
340 assert!(timeframes.contains(&Timeframe::H1));
341 assert!(timeframes.contains(&Timeframe::H4));
342 assert!(timeframes.contains(&Timeframe::D1));
343 assert!(timeframes.contains(&Timeframe::W1));
344 assert!(timeframes.contains(&Timeframe::Mon1));
345 }
346
347 #[test]
348 fn test_bitget_exchange_trait_rate_limit() {
349 let config = ExchangeConfig::default();
350 let bitget = Bitget::new(config).unwrap();
351
352 let exchange: &dyn Exchange = &bitget;
353 assert_eq!(exchange.rate_limit(), 20);
354 }
355
356 #[test]
357 fn test_bitget_exchange_trait_object_safety() {
358 let config = ExchangeConfig::default();
359 let bitget = Bitget::new(config).unwrap();
360
361 let exchange: Box<dyn Exchange> = Box::new(bitget);
363
364 assert_eq!(exchange.id(), "bitget");
365 assert_eq!(exchange.name(), "Bitget");
366 assert_eq!(exchange.rate_limit(), 20);
367 }
368
369 #[test]
370 fn test_bitget_exchange_trait_polymorphic_usage() {
371 let config = ExchangeConfig::default();
372 let bitget = Bitget::new(config).unwrap();
373
374 fn check_exchange_metadata(exchange: &dyn Exchange) -> (&str, &str, bool) {
376 (exchange.id(), exchange.name(), exchange.has_websocket())
377 }
378
379 let (id, name, has_ws) = check_exchange_metadata(&bitget);
380 assert_eq!(id, "bitget");
381 assert_eq!(name, "Bitget");
382 assert!(has_ws);
383 }
384
385 #[test]
386 fn test_bitget_capabilities_has_method() {
387 let config = ExchangeConfig::default();
388 let bitget = Bitget::new(config).unwrap();
389
390 let exchange: &dyn Exchange = &bitget;
391 let caps = exchange.capabilities();
392
393 assert!(caps.has("fetchMarkets"));
395 assert!(caps.has("fetchTicker"));
396 assert!(caps.has("fetchTickers"));
397 assert!(caps.has("fetchOrderBook"));
398 assert!(caps.has("fetchTrades"));
399 assert!(caps.has("fetchOHLCV"));
400 assert!(caps.has("createOrder"));
401 assert!(caps.has("cancelOrder"));
402 assert!(caps.has("fetchOrder"));
403 assert!(caps.has("fetchOpenOrders"));
404 assert!(caps.has("fetchClosedOrders"));
405 assert!(caps.has("fetchBalance"));
406 assert!(caps.has("fetchMyTrades"));
407 assert!(caps.has("websocket"));
408
409 assert!(!caps.has("editOrder"));
411 assert!(!caps.has("cancelAllOrders"));
412 assert!(!caps.has("fetchCurrencies"));
413 assert!(!caps.has("unknownCapability"));
414 }
415}