ccxt_core/capability/
mod.rs

1//! Exchange Capabilities Module
2//!
3//! This module provides efficient representation and manipulation of exchange capabilities
4//! using bitflags for compact storage (8 bytes instead of 46+ bytes) and type-safe operations.
5//!
6// Allow missing docs for bitflags-generated constants which are self-documenting by name
7#![allow(missing_docs)]
8//!
9//! # Design
10//!
11//! The capability system uses a hybrid approach:
12//! - `Capability` enum: Individual capability identifiers for type-safe API
13//! - `Capabilities` bitflags: Efficient storage and set operations
14//! - `ExchangeCapabilities` struct: High-level API with backward compatibility
15//!
16//! # Example
17//!
18//! ```rust
19//! use ccxt_core::capability::{Capability, Capabilities, ExchangeCapabilities};
20//!
21//! // Using bitflags directly
22//! let caps = Capabilities::MARKET_DATA | Capabilities::TRADING;
23//! assert!(caps.contains(Capabilities::FETCH_TICKER));
24//!
25//! // Using the high-level API
26//! let exchange_caps = ExchangeCapabilities::public_only();
27//! assert!(exchange_caps.has("fetchTicker"));
28//! assert!(!exchange_caps.has("createOrder"));
29//! ```
30
31use std::fmt;
32
33mod exchange_caps;
34mod flags;
35mod macros;
36
37pub use exchange_caps::{ExchangeCapabilities, ExchangeCapabilitiesBuilder, TraitCategory};
38pub use flags::Capabilities;
39
40// ============================================================================
41// Capability Enum
42// ============================================================================
43
44/// Individual capability identifier
45///
46/// This enum provides type-safe capability names that can be converted to/from
47/// bitflags and string representations. It supports all 46 exchange capabilities
48/// organized into logical categories.
49///
50/// # Categories
51///
52/// - **Market Data**: Public API endpoints for market information
53/// - **Trading**: Order management and execution
54/// - **Account**: Balance and trade history
55/// - **Funding**: Deposits, withdrawals, and transfers
56/// - **Margin**: Margin/futures trading features
57/// - **WebSocket**: Real-time data streaming
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59#[repr(u8)]
60pub enum Capability {
61    // ==================== Market Data (0-8) ====================
62    FetchMarkets = 0,
63    FetchCurrencies = 1,
64    FetchTicker = 2,
65    FetchTickers = 3,
66    FetchOrderBook = 4,
67    FetchTrades = 5,
68    FetchOhlcv = 6,
69    FetchStatus = 7,
70    FetchTime = 8,
71
72    // ==================== Trading (9-19) ====================
73    CreateOrder = 9,
74    CreateMarketOrder = 10,
75    CreateLimitOrder = 11,
76    CancelOrder = 12,
77    CancelAllOrders = 13,
78    EditOrder = 14,
79    FetchOrder = 15,
80    FetchOrders = 16,
81    FetchOpenOrders = 17,
82    FetchClosedOrders = 18,
83    FetchCanceledOrders = 19,
84
85    // ==================== Account (20-25) ====================
86    FetchBalance = 20,
87    FetchMyTrades = 21,
88    FetchDeposits = 22,
89    FetchWithdrawals = 23,
90    FetchTransactions = 24,
91    FetchLedger = 25,
92
93    // ==================== Funding (26-29) ====================
94    FetchDepositAddress = 26,
95    CreateDepositAddress = 27,
96    Withdraw = 28,
97    Transfer = 29,
98
99    // ==================== Margin Trading (30-36) ====================
100    FetchBorrowRate = 30,
101    FetchBorrowRates = 31,
102    FetchFundingRate = 32,
103    FetchFundingRates = 33,
104    FetchPositions = 34,
105    SetLeverage = 35,
106    SetMarginMode = 36,
107
108    // ==================== WebSocket (37-45) ====================
109    Websocket = 37,
110    WatchTicker = 38,
111    WatchTickers = 39,
112    WatchOrderBook = 40,
113    WatchTrades = 41,
114    WatchOhlcv = 42,
115    WatchBalance = 43,
116    WatchOrders = 44,
117    WatchMyTrades = 45,
118}
119
120impl Capability {
121    /// Total number of defined capabilities
122    pub const COUNT: usize = 46;
123
124    /// Get the CCXT-style camelCase name for this capability
125    pub const fn as_ccxt_name(&self) -> &'static str {
126        match self {
127            // Market Data
128            Self::FetchMarkets => "fetchMarkets",
129            Self::FetchCurrencies => "fetchCurrencies",
130            Self::FetchTicker => "fetchTicker",
131            Self::FetchTickers => "fetchTickers",
132            Self::FetchOrderBook => "fetchOrderBook",
133            Self::FetchTrades => "fetchTrades",
134            Self::FetchOhlcv => "fetchOHLCV",
135            Self::FetchStatus => "fetchStatus",
136            Self::FetchTime => "fetchTime",
137            // Trading
138            Self::CreateOrder => "createOrder",
139            Self::CreateMarketOrder => "createMarketOrder",
140            Self::CreateLimitOrder => "createLimitOrder",
141            Self::CancelOrder => "cancelOrder",
142            Self::CancelAllOrders => "cancelAllOrders",
143            Self::EditOrder => "editOrder",
144            Self::FetchOrder => "fetchOrder",
145            Self::FetchOrders => "fetchOrders",
146            Self::FetchOpenOrders => "fetchOpenOrders",
147            Self::FetchClosedOrders => "fetchClosedOrders",
148            Self::FetchCanceledOrders => "fetchCanceledOrders",
149            // Account
150            Self::FetchBalance => "fetchBalance",
151            Self::FetchMyTrades => "fetchMyTrades",
152            Self::FetchDeposits => "fetchDeposits",
153            Self::FetchWithdrawals => "fetchWithdrawals",
154            Self::FetchTransactions => "fetchTransactions",
155            Self::FetchLedger => "fetchLedger",
156            // Funding
157            Self::FetchDepositAddress => "fetchDepositAddress",
158            Self::CreateDepositAddress => "createDepositAddress",
159            Self::Withdraw => "withdraw",
160            Self::Transfer => "transfer",
161            // Margin
162            Self::FetchBorrowRate => "fetchBorrowRate",
163            Self::FetchBorrowRates => "fetchBorrowRates",
164            Self::FetchFundingRate => "fetchFundingRate",
165            Self::FetchFundingRates => "fetchFundingRates",
166            Self::FetchPositions => "fetchPositions",
167            Self::SetLeverage => "setLeverage",
168            Self::SetMarginMode => "setMarginMode",
169            // WebSocket
170            Self::Websocket => "websocket",
171            Self::WatchTicker => "watchTicker",
172            Self::WatchTickers => "watchTickers",
173            Self::WatchOrderBook => "watchOrderBook",
174            Self::WatchTrades => "watchTrades",
175            Self::WatchOhlcv => "watchOHLCV",
176            Self::WatchBalance => "watchBalance",
177            Self::WatchOrders => "watchOrders",
178            Self::WatchMyTrades => "watchMyTrades",
179        }
180    }
181
182    /// Parse a CCXT-style camelCase name into a Capability
183    pub fn from_ccxt_name(name: &str) -> Option<Self> {
184        match name {
185            // Market Data
186            "fetchMarkets" => Some(Self::FetchMarkets),
187            "fetchCurrencies" => Some(Self::FetchCurrencies),
188            "fetchTicker" => Some(Self::FetchTicker),
189            "fetchTickers" => Some(Self::FetchTickers),
190            "fetchOrderBook" => Some(Self::FetchOrderBook),
191            "fetchTrades" => Some(Self::FetchTrades),
192            "fetchOHLCV" => Some(Self::FetchOhlcv),
193            "fetchStatus" => Some(Self::FetchStatus),
194            "fetchTime" => Some(Self::FetchTime),
195            // Trading
196            "createOrder" => Some(Self::CreateOrder),
197            "createMarketOrder" => Some(Self::CreateMarketOrder),
198            "createLimitOrder" => Some(Self::CreateLimitOrder),
199            "cancelOrder" => Some(Self::CancelOrder),
200            "cancelAllOrders" => Some(Self::CancelAllOrders),
201            "editOrder" => Some(Self::EditOrder),
202            "fetchOrder" => Some(Self::FetchOrder),
203            "fetchOrders" => Some(Self::FetchOrders),
204            "fetchOpenOrders" => Some(Self::FetchOpenOrders),
205            "fetchClosedOrders" => Some(Self::FetchClosedOrders),
206            "fetchCanceledOrders" => Some(Self::FetchCanceledOrders),
207            // Account
208            "fetchBalance" => Some(Self::FetchBalance),
209            "fetchMyTrades" => Some(Self::FetchMyTrades),
210            "fetchDeposits" => Some(Self::FetchDeposits),
211            "fetchWithdrawals" => Some(Self::FetchWithdrawals),
212            "fetchTransactions" => Some(Self::FetchTransactions),
213            "fetchLedger" => Some(Self::FetchLedger),
214            // Funding
215            "fetchDepositAddress" => Some(Self::FetchDepositAddress),
216            "createDepositAddress" => Some(Self::CreateDepositAddress),
217            "withdraw" => Some(Self::Withdraw),
218            "transfer" => Some(Self::Transfer),
219            // Margin
220            "fetchBorrowRate" => Some(Self::FetchBorrowRate),
221            "fetchBorrowRates" => Some(Self::FetchBorrowRates),
222            "fetchFundingRate" => Some(Self::FetchFundingRate),
223            "fetchFundingRates" => Some(Self::FetchFundingRates),
224            "fetchPositions" => Some(Self::FetchPositions),
225            "setLeverage" => Some(Self::SetLeverage),
226            "setMarginMode" => Some(Self::SetMarginMode),
227            // WebSocket
228            "websocket" => Some(Self::Websocket),
229            "watchTicker" => Some(Self::WatchTicker),
230            "watchTickers" => Some(Self::WatchTickers),
231            "watchOrderBook" => Some(Self::WatchOrderBook),
232            "watchTrades" => Some(Self::WatchTrades),
233            "watchOHLCV" => Some(Self::WatchOhlcv),
234            "watchBalance" => Some(Self::WatchBalance),
235            "watchOrders" => Some(Self::WatchOrders),
236            "watchMyTrades" => Some(Self::WatchMyTrades),
237            _ => None,
238        }
239    }
240
241    /// Get all capabilities as an array
242    pub const fn all() -> [Self; Self::COUNT] {
243        [
244            Self::FetchMarkets,
245            Self::FetchCurrencies,
246            Self::FetchTicker,
247            Self::FetchTickers,
248            Self::FetchOrderBook,
249            Self::FetchTrades,
250            Self::FetchOhlcv,
251            Self::FetchStatus,
252            Self::FetchTime,
253            Self::CreateOrder,
254            Self::CreateMarketOrder,
255            Self::CreateLimitOrder,
256            Self::CancelOrder,
257            Self::CancelAllOrders,
258            Self::EditOrder,
259            Self::FetchOrder,
260            Self::FetchOrders,
261            Self::FetchOpenOrders,
262            Self::FetchClosedOrders,
263            Self::FetchCanceledOrders,
264            Self::FetchBalance,
265            Self::FetchMyTrades,
266            Self::FetchDeposits,
267            Self::FetchWithdrawals,
268            Self::FetchTransactions,
269            Self::FetchLedger,
270            Self::FetchDepositAddress,
271            Self::CreateDepositAddress,
272            Self::Withdraw,
273            Self::Transfer,
274            Self::FetchBorrowRate,
275            Self::FetchBorrowRates,
276            Self::FetchFundingRate,
277            Self::FetchFundingRates,
278            Self::FetchPositions,
279            Self::SetLeverage,
280            Self::SetMarginMode,
281            Self::Websocket,
282            Self::WatchTicker,
283            Self::WatchTickers,
284            Self::WatchOrderBook,
285            Self::WatchTrades,
286            Self::WatchOhlcv,
287            Self::WatchBalance,
288            Self::WatchOrders,
289            Self::WatchMyTrades,
290        ]
291    }
292
293    /// Convert to bit position for bitflags
294    #[inline]
295    pub const fn bit_position(&self) -> u64 {
296        1u64 << (*self as u8)
297    }
298
299    /// Get the trait category this capability belongs to
300    pub const fn trait_category(&self) -> TraitCategory {
301        match self {
302            // Market Data
303            Self::FetchMarkets
304            | Self::FetchCurrencies
305            | Self::FetchTicker
306            | Self::FetchTickers
307            | Self::FetchOrderBook
308            | Self::FetchTrades
309            | Self::FetchOhlcv
310            | Self::FetchStatus
311            | Self::FetchTime => TraitCategory::MarketData,
312
313            // Trading
314            Self::CreateOrder
315            | Self::CreateMarketOrder
316            | Self::CreateLimitOrder
317            | Self::CancelOrder
318            | Self::CancelAllOrders
319            | Self::EditOrder
320            | Self::FetchOrder
321            | Self::FetchOrders
322            | Self::FetchOpenOrders
323            | Self::FetchClosedOrders
324            | Self::FetchCanceledOrders => TraitCategory::Trading,
325
326            // Account
327            Self::FetchBalance
328            | Self::FetchMyTrades
329            | Self::FetchDeposits
330            | Self::FetchWithdrawals
331            | Self::FetchTransactions
332            | Self::FetchLedger => TraitCategory::Account,
333
334            // Funding
335            Self::FetchDepositAddress
336            | Self::CreateDepositAddress
337            | Self::Withdraw
338            | Self::Transfer => TraitCategory::Funding,
339
340            // Margin
341            Self::FetchBorrowRate
342            | Self::FetchBorrowRates
343            | Self::FetchFundingRate
344            | Self::FetchFundingRates
345            | Self::FetchPositions
346            | Self::SetLeverage
347            | Self::SetMarginMode => TraitCategory::Margin,
348
349            // WebSocket
350            Self::Websocket
351            | Self::WatchTicker
352            | Self::WatchTickers
353            | Self::WatchOrderBook
354            | Self::WatchTrades
355            | Self::WatchOhlcv
356            | Self::WatchBalance
357            | Self::WatchOrders
358            | Self::WatchMyTrades => TraitCategory::WebSocket,
359        }
360    }
361}
362
363impl fmt::Display for Capability {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        write!(f, "{}", self.as_ccxt_name())
366    }
367}
368
369// ============================================================================
370// Unit Tests
371// ============================================================================
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use crate::capabilities;
377
378    #[test]
379    fn test_capability_count() {
380        assert_eq!(Capability::COUNT, 46);
381    }
382
383    #[test]
384    fn test_capability_ccxt_names() {
385        assert_eq!(Capability::FetchTicker.as_ccxt_name(), "fetchTicker");
386        assert_eq!(Capability::CreateOrder.as_ccxt_name(), "createOrder");
387        assert_eq!(Capability::FetchOhlcv.as_ccxt_name(), "fetchOHLCV");
388        assert_eq!(Capability::WatchOhlcv.as_ccxt_name(), "watchOHLCV");
389    }
390
391    #[test]
392    fn test_capability_from_ccxt_name() {
393        assert_eq!(
394            Capability::from_ccxt_name("fetchTicker"),
395            Some(Capability::FetchTicker)
396        );
397        assert_eq!(
398            Capability::from_ccxt_name("createOrder"),
399            Some(Capability::CreateOrder)
400        );
401        assert_eq!(Capability::from_ccxt_name("unknown"), None);
402    }
403
404    #[test]
405    fn test_capabilities_bitflags() {
406        let caps = Capabilities::FETCH_TICKER | Capabilities::CREATE_ORDER;
407        assert!(caps.contains(Capabilities::FETCH_TICKER));
408        assert!(caps.contains(Capabilities::CREATE_ORDER));
409        assert!(!caps.contains(Capabilities::WEBSOCKET));
410    }
411
412    #[test]
413    fn test_capabilities_presets() {
414        assert!(Capabilities::MARKET_DATA.contains(Capabilities::FETCH_TICKER));
415        assert!(Capabilities::MARKET_DATA.contains(Capabilities::FETCH_ORDER_BOOK));
416        assert!(!Capabilities::MARKET_DATA.contains(Capabilities::CREATE_ORDER));
417
418        assert!(Capabilities::TRADING.contains(Capabilities::CREATE_ORDER));
419        assert!(Capabilities::TRADING.contains(Capabilities::CANCEL_ORDER));
420        assert!(!Capabilities::TRADING.contains(Capabilities::FETCH_TICKER));
421    }
422
423    #[test]
424    fn test_capabilities_has() {
425        let caps = Capabilities::MARKET_DATA;
426        assert!(caps.has("fetchTicker"));
427        assert!(caps.has("fetchOrderBook"));
428        assert!(!caps.has("createOrder"));
429        assert!(!caps.has("unknownCapability"));
430    }
431
432    #[test]
433    fn test_capabilities_count() {
434        assert_eq!(Capabilities::empty().count(), 0);
435        assert_eq!(Capabilities::FETCH_TICKER.count(), 1);
436        assert_eq!(
437            (Capabilities::FETCH_TICKER | Capabilities::CREATE_ORDER).count(),
438            2
439        );
440        assert_eq!(Capabilities::MARKET_DATA.count(), 9);
441    }
442
443    #[test]
444    fn test_capabilities_from_iter() {
445        let caps = Capabilities::from_iter([
446            Capability::FetchTicker,
447            Capability::CreateOrder,
448            Capability::Websocket,
449        ]);
450        assert!(caps.contains(Capabilities::FETCH_TICKER));
451        assert!(caps.contains(Capabilities::CREATE_ORDER));
452        assert!(caps.contains(Capabilities::WEBSOCKET));
453        assert_eq!(caps.count(), 3);
454    }
455
456    #[test]
457    fn test_exchange_capabilities_all() {
458        let caps = ExchangeCapabilities::all();
459        assert!(caps.fetch_ticker());
460        assert!(caps.create_order());
461        assert!(caps.websocket());
462        assert!(caps.fetch_positions());
463    }
464
465    #[test]
466    fn test_exchange_capabilities_public_only() {
467        let caps = ExchangeCapabilities::public_only();
468        assert!(caps.fetch_ticker());
469        assert!(caps.fetch_order_book());
470        assert!(!caps.create_order());
471        assert!(!caps.fetch_balance());
472    }
473
474    #[test]
475    fn test_exchange_capabilities_has() {
476        let caps = ExchangeCapabilities::all();
477        assert!(caps.has("fetchTicker"));
478        assert!(caps.has("createOrder"));
479        assert!(!caps.has("unknownCapability"));
480    }
481
482    #[test]
483    fn test_exchange_capabilities_builder() {
484        let caps = ExchangeCapabilities::builder()
485            .market_data()
486            .trading()
487            .build();
488
489        assert!(caps.fetch_ticker());
490        assert!(caps.create_order());
491        assert!(!caps.websocket());
492    }
493
494    #[test]
495    fn test_exchange_capabilities_builder_with_capability() {
496        let caps = ExchangeCapabilities::builder()
497            .capability(Capability::FetchTicker)
498            .capability(Capability::Websocket)
499            .build();
500
501        assert!(caps.fetch_ticker());
502        assert!(caps.websocket());
503        assert!(!caps.create_order());
504    }
505
506    #[test]
507    fn test_exchange_capabilities_builder_without() {
508        let caps = ExchangeCapabilities::builder()
509            .all()
510            .without_capability(Capability::Websocket)
511            .build();
512
513        assert!(caps.fetch_ticker());
514        assert!(caps.create_order());
515        assert!(!caps.websocket());
516    }
517
518    #[test]
519    fn test_exchange_capabilities_presets() {
520        let spot = ExchangeCapabilities::spot_exchange();
521        assert!(spot.fetch_ticker());
522        assert!(spot.create_order());
523        assert!(spot.websocket());
524        assert!(!spot.fetch_positions());
525
526        let futures = ExchangeCapabilities::futures_exchange();
527        assert!(futures.fetch_ticker());
528        assert!(futures.create_order());
529        assert!(futures.fetch_positions());
530        assert!(futures.set_leverage());
531    }
532
533    #[test]
534    fn test_capabilities_macro() {
535        let caps = capabilities!(MARKET_DATA);
536        assert!(caps.contains(Capabilities::FETCH_TICKER));
537
538        let caps = capabilities!(FETCH_TICKER | CREATE_ORDER);
539        assert!(caps.contains(Capabilities::FETCH_TICKER));
540        assert!(caps.contains(Capabilities::CREATE_ORDER));
541
542        let caps = capabilities!(MARKET_DATA, TRADING);
543        assert!(caps.contains(Capabilities::FETCH_TICKER));
544        assert!(caps.contains(Capabilities::CREATE_ORDER));
545    }
546
547    #[test]
548    fn test_capability_bit_positions() {
549        assert_eq!(Capability::FetchMarkets.bit_position(), 1 << 0);
550        assert_eq!(Capability::FetchTicker.bit_position(), 1 << 2);
551        assert_eq!(Capability::CreateOrder.bit_position(), 1 << 9);
552        assert_eq!(Capability::Websocket.bit_position(), 1 << 37);
553    }
554
555    #[test]
556    fn test_memory_efficiency() {
557        assert_eq!(std::mem::size_of::<ExchangeCapabilities>(), 8);
558        assert_eq!(std::mem::size_of::<Capabilities>(), 8);
559    }
560
561    #[test]
562    fn test_trait_category_all() {
563        let categories = TraitCategory::all();
564        assert_eq!(categories.len(), 7);
565        assert!(categories.contains(&TraitCategory::PublicExchange));
566        assert!(categories.contains(&TraitCategory::MarketData));
567        assert!(categories.contains(&TraitCategory::Trading));
568        assert!(categories.contains(&TraitCategory::Account));
569        assert!(categories.contains(&TraitCategory::Margin));
570        assert!(categories.contains(&TraitCategory::Funding));
571        assert!(categories.contains(&TraitCategory::WebSocket));
572    }
573
574    #[test]
575    fn test_capability_trait_category() {
576        assert_eq!(
577            Capability::FetchTicker.trait_category(),
578            TraitCategory::MarketData
579        );
580        assert_eq!(
581            Capability::CreateOrder.trait_category(),
582            TraitCategory::Trading
583        );
584        assert_eq!(
585            Capability::FetchBalance.trait_category(),
586            TraitCategory::Account
587        );
588    }
589
590    #[test]
591    fn test_supports_market_data() {
592        let caps = ExchangeCapabilities::public_only();
593        assert!(caps.supports_market_data());
594
595        let caps = ExchangeCapabilities::none();
596        assert!(!caps.supports_market_data());
597    }
598
599    #[test]
600    fn test_supports_trading() {
601        let caps = ExchangeCapabilities::all();
602        assert!(caps.supports_trading());
603
604        let caps = ExchangeCapabilities::public_only();
605        assert!(!caps.supports_trading());
606    }
607
608    #[test]
609    fn test_supports_full_exchange() {
610        let caps = ExchangeCapabilities::all();
611        assert!(caps.supports_full_exchange());
612
613        let caps = ExchangeCapabilities::spot_exchange();
614        assert!(!caps.supports_full_exchange());
615    }
616}