ccxt_exchanges/bitget/rest/
market_data.rs

1//! Market data endpoints for Bitget REST API.
2
3use super::super::{Bitget, parser};
4use ccxt_core::{
5    Error, ParseError, Result,
6    types::{Market, OHLCV, OhlcvRequest, OrderBook, Ticker, Trade},
7};
8use std::{collections::HashMap, sync::Arc};
9use tracing::{info, warn};
10
11impl Bitget {
12    /// Fetch all trading markets.
13    pub async fn fetch_markets(&self) -> Result<Arc<HashMap<String, Arc<Market>>>> {
14        let path = self.build_api_path("/public/symbols");
15        let response = self.public_request("GET", &path, None).await?;
16
17        let data = response
18            .get("data")
19            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
20
21        let symbols = data.as_array().ok_or_else(|| {
22            Error::from(ParseError::invalid_format(
23                "data",
24                "Expected array of symbols",
25            ))
26        })?;
27
28        let mut markets = Vec::new();
29        for symbol in symbols {
30            match parser::parse_market(symbol) {
31                Ok(market) => markets.push(market),
32                Err(e) => {
33                    warn!(error = %e, "Failed to parse market");
34                }
35            }
36        }
37
38        let result = self.base().set_markets(markets, None).await?;
39        info!("Loaded {} markets for Bitget", result.len());
40        Ok(result)
41    }
42
43    /// Load and cache market data.
44    pub async fn load_markets(&self, reload: bool) -> Result<Arc<HashMap<String, Arc<Market>>>> {
45        let _loading_guard = self.base().market_loading_lock.lock().await;
46
47        {
48            let cache = self.base().market_cache.read().await;
49            if cache.is_loaded() && !reload {
50                return Ok(cache.markets());
51            }
52        }
53
54        info!("Loading markets for Bitget (reload: {})", reload);
55        let _markets = self.fetch_markets().await?;
56
57        let cache = self.base().market_cache.read().await;
58        Ok(cache.markets())
59    }
60
61    /// Fetch ticker for a single trading pair.
62    pub async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
63        let market = self.base().market(symbol).await?;
64
65        let path = self.build_api_path("/market/tickers");
66        let mut params = HashMap::new();
67        params.insert("symbol".to_string(), market.id.clone());
68
69        let response = self.public_request("GET", &path, Some(&params)).await?;
70
71        let data = response
72            .get("data")
73            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
74
75        let tickers = data.as_array().ok_or_else(|| {
76            Error::from(ParseError::invalid_format(
77                "data",
78                "Expected array of tickers",
79            ))
80        })?;
81
82        if tickers.is_empty() {
83            return Err(Error::bad_symbol(format!("No ticker data for {}", symbol)));
84        }
85
86        parser::parse_ticker(&tickers[0], Some(&market))
87    }
88
89    /// Fetch tickers for multiple trading pairs.
90    pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
91        let cache = self.base().market_cache.read().await;
92        if !cache.is_loaded() {
93            drop(cache);
94            return Err(Error::exchange(
95                "-1",
96                "Markets not loaded. Call load_markets() first.",
97            ));
98        }
99        // Build a snapshot of markets by ID for efficient lookup
100        let markets_snapshot: std::collections::HashMap<String, Arc<Market>> = cache
101            .iter_markets()
102            .map(|(_, m)| (m.id.clone(), m))
103            .collect();
104        drop(cache);
105
106        let path = self.build_api_path("/market/tickers");
107        let response = self.public_request("GET", &path, None).await?;
108
109        let data = response
110            .get("data")
111            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
112
113        let tickers_array = data.as_array().ok_or_else(|| {
114            Error::from(ParseError::invalid_format(
115                "data",
116                "Expected array of tickers",
117            ))
118        })?;
119
120        let mut tickers = Vec::new();
121        for ticker_data in tickers_array {
122            if let Some(bitget_symbol) = ticker_data["symbol"].as_str() {
123                if let Some(market) = markets_snapshot.get(bitget_symbol) {
124                    match parser::parse_ticker(ticker_data, Some(market)) {
125                        Ok(ticker) => {
126                            if let Some(ref syms) = symbols {
127                                if syms.contains(&ticker.symbol) {
128                                    tickers.push(ticker);
129                                }
130                            } else {
131                                tickers.push(ticker);
132                            }
133                        }
134                        Err(e) => {
135                            warn!(
136                                error = %e,
137                                symbol = %bitget_symbol,
138                                "Failed to parse ticker"
139                            );
140                        }
141                    }
142                }
143            }
144        }
145
146        Ok(tickers)
147    }
148
149    /// Fetch order book for a trading pair.
150    pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
151        let market = self.base().market(symbol).await?;
152
153        let path = self.build_api_path("/market/orderbook");
154        let mut params = HashMap::new();
155        params.insert("symbol".to_string(), market.id.clone());
156
157        let actual_limit = limit.map_or(100, |l| l.min(100));
158        params.insert("limit".to_string(), actual_limit.to_string());
159
160        let response = self.public_request("GET", &path, Some(&params)).await?;
161
162        let data = response
163            .get("data")
164            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
165
166        parser::parse_orderbook(data, market.symbol.clone())
167    }
168
169    /// Fetch recent public trades.
170    pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
171        let market = self.base().market(symbol).await?;
172
173        let path = self.build_api_path("/market/fills");
174        let mut params = HashMap::new();
175        params.insert("symbol".to_string(), market.id.clone());
176
177        let actual_limit = limit.map_or(100, |l| l.min(500));
178        params.insert("limit".to_string(), actual_limit.to_string());
179
180        let response = self.public_request("GET", &path, Some(&params)).await?;
181
182        let data = response
183            .get("data")
184            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
185
186        let trades_array = data.as_array().ok_or_else(|| {
187            Error::from(ParseError::invalid_format(
188                "data",
189                "Expected array of trades",
190            ))
191        })?;
192
193        let mut trades = Vec::new();
194        for trade_data in trades_array {
195            match parser::parse_trade(trade_data, Some(&market)) {
196                Ok(trade) => trades.push(trade),
197                Err(e) => {
198                    warn!(error = %e, "Failed to parse trade");
199                }
200            }
201        }
202
203        trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
204        Ok(trades)
205    }
206
207    /// Fetch OHLCV (candlestick) data.
208    pub async fn fetch_ohlcv_v2(&self, request: OhlcvRequest) -> Result<Vec<OHLCV>> {
209        let market = self.base().market(&request.symbol).await?;
210
211        let timeframes = self.timeframes();
212        let bitget_timeframe = timeframes.get(&request.timeframe).ok_or_else(|| {
213            Error::invalid_request(format!("Unsupported timeframe: {}", request.timeframe))
214        })?;
215
216        let path = self.build_api_path("/market/candles");
217        let mut params = HashMap::new();
218        params.insert("symbol".to_string(), market.id.clone());
219        params.insert("granularity".to_string(), bitget_timeframe.clone());
220
221        let actual_limit = request.limit.map_or(100, |l| l.min(1000));
222        params.insert("limit".to_string(), actual_limit.to_string());
223
224        if let Some(start_time) = request.since {
225            params.insert("startTime".to_string(), start_time.to_string());
226        }
227
228        if let Some(end_time) = request.until {
229            params.insert("endTime".to_string(), end_time.to_string());
230        }
231
232        let response = self.public_request("GET", &path, Some(&params)).await?;
233
234        let data = response
235            .get("data")
236            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
237
238        let candles_array = data.as_array().ok_or_else(|| {
239            Error::from(ParseError::invalid_format(
240                "data",
241                "Expected array of candles",
242            ))
243        })?;
244
245        let mut ohlcv = Vec::new();
246        for candle_data in candles_array {
247            match parser::parse_ohlcv(candle_data) {
248                Ok(candle) => ohlcv.push(candle),
249                Err(e) => {
250                    warn!(error = %e, "Failed to parse OHLCV");
251                }
252            }
253        }
254
255        ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
256        Ok(ohlcv)
257    }
258
259    /// Fetch OHLCV (candlestick) data (deprecated).
260    #[deprecated(
261        since = "0.2.0",
262        note = "Use fetch_ohlcv_v2 with OhlcvRequest::builder() instead"
263    )]
264    pub async fn fetch_ohlcv(
265        &self,
266        symbol: &str,
267        timeframe: &str,
268        since: Option<i64>,
269        limit: Option<u32>,
270    ) -> Result<Vec<OHLCV>> {
271        let market = self.base().market(symbol).await?;
272
273        let timeframes = self.timeframes();
274        let bitget_timeframe = timeframes.get(timeframe).ok_or_else(|| {
275            Error::invalid_request(format!("Unsupported timeframe: {}", timeframe))
276        })?;
277
278        let path = self.build_api_path("/market/candles");
279        let mut params = HashMap::new();
280        params.insert("symbol".to_string(), market.id.clone());
281        params.insert("granularity".to_string(), bitget_timeframe.clone());
282
283        let actual_limit = limit.map_or(100, |l| l.min(1000));
284        params.insert("limit".to_string(), actual_limit.to_string());
285
286        if let Some(start_time) = since {
287            params.insert("startTime".to_string(), start_time.to_string());
288        }
289
290        let response = self.public_request("GET", &path, Some(&params)).await?;
291
292        let data = response
293            .get("data")
294            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
295
296        let candles_array = data.as_array().ok_or_else(|| {
297            Error::from(ParseError::invalid_format(
298                "data",
299                "Expected array of candles",
300            ))
301        })?;
302
303        let mut ohlcv = Vec::new();
304        for candle_data in candles_array {
305            match parser::parse_ohlcv(candle_data) {
306                Ok(candle) => ohlcv.push(candle),
307                Err(e) => {
308                    warn!(error = %e, "Failed to parse OHLCV");
309                }
310            }
311        }
312
313        ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
314        Ok(ohlcv)
315    }
316}