Skip to main content

ccxt_core/traits/
mod.rs

1//! Exchange trait hierarchy for modular capability composition.
2//!
3//! This module provides a decomposed trait hierarchy that allows exchanges to
4//! implement only the capabilities they support, without requiring stub
5//! implementations for unsupported features.
6//!
7//! # Trait Hierarchy
8//!
9//! ```text
10//! PublicExchange (base trait - metadata, capabilities)
11//!     │
12//!     ├── MarketData (public market data)
13//!     ├── Trading (order management)
14//!     ├── Account (balance, trade history)
15//!     ├── Margin (positions, leverage, funding)
16//!     └── Funding (deposits, withdrawals, transfers)
17//!
18//! FullExchange = PublicExchange + MarketData + Trading + Account + Margin + Funding
19//! ```
20//!
21//! # Object Safety
22//!
23//! All traits are designed to be object-safe, allowing for dynamic dispatch:
24//!
25//! ```rust,ignore
26//! use ccxt_core::traits::{BoxedMarketData, BoxedTrading, BoxedFullExchange};
27//!
28//! let market_data: BoxedMarketData = Box::new(my_exchange);
29//! let trading: BoxedTrading = Box::new(my_exchange);
30//! let full: BoxedFullExchange = Box::new(my_exchange);
31//! ```
32//!
33//! # Thread Safety
34//!
35//! All traits require `Send + Sync` bounds for async runtime compatibility.
36//!
37//! # Example
38//!
39//! ```rust,ignore
40//! use ccxt_core::traits::{PublicExchange, MarketData, Trading};
41//!
42//! // Exchange that only supports market data (no trading)
43//! struct ReadOnlyExchange;
44//!
45//! impl PublicExchange for ReadOnlyExchange {
46//!     fn id(&self) -> &str { "readonly" }
47//!     fn name(&self) -> &str { "Read Only Exchange" }
48//!     // ...
49//! }
50//!
51//! impl MarketData for ReadOnlyExchange {
52//!     // Only implement market data methods
53//! }
54//!
55//! // Full-featured exchange
56//! struct FullFeaturedExchange;
57//!
58//! impl PublicExchange for FullFeaturedExchange { /* ... */ }
59//! impl MarketData for FullFeaturedExchange { /* ... */ }
60//! impl Trading for FullFeaturedExchange { /* ... */ }
61//! impl Account for FullFeaturedExchange { /* ... */ }
62//! impl Margin for FullFeaturedExchange { /* ... */ }
63//! impl Funding for FullFeaturedExchange { /* ... */ }
64//! // Automatically implements FullExchange via blanket impl
65//! ```
66
67use std::sync::Arc;
68
69// Trait modules
70mod account;
71mod funding;
72mod margin;
73mod market_data;
74mod public_exchange;
75mod trading;
76
77// Re-export traits
78pub use account::{Account, BoxedAccount};
79pub use funding::{BoxedFunding, Funding};
80pub use margin::{BoxedMargin, Margin};
81pub use market_data::{BoxedMarketData, MarketData};
82pub use public_exchange::PublicExchange;
83pub use trading::{BoxedTrading, Trading};
84
85// ============================================================================
86// FullExchange Trait
87// ============================================================================
88
89/// Combined trait for exchanges supporting all capabilities.
90///
91/// This trait is automatically implemented for any type that implements
92/// all of the component traits: `PublicExchange`, `MarketData`, `Trading`,
93/// `Account`, `Margin`, and `Funding`.
94///
95/// # Example
96///
97/// ```rust,ignore
98/// use ccxt_core::traits::FullExchange;
99///
100/// async fn use_full_exchange<E: FullExchange>(exchange: &E) {
101///     // Can use all exchange capabilities
102///     let markets = exchange.fetch_markets().await?;
103///     let balance = exchange.fetch_balance().await?;
104///     let positions = exchange.fetch_positions().await?;
105/// }
106/// ```
107pub trait FullExchange: PublicExchange + MarketData + Trading + Account + Margin + Funding {}
108
109/// Blanket implementation of FullExchange for any type implementing all component traits.
110impl<T> FullExchange for T where
111    T: PublicExchange + MarketData + Trading + Account + Margin + Funding
112{
113}
114
115// ============================================================================
116// Type Aliases for Trait Objects
117// ============================================================================
118
119/// Type alias for boxed FullExchange trait object.
120///
121/// Use this when you need a heap-allocated exchange with all capabilities
122/// and single ownership.
123///
124/// # Example
125///
126/// ```rust,ignore
127/// let exchange: BoxedFullExchange = Box::new(my_exchange);
128/// ```
129pub type BoxedFullExchange = Box<dyn FullExchange>;
130
131/// Type alias for Arc-wrapped FullExchange trait object.
132///
133/// Use this when you need shared ownership of an exchange across multiple
134/// tasks or threads.
135///
136/// # Example
137///
138/// ```rust,ignore
139/// let exchange: ArcFullExchange = Arc::new(my_exchange);
140/// let exchange_clone = exchange.clone();
141/// tokio::spawn(async move {
142///     exchange_clone.fetch_ticker("BTC/USDT").await
143/// });
144/// ```
145pub type ArcFullExchange = Arc<dyn FullExchange>;
146
147/// Type alias for boxed PublicExchange trait object.
148pub type BoxedPublicExchange = Box<dyn PublicExchange>;
149
150/// Type alias for Arc-wrapped PublicExchange trait object.
151pub type ArcPublicExchange = Arc<dyn PublicExchange>;
152
153/// Type alias for Arc-wrapped MarketData trait object.
154pub type ArcMarketData = Arc<dyn MarketData>;
155
156/// Type alias for Arc-wrapped Trading trait object.
157pub type ArcTrading = Arc<dyn Trading>;
158
159/// Type alias for Arc-wrapped Account trait object.
160pub type ArcAccount = Arc<dyn Account>;
161
162/// Type alias for Arc-wrapped Margin trait object.
163pub type ArcMargin = Arc<dyn Margin>;
164
165/// Type alias for Arc-wrapped Funding trait object.
166pub type ArcFunding = Arc<dyn Funding>;
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::capability::ExchangeCapabilities;
172    use crate::error::Result;
173    use crate::types::{
174        Balance, BalanceEntry, DepositAddress, FundingRate, FundingRateHistory, Market, Ohlcv,
175        Order, OrderBook, Position, Ticker, Timeframe, Trade, Transaction, Transfer,
176        params::{
177            BalanceParams, LeverageParams, MarginMode, OhlcvParams, OrderBookParams, OrderParams,
178            TransferParams, WithdrawParams,
179        },
180        transaction::{TransactionStatus, TransactionType},
181    };
182    use async_trait::async_trait;
183    use rust_decimal_macros::dec;
184    use std::collections::HashMap;
185
186    // Full mock implementation for testing FullExchange
187    struct MockFullExchange;
188
189    impl PublicExchange for MockFullExchange {
190        fn id(&self) -> &str {
191            "mock_full"
192        }
193        fn name(&self) -> &str {
194            "Mock Full Exchange"
195        }
196        fn capabilities(&self) -> ExchangeCapabilities {
197            ExchangeCapabilities::all()
198        }
199        fn timeframes(&self) -> Vec<Timeframe> {
200            vec![Timeframe::H1, Timeframe::D1]
201        }
202    }
203
204    #[async_trait]
205    impl MarketData for MockFullExchange {
206        async fn fetch_markets(&self) -> Result<Vec<Market>> {
207            Ok(vec![])
208        }
209
210        async fn load_markets_with_reload(
211            &self,
212            _reload: bool,
213        ) -> Result<Arc<HashMap<String, Arc<Market>>>> {
214            Ok(Arc::new(HashMap::new()))
215        }
216
217        async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
218            Ok(Ticker {
219                symbol: symbol.to_string(),
220                ..Default::default()
221            })
222        }
223
224        async fn fetch_tickers(&self, _symbols: &[&str]) -> Result<Vec<Ticker>> {
225            Ok(vec![])
226        }
227
228        async fn fetch_order_book_with_params(
229            &self,
230            symbol: &str,
231            _params: OrderBookParams,
232        ) -> Result<OrderBook> {
233            Ok(OrderBook::new(symbol.to_string(), 0))
234        }
235
236        async fn fetch_trades_with_limit(
237            &self,
238            _symbol: &str,
239            _since: Option<i64>,
240            _limit: Option<u32>,
241        ) -> Result<Vec<Trade>> {
242            Ok(vec![])
243        }
244
245        async fn fetch_ohlcv_with_params(
246            &self,
247            _symbol: &str,
248            _params: OhlcvParams,
249        ) -> Result<Vec<Ohlcv>> {
250            Ok(vec![])
251        }
252
253        async fn market(&self, symbol: &str) -> Result<Arc<Market>> {
254            Ok(Arc::new(Market {
255                symbol: symbol.to_string(),
256                ..Default::default()
257            }))
258        }
259
260        async fn markets(&self) -> Arc<HashMap<String, Arc<Market>>> {
261            Arc::new(HashMap::new())
262        }
263    }
264
265    #[async_trait]
266    impl Trading for MockFullExchange {
267        async fn create_order(&self, params: OrderParams) -> Result<Order> {
268            Ok(Order::new(
269                "order_123".to_string(),
270                params.symbol,
271                params.order_type,
272                params.side,
273                params.amount,
274                params.price,
275                crate::types::OrderStatus::Open,
276            ))
277        }
278
279        async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
280            Ok(Order::new(
281                id.to_string(),
282                symbol.to_string(),
283                crate::types::OrderType::Limit,
284                crate::types::OrderSide::Buy,
285                dec!(0),
286                None,
287                crate::types::OrderStatus::Cancelled,
288            ))
289        }
290
291        async fn cancel_all_orders(&self, _symbol: &str) -> Result<Vec<Order>> {
292            Ok(vec![])
293        }
294
295        async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
296            Ok(Order::new(
297                id.to_string(),
298                symbol.to_string(),
299                crate::types::OrderType::Limit,
300                crate::types::OrderSide::Buy,
301                dec!(0),
302                None,
303                crate::types::OrderStatus::Open,
304            ))
305        }
306
307        async fn fetch_open_orders(&self, _symbol: Option<&str>) -> Result<Vec<Order>> {
308            Ok(vec![])
309        }
310
311        async fn fetch_closed_orders(
312            &self,
313            _symbol: Option<&str>,
314            _since: Option<i64>,
315            _limit: Option<u32>,
316        ) -> Result<Vec<Order>> {
317            Ok(vec![])
318        }
319    }
320
321    #[async_trait]
322    impl Account for MockFullExchange {
323        async fn fetch_balance_with_params(&self, _params: BalanceParams) -> Result<Balance> {
324            let mut balance = Balance::new();
325            balance.set("USDT".to_string(), BalanceEntry::new(dec!(10000), dec!(0)));
326            Ok(balance)
327        }
328
329        async fn fetch_my_trades_since(
330            &self,
331            _symbol: &str,
332            _since: Option<i64>,
333            _limit: Option<u32>,
334        ) -> Result<Vec<Trade>> {
335            Ok(vec![])
336        }
337    }
338
339    #[async_trait]
340    impl Margin for MockFullExchange {
341        async fn fetch_positions_for(&self, _symbols: &[&str]) -> Result<Vec<Position>> {
342            Ok(vec![])
343        }
344
345        async fn fetch_position(&self, symbol: &str) -> Result<Position> {
346            Ok(Position {
347                symbol: symbol.to_string(),
348                ..Default::default()
349            })
350        }
351
352        async fn set_leverage_with_params(&self, _params: LeverageParams) -> Result<()> {
353            Ok(())
354        }
355
356        async fn get_leverage(&self, _symbol: &str) -> Result<u32> {
357            Ok(10)
358        }
359
360        async fn set_margin_mode(&self, _symbol: &str, _mode: MarginMode) -> Result<()> {
361            Ok(())
362        }
363
364        async fn fetch_funding_rate(&self, symbol: &str) -> Result<FundingRate> {
365            Ok(FundingRate {
366                symbol: symbol.to_string(),
367                ..Default::default()
368            })
369        }
370
371        async fn fetch_funding_rates(&self, _symbols: &[&str]) -> Result<Vec<FundingRate>> {
372            Ok(vec![])
373        }
374
375        async fn fetch_funding_rate_history(
376            &self,
377            _symbol: &str,
378            _since: Option<i64>,
379            _limit: Option<u32>,
380        ) -> Result<Vec<FundingRateHistory>> {
381            Ok(vec![])
382        }
383    }
384
385    #[async_trait]
386    impl Funding for MockFullExchange {
387        async fn fetch_deposit_address(&self, code: &str) -> Result<DepositAddress> {
388            Ok(DepositAddress::new(code.to_string(), "0x123".to_string()))
389        }
390
391        async fn fetch_deposit_address_on_network(
392            &self,
393            code: &str,
394            network: &str,
395        ) -> Result<DepositAddress> {
396            let mut addr = DepositAddress::new(code.to_string(), "0x123".to_string());
397            addr.network = Some(network.to_string());
398            Ok(addr)
399        }
400
401        async fn withdraw(&self, params: WithdrawParams) -> Result<Transaction> {
402            Ok(Transaction::new(
403                "tx_123".to_string(),
404                TransactionType::Withdrawal,
405                params.amount,
406                params.currency,
407                TransactionStatus::Pending,
408            ))
409        }
410
411        async fn transfer(&self, params: TransferParams) -> Result<Transfer> {
412            Ok(Transfer {
413                id: Some("transfer_123".to_string()),
414                timestamp: 0,
415                datetime: "".to_string(),
416                currency: params.currency,
417                amount: 0.0,
418                from_account: None,
419                to_account: None,
420                status: "success".to_string(),
421                info: None,
422            })
423        }
424
425        async fn fetch_deposits(
426            &self,
427            _code: Option<&str>,
428            _since: Option<i64>,
429            _limit: Option<u32>,
430        ) -> Result<Vec<Transaction>> {
431            Ok(vec![])
432        }
433
434        async fn fetch_withdrawals(
435            &self,
436            _code: Option<&str>,
437            _since: Option<i64>,
438            _limit: Option<u32>,
439        ) -> Result<Vec<Transaction>> {
440            Ok(vec![])
441        }
442    }
443
444    #[test]
445    fn test_full_exchange_blanket_impl() {
446        // MockFullExchange should automatically implement FullExchange
447        fn assert_full_exchange<T: FullExchange>(_: &T) {}
448        let exchange = MockFullExchange;
449        assert_full_exchange(&exchange);
450    }
451
452    #[test]
453    fn test_boxed_full_exchange() {
454        let _exchange: BoxedFullExchange = Box::new(MockFullExchange);
455    }
456
457    #[test]
458    fn test_arc_full_exchange() {
459        let _exchange: ArcFullExchange = Arc::new(MockFullExchange);
460    }
461
462    #[tokio::test]
463    async fn test_full_exchange_methods() {
464        let exchange = MockFullExchange;
465
466        // Test methods from different traits
467        assert_eq!(exchange.id(), "mock_full");
468
469        let ticker = exchange.fetch_ticker("BTC/USDT").await.unwrap();
470        assert_eq!(ticker.symbol, "BTC/USDT");
471
472        let balance = exchange.fetch_balance().await.unwrap();
473        assert!(balance.get("USDT").is_some());
474
475        let positions = exchange.fetch_positions().await.unwrap();
476        assert!(positions.is_empty());
477
478        let address = exchange.fetch_deposit_address("USDT").await.unwrap();
479        assert_eq!(address.currency, "USDT");
480    }
481
482    #[test]
483    fn test_arc_type_aliases() {
484        let exchange = Arc::new(MockFullExchange);
485
486        // All Arc type aliases should work
487        let _: ArcFullExchange = exchange.clone();
488        let _: ArcMarketData = exchange.clone();
489        let _: ArcTrading = exchange.clone();
490        let _: ArcAccount = exchange.clone();
491        let _: ArcMargin = exchange.clone();
492        let _: ArcFunding = exchange.clone();
493    }
494}