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}