ccxt_core/traits/
public_exchange.rs

1//! PublicExchange trait definition.
2//!
3//! The `PublicExchange` trait is the base trait for all exchange implementations,
4//! providing metadata and capability information. All other exchange traits
5//! (MarketData, Trading, Account, Margin, Funding) require this as a supertrait.
6//!
7//! # Object Safety
8//!
9//! This trait is designed to be object-safe, allowing for dynamic dispatch via
10//! trait objects (`dyn PublicExchange`). All methods return concrete types or
11//! references, and the trait requires `Send + Sync` bounds for async runtime
12//! compatibility.
13//!
14//! # Example
15//!
16//! ```rust,ignore
17//! use ccxt_core::traits::PublicExchange;
18//! use ccxt_core::capability::ExchangeCapabilities;
19//!
20//! struct MyExchange;
21//!
22//! impl PublicExchange for MyExchange {
23//!     fn id(&self) -> &str { "myexchange" }
24//!     fn name(&self) -> &str { "My Exchange" }
25//!     fn capabilities(&self) -> ExchangeCapabilities {
26//!         ExchangeCapabilities::public_only()
27//!     }
28//!     fn timeframes(&self) -> Vec<crate::types::Timeframe> {
29//!         vec![crate::types::Timeframe::H1, crate::types::Timeframe::D1]
30//!     }
31//! }
32//! ```
33
34use crate::capability::ExchangeCapabilities;
35use crate::types::Timeframe;
36
37/// Base trait for all exchange implementations.
38///
39/// Provides exchange metadata and capability information. This trait is the
40/// foundation of the exchange trait hierarchy and is required by all other
41/// exchange traits.
42///
43/// # Thread Safety
44///
45/// This trait requires `Send + Sync` bounds to ensure safe usage across
46/// thread boundaries in async contexts.
47///
48/// # Object Safety
49///
50/// This trait is object-safe and can be used with trait objects:
51///
52/// ```rust,ignore
53/// let exchange: Box<dyn PublicExchange> = Box::new(my_exchange);
54/// println!("Exchange: {}", exchange.name());
55/// ```
56pub trait PublicExchange: Send + Sync {
57    /// Returns the exchange identifier (e.g., "binance", "okx").
58    ///
59    /// This is a lowercase, unique identifier used internally.
60    fn id(&self) -> &str;
61
62    /// Returns the human-readable exchange name (e.g., "Binance", "OKX").
63    fn name(&self) -> &str;
64
65    /// Returns the API version string.
66    ///
67    /// Default implementation returns "1.0.0".
68    fn version(&self) -> &'static str {
69        "1.0.0"
70    }
71
72    /// Returns whether this exchange implementation is CCXT certified.
73    ///
74    /// Certified exchanges have been thoroughly tested and verified.
75    /// Default implementation returns `false`.
76    fn certified(&self) -> bool {
77        false
78    }
79
80    /// Returns the exchange capabilities.
81    ///
82    /// Capabilities indicate which API methods are supported by this exchange.
83    fn capabilities(&self) -> ExchangeCapabilities;
84
85    /// Returns supported timeframes for OHLCV data.
86    ///
87    /// Returns a vector of timeframes that can be used with `fetch_ohlcv`.
88    fn timeframes(&self) -> Vec<Timeframe>;
89
90    /// Returns the rate limit (requests per second).
91    ///
92    /// Default implementation returns 10 requests per second.
93    fn rate_limit(&self) -> u32 {
94        10
95    }
96
97    /// Returns whether WebSocket is supported.
98    ///
99    /// Default implementation returns `false`.
100    fn has_websocket(&self) -> bool {
101        false
102    }
103
104    /// Returns whether the exchange is in sandbox/testnet mode.
105    ///
106    /// Default implementation returns `false`.
107    fn is_sandbox(&self) -> bool {
108        false
109    }
110
111    /// Returns the exchange's countries of operation.
112    ///
113    /// Default implementation returns an empty vector.
114    fn countries(&self) -> Vec<&'static str> {
115        vec![]
116    }
117
118    /// Returns the exchange's website URLs.
119    ///
120    /// Default implementation returns an empty vector.
121    fn urls(&self) -> Vec<&'static str> {
122        vec![]
123    }
124
125    /// Check if a specific capability is supported.
126    ///
127    /// Convenience method that delegates to `capabilities().has(name)`.
128    ///
129    /// # Arguments
130    ///
131    /// * `name` - The capability name in camelCase format (e.g., "fetchTicker")
132    ///
133    /// # Example
134    ///
135    /// ```rust,ignore
136    /// if exchange.has_capability("fetchOHLCV") {
137    ///     // Fetch OHLCV data
138    /// }
139    /// ```
140    fn has_capability(&self, name: &str) -> bool {
141        self.capabilities().has(name)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    struct TestExchange;
150
151    impl PublicExchange for TestExchange {
152        fn id(&self) -> &str {
153            "test"
154        }
155
156        fn name(&self) -> &str {
157            "Test Exchange"
158        }
159
160        fn capabilities(&self) -> ExchangeCapabilities {
161            ExchangeCapabilities::public_only()
162        }
163
164        fn timeframes(&self) -> Vec<Timeframe> {
165            vec![Timeframe::H1, Timeframe::D1]
166        }
167    }
168
169    #[test]
170    fn test_public_exchange_defaults() {
171        let exchange = TestExchange;
172
173        assert_eq!(exchange.id(), "test");
174        assert_eq!(exchange.name(), "Test Exchange");
175        assert_eq!(exchange.version(), "1.0.0");
176        assert!(!exchange.certified());
177        assert_eq!(exchange.rate_limit(), 10);
178        assert!(!exchange.has_websocket());
179        assert!(!exchange.is_sandbox());
180        assert!(exchange.countries().is_empty());
181        assert!(exchange.urls().is_empty());
182    }
183
184    #[test]
185    fn test_has_capability() {
186        let exchange = TestExchange;
187
188        assert!(exchange.has_capability("fetchTicker"));
189        assert!(exchange.has_capability("fetchMarkets"));
190        assert!(!exchange.has_capability("createOrder"));
191    }
192
193    #[test]
194    fn test_timeframes() {
195        let exchange = TestExchange;
196        let timeframes = exchange.timeframes();
197
198        assert_eq!(timeframes.len(), 2);
199        assert!(timeframes.contains(&Timeframe::H1));
200        assert!(timeframes.contains(&Timeframe::D1));
201    }
202
203    #[test]
204    fn test_trait_object_safety() {
205        // Verify trait is object-safe by creating a trait object
206        let exchange: Box<dyn PublicExchange> = Box::new(TestExchange);
207        assert_eq!(exchange.id(), "test");
208        assert_eq!(exchange.name(), "Test Exchange");
209    }
210
211    #[test]
212    fn test_send_sync_bounds() {
213        // Verify Send + Sync bounds are satisfied
214        fn assert_send_sync<T: Send + Sync>() {}
215        assert_send_sync::<TestExchange>();
216    }
217}