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