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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms).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(¶ms) {
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(¶ms) {
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(¶ms) {
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(¶ms) {
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}