ccxt_core/traits/
market_data.rs

1//! MarketData trait definition.
2//!
3//! The `MarketData` trait provides methods for fetching public market data
4//! that doesn't require authentication. This includes markets, tickers,
5//! order books, trades, and OHLCV candlestick data.
6//!
7//! # Timestamp Format
8//!
9//! All timestamp parameters and return values in this trait use the standardized format:
10//! - **Type**: `i64`
11//! - **Unit**: Milliseconds since Unix epoch (January 1, 1970, 00:00:00 UTC)
12//! - **Range**: Supports dates from 1970 to approximately year 294,276
13//!
14//! # Object Safety
15//!
16//! This trait is designed to be object-safe, allowing for dynamic dispatch via
17//! trait objects (`dyn MarketData`). All async methods return boxed futures
18//! to maintain object safety.
19//!
20//! # Example
21//!
22//! ```rust,ignore
23//! use ccxt_core::traits::MarketData;
24//! use ccxt_core::types::{Timeframe, params::OhlcvParams};
25//!
26//! async fn fetch_data(exchange: &dyn MarketData) -> Result<(), ccxt_core::Error> {
27//!     // Fetch markets
28//!     let markets = exchange.fetch_markets().await?;
29//!     
30//!     // Fetch ticker
31//!     let ticker = exchange.fetch_ticker("BTC/USDT").await?;
32//!     
33//!     // Fetch OHLCV with i64 timestamp parameters
34//!     let since: i64 = chrono::Utc::now().timestamp_millis() - 3600000; // 1 hour ago
35//!     let candles = exchange.fetch_ohlcv_with_params(
36//!         "BTC/USDT",
37//!         OhlcvParams::new(Timeframe::H1).since(since).limit(100)
38//!     ).await?;
39//!     
40//!     Ok(())
41//! }
42//! ```
43
44use async_trait::async_trait;
45use std::collections::HashMap;
46
47use crate::error::Result;
48use crate::traits::PublicExchange;
49use crate::types::{
50    Market, Ohlcv, OrderBook, Ticker, Timeframe, Trade,
51    params::{OhlcvParams, OrderBookParams},
52};
53
54/// Trait for fetching public market data.
55///
56/// This trait provides methods for accessing public market information
57/// that doesn't require authentication. All methods are async and return
58/// `Result<T>` for proper error handling.
59///
60/// # Timestamp Format
61///
62/// All timestamp parameters and fields in returned data structures use:
63/// - **Type**: `i64`
64/// - **Unit**: Milliseconds since Unix epoch (January 1, 1970, 00:00:00 UTC)
65/// - **Example**: `1609459200000` represents January 1, 2021, 00:00:00 UTC
66///
67/// # Supertrait
68///
69/// Requires `PublicExchange` as a supertrait to access exchange metadata
70/// and capabilities.
71///
72/// # Thread Safety
73///
74/// This trait requires `Send + Sync` bounds (inherited from `PublicExchange`)
75/// to ensure safe usage across thread boundaries in async contexts.
76#[async_trait]
77pub trait MarketData: PublicExchange {
78    // ========================================================================
79    // Markets
80    // ========================================================================
81
82    /// Fetch all available markets from the exchange.
83    ///
84    /// Returns a vector of `Market` structures containing information about
85    /// each trading pair including symbols, precision, limits, and fees.
86    ///
87    /// # Example
88    ///
89    /// ```rust,ignore
90    /// let markets = exchange.fetch_markets().await?;
91    /// for market in markets {
92    ///     println!("{}: {} ({})", market.symbol, market.base, market.quote);
93    /// }
94    /// ```
95    async fn fetch_markets(&self) -> Result<Vec<Market>>;
96
97    /// Load and cache markets.
98    ///
99    /// This method fetches markets if not already cached, or returns the
100    /// cached markets. Use `reload_markets()` to force a refresh.
101    ///
102    /// # Example
103    ///
104    /// ```rust,ignore
105    /// // First call loads from API
106    /// let markets = exchange.load_markets().await?;
107    ///
108    /// // Subsequent calls use cache
109    /// let markets = exchange.load_markets().await?;
110    /// ```
111    async fn load_markets(
112        &self,
113    ) -> Result<std::sync::Arc<HashMap<String, std::sync::Arc<Market>>>> {
114        self.load_markets_with_reload(false).await
115    }
116
117    /// Force reload markets from the API.
118    ///
119    /// Clears the cache and fetches fresh market data.
120    async fn reload_markets(
121        &self,
122    ) -> Result<std::sync::Arc<HashMap<String, std::sync::Arc<Market>>>> {
123        self.load_markets_with_reload(true).await
124    }
125
126    /// Internal method to load markets with optional reload.
127    ///
128    /// Implementations should cache markets and only fetch from the API
129    /// when `reload` is `true` or the cache is empty.
130    async fn load_markets_with_reload(
131        &self,
132        reload: bool,
133    ) -> Result<std::sync::Arc<HashMap<String, std::sync::Arc<Market>>>>;
134
135    /// Get a specific market by symbol.
136    ///
137    /// Returns the market information for the given symbol, or an error
138    /// if the symbol is not found.
139    async fn market(&self, symbol: &str) -> Result<std::sync::Arc<Market>>;
140
141    /// Get all cached markets.
142    ///
143    /// Returns the currently cached markets without fetching from the API.
144    async fn markets(&self) -> std::sync::Arc<HashMap<String, std::sync::Arc<Market>>>;
145
146    /// Check if a symbol exists and is active.
147    ///
148    /// Returns `true` if the symbol exists in the market cache and is active.
149    async fn has_symbol(&self, symbol: &str) -> bool {
150        self.market(symbol).await.map(|m| m.active).unwrap_or(false)
151    }
152
153    // ========================================================================
154    // Tickers
155    // ========================================================================
156
157    /// Fetch ticker for a single symbol.
158    ///
159    /// Returns current price and volume information for the specified symbol.
160    ///
161    /// # Arguments
162    ///
163    /// * `symbol` - The trading pair symbol (e.g., "BTC/USDT")
164    ///
165    /// # Example
166    ///
167    /// ```rust,ignore
168    /// let ticker = exchange.fetch_ticker("BTC/USDT").await?;
169    /// println!("Last price: {:?}", ticker.last);
170    /// ```
171    async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker>;
172
173    /// Fetch tickers for multiple symbols.
174    ///
175    /// Returns tickers for the specified symbols. If `symbols` is empty,
176    /// returns tickers for all available symbols.
177    ///
178    /// # Arguments
179    ///
180    /// * `symbols` - Slice of trading pair symbols
181    ///
182    /// # Example
183    ///
184    /// ```rust,ignore
185    /// // Fetch specific symbols
186    /// let tickers = exchange.fetch_tickers(&["BTC/USDT", "ETH/USDT"]).await?;
187    ///
188    /// // Fetch all tickers
189    /// let all_tickers = exchange.fetch_all_tickers().await?;
190    /// ```
191    async fn fetch_tickers(&self, symbols: &[&str]) -> Result<Vec<Ticker>>;
192
193    /// Fetch all tickers.
194    ///
195    /// Convenience method that calls `fetch_tickers` with an empty slice.
196    async fn fetch_all_tickers(&self) -> Result<Vec<Ticker>> {
197        self.fetch_tickers(&[]).await
198    }
199
200    // ========================================================================
201    // Order Book
202    // ========================================================================
203
204    /// Fetch order book with default depth.
205    ///
206    /// Returns the current order book (bids and asks) for the specified symbol.
207    ///
208    /// # Arguments
209    ///
210    /// * `symbol` - The trading pair symbol
211    ///
212    /// # Example
213    ///
214    /// ```rust,ignore
215    /// let orderbook = exchange.fetch_order_book("BTC/USDT").await?;
216    /// println!("Best bid: {:?}", orderbook.bids.first());
217    /// println!("Best ask: {:?}", orderbook.asks.first());
218    /// ```
219    async fn fetch_order_book(&self, symbol: &str) -> Result<OrderBook> {
220        self.fetch_order_book_with_params(symbol, OrderBookParams::default())
221            .await
222    }
223
224    /// Fetch order book with custom depth.
225    ///
226    /// # Arguments
227    ///
228    /// * `symbol` - The trading pair symbol
229    /// * `limit` - Maximum number of price levels to return
230    async fn fetch_order_book_with_depth(&self, symbol: &str, limit: u32) -> Result<OrderBook> {
231        self.fetch_order_book_with_params(symbol, OrderBookParams::default().limit(limit))
232            .await
233    }
234
235    /// Fetch order book with full parameters.
236    ///
237    /// # Arguments
238    ///
239    /// * `symbol` - The trading pair symbol
240    /// * `params` - Order book parameters including depth limit
241    async fn fetch_order_book_with_params(
242        &self,
243        symbol: &str,
244        params: OrderBookParams,
245    ) -> Result<OrderBook>;
246
247    // ========================================================================
248    // Trades
249    // ========================================================================
250
251    /// Fetch recent public trades.
252    ///
253    /// Returns recent trades for the specified symbol.
254    ///
255    /// # Arguments
256    ///
257    /// * `symbol` - The trading pair symbol
258    ///
259    /// # Example
260    ///
261    /// ```rust,ignore
262    /// let trades = exchange.fetch_trades("BTC/USDT").await?;
263    /// for trade in trades {
264    ///     println!("{}: {} @ {}", trade.side, trade.amount, trade.price);
265    /// }
266    /// ```
267    async fn fetch_trades(&self, symbol: &str) -> Result<Vec<Trade>> {
268        self.fetch_trades_with_limit(symbol, None, None).await
269    }
270
271    /// Fetch recent trades with limit only.
272    ///
273    /// # Arguments
274    ///
275    /// * `symbol` - The trading pair symbol
276    /// * `limit` - Maximum number of trades to return
277    async fn fetch_trades_with_limit_only(
278        &self,
279        symbol: &str,
280        limit: Option<u32>,
281    ) -> Result<Vec<Trade>> {
282        self.fetch_trades_with_limit(symbol, None, limit).await
283    }
284
285    /// Fetch recent trades with limit and optional timestamp filtering.
286    ///
287    /// Returns recent trades for the specified symbol, optionally filtered by timestamp.
288    ///
289    /// # Arguments
290    ///
291    /// * `symbol` - The trading pair symbol (e.g., "BTC/USDT")
292    /// * `since` - Optional start timestamp in milliseconds (i64) since Unix epoch
293    /// * `limit` - Maximum number of trades to return
294    ///
295    /// # Timestamp Format
296    ///
297    /// The `since` parameter uses `i64` milliseconds since Unix epoch:
298    /// - `1609459200000` = January 1, 2021, 00:00:00 UTC
299    /// - `chrono::Utc::now().timestamp_millis()` = Current time
300    /// - `chrono::Utc::now().timestamp_millis() - 3600000` = 1 hour ago
301    ///
302    /// # Example
303    ///
304    /// ```rust,ignore
305    /// // Fetch recent trades without timestamp filter
306    /// let trades = exchange.fetch_trades_with_limit("BTC/USDT", None, Some(100)).await?;
307    ///
308    /// // Fetch trades from the last hour
309    /// let since = chrono::Utc::now().timestamp_millis() - 3600000;
310    /// let trades = exchange.fetch_trades_with_limit("BTC/USDT", Some(since), Some(50)).await?;
311    /// ```
312    async fn fetch_trades_with_limit(
313        &self,
314        symbol: &str,
315        since: Option<i64>,
316        limit: Option<u32>,
317    ) -> Result<Vec<Trade>>;
318
319    // ========================================================================
320    // Deprecated u64 Wrapper Methods (Backward Compatibility)
321    // ========================================================================
322
323    /// Fetch OHLCV candlestick data with u64 timestamps (deprecated).
324    ///
325    /// **DEPRECATED**: Use `fetch_ohlcv_with_params` with i64 timestamps instead.
326    /// This method is provided for backward compatibility during migration.
327    ///
328    /// # Migration
329    ///
330    /// ```rust,ignore
331    /// // Old code (deprecated)
332    /// let candles = exchange.fetch_ohlcv_u64("BTC/USDT", Timeframe::H1, Some(1609459200000u64), Some(100), None).await?;
333    ///
334    /// // New code (recommended)
335    /// let params = OhlcvParams::new(Timeframe::H1).since(1609459200000i64).limit(100);
336    /// let candles = exchange.fetch_ohlcv_with_params("BTC/USDT", params).await?;
337    /// ```
338    #[deprecated(
339        since = "0.1.0",
340        note = "Use fetch_ohlcv_with_params with i64 timestamps. Convert using TimestampUtils::u64_to_i64()"
341    )]
342    async fn fetch_ohlcv_u64(
343        &self,
344        symbol: &str,
345        timeframe: Timeframe,
346        since: Option<u64>,
347        limit: Option<u32>,
348        until: Option<u64>,
349    ) -> Result<Vec<Ohlcv>> {
350        use crate::time::TimestampConversion;
351        use crate::types::params::OhlcvParams;
352
353        let since_i64 = since.to_i64()?;
354        let until_i64 = until.to_i64()?;
355
356        let mut params = OhlcvParams::new(timeframe);
357        if let Some(ts) = since_i64 {
358            params = params.since(ts);
359        }
360        if let Some(ts) = until_i64 {
361            params = params.until(ts);
362        }
363        if let Some(n) = limit {
364            params = params.limit(n);
365        }
366
367        self.fetch_ohlcv_with_params(symbol, params).await
368    }
369
370    /// Fetch recent trades with u64 timestamp filtering (deprecated).
371    ///
372    /// **DEPRECATED**: Use `fetch_trades_with_limit` with i64 timestamps instead.
373    /// This method is provided for backward compatibility during migration.
374    ///
375    /// # Migration
376    ///
377    /// ```rust,ignore
378    /// // Old code (deprecated)
379    /// let trades = exchange.fetch_trades_u64("BTC/USDT", Some(1609459200000u64), Some(100)).await?;
380    ///
381    /// // New code (recommended)
382    /// let trades = exchange.fetch_trades_with_limit("BTC/USDT", Some(1609459200000i64), Some(100)).await?;
383    /// ```
384    #[deprecated(
385        since = "0.1.0",
386        note = "Use fetch_trades_with_limit with i64 timestamps. Convert using TimestampUtils::u64_to_i64()"
387    )]
388    async fn fetch_trades_u64(
389        &self,
390        symbol: &str,
391        since: Option<u64>,
392        limit: Option<u32>,
393    ) -> Result<Vec<Trade>> {
394        use crate::time::TimestampConversion;
395
396        let since_i64 = since.to_i64()?;
397        self.fetch_trades_with_limit(symbol, since_i64, limit).await
398    }
399
400    // ========================================================================
401    // OHLCV (Candlesticks)
402    // ========================================================================
403
404    /// Fetch OHLCV candlestick data.
405    ///
406    /// Returns candlestick data for the specified symbol and timeframe.
407    ///
408    /// # Arguments
409    ///
410    /// * `symbol` - The trading pair symbol
411    /// * `timeframe` - The candlestick timeframe
412    ///
413    /// # Example
414    ///
415    /// ```rust,ignore
416    /// let candles = exchange.fetch_ohlcv("BTC/USDT", Timeframe::H1).await?;
417    /// for candle in candles {
418    ///     println!("O: {} H: {} L: {} C: {}",
419    ///         candle.open, candle.high, candle.low, candle.close);
420    /// }
421    /// ```
422    async fn fetch_ohlcv(&self, symbol: &str, timeframe: Timeframe) -> Result<Vec<Ohlcv>> {
423        self.fetch_ohlcv_with_params(symbol, OhlcvParams::new(timeframe))
424            .await
425    }
426
427    /// Fetch OHLCV with full parameters.
428    ///
429    /// Allows specifying additional parameters like start time, limit, and
430    /// price type (for futures).
431    ///
432    /// # Arguments
433    ///
434    /// * `symbol` - The trading pair symbol (e.g., "BTC/USDT")
435    /// * `params` - OHLCV parameters including timeframe, since, limit, until
436    ///
437    /// # Timestamp Format
438    ///
439    /// All timestamp parameters in `OhlcvParams` use `i64` milliseconds since Unix epoch:
440    /// - `since`: Start time for historical data
441    /// - `until`: End time for historical data
442    /// - Returned `Ohlcv` structures have `timestamp` field as `i64`
443    ///
444    /// # Example
445    ///
446    /// ```rust,ignore
447    /// use ccxt_core::types::params::OhlcvParams;
448    ///
449    /// // Fetch recent candles
450    /// let candles = exchange.fetch_ohlcv_with_params(
451    ///     "BTC/USDT",
452    ///     OhlcvParams::new(Timeframe::H1).limit(100)
453    /// ).await?;
454    ///
455    /// // Fetch historical candles with timestamp range
456    /// let since = chrono::Utc::now().timestamp_millis() - (7 * 24 * 60 * 60 * 1000); // 7 days ago
457    /// let until = chrono::Utc::now().timestamp_millis();
458    /// let candles = exchange.fetch_ohlcv_with_params(
459    ///     "BTC/USDT",
460    ///     OhlcvParams::new(Timeframe::D1)
461    ///         .since(since)
462    ///         .until(until)
463    ///         .limit(7)
464    /// ).await?;
465    /// ```
466    async fn fetch_ohlcv_with_params(
467        &self,
468        symbol: &str,
469        params: OhlcvParams,
470    ) -> Result<Vec<Ohlcv>>;
471}
472
473/// Type alias for boxed MarketData trait object.
474pub type BoxedMarketData = Box<dyn MarketData>;
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use crate::capability::ExchangeCapabilities;
480
481    // Mock implementation for testing trait object safety
482    struct MockExchange;
483
484    impl PublicExchange for MockExchange {
485        fn id(&self) -> &str {
486            "mock"
487        }
488        fn name(&self) -> &str {
489            "Mock Exchange"
490        }
491        fn capabilities(&self) -> ExchangeCapabilities {
492            ExchangeCapabilities::public_only()
493        }
494        fn timeframes(&self) -> Vec<Timeframe> {
495            vec![Timeframe::H1, Timeframe::D1]
496        }
497    }
498
499    #[async_trait]
500    impl MarketData for MockExchange {
501        async fn fetch_markets(&self) -> Result<Vec<Market>> {
502            Ok(vec![])
503        }
504
505        async fn load_markets_with_reload(
506            &self,
507            _reload: bool,
508        ) -> Result<std::sync::Arc<HashMap<String, std::sync::Arc<Market>>>> {
509            Ok(std::sync::Arc::new(HashMap::new()))
510        }
511
512        async fn market(&self, _symbol: &str) -> Result<std::sync::Arc<Market>> {
513            Err(crate::Error::invalid_request("Not found"))
514        }
515
516        async fn markets(&self) -> std::sync::Arc<HashMap<String, std::sync::Arc<Market>>> {
517            std::sync::Arc::new(HashMap::new())
518        }
519
520        async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
521            Ok(Ticker::new(symbol.to_string(), 0))
522        }
523
524        async fn fetch_tickers(&self, _symbols: &[&str]) -> Result<Vec<Ticker>> {
525            Ok(vec![])
526        }
527
528        async fn fetch_order_book_with_params(
529            &self,
530            symbol: &str,
531            _params: OrderBookParams,
532        ) -> Result<OrderBook> {
533            Ok(OrderBook::new(symbol.to_string(), 0))
534        }
535
536        async fn fetch_trades_with_limit(
537            &self,
538            _symbol: &str,
539            _since: Option<i64>,
540            _limit: Option<u32>,
541        ) -> Result<Vec<Trade>> {
542            Ok(vec![])
543        }
544
545        async fn fetch_ohlcv_with_params(
546            &self,
547            _symbol: &str,
548            _params: OhlcvParams,
549        ) -> Result<Vec<Ohlcv>> {
550            Ok(vec![])
551        }
552    }
553
554    #[test]
555    fn test_trait_object_safety() {
556        // Verify trait is object-safe by creating a trait object
557        let _exchange: BoxedMarketData = Box::new(MockExchange);
558    }
559
560    #[tokio::test]
561    async fn test_default_implementations() {
562        let exchange = MockExchange;
563
564        // Test load_markets default
565        let markets = exchange.load_markets().await.unwrap();
566        assert!(markets.is_empty());
567
568        // Test reload_markets default
569        let markets = exchange.reload_markets().await.unwrap();
570        assert!(markets.is_empty());
571
572        // Test has_symbol default
573        let has = exchange.has_symbol("BTC/USDT").await;
574        assert!(!has);
575
576        // Test fetch_all_tickers default
577        let tickers = exchange.fetch_all_tickers().await.unwrap();
578        assert!(tickers.is_empty());
579
580        // Test fetch_order_book default
581        let orderbook = exchange.fetch_order_book("BTC/USDT").await.unwrap();
582        assert_eq!(orderbook.symbol, "BTC/USDT");
583
584        // Test fetch_order_book_with_depth default
585        let orderbook = exchange
586            .fetch_order_book_with_depth("BTC/USDT", 100)
587            .await
588            .unwrap();
589        assert_eq!(orderbook.symbol, "BTC/USDT");
590
591        // Test fetch_trades default
592        let trades = exchange.fetch_trades("BTC/USDT").await.unwrap();
593        assert!(trades.is_empty());
594
595        // Test fetch_ohlcv default
596        let candles = exchange
597            .fetch_ohlcv("BTC/USDT", Timeframe::H1)
598            .await
599            .unwrap();
600        assert!(candles.is_empty());
601    }
602}