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}