ccxt_core/traits/
trading.rs

1//! Trading trait definition.
2//!
3//! The `Trading` trait provides methods for order management operations
4//! including creating, canceling, and fetching orders. These operations
5//! require authentication.
6//!
7//! # Object Safety
8//!
9//! This trait is designed to be object-safe, allowing for dynamic dispatch via
10//! trait objects (`dyn Trading`).
11//!
12//! # Example
13//!
14//! ```rust,ignore
15//! use ccxt_core::traits::Trading;
16//! use ccxt_core::types::params::OrderParams;
17//! use rust_decimal_macros::dec;
18//!
19//! async fn place_order(exchange: &dyn Trading) -> Result<(), ccxt_core::Error> {
20//!     // Market buy
21//!     let order = exchange.market_buy("BTC/USDT", dec!(0.01)).await?;
22//!     
23//!     // Limit sell with options
24//!     let order = exchange.create_order(
25//!         OrderParams::limit_sell("BTC/USDT", dec!(0.01), dec!(50000))
26//!             .time_in_force(ccxt_core::types::TimeInForce::GTC)
27//!     ).await?;
28//!     
29//!     Ok(())
30//! }
31//! ```
32
33use async_trait::async_trait;
34use rust_decimal::Decimal;
35
36use crate::error::Result;
37use crate::traits::PublicExchange;
38use crate::types::{Order, params::OrderParams};
39
40/// Trait for order management operations.
41///
42/// This trait provides methods for creating, canceling, and fetching orders.
43/// All methods require authentication and are async.
44///
45/// # Supertrait
46///
47/// Requires `PublicExchange` as a supertrait to access exchange metadata
48/// and capabilities.
49///
50/// # Thread Safety
51///
52/// This trait requires `Send + Sync` bounds (inherited from `PublicExchange`)
53/// to ensure safe usage across thread boundaries in async contexts.
54#[async_trait]
55pub trait Trading: PublicExchange {
56    // ========================================================================
57    // Order Creation
58    // ========================================================================
59
60    /// Create a new order using OrderParams builder.
61    ///
62    /// This is the primary method for creating orders. Use the `OrderParams`
63    /// builder for ergonomic order construction.
64    ///
65    /// # Arguments
66    ///
67    /// * `params` - Order parameters including symbol, side, type, amount, price
68    ///
69    /// # Example
70    ///
71    /// ```rust,ignore
72    /// use ccxt_core::types::params::OrderParams;
73    /// use rust_decimal_macros::dec;
74    ///
75    /// // Market buy
76    /// let order = exchange.create_order(
77    ///     OrderParams::market_buy("BTC/USDT", dec!(0.01))
78    /// ).await?;
79    ///
80    /// // Limit sell with custom options
81    /// let order = exchange.create_order(
82    ///     OrderParams::limit_sell("BTC/USDT", dec!(0.01), dec!(50000))
83    ///         .time_in_force(TimeInForce::IOC)
84    ///         .client_id("my-order-123")
85    /// ).await?;
86    /// ```
87    async fn create_order(&self, params: OrderParams) -> Result<Order>;
88
89    /// Convenience method for market buy order.
90    ///
91    /// # Arguments
92    ///
93    /// * `symbol` - Trading pair symbol (e.g., "BTC/USDT")
94    /// * `amount` - Order amount
95    async fn market_buy(&self, symbol: &str, amount: Decimal) -> Result<Order> {
96        self.create_order(OrderParams::market_buy(symbol, amount))
97            .await
98    }
99
100    /// Convenience method for market sell order.
101    ///
102    /// # Arguments
103    ///
104    /// * `symbol` - Trading pair symbol
105    /// * `amount` - Order amount
106    async fn market_sell(&self, symbol: &str, amount: Decimal) -> Result<Order> {
107        self.create_order(OrderParams::market_sell(symbol, amount))
108            .await
109    }
110
111    /// Convenience method for limit buy order.
112    ///
113    /// # Arguments
114    ///
115    /// * `symbol` - Trading pair symbol
116    /// * `amount` - Order amount
117    /// * `price` - Limit price
118    async fn limit_buy(&self, symbol: &str, amount: Decimal, price: Decimal) -> Result<Order> {
119        self.create_order(OrderParams::limit_buy(symbol, amount, price))
120            .await
121    }
122
123    /// Convenience method for limit sell order.
124    ///
125    /// # Arguments
126    ///
127    /// * `symbol` - Trading pair symbol
128    /// * `amount` - Order amount
129    /// * `price` - Limit price
130    async fn limit_sell(&self, symbol: &str, amount: Decimal, price: Decimal) -> Result<Order> {
131        self.create_order(OrderParams::limit_sell(symbol, amount, price))
132            .await
133    }
134
135    // ========================================================================
136    // Order Cancellation
137    // ========================================================================
138
139    /// Cancel an existing order.
140    ///
141    /// # Arguments
142    ///
143    /// * `id` - Order ID to cancel
144    /// * `symbol` - Trading pair symbol (required for most exchanges)
145    ///
146    /// # Example
147    ///
148    /// ```rust,ignore
149    /// let cancelled = exchange.cancel_order("12345", "BTC/USDT").await?;
150    /// println!("Cancelled order: {}", cancelled.id);
151    /// ```
152    async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order>;
153
154    /// Cancel all orders for a symbol.
155    ///
156    /// # Arguments
157    ///
158    /// * `symbol` - Trading pair symbol
159    ///
160    /// # Returns
161    ///
162    /// Returns a vector of cancelled orders.
163    async fn cancel_all_orders(&self, symbol: &str) -> Result<Vec<Order>>;
164
165    // ========================================================================
166    // Order Queries
167    // ========================================================================
168
169    /// Fetch a specific order by ID.
170    ///
171    /// # Arguments
172    ///
173    /// * `id` - Order ID
174    /// * `symbol` - Trading pair symbol (required for most exchanges)
175    ///
176    /// # Example
177    ///
178    /// ```rust,ignore
179    /// let order = exchange.fetch_order("12345", "BTC/USDT").await?;
180    /// println!("Order status: {:?}", order.status);
181    /// ```
182    async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order>;
183
184    /// Fetch open orders.
185    ///
186    /// # Arguments
187    ///
188    /// * `symbol` - Optional trading pair symbol. If `None`, returns all open orders.
189    ///
190    /// # Example
191    ///
192    /// ```rust,ignore
193    /// // All open orders
194    /// let orders = exchange.fetch_open_orders(None).await?;
195    ///
196    /// // Open orders for specific symbol
197    /// let orders = exchange.fetch_open_orders(Some("BTC/USDT")).await?;
198    /// ```
199    async fn fetch_open_orders(&self, symbol: Option<&str>) -> Result<Vec<Order>>;
200
201    /// Fetch closed orders with pagination.
202    ///
203    /// # Arguments
204    ///
205    /// * `symbol` - Optional trading pair symbol
206    /// * `since` - Optional start timestamp in milliseconds (i64) since Unix epoch
207    /// * `limit` - Optional maximum number of orders to return
208    ///
209    /// # Timestamp Format
210    ///
211    /// The `since` parameter uses `i64` milliseconds since Unix epoch:
212    /// - `1609459200000` = January 1, 2021, 00:00:00 UTC
213    /// - `chrono::Utc::now().timestamp_millis()` = Current time
214    /// - `chrono::Utc::now().timestamp_millis() - 86400000` = 24 hours ago
215    ///
216    /// # Example
217    ///
218    /// ```rust,ignore
219    /// // Recent closed orders
220    /// let orders = exchange.fetch_closed_orders(Some("BTC/USDT"), None, Some(100)).await?;
221    ///
222    /// // Closed orders since timestamp
223    /// let orders = exchange.fetch_closed_orders(
224    ///     Some("BTC/USDT"),
225    ///     Some(1609459200000i64),
226    ///     Some(50)
227    /// ).await?;
228    /// ```
229    async fn fetch_closed_orders(
230        &self,
231        symbol: Option<&str>,
232        since: Option<i64>,
233        limit: Option<u32>,
234    ) -> Result<Vec<Order>>;
235
236    // ========================================================================
237    // Deprecated u64 Wrapper Methods (Backward Compatibility)
238    // ========================================================================
239
240    /// Fetch closed orders with u64 timestamp filtering (deprecated).
241    ///
242    /// **DEPRECATED**: Use `fetch_closed_orders` with i64 timestamps instead.
243    /// This method is provided for backward compatibility during migration.
244    ///
245    /// # Migration
246    ///
247    /// ```rust,ignore
248    /// // Old code (deprecated)
249    /// let orders = exchange.fetch_closed_orders_u64(Some("BTC/USDT"), Some(1609459200000u64), Some(100)).await?;
250    ///
251    /// // New code (recommended)
252    /// let orders = exchange.fetch_closed_orders(Some("BTC/USDT"), Some(1609459200000i64), Some(100)).await?;
253    /// ```
254    #[deprecated(
255        since = "0.1.0",
256        note = "Use fetch_closed_orders with i64 timestamps. Convert using TimestampUtils::u64_to_i64()"
257    )]
258    async fn fetch_closed_orders_u64(
259        &self,
260        symbol: Option<&str>,
261        since: Option<u64>,
262        limit: Option<u32>,
263    ) -> Result<Vec<Order>> {
264        use crate::time::TimestampConversion;
265
266        let since_i64 = since.to_i64()?;
267        self.fetch_closed_orders(symbol, since_i64, limit).await
268    }
269}
270
271/// Type alias for boxed Trading trait object.
272pub type BoxedTrading = Box<dyn Trading>;
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::capability::ExchangeCapabilities;
278    use crate::types::{OrderSide, OrderStatus, OrderType, Timeframe};
279
280    // Mock implementation for testing trait object safety
281    struct MockExchange;
282
283    impl PublicExchange for MockExchange {
284        fn id(&self) -> &str {
285            "mock"
286        }
287        fn name(&self) -> &str {
288            "Mock Exchange"
289        }
290        fn capabilities(&self) -> ExchangeCapabilities {
291            ExchangeCapabilities::all()
292        }
293        fn timeframes(&self) -> Vec<Timeframe> {
294            vec![Timeframe::H1]
295        }
296    }
297
298    #[async_trait]
299    impl Trading for MockExchange {
300        async fn create_order(&self, params: OrderParams) -> Result<Order> {
301            Ok(Order::new(
302                "test-order-123".to_string(),
303                params.symbol,
304                params.order_type,
305                params.side,
306                params.amount,
307                params.price,
308                OrderStatus::Open,
309            ))
310        }
311
312        async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
313            Ok(Order::new(
314                id.to_string(),
315                symbol.to_string(),
316                OrderType::Limit,
317                OrderSide::Buy,
318                Decimal::ZERO,
319                None,
320                OrderStatus::Cancelled,
321            ))
322        }
323
324        async fn cancel_all_orders(&self, _symbol: &str) -> Result<Vec<Order>> {
325            Ok(vec![])
326        }
327
328        async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
329            Ok(Order::new(
330                id.to_string(),
331                symbol.to_string(),
332                OrderType::Limit,
333                OrderSide::Buy,
334                Decimal::ZERO,
335                None,
336                OrderStatus::Open,
337            ))
338        }
339
340        async fn fetch_open_orders(&self, _symbol: Option<&str>) -> Result<Vec<Order>> {
341            Ok(vec![])
342        }
343
344        async fn fetch_closed_orders(
345            &self,
346            _symbol: Option<&str>,
347            _since: Option<i64>,
348            _limit: Option<u32>,
349        ) -> Result<Vec<Order>> {
350            Ok(vec![])
351        }
352    }
353
354    #[test]
355    fn test_trait_object_safety() {
356        // Verify trait is object-safe by creating a trait object
357        let _exchange: BoxedTrading = Box::new(MockExchange);
358    }
359
360    #[tokio::test]
361    async fn test_create_order() {
362        use rust_decimal_macros::dec;
363
364        let exchange = MockExchange;
365
366        let order = exchange
367            .create_order(OrderParams::market_buy("BTC/USDT", dec!(0.01)))
368            .await
369            .unwrap();
370
371        assert_eq!(order.symbol, "BTC/USDT");
372        assert_eq!(order.side, OrderSide::Buy);
373        assert_eq!(order.order_type, OrderType::Market);
374    }
375
376    #[tokio::test]
377    async fn test_convenience_methods() {
378        use rust_decimal_macros::dec;
379
380        let exchange = MockExchange;
381
382        // Test market_buy
383        let order = exchange.market_buy("BTC/USDT", dec!(0.01)).await.unwrap();
384        assert_eq!(order.side, OrderSide::Buy);
385        assert_eq!(order.order_type, OrderType::Market);
386
387        // Test market_sell
388        let order = exchange.market_sell("BTC/USDT", dec!(0.01)).await.unwrap();
389        assert_eq!(order.side, OrderSide::Sell);
390        assert_eq!(order.order_type, OrderType::Market);
391
392        // Test limit_buy
393        let order = exchange
394            .limit_buy("BTC/USDT", dec!(0.01), dec!(50000))
395            .await
396            .unwrap();
397        assert_eq!(order.side, OrderSide::Buy);
398        assert_eq!(order.order_type, OrderType::Limit);
399
400        // Test limit_sell
401        let order = exchange
402            .limit_sell("BTC/USDT", dec!(0.01), dec!(50000))
403            .await
404            .unwrap();
405        assert_eq!(order.side, OrderSide::Sell);
406        assert_eq!(order.order_type, OrderType::Limit);
407    }
408
409    #[tokio::test]
410    async fn test_cancel_order() {
411        let exchange = MockExchange;
412
413        let order = exchange.cancel_order("12345", "BTC/USDT").await.unwrap();
414        assert_eq!(order.id, "12345");
415        assert_eq!(order.status, OrderStatus::Cancelled);
416    }
417
418    #[tokio::test]
419    async fn test_fetch_orders() {
420        let exchange = MockExchange;
421
422        // Test fetch_order
423        let order = exchange.fetch_order("12345", "BTC/USDT").await.unwrap();
424        assert_eq!(order.id, "12345");
425
426        // Test fetch_open_orders
427        let orders = exchange.fetch_open_orders(Some("BTC/USDT")).await.unwrap();
428        assert!(orders.is_empty());
429
430        // Test fetch_closed_orders
431        let orders = exchange
432            .fetch_closed_orders(Some("BTC/USDT"), None, Some(100))
433            .await
434            .unwrap();
435        assert!(orders.is_empty());
436    }
437}