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 rust_decimal::prelude::ToPrimitive;
16use std::collections::HashMap;
17
18use super::Bitget;
19
20#[async_trait]
21impl Exchange for Bitget {
22 fn id(&self) -> &str {
25 "bitget"
26 }
27
28 fn name(&self) -> &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.into_values().map(|v| (*v).clone()).collect())
102 }
103
104 async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>> {
105 let arc_markets = Bitget::load_markets(self, reload).await?;
106 Ok(arc_markets
107 .into_iter()
108 .map(|(k, v)| (k, (*v).clone()))
109 .collect())
110 }
111
112 async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
113 Bitget::fetch_ticker(self, symbol).await
114 }
115
116 async fn fetch_tickers(&self, symbols: Option<&[String]>) -> Result<Vec<Ticker>> {
117 let symbols_vec = symbols.map(|s| s.to_vec());
118 Bitget::fetch_tickers(self, symbols_vec).await
119 }
120
121 async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
122 Bitget::fetch_order_book(self, symbol, limit).await
123 }
124
125 async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
126 Bitget::fetch_trades(self, symbol, limit).await
127 }
128
129 async fn fetch_ohlcv(
130 &self,
131 symbol: &str,
132 timeframe: Timeframe,
133 since: Option<i64>,
134 limit: Option<u32>,
135 ) -> Result<Vec<Ohlcv>> {
136 let timeframe_str = timeframe.to_string();
137 let ohlcv_data = Bitget::fetch_ohlcv(self, symbol, &timeframe_str, since, limit).await?;
138
139 Ok(ohlcv_data
141 .into_iter()
142 .map(|o| Ohlcv {
143 timestamp: o.timestamp,
144 open: Price::from(Decimal::try_from(o.open).unwrap_or_default()),
145 high: Price::from(Decimal::try_from(o.high).unwrap_or_default()),
146 low: Price::from(Decimal::try_from(o.low).unwrap_or_default()),
147 close: Price::from(Decimal::try_from(o.close).unwrap_or_default()),
148 volume: Amount::from(Decimal::try_from(o.volume).unwrap_or_default()),
149 })
150 .collect())
151 }
152
153 async fn create_order(
156 &self,
157 symbol: &str,
158 order_type: OrderType,
159 side: OrderSide,
160 amount: Decimal,
161 price: Option<Decimal>,
162 ) -> Result<Order> {
163 let amount_f64 = amount
164 .to_f64()
165 .ok_or_else(|| ccxt_core::Error::invalid_request("Failed to convert amount to f64"))?;
166 let price_f64 = match price {
167 Some(p) => Some(p.to_f64().ok_or_else(|| {
168 ccxt_core::Error::invalid_request("Failed to convert price to f64")
169 })?),
170 None => None,
171 };
172
173 Bitget::create_order(self, symbol, order_type, side, amount_f64, price_f64).await
174 }
175
176 async fn cancel_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
177 let symbol_str = symbol.ok_or_else(|| {
178 ccxt_core::Error::invalid_request("Symbol is required for cancel_order on Bitget")
179 })?;
180 Bitget::cancel_order(self, id, symbol_str).await
181 }
182
183 async fn cancel_all_orders(&self, _symbol: Option<&str>) -> Result<Vec<Order>> {
184 Err(ccxt_core::Error::not_implemented("cancel_all_orders"))
185 }
186
187 async fn fetch_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
188 let symbol_str = symbol.ok_or_else(|| {
189 ccxt_core::Error::invalid_request("Symbol is required for fetch_order on Bitget")
190 })?;
191 Bitget::fetch_order(self, id, symbol_str).await
192 }
193
194 async fn fetch_open_orders(
195 &self,
196 symbol: Option<&str>,
197 since: Option<i64>,
198 limit: Option<u32>,
199 ) -> Result<Vec<Order>> {
200 Bitget::fetch_open_orders(self, symbol, since, limit).await
201 }
202
203 async fn fetch_closed_orders(
204 &self,
205 symbol: Option<&str>,
206 since: Option<i64>,
207 limit: Option<u32>,
208 ) -> Result<Vec<Order>> {
209 Bitget::fetch_closed_orders(self, symbol, since, limit).await
210 }
211
212 async fn fetch_balance(&self) -> Result<Balance> {
215 Bitget::fetch_balance(self).await
216 }
217
218 async fn fetch_my_trades(
219 &self,
220 symbol: Option<&str>,
221 since: Option<i64>,
222 limit: Option<u32>,
223 ) -> Result<Vec<Trade>> {
224 let symbol_str = symbol.ok_or_else(|| {
225 ccxt_core::Error::invalid_request("Symbol is required for fetch_my_trades on Bitget")
226 })?;
227 Bitget::fetch_my_trades(self, symbol_str, since, limit).await
228 }
229
230 async fn market(&self, symbol: &str) -> Result<Market> {
233 let cache = self.base().market_cache.read().await;
234
235 if !cache.loaded {
236 return Err(ccxt_core::Error::exchange(
237 "-1",
238 "Markets not loaded. Call load_markets() first.",
239 ));
240 }
241
242 cache
243 .markets
244 .get(symbol)
245 .map(|v| (**v).clone())
246 .ok_or_else(|| ccxt_core::Error::bad_symbol(format!("Market {} not found", symbol)))
247 }
248
249 async fn markets(&self) -> HashMap<String, Market> {
250 let cache = self.base().market_cache.read().await;
251 cache
252 .markets
253 .iter()
254 .map(|(k, v)| (k.clone(), (**v).clone()))
255 .collect()
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use ccxt_core::ExchangeConfig;
263
264 #[test]
265 fn test_bitget_exchange_trait_metadata() {
266 let config = ExchangeConfig::default();
267 let bitget = Bitget::new(config).unwrap();
268
269 let exchange: &dyn Exchange = &bitget;
271
272 assert_eq!(exchange.id(), "bitget");
273 assert_eq!(exchange.name(), "Bitget");
274 assert_eq!(exchange.version(), "v2");
275 assert!(!exchange.certified());
276 assert!(exchange.has_websocket());
277 }
278
279 #[test]
280 fn test_bitget_exchange_trait_capabilities() {
281 let config = ExchangeConfig::default();
282 let bitget = Bitget::new(config).unwrap();
283
284 let exchange: &dyn Exchange = &bitget;
285 let caps = exchange.capabilities();
286
287 assert!(caps.fetch_markets());
289 assert!(caps.fetch_ticker());
290 assert!(caps.fetch_tickers());
291 assert!(caps.fetch_order_book());
292 assert!(caps.fetch_trades());
293 assert!(caps.fetch_ohlcv());
294
295 assert!(caps.create_order());
297 assert!(caps.cancel_order());
298 assert!(caps.fetch_order());
299 assert!(caps.fetch_open_orders());
300 assert!(caps.fetch_closed_orders());
301 assert!(caps.fetch_balance());
302 assert!(caps.fetch_my_trades());
303
304 assert!(caps.websocket());
306 assert!(caps.watch_ticker());
307 assert!(caps.watch_order_book());
308 assert!(caps.watch_trades());
309
310 assert!(!caps.edit_order());
312 assert!(!caps.cancel_all_orders());
313 assert!(!caps.fetch_currencies());
314 }
315
316 #[test]
317 fn test_bitget_exchange_trait_timeframes() {
318 let config = ExchangeConfig::default();
319 let bitget = Bitget::new(config).unwrap();
320
321 let exchange: &dyn Exchange = &bitget;
322 let timeframes = exchange.timeframes();
323
324 assert!(!timeframes.is_empty());
325 assert!(timeframes.contains(&Timeframe::M1));
326 assert!(timeframes.contains(&Timeframe::M5));
327 assert!(timeframes.contains(&Timeframe::M15));
328 assert!(timeframes.contains(&Timeframe::H1));
329 assert!(timeframes.contains(&Timeframe::H4));
330 assert!(timeframes.contains(&Timeframe::D1));
331 assert!(timeframes.contains(&Timeframe::W1));
332 assert!(timeframes.contains(&Timeframe::Mon1));
333 }
334
335 #[test]
336 fn test_bitget_exchange_trait_rate_limit() {
337 let config = ExchangeConfig::default();
338 let bitget = Bitget::new(config).unwrap();
339
340 let exchange: &dyn Exchange = &bitget;
341 assert_eq!(exchange.rate_limit(), 20);
342 }
343
344 #[test]
345 fn test_bitget_exchange_trait_object_safety() {
346 let config = ExchangeConfig::default();
347 let bitget = Bitget::new(config).unwrap();
348
349 let exchange: Box<dyn Exchange> = Box::new(bitget);
351
352 assert_eq!(exchange.id(), "bitget");
353 assert_eq!(exchange.name(), "Bitget");
354 assert_eq!(exchange.rate_limit(), 20);
355 }
356
357 #[test]
358 fn test_bitget_exchange_trait_polymorphic_usage() {
359 let config = ExchangeConfig::default();
360 let bitget = Bitget::new(config).unwrap();
361
362 fn check_exchange_metadata(exchange: &dyn Exchange) -> (&str, &str, bool) {
364 (exchange.id(), exchange.name(), exchange.has_websocket())
365 }
366
367 let (id, name, has_ws) = check_exchange_metadata(&bitget);
368 assert_eq!(id, "bitget");
369 assert_eq!(name, "Bitget");
370 assert!(has_ws);
371 }
372
373 #[test]
374 fn test_bitget_capabilities_has_method() {
375 let config = ExchangeConfig::default();
376 let bitget = Bitget::new(config).unwrap();
377
378 let exchange: &dyn Exchange = &bitget;
379 let caps = exchange.capabilities();
380
381 assert!(caps.has("fetchMarkets"));
383 assert!(caps.has("fetchTicker"));
384 assert!(caps.has("fetchTickers"));
385 assert!(caps.has("fetchOrderBook"));
386 assert!(caps.has("fetchTrades"));
387 assert!(caps.has("fetchOHLCV"));
388 assert!(caps.has("createOrder"));
389 assert!(caps.has("cancelOrder"));
390 assert!(caps.has("fetchOrder"));
391 assert!(caps.has("fetchOpenOrders"));
392 assert!(caps.has("fetchClosedOrders"));
393 assert!(caps.has("fetchBalance"));
394 assert!(caps.has("fetchMyTrades"));
395 assert!(caps.has("websocket"));
396
397 assert!(!caps.has("editOrder"));
399 assert!(!caps.has("cancelAllOrders"));
400 assert!(!caps.has("fetchCurrencies"));
401 assert!(!caps.has("unknownCapability"));
402 }
403}