ccxt_core/
exchange.rs

1//! # Unified Exchange Trait
2//!
3//! This module defines the core [`Exchange`] trait that all exchange implementations must implement.
4//! It provides a unified, polymorphic interface for interacting with cryptocurrency exchanges.
5//!
6//! ## Overview
7//!
8//! The `Exchange` trait is the central abstraction in CCXT-Rust. It enables:
9//!
10//! - **Polymorphic Exchange Usage**: Write exchange-agnostic trading code using `dyn Exchange`
11//! - **Capability Discovery**: Query exchange features at runtime via [`ExchangeCapabilities`]
12//! - **Type Safety**: Leverage Rust's type system for compile-time guarantees
13//! - **Thread Safety**: All implementations are `Send + Sync` for async runtime compatibility
14//!
15//! ## Architecture
16//!
17//! ```text
18//! ┌─────────────────────────────────────────────────────────────┐
19//! │                      Exchange Trait                         │
20//! ├─────────────────────────────────────────────────────────────┤
21//! │  Metadata Methods                                           │
22//! │  ├── id(), name(), version(), certified()                   │
23//! │  ├── capabilities(), timeframes(), rate_limit()             │
24//! │  └── has_websocket()                                        │
25//! ├─────────────────────────────────────────────────────────────┤
26//! │  Market Data Methods (Public API)                           │
27//! │  ├── fetch_markets(), load_markets()                        │
28//! │  ├── fetch_ticker(), fetch_tickers()                        │
29//! │  ├── fetch_order_book(), fetch_trades()                     │
30//! │  └── fetch_ohlcv()                                          │
31//! ├─────────────────────────────────────────────────────────────┤
32//! │  Trading Methods (Private API)                              │
33//! │  ├── create_order(), cancel_order(), cancel_all_orders()    │
34//! │  └── fetch_order(), fetch_open_orders(), fetch_closed_orders()│
35//! ├─────────────────────────────────────────────────────────────┤
36//! │  Account Methods (Private API)                              │
37//! │  └── fetch_balance(), fetch_my_trades()                     │
38//! └─────────────────────────────────────────────────────────────┘
39//! ```
40//!
41//! ## Key Types
42//!
43//! - [`Exchange`]: The core trait defining the unified exchange interface
44//! - [`ExchangeCapabilities`]: Describes which features an exchange supports
45//! - [`BoxedExchange`]: Type alias for `Box<dyn Exchange>` (owned trait object)
46//! - [`ArcExchange`]: Type alias for `Arc<dyn Exchange>` (shared trait object)
47//!
48//! ## Usage Examples
49//!
50//! ### Basic Exchange Usage
51//!
52//! ```rust,no_run
53//! use ccxt_core::exchange::{Exchange, ExchangeCapabilities};
54//!
55//! async fn print_exchange_info(exchange: &dyn Exchange) {
56//!     println!("Exchange: {} ({})", exchange.name(), exchange.id());
57//!     println!("Version: {}", exchange.version());
58//!     println!("Certified: {}", exchange.certified());
59//!     println!("Rate Limit: {} req/s", exchange.rate_limit());
60//! }
61//! ```
62//!
63//! ### Checking Capabilities Before Calling Methods
64//!
65//! ```rust,no_run
66//! use ccxt_core::exchange::{Exchange, ExchangeCapabilities};
67//!
68//! async fn safe_fetch_ticker(
69//!     exchange: &dyn Exchange,
70//!     symbol: &str,
71//! ) -> ccxt_core::Result<ccxt_core::Ticker> {
72//!     // Always check capability before calling
73//!     if !exchange.capabilities().fetch_ticker {
74//!         return Err(ccxt_core::Error::not_implemented("fetch_ticker"));
75//!     }
76//!     exchange.fetch_ticker(symbol).await
77//! }
78//! ```
79//!
80//! ### Using Multiple Exchanges Polymorphically
81//!
82//! ```rust,no_run
83//! use ccxt_core::exchange::{Exchange, BoxedExchange};
84//! use ccxt_core::types::Price;
85//!
86//! async fn fetch_best_price(
87//!     exchanges: &[BoxedExchange],
88//!     symbol: &str,
89//! ) -> ccxt_core::Result<Price> {
90//!     let mut best_price: Option<Price> = None;
91//!     
92//!     for exchange in exchanges {
93//!         if exchange.capabilities().fetch_ticker {
94//!             if let Ok(ticker) = exchange.fetch_ticker(symbol).await {
95//!                 if let Some(last) = ticker.last {
96//!                     best_price = Some(match best_price {
97//!                         None => last,
98//!                         Some(current) => if current < last { current } else { last },
99//!                     });
100//!                 }
101//!             }
102//!         }
103//!     }
104//!     
105//!     best_price.ok_or_else(|| ccxt_core::Error::market_not_found("symbol"))
106//! }
107//! ```
108//!
109//! ### Thread-Safe Shared Exchange
110//!
111//! ```rust,no_run
112//! use ccxt_core::exchange::{Exchange, ArcExchange};
113//! use std::sync::Arc;
114//!
115//! async fn spawn_ticker_tasks(
116//!     exchange: ArcExchange,
117//!     symbols: Vec<String>,
118//! ) {
119//!     let handles: Vec<_> = symbols
120//!         .into_iter()
121//!         .map(|symbol| {
122//!             let ex = Arc::clone(&exchange);
123//!             tokio::spawn(async move {
124//!                 ex.fetch_ticker(&symbol).await
125//!             })
126//!         })
127//!         .collect();
128//!     
129//!     for handle in handles {
130//!         let _ = handle.await;
131//!     }
132//! }
133//! ```
134//!
135//! ## ExchangeCapabilities
136//!
137//! The [`ExchangeCapabilities`] struct provides runtime feature discovery:
138//!
139//! ```rust
140//! use ccxt_core::exchange::ExchangeCapabilities;
141//!
142//! // Create capabilities for public-only access
143//! let public_caps = ExchangeCapabilities::public_only();
144//! assert!(public_caps.fetch_ticker);
145//! assert!(!public_caps.create_order);
146//!
147//! // Create capabilities with all features
148//! let all_caps = ExchangeCapabilities::all();
149//! assert!(all_caps.create_order);
150//! assert!(all_caps.websocket);
151//!
152//! // Check capability by name (CCXT-style camelCase)
153//! assert!(all_caps.has("fetchTicker"));
154//! assert!(all_caps.has("createOrder"));
155//! ```
156//!
157//! ## Error Handling
158//!
159//! All exchange methods return `Result<T>` with comprehensive error types:
160//!
161//! - `NotImplemented`: Method not supported by this exchange
162//! - `Authentication`: API credentials missing or invalid
163//! - `RateLimit`: Too many requests
164//! - `Network`: Connection or timeout errors
165//! - `Exchange`: Exchange-specific errors
166//!
167//! ## Thread Safety
168//!
169//! The `Exchange` trait requires `Send + Sync` bounds, ensuring:
170//!
171//! - Exchanges can be sent across thread boundaries (`Send`)
172//! - Exchanges can be shared across threads via `Arc` (`Sync`)
173//! - Compatible with Tokio and other async runtimes
174//!
175//! ## See Also
176//!
177//! - [`crate::ws_exchange::WsExchange`]: WebSocket streaming trait
178//! - [`crate::ws_exchange::FullExchange`]: Combined REST + WebSocket trait
179//! - [`crate::base_exchange::BaseExchange`]: Base implementation utilities
180
181use async_trait::async_trait;
182use rust_decimal::Decimal;
183use std::collections::HashMap;
184use std::sync::Arc;
185
186use crate::error::Result;
187use crate::types::*;
188
189// ============================================================================
190// ExchangeCapabilities
191// ============================================================================
192
193/// Exchange capabilities - describes what features an exchange supports
194///
195/// This struct contains boolean flags for each capability that an exchange
196/// may or may not support. Use this to check feature availability before
197/// calling methods that may not be implemented.
198///
199/// # Example
200///
201/// ```rust
202/// use ccxt_core::exchange::ExchangeCapabilities;
203///
204/// let caps = ExchangeCapabilities::public_only();
205/// assert!(caps.fetch_ticker);
206/// assert!(!caps.create_order);
207/// ```
208#[derive(Debug, Clone, Default, PartialEq, Eq)]
209pub struct ExchangeCapabilities {
210    // ==================== Market Data (Public API) ====================
211    /// Can fetch market definitions
212    pub fetch_markets: bool,
213    /// Can fetch currency definitions
214    pub fetch_currencies: bool,
215    /// Can fetch single ticker
216    pub fetch_ticker: bool,
217    /// Can fetch multiple tickers
218    pub fetch_tickers: bool,
219    /// Can fetch order book
220    pub fetch_order_book: bool,
221    /// Can fetch public trades
222    pub fetch_trades: bool,
223    /// Can fetch OHLCV candlestick data
224    pub fetch_ohlcv: bool,
225    /// Can fetch exchange status
226    pub fetch_status: bool,
227    /// Can fetch server time
228    pub fetch_time: bool,
229
230    // ==================== Trading (Private API) ====================
231    /// Can create orders
232    pub create_order: bool,
233    /// Can create market orders
234    pub create_market_order: bool,
235    /// Can create limit orders
236    pub create_limit_order: bool,
237    /// Can cancel orders
238    pub cancel_order: bool,
239    /// Can cancel all orders
240    pub cancel_all_orders: bool,
241    /// Can edit/modify orders
242    pub edit_order: bool,
243    /// Can fetch single order
244    pub fetch_order: bool,
245    /// Can fetch all orders
246    pub fetch_orders: bool,
247    /// Can fetch open orders
248    pub fetch_open_orders: bool,
249    /// Can fetch closed orders
250    pub fetch_closed_orders: bool,
251    /// Can fetch canceled orders
252    pub fetch_canceled_orders: bool,
253
254    // ==================== Account (Private API) ====================
255    /// Can fetch account balance
256    pub fetch_balance: bool,
257    /// Can fetch user's trade history
258    pub fetch_my_trades: bool,
259    /// Can fetch deposit history
260    pub fetch_deposits: bool,
261    /// Can fetch withdrawal history
262    pub fetch_withdrawals: bool,
263    /// Can fetch transaction history
264    pub fetch_transactions: bool,
265    /// Can fetch ledger entries
266    pub fetch_ledger: bool,
267
268    // ==================== Funding ====================
269    /// Can fetch deposit address
270    pub fetch_deposit_address: bool,
271    /// Can create deposit address
272    pub create_deposit_address: bool,
273    /// Can withdraw funds
274    pub withdraw: bool,
275    /// Can transfer between accounts
276    pub transfer: bool,
277
278    // ==================== Margin Trading ====================
279    /// Can fetch borrow rate
280    pub fetch_borrow_rate: bool,
281    /// Can fetch multiple borrow rates
282    pub fetch_borrow_rates: bool,
283    /// Can fetch funding rate
284    pub fetch_funding_rate: bool,
285    /// Can fetch multiple funding rates
286    pub fetch_funding_rates: bool,
287    /// Can fetch positions
288    pub fetch_positions: bool,
289    /// Can set leverage
290    pub set_leverage: bool,
291    /// Can set margin mode
292    pub set_margin_mode: bool,
293
294    // ==================== WebSocket ====================
295    /// WebSocket support available
296    pub websocket: bool,
297    /// Can watch ticker updates
298    pub watch_ticker: bool,
299    /// Can watch multiple ticker updates
300    pub watch_tickers: bool,
301    /// Can watch order book updates
302    pub watch_order_book: bool,
303    /// Can watch trade updates
304    pub watch_trades: bool,
305    /// Can watch OHLCV updates
306    pub watch_ohlcv: bool,
307    /// Can watch balance updates
308    pub watch_balance: bool,
309    /// Can watch order updates
310    pub watch_orders: bool,
311    /// Can watch user trade updates
312    pub watch_my_trades: bool,
313}
314
315impl ExchangeCapabilities {
316    /// Create capabilities with all features enabled
317    ///
318    /// # Example
319    ///
320    /// ```rust
321    /// use ccxt_core::exchange::ExchangeCapabilities;
322    ///
323    /// let caps = ExchangeCapabilities::all();
324    /// assert!(caps.fetch_ticker);
325    /// assert!(caps.create_order);
326    /// assert!(caps.websocket);
327    /// ```
328    pub fn all() -> Self {
329        Self {
330            fetch_markets: true,
331            fetch_currencies: true,
332            fetch_ticker: true,
333            fetch_tickers: true,
334            fetch_order_book: true,
335            fetch_trades: true,
336            fetch_ohlcv: true,
337            fetch_status: true,
338            fetch_time: true,
339            create_order: true,
340            create_market_order: true,
341            create_limit_order: true,
342            cancel_order: true,
343            cancel_all_orders: true,
344            edit_order: true,
345            fetch_order: true,
346            fetch_orders: true,
347            fetch_open_orders: true,
348            fetch_closed_orders: true,
349            fetch_canceled_orders: true,
350            fetch_balance: true,
351            fetch_my_trades: true,
352            fetch_deposits: true,
353            fetch_withdrawals: true,
354            fetch_transactions: true,
355            fetch_ledger: true,
356            fetch_deposit_address: true,
357            create_deposit_address: true,
358            withdraw: true,
359            transfer: true,
360            fetch_borrow_rate: true,
361            fetch_borrow_rates: true,
362            fetch_funding_rate: true,
363            fetch_funding_rates: true,
364            fetch_positions: true,
365            set_leverage: true,
366            set_margin_mode: true,
367            websocket: true,
368            watch_ticker: true,
369            watch_tickers: true,
370            watch_order_book: true,
371            watch_trades: true,
372            watch_ohlcv: true,
373            watch_balance: true,
374            watch_orders: true,
375            watch_my_trades: true,
376        }
377    }
378
379    /// Create capabilities for public API only (no authentication required)
380    ///
381    /// # Example
382    ///
383    /// ```rust
384    /// use ccxt_core::exchange::ExchangeCapabilities;
385    ///
386    /// let caps = ExchangeCapabilities::public_only();
387    /// assert!(caps.fetch_ticker);
388    /// assert!(!caps.create_order);
389    /// ```
390    pub fn public_only() -> Self {
391        Self {
392            fetch_markets: true,
393            fetch_currencies: true,
394            fetch_ticker: true,
395            fetch_tickers: true,
396            fetch_order_book: true,
397            fetch_trades: true,
398            fetch_ohlcv: true,
399            fetch_status: true,
400            fetch_time: true,
401            ..Default::default()
402        }
403    }
404
405    /// Check if a capability is supported by name
406    ///
407    /// This method allows checking capabilities using CCXT-style camelCase names.
408    ///
409    /// # Arguments
410    ///
411    /// * `capability` - The capability name in camelCase format
412    ///
413    /// # Example
414    ///
415    /// ```rust
416    /// use ccxt_core::exchange::ExchangeCapabilities;
417    ///
418    /// let caps = ExchangeCapabilities::all();
419    /// assert!(caps.has("fetchTicker"));
420    /// assert!(caps.has("createOrder"));
421    /// assert!(!caps.has("unknownCapability"));
422    /// ```
423    pub fn has(&self, capability: &str) -> bool {
424        match capability {
425            "fetchMarkets" => self.fetch_markets,
426            "fetchCurrencies" => self.fetch_currencies,
427            "fetchTicker" => self.fetch_ticker,
428            "fetchTickers" => self.fetch_tickers,
429            "fetchOrderBook" => self.fetch_order_book,
430            "fetchTrades" => self.fetch_trades,
431            "fetchOHLCV" => self.fetch_ohlcv,
432            "fetchStatus" => self.fetch_status,
433            "fetchTime" => self.fetch_time,
434            "createOrder" => self.create_order,
435            "createMarketOrder" => self.create_market_order,
436            "createLimitOrder" => self.create_limit_order,
437            "cancelOrder" => self.cancel_order,
438            "cancelAllOrders" => self.cancel_all_orders,
439            "editOrder" => self.edit_order,
440            "fetchOrder" => self.fetch_order,
441            "fetchOrders" => self.fetch_orders,
442            "fetchOpenOrders" => self.fetch_open_orders,
443            "fetchClosedOrders" => self.fetch_closed_orders,
444            "fetchCanceledOrders" => self.fetch_canceled_orders,
445            "fetchBalance" => self.fetch_balance,
446            "fetchMyTrades" => self.fetch_my_trades,
447            "fetchDeposits" => self.fetch_deposits,
448            "fetchWithdrawals" => self.fetch_withdrawals,
449            "fetchTransactions" => self.fetch_transactions,
450            "fetchLedger" => self.fetch_ledger,
451            "fetchDepositAddress" => self.fetch_deposit_address,
452            "createDepositAddress" => self.create_deposit_address,
453            "withdraw" => self.withdraw,
454            "transfer" => self.transfer,
455            "fetchBorrowRate" => self.fetch_borrow_rate,
456            "fetchBorrowRates" => self.fetch_borrow_rates,
457            "fetchFundingRate" => self.fetch_funding_rate,
458            "fetchFundingRates" => self.fetch_funding_rates,
459            "fetchPositions" => self.fetch_positions,
460            "setLeverage" => self.set_leverage,
461            "setMarginMode" => self.set_margin_mode,
462            "websocket" => self.websocket,
463            "watchTicker" => self.watch_ticker,
464            "watchTickers" => self.watch_tickers,
465            "watchOrderBook" => self.watch_order_book,
466            "watchTrades" => self.watch_trades,
467            "watchOHLCV" => self.watch_ohlcv,
468            "watchBalance" => self.watch_balance,
469            "watchOrders" => self.watch_orders,
470            "watchMyTrades" => self.watch_my_trades,
471            _ => false,
472        }
473    }
474
475    /// Get a list of all supported capability names
476    pub fn supported_capabilities(&self) -> Vec<&'static str> {
477        let mut caps = Vec::new();
478        if self.fetch_markets {
479            caps.push("fetchMarkets");
480        }
481        if self.fetch_currencies {
482            caps.push("fetchCurrencies");
483        }
484        if self.fetch_ticker {
485            caps.push("fetchTicker");
486        }
487        if self.fetch_tickers {
488            caps.push("fetchTickers");
489        }
490        if self.fetch_order_book {
491            caps.push("fetchOrderBook");
492        }
493        if self.fetch_trades {
494            caps.push("fetchTrades");
495        }
496        if self.fetch_ohlcv {
497            caps.push("fetchOHLCV");
498        }
499        if self.fetch_status {
500            caps.push("fetchStatus");
501        }
502        if self.fetch_time {
503            caps.push("fetchTime");
504        }
505        if self.create_order {
506            caps.push("createOrder");
507        }
508        if self.create_market_order {
509            caps.push("createMarketOrder");
510        }
511        if self.create_limit_order {
512            caps.push("createLimitOrder");
513        }
514        if self.cancel_order {
515            caps.push("cancelOrder");
516        }
517        if self.cancel_all_orders {
518            caps.push("cancelAllOrders");
519        }
520        if self.edit_order {
521            caps.push("editOrder");
522        }
523        if self.fetch_order {
524            caps.push("fetchOrder");
525        }
526        if self.fetch_orders {
527            caps.push("fetchOrders");
528        }
529        if self.fetch_open_orders {
530            caps.push("fetchOpenOrders");
531        }
532        if self.fetch_closed_orders {
533            caps.push("fetchClosedOrders");
534        }
535        if self.fetch_canceled_orders {
536            caps.push("fetchCanceledOrders");
537        }
538        if self.fetch_balance {
539            caps.push("fetchBalance");
540        }
541        if self.fetch_my_trades {
542            caps.push("fetchMyTrades");
543        }
544        if self.websocket {
545            caps.push("websocket");
546        }
547        caps
548    }
549}
550
551// ============================================================================
552// Exchange Trait
553// ============================================================================
554
555/// Core Exchange trait - the unified interface for all exchanges
556///
557/// This trait defines the standard API that all exchange implementations
558/// must provide. It is designed to be object-safe for dynamic dispatch,
559/// allowing exchanges to be used polymorphically via `dyn Exchange`.
560///
561/// # Thread Safety
562///
563/// All implementations must be `Send + Sync` to allow safe usage across
564/// thread boundaries.
565///
566/// # Example
567///
568/// ```rust,no_run
569/// use ccxt_core::exchange::Exchange;
570///
571/// async fn print_exchange_info(exchange: &dyn Exchange) {
572///     println!("Exchange: {} ({})", exchange.name(), exchange.id());
573///     println!("Version: {}", exchange.version());
574///     println!("Certified: {}", exchange.certified());
575/// }
576/// ```
577#[async_trait]
578pub trait Exchange: Send + Sync {
579    // ==================== Metadata ====================
580
581    /// Returns the exchange identifier (e.g., "binance", "coinbase")
582    ///
583    /// This is a lowercase, URL-safe identifier used internally.
584    fn id(&self) -> &str;
585
586    /// Returns the human-readable exchange name (e.g., "Binance", "Coinbase")
587    fn name(&self) -> &str;
588
589    /// Returns the API version string
590    fn version(&self) -> &'static str {
591        "1.0.0"
592    }
593
594    /// Returns whether this exchange is CCXT certified
595    ///
596    /// Certified exchanges have been thoroughly tested and verified.
597    fn certified(&self) -> bool {
598        false
599    }
600
601    /// Returns whether this exchange supports WebSocket (pro features)
602    fn has_websocket(&self) -> bool {
603        self.capabilities().websocket
604    }
605
606    /// Returns the exchange capabilities
607    ///
608    /// Use this to check which features are supported before calling methods.
609    fn capabilities(&self) -> ExchangeCapabilities;
610
611    /// Returns supported timeframes for OHLCV data
612    fn timeframes(&self) -> Vec<Timeframe> {
613        vec![
614            Timeframe::M1,
615            Timeframe::M5,
616            Timeframe::M15,
617            Timeframe::H1,
618            Timeframe::H4,
619            Timeframe::D1,
620        ]
621    }
622
623    /// Returns the rate limit (requests per second)
624    fn rate_limit(&self) -> f64 {
625        10.0
626    }
627
628    // ==================== Market Data (Public API) ====================
629
630    /// Fetch all available markets
631    ///
632    /// # Returns
633    ///
634    /// A vector of `Market` structs containing market definitions.
635    ///
636    /// # Errors
637    ///
638    /// Returns an error if the request fails or the exchange is unavailable.
639    async fn fetch_markets(&self) -> Result<Vec<Market>>;
640
641    /// Load markets and cache them
642    ///
643    /// # Arguments
644    ///
645    /// * `reload` - If true, force reload even if markets are cached
646    ///
647    /// # Returns
648    ///
649    /// A HashMap of markets indexed by symbol.
650    async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>>;
651
652    /// Fetch ticker for a single symbol
653    ///
654    /// # Arguments
655    ///
656    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
657    ///
658    /// # Returns
659    ///
660    /// The ticker data for the specified symbol.
661    async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker>;
662
663    /// Fetch tickers for multiple symbols (or all if None)
664    ///
665    /// # Arguments
666    ///
667    /// * `symbols` - Optional list of symbols to fetch. If None, fetches all.
668    ///
669    /// # Returns
670    ///
671    /// A vector of tickers.
672    async fn fetch_tickers(&self, symbols: Option<&[String]>) -> Result<Vec<Ticker>>;
673
674    /// Fetch order book for a symbol
675    ///
676    /// # Arguments
677    ///
678    /// * `symbol` - Trading pair symbol
679    /// * `limit` - Optional limit on the number of orders per side
680    ///
681    /// # Returns
682    ///
683    /// The order book containing bids and asks.
684    async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook>;
685
686    /// Fetch recent public trades
687    ///
688    /// # Arguments
689    ///
690    /// * `symbol` - Trading pair symbol
691    /// * `limit` - Optional limit on the number of trades
692    ///
693    /// # Returns
694    ///
695    /// A vector of recent trades.
696    async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>>;
697
698    /// Fetch OHLCV candlestick data
699    ///
700    /// # Arguments
701    ///
702    /// * `symbol` - Trading pair symbol
703    /// * `timeframe` - Candlestick timeframe
704    /// * `since` - Optional start timestamp in milliseconds
705    /// * `limit` - Optional limit on the number of candles
706    ///
707    /// # Returns
708    ///
709    /// A vector of OHLCV candles.
710    async fn fetch_ohlcv(
711        &self,
712        symbol: &str,
713        timeframe: Timeframe,
714        since: Option<i64>,
715        limit: Option<u32>,
716    ) -> Result<Vec<Ohlcv>>;
717
718    // ==================== Trading (Private API) ====================
719
720    /// Create a new order
721    ///
722    /// # Arguments
723    ///
724    /// * `symbol` - Trading pair symbol
725    /// * `order_type` - Order type (limit, market, etc.)
726    /// * `side` - Order side (buy or sell)
727    /// * `amount` - Order amount
728    /// * `price` - Optional price (required for limit orders)
729    ///
730    /// # Returns
731    ///
732    /// The created order.
733    ///
734    /// # Errors
735    ///
736    /// Returns an error if authentication fails or the order is invalid.
737    async fn create_order(
738        &self,
739        symbol: &str,
740        order_type: OrderType,
741        side: OrderSide,
742        amount: Decimal,
743        price: Option<Decimal>,
744    ) -> Result<Order>;
745
746    /// Cancel an existing order
747    ///
748    /// # Arguments
749    ///
750    /// * `id` - Order ID to cancel
751    /// * `symbol` - Optional symbol (required by some exchanges)
752    ///
753    /// # Returns
754    ///
755    /// The canceled order.
756    async fn cancel_order(&self, id: &str, symbol: Option<&str>) -> Result<Order>;
757
758    /// Cancel all orders (optionally for a specific symbol)
759    ///
760    /// # Arguments
761    ///
762    /// * `symbol` - Optional symbol to cancel orders for
763    ///
764    /// # Returns
765    ///
766    /// A vector of canceled orders.
767    async fn cancel_all_orders(&self, symbol: Option<&str>) -> Result<Vec<Order>>;
768
769    /// Fetch a specific order by ID
770    ///
771    /// # Arguments
772    ///
773    /// * `id` - Order ID
774    /// * `symbol` - Optional symbol (required by some exchanges)
775    ///
776    /// # Returns
777    ///
778    /// The order details.
779    async fn fetch_order(&self, id: &str, symbol: Option<&str>) -> Result<Order>;
780
781    /// Fetch all open orders
782    ///
783    /// # Arguments
784    ///
785    /// * `symbol` - Optional symbol to filter by
786    /// * `since` - Optional start timestamp
787    /// * `limit` - Optional limit on results
788    ///
789    /// # Returns
790    ///
791    /// A vector of open orders.
792    async fn fetch_open_orders(
793        &self,
794        symbol: Option<&str>,
795        since: Option<i64>,
796        limit: Option<u32>,
797    ) -> Result<Vec<Order>>;
798
799    /// Fetch closed orders
800    ///
801    /// # Arguments
802    ///
803    /// * `symbol` - Optional symbol to filter by
804    /// * `since` - Optional start timestamp
805    /// * `limit` - Optional limit on results
806    ///
807    /// # Returns
808    ///
809    /// A vector of closed orders.
810    async fn fetch_closed_orders(
811        &self,
812        symbol: Option<&str>,
813        since: Option<i64>,
814        limit: Option<u32>,
815    ) -> Result<Vec<Order>>;
816
817    // ==================== Account (Private API) ====================
818
819    /// Fetch account balance
820    ///
821    /// # Returns
822    ///
823    /// The account balance containing all currencies.
824    async fn fetch_balance(&self) -> Result<Balance>;
825
826    /// Fetch user's trade history
827    ///
828    /// # Arguments
829    ///
830    /// * `symbol` - Optional symbol to filter by
831    /// * `since` - Optional start timestamp
832    /// * `limit` - Optional limit on results
833    ///
834    /// # Returns
835    ///
836    /// A vector of user's trades.
837    async fn fetch_my_trades(
838        &self,
839        symbol: Option<&str>,
840        since: Option<i64>,
841        limit: Option<u32>,
842    ) -> Result<Vec<Trade>>;
843
844    // ==================== Helper Methods ====================
845
846    /// Get a specific market by symbol
847    ///
848    /// # Arguments
849    ///
850    /// * `symbol` - Trading pair symbol
851    ///
852    /// # Returns
853    ///
854    /// The market definition.
855    ///
856    /// # Errors
857    ///
858    /// Returns an error if the market is not found or markets are not loaded.
859    async fn market(&self, symbol: &str) -> Result<Market>;
860
861    /// Get all cached markets
862    ///
863    /// # Returns
864    ///
865    /// A HashMap of all markets indexed by symbol.
866    async fn markets(&self) -> HashMap<String, Market>;
867
868    /// Check if a symbol is valid and active
869    ///
870    /// # Arguments
871    ///
872    /// * `symbol` - Trading pair symbol
873    ///
874    /// # Returns
875    ///
876    /// True if the symbol exists and is active.
877    async fn is_symbol_active(&self, symbol: &str) -> bool {
878        self.market(symbol).await.map(|m| m.active).unwrap_or(false)
879    }
880}
881
882// ============================================================================
883// Type Aliases
884// ============================================================================
885
886/// Type alias for a boxed Exchange trait object
887///
888/// Use this when you need owned, heap-allocated exchange instances.
889pub type BoxedExchange = Box<dyn Exchange>;
890
891/// Type alias for an Arc-wrapped Exchange trait object
892///
893/// Use this when you need shared ownership across threads.
894pub type ArcExchange = Arc<dyn Exchange>;
895
896// ============================================================================
897// Tests
898// ============================================================================
899
900#[cfg(test)]
901mod tests {
902    use super::*;
903
904    #[test]
905    fn test_capabilities_default() {
906        let caps = ExchangeCapabilities::default();
907        assert!(!caps.fetch_ticker);
908        assert!(!caps.create_order);
909        assert!(!caps.websocket);
910    }
911
912    #[test]
913    fn test_capabilities_all() {
914        let caps = ExchangeCapabilities::all();
915        assert!(caps.fetch_ticker);
916        assert!(caps.create_order);
917        assert!(caps.websocket);
918        assert!(caps.fetch_ohlcv);
919        assert!(caps.fetch_balance);
920    }
921
922    #[test]
923    fn test_capabilities_public_only() {
924        let caps = ExchangeCapabilities::public_only();
925        assert!(caps.fetch_ticker);
926        assert!(caps.fetch_order_book);
927        assert!(caps.fetch_trades);
928        assert!(!caps.create_order);
929        assert!(!caps.fetch_balance);
930        assert!(!caps.websocket);
931    }
932
933    #[test]
934    fn test_capabilities_has() {
935        let caps = ExchangeCapabilities::all();
936        assert!(caps.has("fetchTicker"));
937        assert!(caps.has("createOrder"));
938        assert!(caps.has("websocket"));
939        assert!(!caps.has("unknownCapability"));
940    }
941
942    #[test]
943    fn test_capabilities_supported_list() {
944        let caps = ExchangeCapabilities::public_only();
945        let supported = caps.supported_capabilities();
946        assert!(supported.contains(&"fetchTicker"));
947        assert!(supported.contains(&"fetchOrderBook"));
948        assert!(!supported.contains(&"createOrder"));
949    }
950
951    #[test]
952    fn test_capabilities_equality() {
953        let caps1 = ExchangeCapabilities::all();
954        let caps2 = ExchangeCapabilities::all();
955        assert_eq!(caps1, caps2);
956
957        let caps3 = ExchangeCapabilities::public_only();
958        assert_ne!(caps1, caps3);
959    }
960}
961
962// ============================================================================
963// Property-Based Tests
964// ============================================================================
965
966#[cfg(test)]
967mod property_tests {
968    use super::*;
969    use crate::error::Error;
970    use proptest::prelude::*;
971    use std::thread;
972
973    // ==================== Strategies ====================
974
975    /// Strategy to generate arbitrary ExchangeCapabilities
976    fn arb_capabilities() -> impl Strategy<Value = ExchangeCapabilities> {
977        prop_oneof![
978            Just(ExchangeCapabilities::default()),
979            Just(ExchangeCapabilities::all()),
980            Just(ExchangeCapabilities::public_only()),
981            // Random capabilities
982            (
983                prop::bool::ANY,
984                prop::bool::ANY,
985                prop::bool::ANY,
986                prop::bool::ANY,
987                prop::bool::ANY,
988                prop::bool::ANY,
989            )
990                .prop_map(
991                    |(
992                        fetch_ticker,
993                        fetch_order_book,
994                        create_order,
995                        websocket,
996                        fetch_balance,
997                        fetch_ohlcv,
998                    )| {
999                        ExchangeCapabilities {
1000                            fetch_ticker,
1001                            fetch_order_book,
1002                            create_order,
1003                            websocket,
1004                            fetch_balance,
1005                            fetch_ohlcv,
1006                            ..Default::default()
1007                        }
1008                    }
1009                ),
1010        ]
1011    }
1012
1013    /// Strategy to generate arbitrary error messages
1014    fn arb_error_message() -> impl Strategy<Value = String> {
1015        prop_oneof![
1016            Just("".to_string()),
1017            "[a-zA-Z0-9 .,!?-]{1,100}",
1018            // Unicode messages
1019            "\\PC{1,50}",
1020        ]
1021    }
1022
1023    /// Strategy to generate arbitrary Error variants for testing error propagation
1024    fn arb_error() -> impl Strategy<Value = Error> {
1025        prop_oneof![
1026            // Authentication errors
1027            arb_error_message().prop_map(|msg| Error::authentication(msg)),
1028            // Invalid request errors
1029            arb_error_message().prop_map(|msg| Error::invalid_request(msg)),
1030            // Market not found errors
1031            arb_error_message().prop_map(|msg| Error::market_not_found(msg)),
1032            // Timeout errors
1033            arb_error_message().prop_map(|msg| Error::timeout(msg)),
1034            // Not implemented errors
1035            arb_error_message().prop_map(|msg| Error::not_implemented(msg)),
1036            // Network errors
1037            arb_error_message().prop_map(|msg| Error::network(msg)),
1038            // WebSocket errors
1039            arb_error_message().prop_map(|msg| Error::websocket(msg)),
1040        ]
1041    }
1042
1043    // ==================== Property 3: Thread Safety ====================
1044
1045    proptest! {
1046        #![proptest_config(ProptestConfig::with_cases(100))]
1047
1048        /// **Feature: unified-exchange-trait, Property 3: Thread Safety**
1049        ///
1050        /// *For any* exchange trait object, it should be possible to send it across
1051        /// thread boundaries (`Send`) and share references across threads (`Sync`).
1052        ///
1053        /// **Validates: Requirements 5.3**
1054        #[test]
1055        fn prop_exchange_capabilities_send_sync(caps in arb_capabilities()) {
1056            // Compile-time assertion: ExchangeCapabilities must be Send + Sync
1057            fn assert_send_sync<T: Send + Sync>(_: &T) {}
1058            assert_send_sync(&caps);
1059
1060            // Runtime verification: ExchangeCapabilities can be sent across threads
1061            let caps_clone = caps.clone();
1062            let handle = thread::spawn(move || {
1063                // Capabilities were successfully moved to another thread (Send)
1064                caps_clone.fetch_ticker
1065            });
1066            let result = handle.join().expect("Thread should not panic");
1067            prop_assert_eq!(result, caps.fetch_ticker);
1068        }
1069
1070        /// **Feature: unified-exchange-trait, Property 3: Thread Safety (Arc sharing)**
1071        ///
1072        /// *For any* ExchangeCapabilities, wrapping in Arc should allow safe sharing
1073        /// across multiple threads simultaneously.
1074        ///
1075        /// **Validates: Requirements 5.3**
1076        #[test]
1077        fn prop_exchange_capabilities_arc_sharing(caps in arb_capabilities()) {
1078            use std::sync::Arc;
1079
1080            let shared_caps = Arc::new(caps.clone());
1081
1082            // Spawn multiple threads that read from the shared capabilities
1083            let handles: Vec<_> = (0..4)
1084                .map(|_| {
1085                    let caps_ref = Arc::clone(&shared_caps);
1086                    thread::spawn(move || {
1087                        // Read various capabilities from different threads
1088                        (
1089                            caps_ref.fetch_ticker,
1090                            caps_ref.create_order,
1091                            caps_ref.websocket,
1092                        )
1093                    })
1094                })
1095                .collect();
1096
1097            // All threads should complete successfully with consistent values
1098            for handle in handles {
1099                let (fetch_ticker, create_order, websocket) =
1100                    handle.join().expect("Thread should not panic");
1101                prop_assert_eq!(fetch_ticker, caps.fetch_ticker);
1102                prop_assert_eq!(create_order, caps.create_order);
1103                prop_assert_eq!(websocket, caps.websocket);
1104            }
1105        }
1106
1107        /// **Feature: unified-exchange-trait, Property 3: Thread Safety (BoxedExchange type alias)**
1108        ///
1109        /// Verifies that the BoxedExchange type alias (Box<dyn Exchange>) satisfies
1110        /// Send + Sync bounds required for async runtime usage.
1111        ///
1112        /// **Validates: Requirements 5.3**
1113        #[test]
1114        fn prop_boxed_exchange_type_is_send_sync(_dummy in Just(())) {
1115            // Compile-time assertion: BoxedExchange must be Send
1116            fn assert_send<T: Send>() {}
1117            assert_send::<BoxedExchange>();
1118
1119            // Note: Box<dyn Exchange> is Send because Exchange: Send + Sync
1120            // This is a compile-time check that validates the trait bounds
1121            prop_assert!(true, "BoxedExchange type satisfies Send bound");
1122        }
1123
1124        /// **Feature: unified-exchange-trait, Property 3: Thread Safety (ArcExchange type alias)**
1125        ///
1126        /// Verifies that the ArcExchange type alias (Arc<dyn Exchange>) satisfies
1127        /// Send + Sync bounds required for shared ownership across threads.
1128        ///
1129        /// **Validates: Requirements 5.3**
1130        #[test]
1131        fn prop_arc_exchange_type_is_send_sync(_dummy in Just(())) {
1132            // Compile-time assertion: ArcExchange must be Send + Sync
1133            fn assert_send_sync<T: Send + Sync>() {}
1134            assert_send_sync::<ArcExchange>();
1135
1136            // This is a compile-time check that validates the trait bounds
1137            prop_assert!(true, "ArcExchange type satisfies Send + Sync bounds");
1138        }
1139    }
1140
1141    // ==================== Property 4: Error Propagation ====================
1142
1143    proptest! {
1144        #![proptest_config(ProptestConfig::with_cases(100))]
1145
1146        /// **Feature: unified-exchange-trait, Property 4: Error Propagation**
1147        ///
1148        /// *For any* async method call that fails, the error should be properly
1149        /// propagated through the `Result` type without panicking.
1150        ///
1151        /// **Validates: Requirements 5.4**
1152        #[test]
1153        fn prop_error_propagation_through_result(error in arb_error()) {
1154            // Store the error string before moving
1155            let error_string = error.to_string();
1156
1157            // Create a Result with the error
1158            let result: Result<()> = Err(error);
1159
1160            // Verify error can be extracted without panicking
1161            prop_assert!(result.is_err());
1162
1163            let extracted_error = result.unwrap_err();
1164
1165            // Error should preserve its display message
1166            prop_assert_eq!(
1167                extracted_error.to_string(),
1168                error_string,
1169                "Error display should be preserved"
1170            );
1171        }
1172
1173        /// **Feature: unified-exchange-trait, Property 4: Error Propagation (with context)**
1174        ///
1175        /// *For any* error with context attached, the context should be preserved
1176        /// and the error chain should be traversable.
1177        ///
1178        /// **Validates: Requirements 5.4**
1179        #[test]
1180        fn prop_error_propagation_with_context(
1181            base_error in arb_error(),
1182            context in "[a-zA-Z0-9 ]{1,50}"
1183        ) {
1184            // Add context to the error
1185            let error_with_context = base_error.context(context.clone());
1186
1187            // The error display should contain the context
1188            let display = error_with_context.to_string();
1189            prop_assert!(
1190                display.contains(&context),
1191                "Error display '{}' should contain context '{}'",
1192                display,
1193                context
1194            );
1195
1196            // Error should still be usable in Result
1197            let result: Result<()> = Err(error_with_context);
1198            prop_assert!(result.is_err());
1199        }
1200
1201        /// **Feature: unified-exchange-trait, Property 4: Error Propagation (Send + Sync)**
1202        ///
1203        /// *For any* error, it should be possible to send it across thread boundaries,
1204        /// which is essential for async error propagation.
1205        ///
1206        /// **Validates: Requirements 5.4**
1207        #[test]
1208        fn prop_error_send_across_threads(error in arb_error()) {
1209            // Compile-time assertion: Error must be Send + Sync
1210            fn assert_send_sync<T: Send + Sync + 'static>(_: &T) {}
1211            assert_send_sync(&error);
1212
1213            // Runtime verification: Error can be sent across threads
1214            let error_string = error.to_string();
1215            let handle = thread::spawn(move || {
1216                // Error was successfully moved to another thread (Send)
1217                error.to_string()
1218            });
1219            let result = handle.join().expect("Thread should not panic");
1220            prop_assert_eq!(result, error_string);
1221        }
1222
1223        /// **Feature: unified-exchange-trait, Property 4: Error Propagation (Result chain)**
1224        ///
1225        /// *For any* sequence of operations that may fail, errors should propagate
1226        /// correctly through the ? operator pattern.
1227        ///
1228        /// **Validates: Requirements 5.4**
1229        #[test]
1230        fn prop_error_propagation_chain(
1231            error_msg in arb_error_message(),
1232            should_fail_first in prop::bool::ANY,
1233            should_fail_second in prop::bool::ANY
1234        ) {
1235            fn operation_one(fail: bool, msg: &str) -> Result<i32> {
1236                if fail {
1237                    Err(Error::invalid_request(msg.to_string()))
1238                } else {
1239                    Ok(42)
1240                }
1241            }
1242
1243            fn operation_two(fail: bool, msg: &str, input: i32) -> Result<i32> {
1244                if fail {
1245                    Err(Error::invalid_request(msg.to_string()))
1246                } else {
1247                    Ok(input * 2)
1248                }
1249            }
1250
1251            fn chained_operations(
1252                fail_first: bool,
1253                fail_second: bool,
1254                msg: &str,
1255            ) -> Result<i32> {
1256                let result = operation_one(fail_first, msg)?;
1257                operation_two(fail_second, msg, result)
1258            }
1259
1260            let result = chained_operations(should_fail_first, should_fail_second, &error_msg);
1261
1262            // Verify the result matches expected behavior
1263            if should_fail_first {
1264                prop_assert!(result.is_err(), "Should fail on first operation");
1265            } else if should_fail_second {
1266                prop_assert!(result.is_err(), "Should fail on second operation");
1267            } else {
1268                prop_assert!(result.is_ok(), "Should succeed when no failures");
1269                prop_assert_eq!(result.unwrap(), 84, "Result should be 42 * 2 = 84");
1270            }
1271        }
1272
1273        /// **Feature: unified-exchange-trait, Property 4: Error Propagation (async compatibility)**
1274        ///
1275        /// *For any* error, it should be compatible with async/await patterns,
1276        /// meaning it can be returned from async functions.
1277        ///
1278        /// **Validates: Requirements 5.4**
1279        #[test]
1280        fn prop_error_async_compatible(error in arb_error()) {
1281            // Verify error implements required traits for async usage
1282            fn assert_async_compatible<T: Send + Sync + 'static + std::error::Error>(_: &T) {}
1283            assert_async_compatible(&error);
1284
1285            // Verify error can be boxed as dyn Error (required for anyhow compatibility)
1286            let boxed: Box<dyn std::error::Error + Send + Sync + 'static> = Box::new(error);
1287
1288            // Verify the boxed error can be sent across threads (simulating async task spawn)
1289            let handle = thread::spawn(move || {
1290                boxed.to_string()
1291            });
1292            let _ = handle.join().expect("Thread should not panic");
1293        }
1294    }
1295}