Skip to main content

bybit_client/api/
trade.rs

1//! Trade API endpoints for order management.
2
3use serde::Serialize;
4
5use crate::error::BybitError;
6use crate::http::HttpClient;
7use crate::types::trade::*;
8use crate::types::Category;
9
10/// Trade service for order management endpoints.
11#[derive(Debug, Clone)]
12pub struct TradeService {
13    http: HttpClient,
14}
15
16impl TradeService {
17    /// Create a new trade service.
18    pub fn new(http: HttpClient) -> Self {
19        Self { http }
20    }
21
22    /// Submit a new order.
23    ///
24    /// # Example
25    ///
26    /// ```no_run
27    /// # use bybit_client::{BybitClient, Category, Side, OrderType};
28    /// # use bybit_client::types::trade::OrderParams;
29    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
30    /// let client = BybitClient::new("api_key", "api_secret")?;
31    ///
32    /// // Place a market order.
33    /// let params = OrderParams::market(Category::Linear, "BTCUSDT", Side::Buy, "0.001");
34    /// let result = client.trade().submit_order(&params).await?;
35    /// println!("Order ID: {}", result.order_id);
36    ///
37    /// // Place a limit order.
38    /// let params = OrderParams::limit(Category::Spot, "BTCUSDT", Side::Buy, "0.001", "50000");
39    /// let result = client.trade().submit_order(&params).await?;
40    /// # Ok(())
41    /// # }
42    /// ```
43    pub async fn submit_order(&self, params: &OrderParams) -> Result<OrderResult, BybitError> {
44        self.http.post_signed("/v5/order/create", Some(params)).await
45    }
46
47    /// Amend an existing order.
48    ///
49    /// You can modify the order quantity, price, or TP/SL settings.
50    /// Either `order_id` or `order_link_id` must be provided.
51    ///
52    /// # Example
53    ///
54    /// ```no_run
55    /// # use bybit_client::{BybitClient, Category};
56    /// # use bybit_client::types::trade::AmendOrderParams;
57    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
58    /// let client = BybitClient::new("api_key", "api_secret")?;
59    ///
60    /// let params = AmendOrderParams::by_order_id(Category::Linear, "BTCUSDT", "order123")
61    ///     .price("51000")
62    ///     .qty("0.002");
63    /// let result = client.trade().amend_order(&params).await?;
64    /// # Ok(())
65    /// # }
66    /// ```
67    pub async fn amend_order(&self, params: &AmendOrderParams) -> Result<OrderResult, BybitError> {
68        self.http.post_signed("/v5/order/amend", Some(params)).await
69    }
70
71    /// Cancel an existing order.
72    ///
73    /// Either `order_id` or `order_link_id` must be provided.
74    ///
75    /// # Example
76    ///
77    /// ```no_run
78    /// # use bybit_client::{BybitClient, Category};
79    /// # use bybit_client::types::trade::CancelOrderParams;
80    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
81    /// let client = BybitClient::new("api_key", "api_secret")?;
82    ///
83    /// let params = CancelOrderParams::by_order_id(Category::Linear, "BTCUSDT", "order123");
84    /// let result = client.trade().cancel_order(&params).await?;
85    /// # Ok(())
86    /// # }
87    /// ```
88    pub async fn cancel_order(&self, params: &CancelOrderParams) -> Result<OrderResult, BybitError> {
89        self.http
90            .post_signed("/v5/order/cancel", Some(params))
91            .await
92    }
93
94    /// Cancel all active orders.
95    ///
96    /// # Example
97    ///
98    /// ```no_run
99    /// # use bybit_client::{BybitClient, Category};
100    /// # use bybit_client::types::trade::CancelAllOrdersParams;
101    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
102    /// let client = BybitClient::new("api_key", "api_secret")?;
103    ///
104    /// // Cancel all linear orders.
105    /// let params = CancelAllOrdersParams::new(Category::Linear);
106    /// let result = client.trade().cancel_all_orders(&params).await?;
107    ///
108    /// // Cancel all orders for a specific symbol.
109    /// let params = CancelAllOrdersParams::new(Category::Spot)
110    ///     .symbol("BTCUSDT");
111    /// let result = client.trade().cancel_all_orders(&params).await?;
112    /// # Ok(())
113    /// # }
114    /// ```
115    pub async fn cancel_all_orders(
116        &self,
117        params: &CancelAllOrdersParams,
118    ) -> Result<CancelAllResult, BybitError> {
119        self.http
120            .post_signed("/v5/order/cancel-all", Some(params))
121            .await
122    }
123
124    /// Get open/active orders.
125    ///
126    /// Returns real-time order data. For conditional orders, use `order_filter`.
127    ///
128    /// # Example
129    ///
130    /// ```no_run
131    /// # use bybit_client::{BybitClient, Category};
132    /// # use bybit_client::types::trade::GetOpenOrdersParams;
133    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
134    /// let client = BybitClient::new("api_key", "api_secret")?;
135    ///
136    /// let params = GetOpenOrdersParams::new(Category::Linear)
137    ///     .symbol("BTCUSDT")
138    ///     .limit(20);
139    /// let result = client.trade().get_open_orders(&params).await?;
140    /// for order in &result.list {
141    ///     println!("{}: {} {} @ {}", order.order_id, order.side, order.qty, order.price);
142    /// }
143    /// # Ok(())
144    /// # }
145    /// ```
146    pub async fn get_open_orders(
147        &self,
148        params: &GetOpenOrdersParams,
149    ) -> Result<OrderListResult, BybitError> {
150        self.http
151            .get_signed("/v5/order/realtime", Some(params))
152            .await
153    }
154
155    /// Get order history.
156    ///
157    /// Returns historical orders including filled, cancelled, and rejected orders.
158    ///
159    /// # Example
160    ///
161    /// ```no_run
162    /// # use bybit_client::{BybitClient, Category};
163    /// # use bybit_client::types::trade::GetOrderHistoryParams;
164    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
165    /// let client = BybitClient::new("api_key", "api_secret")?;
166    ///
167    /// let params = GetOrderHistoryParams::new(Category::Linear)
168    ///     .symbol("BTCUSDT")
169    ///     .limit(50);
170    /// let result = client.trade().get_order_history(&params).await?;
171    /// # Ok(())
172    /// # }
173    /// ```
174    pub async fn get_order_history(
175        &self,
176        params: &GetOrderHistoryParams,
177    ) -> Result<OrderListResult, BybitError> {
178        self.http
179            .get_signed("/v5/order/history", Some(params))
180            .await
181    }
182
183    /// Get trade execution list.
184    ///
185    /// Returns the trade history (fills) for your orders.
186    ///
187    /// # Example
188    ///
189    /// ```no_run
190    /// # use bybit_client::{BybitClient, Category};
191    /// # use bybit_client::types::trade::GetExecutionListParams;
192    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
193    /// let client = BybitClient::new("api_key", "api_secret")?;
194    ///
195    /// let params = GetExecutionListParams::new(Category::Linear)
196    ///     .symbol("BTCUSDT")
197    ///     .limit(50);
198    /// let result = client.trade().get_execution_list(&params).await?;
199    /// for exec in &result.list {
200    ///     println!("{}: {} {} @ {}", exec.exec_id, exec.side, exec.exec_qty, exec.exec_price);
201    /// }
202    /// # Ok(())
203    /// # }
204    /// ```
205    pub async fn get_execution_list(
206        &self,
207        params: &GetExecutionListParams,
208    ) -> Result<ExecutionListResult, BybitError> {
209        self.http
210            .get_signed("/v5/execution/list", Some(params))
211            .await
212    }
213
214    /// Get spot borrow quota.
215    ///
216    /// Check the maximum borrow amount for spot margin trading.
217    ///
218    /// # Example
219    ///
220    /// ```no_run
221    /// # use bybit_client::{BybitClient, Side};
222    /// # use bybit_client::types::trade::GetBorrowQuotaParams;
223    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
224    /// let client = BybitClient::new("api_key", "api_secret")?;
225    ///
226    /// let params = GetBorrowQuotaParams::new("BTCUSDT", Side::Buy);
227    /// let result = client.trade().get_borrow_quota(&params).await?;
228    /// println!("Max trade qty: {}", result.max_trade_qty);
229    /// # Ok(())
230    /// # }
231    /// ```
232    pub async fn get_borrow_quota(
233        &self,
234        params: &GetBorrowQuotaParams,
235    ) -> Result<BorrowQuotaResult, BybitError> {
236        self.http
237            .get_signed("/v5/order/spot-borrow-check", Some(params))
238            .await
239    }
240
241    /// Submit multiple orders in a single request.
242    ///
243    /// Supports up to 10 orders for options, 20 for USDT perpetual/USDC contracts.
244    ///
245    /// # Example
246    ///
247    /// ```no_run
248    /// # use bybit_client::{BybitClient, Category, Side};
249    /// # use bybit_client::types::trade::BatchOrderParams;
250    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
251    /// let client = BybitClient::new("api_key", "api_secret")?;
252    ///
253    /// let orders = vec![
254    ///     BatchOrderParams::limit("BTCUSDT", Side::Buy, "0.001", "50000")
255    ///         .order_link_id("batch_1"),
256    ///     BatchOrderParams::limit("BTCUSDT", Side::Buy, "0.001", "49000")
257    ///         .order_link_id("batch_2"),
258    /// ];
259    /// let result = client.trade().batch_submit_orders(Category::Linear, orders).await?;
260    /// for order in &result.list {
261    ///     println!("Created: {} - {}", order.order_id, order.order_link_id);
262    /// }
263    /// # Ok(())
264    /// # }
265    /// ```
266    pub async fn batch_submit_orders(
267        &self,
268        category: Category,
269        orders: Vec<BatchOrderParams>,
270    ) -> Result<BatchOperationResult, BybitError> {
271        #[derive(Serialize)]
272        #[serde(rename_all = "camelCase")]
273        struct BatchRequest {
274            category: Category,
275            request: Vec<BatchOrderParams>,
276        }
277
278        let request = BatchRequest {
279            category,
280            request: orders,
281        };
282
283        self.http
284            .post_signed("/v5/order/create-batch", Some(&request))
285            .await
286    }
287
288    /// Amend multiple orders in a single request.
289    ///
290    /// # Example
291    ///
292    /// ```no_run
293    /// # use bybit_client::{BybitClient, Category};
294    /// # use bybit_client::types::trade::BatchAmendOrderParams;
295    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
296    /// let client = BybitClient::new("api_key", "api_secret")?;
297    ///
298    /// let orders = vec![
299    ///     BatchAmendOrderParams::by_order_link_id("BTCUSDT", "batch_1")
300    ///         .price("51000"),
301    ///     BatchAmendOrderParams::by_order_link_id("BTCUSDT", "batch_2")
302    ///         .price("49500"),
303    /// ];
304    /// let result = client.trade().batch_amend_orders(Category::Linear, orders).await?;
305    /// # Ok(())
306    /// # }
307    /// ```
308    pub async fn batch_amend_orders(
309        &self,
310        category: Category,
311        orders: Vec<BatchAmendOrderParams>,
312    ) -> Result<BatchOperationResult, BybitError> {
313        #[derive(Serialize)]
314        #[serde(rename_all = "camelCase")]
315        struct BatchRequest {
316            category: Category,
317            request: Vec<BatchAmendOrderParams>,
318        }
319
320        let request = BatchRequest {
321            category,
322            request: orders,
323        };
324
325        self.http
326            .post_signed("/v5/order/amend-batch", Some(&request))
327            .await
328    }
329
330    /// Cancel multiple orders in a single request.
331    ///
332    /// # Example
333    ///
334    /// ```no_run
335    /// # use bybit_client::{BybitClient, Category};
336    /// # use bybit_client::types::trade::BatchCancelOrderParams;
337    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
338    /// let client = BybitClient::new("api_key", "api_secret")?;
339    ///
340    /// let orders = vec![
341    ///     BatchCancelOrderParams::by_order_link_id("BTCUSDT", "batch_1"),
342    ///     BatchCancelOrderParams::by_order_link_id("BTCUSDT", "batch_2"),
343    /// ];
344    /// let result = client.trade().batch_cancel_orders(Category::Linear, orders).await?;
345    /// # Ok(())
346    /// # }
347    /// ```
348    pub async fn batch_cancel_orders(
349        &self,
350        category: Category,
351        orders: Vec<BatchCancelOrderParams>,
352    ) -> Result<BatchOperationResult, BybitError> {
353        #[derive(Serialize)]
354        #[serde(rename_all = "camelCase")]
355        struct BatchRequest {
356            category: Category,
357            request: Vec<BatchCancelOrderParams>,
358        }
359
360        let request = BatchRequest {
361            category,
362            request: orders,
363        };
364
365        self.http
366            .post_signed("/v5/order/cancel-batch", Some(&request))
367            .await
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374    use crate::types::Side;
375
376    #[test]
377    fn test_order_params_serialization() {
378        let params = OrderParams::limit(Category::Linear, "BTCUSDT", Side::Buy, "0.001", "50000")
379            .time_in_force(crate::types::TimeInForce::GTC)
380            .order_link_id("test_order");
381
382        let json = match serde_json::to_string(&params) {
383            Ok(json) => json,
384            Err(err) => panic!("Failed to serialize order params: {}", err),
385        };
386        assert!(json.contains("\"category\":\"linear\""));
387        assert!(json.contains("\"symbol\":\"BTCUSDT\""));
388        assert!(json.contains("\"side\":\"Buy\""));
389        assert!(json.contains("\"orderType\":\"Limit\""));
390        assert!(json.contains("\"qty\":\"0.001\""));
391        assert!(json.contains("\"price\":\"50000\""));
392        assert!(json.contains("\"timeInForce\":\"GTC\""));
393        assert!(json.contains("\"orderLinkId\":\"test_order\""));
394    }
395
396    #[test]
397    fn test_amend_order_params_serialization() {
398        let params = AmendOrderParams::by_order_id(Category::Spot, "ETHUSDT", "order456")
399            .price("3000")
400            .qty("1.5");
401
402        let json = match serde_json::to_string(&params) {
403            Ok(json) => json,
404            Err(err) => panic!("Failed to serialize amend params: {}", err),
405        };
406        assert!(json.contains("\"category\":\"spot\""));
407        assert!(json.contains("\"symbol\":\"ETHUSDT\""));
408        assert!(json.contains("\"orderId\":\"order456\""));
409        assert!(json.contains("\"price\":\"3000\""));
410        assert!(json.contains("\"qty\":\"1.5\""));
411        assert!(!json.contains("orderLinkId"));
412    }
413
414    #[test]
415    fn test_cancel_order_params_serialization() {
416        let params =
417            CancelOrderParams::by_order_link_id(Category::Linear, "BTCUSDT", "my_order_789");
418
419        let json = match serde_json::to_string(&params) {
420            Ok(json) => json,
421            Err(err) => panic!("Failed to serialize cancel params: {}", err),
422        };
423        assert!(json.contains("\"category\":\"linear\""));
424        assert!(json.contains("\"symbol\":\"BTCUSDT\""));
425        assert!(json.contains("\"orderLinkId\":\"my_order_789\""));
426        assert!(!json.contains("orderId"));
427    }
428
429    #[test]
430    fn test_get_open_orders_params_serialization() {
431        let params = GetOpenOrdersParams::new(Category::Spot)
432            .symbol("BTCUSDT")
433            .limit(25);
434
435        let json = match serde_urlencoded::to_string(&params) {
436            Ok(json) => json,
437            Err(err) => panic!("Failed to serialize open orders params: {}", err),
438        };
439        assert!(json.contains("category=spot"));
440        assert!(json.contains("symbol=BTCUSDT"));
441        assert!(json.contains("limit=25"));
442    }
443
444    #[test]
445    fn test_batch_request_serialization() {
446        #[derive(serde::Serialize)]
447        #[serde(rename_all = "camelCase")]
448        struct BatchRequest {
449            category: Category,
450            request: Vec<BatchOrderParams>,
451        }
452
453        let orders = vec![
454            BatchOrderParams::limit("BTCUSDT", Side::Buy, "0.001", "50000"),
455            BatchOrderParams::market("ETHUSDT", Side::Sell, "0.1"),
456        ];
457
458        let request = BatchRequest {
459            category: Category::Linear,
460            request: orders,
461        };
462
463        let json = match serde_json::to_string(&request) {
464            Ok(json) => json,
465            Err(err) => panic!("Failed to serialize batch request: {}", err),
466        };
467        assert!(json.contains("\"category\":\"linear\""));
468        assert!(json.contains("\"request\":["));
469        assert!(json.contains("BTCUSDT"));
470        assert!(json.contains("ETHUSDT"));
471    }
472}