bybit_rust_api/rest/order/
order_client.rs

1use crate::rest::client::{RestClient, SecType, ServerResponse};
2use crate::rest::enums::category::Category;
3use crate::rest::order::dto::*;
4use anyhow::Result;
5use serde_json::json;
6
7pub struct OrderClient {
8    client: RestClient,
9}
10
11impl OrderClient {
12    pub fn new(client: RestClient) -> Self {
13        OrderClient { client }
14    }
15
16    /// Place an order
17    ///
18    /// API: POST /v5/order/create
19    /// https://bybit-exchange.github.io/docs/v5/order/create-order
20    pub async fn place_order(
21        &self,
22        order: PlaceOrderRequest,
23    ) -> Result<ServerResponse<PlaceOrderResponse>> {
24        let endpoint = "v5/order/create";
25        let body = serde_json::to_value(&order)?;
26
27        let response: ServerResponse<PlaceOrderResponse> =
28            self.client.post(endpoint, body, SecType::Signed).await?;
29        Ok(response)
30    }
31
32    /// Batch place orders (Option only)
33    ///
34    /// API: POST /v5/order/create-batch
35    /// https://bybit-exchange.github.io/docs/v5/order/batch-place
36    pub async fn batch_place_orders(
37        &self,
38        category: Category,
39        orders: Vec<PlaceOrderRequest>,
40    ) -> Result<ServerResponse<BatchPlaceOrderResponse>> {
41        let endpoint = "v5/order/create-batch";
42        let body = json!({
43            "category": category,
44            "request": orders
45        });
46
47        let response: ServerResponse<BatchPlaceOrderResponse> =
48            self.client.post(endpoint, body, SecType::Signed).await?;
49        Ok(response)
50    }
51
52    /// Amend order
53    ///
54    /// API: POST /v5/order/amend
55    /// https://bybit-exchange.github.io/docs/v5/order/amend-order
56    pub async fn amend_order(
57        &self,
58        amend_request: AmendOrderRequest,
59    ) -> Result<ServerResponse<AmendOrderResponse>> {
60        let endpoint = "v5/order/amend";
61        let body = serde_json::to_value(&amend_request)?;
62
63        let response: ServerResponse<AmendOrderResponse> =
64            self.client.post(endpoint, body, SecType::Signed).await?;
65        Ok(response)
66    }
67
68    /// Batch amend orders (Option only)
69    ///
70    /// API: POST /v5/order/amend-batch
71    /// https://bybit-exchange.github.io/docs/v5/order/batch-amend
72    pub async fn batch_amend_orders(
73        &self,
74        category: Category,
75        amendments: Vec<AmendOrderRequest>,
76    ) -> Result<ServerResponse<BatchAmendOrderResponse>> {
77        let endpoint = "v5/order/amend-batch";
78        let body = json!({
79            "category": category,
80            "request": amendments
81        });
82
83        let response: ServerResponse<BatchAmendOrderResponse> =
84            self.client.post(endpoint, body, SecType::Signed).await?;
85        Ok(response)
86    }
87
88    /// Cancel order
89    ///
90    /// API: POST /v5/order/cancel
91    /// https://bybit-exchange.github.io/docs/v5/order/cancel-order
92    pub async fn cancel_order(
93        &self,
94        cancel_request: CancelOrderRequest,
95    ) -> Result<ServerResponse<CancelOrderResponse>> {
96        let endpoint = "v5/order/cancel";
97        let body = serde_json::to_value(&cancel_request)?;
98
99        let response: ServerResponse<CancelOrderResponse> =
100            self.client.post(endpoint, body, SecType::Signed).await?;
101        Ok(response)
102    }
103
104    /// Batch cancel orders (Option only)
105    ///
106    /// API: POST /v5/order/cancel-batch
107    /// https://bybit-exchange.github.io/docs/v5/order/batch-cancel
108    pub async fn batch_cancel_orders(
109        &self,
110        category: Category,
111        cancellations: Vec<CancelOrderRequest>,
112    ) -> Result<ServerResponse<BatchCancelOrderResponse>> {
113        let endpoint = "v5/order/cancel-batch";
114        let body = json!({
115            "category": category,
116            "request": cancellations
117        });
118
119        let response: ServerResponse<BatchCancelOrderResponse> =
120            self.client.post(endpoint, body, SecType::Signed).await?;
121        Ok(response)
122    }
123
124    /// Cancel all orders
125    ///
126    /// API: POST /v5/order/cancel-all
127    /// https://bybit-exchange.github.io/docs/v5/order/cancel-all
128    pub async fn cancel_all_orders(
129        &self,
130        category: Category,
131        symbol: Option<&str>,
132        base_coin: Option<&str>,
133        settle_coin: Option<&str>,
134        order_filter: Option<&str>,
135    ) -> Result<ServerResponse<CancelAllOrdersResponse>> {
136        let endpoint = "v5/order/cancel-all";
137        let mut body = json!({
138            "category": category,
139        });
140
141        if let Some(symbol) = symbol {
142            body["symbol"] = json!(symbol);
143        }
144        if let Some(base_coin) = base_coin {
145            body["baseCoin"] = json!(base_coin);
146        }
147        if let Some(settle_coin) = settle_coin {
148            body["settleCoin"] = json!(settle_coin);
149        }
150        if let Some(order_filter) = order_filter {
151            body["orderFilter"] = json!(order_filter);
152        }
153
154        let response: ServerResponse<CancelAllOrdersResponse> =
155            self.client.post(endpoint, body, SecType::Signed).await?;
156        Ok(response)
157    }
158
159    /// Get open orders
160    ///
161    /// API: GET /v5/order/realtime
162    /// https://bybit-exchange.github.io/docs/v5/order/open-order
163    pub async fn get_open_orders(
164        &self,
165        category: Category,
166        symbol: Option<&str>,
167        base_coin: Option<&str>,
168        settle_coin: Option<&str>,
169        order_id: Option<&str>,
170        order_link_id: Option<&str>,
171        open_only: Option<i32>,
172        order_filter: Option<&str>,
173        limit: Option<i32>,
174        cursor: Option<&str>,
175    ) -> Result<ServerResponse<GetOrdersResponse>> {
176        let endpoint = "v5/order/realtime";
177        let mut params = json!({
178            "category": category,
179        });
180
181        if let Some(symbol) = symbol {
182            params["symbol"] = json!(symbol);
183        }
184        if let Some(base_coin) = base_coin {
185            params["baseCoin"] = json!(base_coin);
186        }
187        if let Some(settle_coin) = settle_coin {
188            params["settleCoin"] = json!(settle_coin);
189        }
190        if let Some(order_id) = order_id {
191            params["orderId"] = json!(order_id);
192        }
193        if let Some(order_link_id) = order_link_id {
194            params["orderLinkId"] = json!(order_link_id);
195        }
196        if let Some(open_only) = open_only {
197            params["openOnly"] = json!(open_only);
198        }
199        if let Some(order_filter) = order_filter {
200            params["orderFilter"] = json!(order_filter);
201        }
202        if let Some(limit) = limit {
203            params["limit"] = json!(limit);
204        }
205        if let Some(cursor) = cursor {
206            params["cursor"] = json!(cursor);
207        }
208
209        let response: ServerResponse<GetOrdersResponse> =
210            self.client.get(endpoint, params, SecType::Signed).await?;
211        Ok(response)
212    }
213
214    /// Get order history
215    ///
216    /// API: GET /v5/order/history
217    /// https://bybit-exchange.github.io/docs/v5/order/order-list
218    pub async fn get_order_history(
219        &self,
220        category: Category,
221        symbol: Option<&str>,
222        base_coin: Option<&str>,
223        settle_coin: Option<&str>,
224        order_id: Option<&str>,
225        order_link_id: Option<&str>,
226        order_filter: Option<&str>,
227        order_status: Option<&str>,
228        start_time: Option<i64>,
229        end_time: Option<i64>,
230        limit: Option<i32>,
231        cursor: Option<&str>,
232    ) -> Result<ServerResponse<GetOrdersResponse>> {
233        let endpoint = "v5/order/history";
234        let mut params = json!({
235            "category": category,
236        });
237
238        if let Some(symbol) = symbol {
239            params["symbol"] = json!(symbol);
240        }
241        if let Some(base_coin) = base_coin {
242            params["baseCoin"] = json!(base_coin);
243        }
244        if let Some(settle_coin) = settle_coin {
245            params["settleCoin"] = json!(settle_coin);
246        }
247        if let Some(order_id) = order_id {
248            params["orderId"] = json!(order_id);
249        }
250        if let Some(order_link_id) = order_link_id {
251            params["orderLinkId"] = json!(order_link_id);
252        }
253        if let Some(order_filter) = order_filter {
254            params["orderFilter"] = json!(order_filter);
255        }
256        if let Some(order_status) = order_status {
257            params["orderStatus"] = json!(order_status);
258        }
259        if let Some(start_time) = start_time {
260            params["startTime"] = json!(start_time);
261        }
262        if let Some(end_time) = end_time {
263            params["endTime"] = json!(end_time);
264        }
265        if let Some(limit) = limit {
266            params["limit"] = json!(limit);
267        }
268        if let Some(cursor) = cursor {
269            params["cursor"] = json!(cursor);
270        }
271
272        let response: ServerResponse<GetOrdersResponse> =
273            self.client.get(endpoint, params, SecType::Signed).await?;
274        Ok(response)
275    }
276
277    /// Get trade history
278    ///
279    /// API: GET /v5/execution/list
280    /// https://bybit-exchange.github.io/docs/v5/order/execution
281    pub async fn get_trade_history(
282        &self,
283        category: Category,
284        symbol: Option<&str>,
285        order_id: Option<&str>,
286        order_link_id: Option<&str>,
287        base_coin: Option<&str>,
288        start_time: Option<i64>,
289        end_time: Option<i64>,
290        exec_type: Option<&str>,
291        limit: Option<i32>,
292        cursor: Option<&str>,
293    ) -> Result<ServerResponse<GetTradeHistoryResponse>> {
294        let endpoint = "v5/execution/list";
295        let mut params = json!({
296            "category": category,
297        });
298
299        if let Some(symbol) = symbol {
300            params["symbol"] = json!(symbol);
301        }
302        if let Some(order_id) = order_id {
303            params["orderId"] = json!(order_id);
304        }
305        if let Some(order_link_id) = order_link_id {
306            params["orderLinkId"] = json!(order_link_id);
307        }
308        if let Some(base_coin) = base_coin {
309            params["baseCoin"] = json!(base_coin);
310        }
311        if let Some(start_time) = start_time {
312            params["startTime"] = json!(start_time);
313        }
314        if let Some(end_time) = end_time {
315            params["endTime"] = json!(end_time);
316        }
317        if let Some(exec_type) = exec_type {
318            params["execType"] = json!(exec_type);
319        }
320        if let Some(limit) = limit {
321            params["limit"] = json!(limit);
322        }
323        if let Some(cursor) = cursor {
324            params["cursor"] = json!(cursor);
325        }
326
327        let response: ServerResponse<GetTradeHistoryResponse> =
328            self.client.get(endpoint, params, SecType::Signed).await?;
329        Ok(response)
330    }
331
332    /// Check spot borrow quota
333    ///
334    /// API: GET /v5/order/spot-borrow-check
335    /// https://bybit-exchange.github.io/docs/v5/order/spot-borrow-quota
336    pub async fn spot_borrow_check(
337        &self,
338        category: &str,
339        symbol: &str,
340        side: &str,
341    ) -> Result<ServerResponse<serde_json::Value>> {
342        let endpoint = "v5/order/spot-borrow-check";
343        let params = json!({
344            "category": category,
345            "symbol": symbol,
346            "side": side,
347        });
348
349        let response = self.client.get(endpoint, params, SecType::Signed).await?;
350        Ok(response)
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use crate::rest::enums::order_type::OrderType;
358    use crate::rest::enums::side::Side;
359    use crate::rest::enums::time_in_force::TimeInForce;
360    use crate::rest::ApiKeyPair;
361
362    fn create_test_client() -> OrderClient {
363        let api_key_pair = ApiKeyPair::new(
364            "test".to_string(),
365            "test_key".to_string(),
366            "test_secret".to_string(),
367        );
368        let rest_client =
369            RestClient::new(api_key_pair, "https://api-testnet.bybit.com".to_string());
370        OrderClient::new(rest_client)
371    }
372
373    #[test]
374    fn test_order_client_creation() {
375        let _client = create_test_client();
376    }
377
378    #[test]
379    fn test_place_order_request_creation() {
380        let order = PlaceOrderRequest {
381            category: Category::Spot,
382            symbol: "BTCUSDT".to_string(),
383            side: Side::Buy,
384            order_type: OrderType::Limit,
385            qty: "0.001".to_string(),
386            price: Some("40000".to_string()),
387            time_in_force: Some(TimeInForce::GTC),
388            ..Default::default()
389        };
390
391        assert_eq!(order.symbol, "BTCUSDT");
392        assert_eq!(order.qty, "0.001");
393    }
394
395    #[test]
396    fn test_cancel_order_request_creation() {
397        let cancel_request = CancelOrderRequest {
398            category: Category::Spot,
399            symbol: "BTCUSDT".to_string(),
400            order_id: Some("123456".to_string()),
401            ..Default::default()
402        };
403
404        assert_eq!(cancel_request.symbol, "BTCUSDT");
405        assert_eq!(cancel_request.order_id, Some("123456".to_string()));
406    }
407
408    #[test]
409    fn test_batch_request_creation() {
410        let orders = vec![
411            PlaceOrderRequest {
412                category: Category::Spot,
413                symbol: "BTCUSDT".to_string(),
414                side: Side::Buy,
415                order_type: OrderType::Limit,
416                qty: "0.001".to_string(),
417                ..Default::default()
418            },
419            PlaceOrderRequest {
420                category: Category::Spot,
421                symbol: "ETHUSDT".to_string(),
422                side: Side::Buy,
423                order_type: OrderType::Limit,
424                qty: "0.01".to_string(),
425                ..Default::default()
426            },
427        ];
428
429        assert_eq!(orders.len(), 2);
430        assert_eq!(orders[0].symbol, "BTCUSDT");
431        assert_eq!(orders[1].symbol, "ETHUSDT");
432    }
433}