bybit/
trade.rs

1use serde_json::{json, Value};
2
3use crate::api::{Trade, API};
4use crate::client::Client;
5use crate::errors::BybitError;
6use crate::model::{
7    AmendOrderRequest, AmendOrderResponse, BatchAmendRequest, BatchAmendResponse,
8    BatchCancelRequest, BatchCancelResponse, BatchPlaceRequest, BatchPlaceResponse,
9    CancelOrderRequest, CancelOrderResponse, CancelallRequest, CancelallResponse, Category,
10    OpenOrdersRequest, OpenOrdersResponse, OrderHistoryRequest, OrderHistoryResponse, OrderRequest,
11    OrderResponse, OrderType, RequestType, Side, TradeHistoryRequest, TradeHistoryResponse,
12};
13use crate::util::{build_json_request, build_request, date_to_milliseconds, generate_random_uid};
14
15use std::borrow::Cow;
16use std::collections::BTreeMap;
17
18#[derive(Clone)]
19pub struct Trader {
20    pub client: Client,
21    pub recv_window: u16,
22}
23
24/// Creates an order with various options for different account types and contract types.
25///
26/// # Account Coverage
27/// - Unified account: Spot, USDT perpetual, USDC contract, Inverse contract, Option
28/// - Classic account: Spot, USDT perpetual, Inverse contract
29///
30/// # Order Types
31/// - Limit Order: Must specify quantity and price.
32/// - Market Order: Executes at the best market price. Price parameter can be empty.
33/// - Conditional Order: Set `triggerPrice` to convert to a conditional order.
34///
35/// # Time In Force Strategies
36/// - GTC (Good Till Cancelled)
37/// - IOC (Immediate Or Cancel)
38/// - FOK (Fill Or Kill)
39/// - PostOnly: Cancelled if it would be filled immediately when submitted.
40///
41/// # Take Profit / Stop Loss
42/// - Can be set during order placement and modified later.
43///
44/// # Order Quantity
45/// - Only positive numbers are supported for perpetual contract orders.
46///
47/// # Order Price
48/// - Required for limit orders. Must be higher than liquidation price if in position.
49///
50/// # Order Link ID
51/// - Custom active order ID up to 36 characters.
52///
53/// # Order Limits
54/// - Futures: 500 active orders per contract, 10 conditional orders per account.
55/// - Spot: 500 total orders, 30 open TP/SL orders, 30 open conditional orders.
56/// - Option: 50 open orders.
57///
58/// # Rate Limit
59/// - Refer to the rate limit table. Contact client manager for increases.
60///
61/// # Risk Control
62/// - Bybit monitors API requests. Exceeding daily limits may lead to restrictions.
63///
64/// # Spot Stop Order in Different Account Types
65/// - Classic account: New order ID upon stop order trigger.
66/// - Unified account: Order ID remains unchanged upon stop order trigger.
67pub enum Action<'a> {
68    Order(OrderRequest<'a>, bool),
69    Amend(AmendOrderRequest<'a>, bool),
70    Cancel(CancelOrderRequest<'a>, bool),
71}
72
73impl Trader {
74    pub async fn place_custom_order<'b>(
75        &self,
76        req: OrderRequest<'_>,
77    ) -> Result<OrderResponse, BybitError> {
78        let action = Action::Order(req, false);
79        let parameters = Self::build_orders(action);
80
81        let request = build_json_request(&parameters);
82        let response: OrderResponse = self
83            .client
84            .post_signed(
85                API::Trade(Trade::Place),
86                self.recv_window.into(),
87                Some(request),
88            )
89            .await?;
90        Ok(response)
91    }
92
93    pub async fn place_futures_limit_order(
94        &self,
95        category: Category,
96        symbol: &str,
97        side: Side,
98        qty: f64,
99        price: f64,
100        mode: u8,
101    ) -> Result<OrderResponse, BybitError> {
102        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
103        let req = OrderRequest {
104            category,
105            symbol: Cow::Borrowed(symbol),
106            side,
107            qty,
108            order_type: OrderType::Limit,
109            position_idx: Some(mode),
110            order_link_id: Some(generate_random_uid(36).into()),
111            price: Some(price),
112            ..Default::default()
113        };
114        parameters.insert("category".into(), req.category.as_str().into());
115        parameters.insert("symbol".into(), req.symbol.into_owned());
116        parameters.insert("orderType".into(), req.order_type.as_str().into());
117        parameters.insert("side".into(), req.side.as_str().into());
118        if let Some(v) = req.order_link_id {
119            parameters.insert("orderLinkId".into(), v.into());
120        }
121        parameters.insert("qty".into(), req.qty.to_string());
122        if let Some(v) = req.position_idx {
123            match v {
124                0 | 1 | 2 => {
125                    parameters.insert("positionIdx".into(), v.to_string());
126                }
127                _ => return Err(BybitError::from("Invalid position index".to_string())),
128            }
129        }
130        if let Some(v) = req.price {
131            parameters.insert("price".into(), v.to_string());
132        }
133        parameters.insert("timeInForce".into(), "GTC".into());
134        let request = build_json_request(&parameters);
135        let response: OrderResponse = self
136            .client
137            .post_signed(
138                API::Trade(Trade::Place),
139                self.recv_window.into(),
140                Some(request),
141            )
142            .await?;
143        Ok(response)
144    }
145
146    pub async fn amend_order<'b>(
147        &self,
148        req: AmendOrderRequest<'_>,
149    ) -> Result<AmendOrderResponse, BybitError> {
150        let action = Action::Amend(req, false);
151        let parameters = Self::build_orders(action);
152        let request = build_json_request(&parameters);
153        let response: AmendOrderResponse = self
154            .client
155            .post_signed(
156                API::Trade(Trade::Amend),
157                self.recv_window.into(),
158                Some(request),
159            )
160            .await?;
161        Ok(response)
162    }
163    pub async fn cancel_order<'b>(
164        &self,
165        req: CancelOrderRequest<'_>,
166    ) -> Result<CancelOrderResponse, BybitError> {
167        let action = Action::Cancel(req, false);
168        let parameters = Self::build_orders(action);
169        let request = build_json_request(&parameters);
170        let response: CancelOrderResponse = self
171            .client
172            .post_signed(
173                API::Trade(Trade::Cancel),
174                self.recv_window.into(),
175                Some(request),
176            )
177            .await?;
178        Ok(response)
179    }
180    pub async fn get_open_orders<'b>(
181        &self,
182        req: OpenOrdersRequest<'_>,
183    ) -> Result<OpenOrdersResponse, BybitError> {
184        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
185
186        parameters.insert("category".into(), req.category.as_str().into());
187        parameters.insert("symbol".into(), req.symbol.into());
188
189        if let Some(base_coin) = req.base_coin {
190            parameters.insert("baseCoin".into(), base_coin.into());
191        }
192        if let Some(settle_coin) = req.settle_coin {
193            parameters.insert("settleCoin".into(), settle_coin.into());
194        }
195        if let Some(order_id) = req.order_id {
196            parameters.insert("orderId".into(), order_id.into());
197        }
198        if let Some(order_link_id) = req.order_link_id {
199            parameters.insert("orderLinkId".into(), order_link_id.into());
200        }
201        if let Some(open_only) = req.open_only {
202            if matches!(open_only, 0 | 1 | 2) {
203                parameters.insert("openOnly".into(), open_only.to_string().into());
204            }
205        }
206        if let Some(order_filter) = req.order_filter {
207            parameters.insert("orderFilter".into(), order_filter.into());
208        }
209        if let Some(limit) = req.limit {
210            parameters.insert("limit".into(), limit.to_string().into());
211        }
212
213        let request = build_request(&parameters);
214        let response: OpenOrdersResponse = self
215            .client
216            .get_signed(API::Trade(Trade::OpenOrders), 5000, Some(request))
217            .await?;
218
219        Ok(response)
220    }
221    pub async fn cancel_all_orders<'b>(
222        &self,
223        req: CancelallRequest<'_>,
224    ) -> Result<CancelallResponse, BybitError> {
225        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
226        parameters.insert("category".into(), req.category.as_str().into());
227        parameters.insert("symbol".into(), req.symbol.into());
228        if let Some(base_coin) = req.base_coin {
229            parameters.insert("baseCoin".into(), base_coin.into());
230        }
231        if let Some(settle_coin) = req.settle_coin {
232            parameters.insert("settleCoin".into(), settle_coin.into());
233        }
234        if let Some(order_filter) = req.order_filter {
235            parameters.insert("orderFilter".into(), order_filter.into());
236        }
237        if let Some(stop_order_type) = req.stop_order_type {
238            parameters.insert("stopOrderType".into(), stop_order_type.into());
239        }
240        let request = build_json_request(&parameters);
241        let response: CancelallResponse = self
242            .client
243            .post_signed(
244                API::Trade(Trade::CancelAll),
245                self.recv_window.into(),
246                Some(request),
247            )
248            .await?;
249        Ok(response)
250    }
251
252    /// Retrieves the order history based on the given request parameters.
253    ///
254    /// # Arguments
255    /// * `req` - An instance of `OrderHistoryRequest` containing the request parameters.
256    ///
257    /// # Returns
258    /// A `Result` wrapping `OrderHistory` which contains the historical orders' data.
259    /// If the operation fails, it returns an error.
260    ///
261    pub async fn get_order_history<'b>(
262        &self,
263        req: OrderHistoryRequest<'_>,
264    ) -> Result<OrderHistoryResponse, BybitError> {
265        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
266        parameters.insert("category".into(), req.category.as_str().into());
267        req.symbol
268            .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
269        req.base_coin
270            .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
271        req.settle_coin
272            .map(|settle_coin| parameters.insert("settleCoin".into(), settle_coin.into()));
273        req.order_id
274            .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
275        req.order_link_id
276            .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
277        req.order_filter
278            .map(|order_filter| parameters.insert("orderFilter".into(), order_filter.into()));
279        req.order_status
280            .map(|order_status| parameters.insert("orderStatus".into(), order_status.into()));
281        req.start_time
282            .and_then(|start_time| Some(date_to_milliseconds(start_time.as_ref())))
283            .map(|start_millis| parameters.insert("startTime".into(), start_millis.to_string()));
284        req.end_time
285            .and_then(|end_time| Some(date_to_milliseconds(end_time.as_ref())))
286            .map(|end_millis| parameters.insert("endTime".into(), end_millis.to_string()));
287        req.limit
288            .map(|limit| parameters.insert("limit".into(), limit.to_string()));
289
290        let request = build_request(&parameters);
291        let response: OrderHistoryResponse = self
292            .client
293            .get_signed(
294                API::Trade(Trade::History),
295                self.recv_window.into(),
296                Some(request),
297            )
298            .await?;
299        Ok(response)
300    }
301
302    /// Retrieves the trade history for a specific trading pair, order, or time range.
303    ///
304    /// # Arguments
305    ///
306    /// * `req` - A `TradeHistoryRequest` containing the parameters for the trade history query.
307    ///
308    /// # Returns
309    ///
310    /// Returns a `Result<TradeHistoryResponse, BybitError>` containing the trade history data if the query is successful, or an error detailing the problem if the query fails.
311    pub async fn get_trade_history<'b>(
312        &self,
313        req: TradeHistoryRequest<'_>,
314    ) -> Result<TradeHistoryResponse, BybitError> {
315        // Create a new BTreeMap to store the parameters for the request
316        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
317
318        // Add the category to the request parameters
319        parameters.insert("category".into(), req.category.as_str().into());
320
321        // Add the symbol to the request parameters if it is specified
322        req.symbol
323            .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
324
325        // Add the order ID to the request parameters if it is specified
326        req.order_id
327            .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
328
329        // Add the order link ID to the request parameters if it is specified
330        req.order_link_id
331            .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
332
333        // Add the base coin to the request parameters if it is specified
334        req.base_coin
335            .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
336
337        // Add the start time to the request parameters if it is specified
338        req.start_time
339            .and_then(|start_time| Some(date_to_milliseconds(start_time.as_ref())))
340            .map(|start_millis| parameters.insert("startTime".into(), start_millis.to_string()));
341
342        // Add the end time to the request parameters if it is specified
343        req.end_time
344            .and_then(|end_time| Some(date_to_milliseconds(end_time.as_ref())))
345            .map(|end_millis| parameters.insert("endTime".into(), end_millis.to_string()));
346
347        // Add the limit to the request parameters if it is specified
348        req.limit
349            .map(|limit| parameters.insert("limit".into(), limit.to_string()));
350
351        // Add the execution type to the request parameters if it is specified
352        req.exec_type
353            .map(|exec_type| parameters.insert("execType".into(), exec_type.into()));
354
355        // Build the request from the parameters
356        let request = build_request(&parameters);
357
358        // Send the signed GET request to the Bybit API to retrieve the trade history
359        let response: TradeHistoryResponse = self
360            .client
361            .get_signed(
362                API::Trade(Trade::TradeHistory),
363                self.recv_window.into(),
364                Some(request),
365            )
366            .await?;
367
368        // Return the response
369        Ok(response)
370    }
371
372    /// Asynchronously places a batch of orders using the Bybit API.
373    ///
374    /// # Arguments
375    ///
376    /// * `req` - The `BatchPlaceRequest` containing the orders to be placed.
377    ///
378    /// # Returns
379    ///
380    /// Returns a `Result` containing a `BatchPlaceResponse` on success or a `BybitError` on failure.
381    pub async fn batch_place_order<'b>(
382        &self,
383        req: BatchPlaceRequest<'_>,
384    ) -> Result<BatchPlaceResponse, BybitError> {
385        // Create a new BTreeMap to store the parameters for the request
386        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
387
388        // Check if the category is valid and insert it into the parameters
389        match req.category {
390            Category::Linear | Category::Inverse | Category::Option => {
391                parameters.insert("category".into(), req.category.as_str().into());
392            }
393            // If the category is invalid, print an error message
394            _ => {
395                println!("Invalid category");
396            }
397        }
398
399        // Create an empty array to store the orders
400        let mut requests_array: Vec<Value> = Vec::new();
401
402        // Iterate over each order in the requests and build the orders
403        for value in req.requests {
404            // Create an Action to represent the order
405            let action = Action::Order(value, true);
406
407            // Build the orders using the build_orders method
408            let order_object = Self::build_orders(action);
409
410            // Convert the order object to a JSON Value
411            let built_orders = json!(order_object);
412
413            // Add the built orders to the requests array
414            requests_array.push(built_orders);
415        }
416
417        // Insert the requests array into the parameters
418        parameters.insert("request".into(), Value::Array(requests_array));
419
420        // Build the request from the parameters
421        let request = build_json_request(&parameters);
422
423        // Send the signed POST request to the Bybit API to place the batch of orders
424        let response: BatchPlaceResponse = self
425            .client
426            .post_signed(
427                API::Trade(Trade::BatchPlace),
428                self.recv_window.into(),
429                Some(request),
430            )
431            .await?;
432
433        // Return the response
434        Ok(response)
435    }
436
437    /// Sends a batch request to amend multiple orders at once.
438    ///
439    /// # Arguments
440    ///
441    /// * `req` - The `BatchAmendRequest` containing the orders to amend.
442    ///
443    /// # Returns
444    ///
445    /// A `Result` containing a `BatchAmendResponse` on success or a `BybitError` on failure.
446    pub async fn batch_amend_order<'b>(
447        &self,
448        req: BatchAmendRequest<'_>,
449    ) -> Result<BatchAmendResponse, BybitError> {
450        // Create an empty map to store the parameters
451        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
452
453        // Insert the category into the parameters
454        match req.category {
455            Category::Linear | Category::Inverse | Category::Option => {
456                parameters.insert("category".into(), req.category.as_str().into());
457            }
458            _ => {
459                // Print an error message if the category is invalid
460                println!("Invalid category");
461            }
462        }
463
464        // Create an empty array to store the requests
465        let mut requests_array: Vec<Value> = Vec::new();
466
467        // Iterate over each request in the BatchAmendRequest
468        for value in req.requests {
469            // Create an Action to represent the request
470            let action = Action::Amend(value, true);
471
472            // Build the orders using the build_orders method
473            let amend_object = Self::build_orders(action); // Assuming this returns the correct object structure
474
475            // Convert the amend object to a JSON Value
476            let built_amends = json!(amend_object);
477
478            // Add the built amends to the requests array
479            requests_array.push(built_amends);
480        }
481
482        // Insert the requests array into the parameters
483        parameters.insert("request".into(), Value::Array(requests_array));
484
485        // Build the request from the parameters
486        let request = build_json_request(&parameters);
487
488        // Send the signed POST request to the Bybit API to amend the batch of orders
489        let response: BatchAmendResponse = self
490            .client
491            .post_signed(
492                API::Trade(Trade::BatchAmend),
493                self.recv_window.into(),
494                Some(request),
495            )
496            .await?;
497
498        // Return the response
499        Ok(response)
500    }
501
502    /// Cancel a batch of orders from the Bybit API
503    ///
504    /// This function will send a signed POST request to the Bybit API to cancel
505    /// a batch of orders. The request should contain the category, symbol, order_id,
506    /// and order_link_id for each order to be cancelled.
507    ///
508    /// # Arguments
509    ///
510    /// * `req` - A `BatchCancelRequest` containing the details of the orders to
511    /// be cancelled.
512    ///
513    /// # Returns
514    ///
515    /// A `BatchCancelResponse` containing the result of the request.
516    ///
517    /// # Errors
518    ///
519    /// Returns an error if the request fails or if the Bybit API returns an
520    /// error response.
521    pub async fn batch_cancel_order<'b>(
522        &self,
523        req: BatchCancelRequest<'_>,
524    ) -> Result<BatchCancelResponse, BybitError> {
525        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
526        match req.category {
527            Category::Linear | Category::Inverse | Category::Option => {
528                parameters.insert("category".into(), req.category.as_str().into());
529            }
530            _ => {
531                println!("Invalid category");
532            }
533        }
534        let mut requests_array: Vec<Value> = Vec::new();
535        for value in req.requests {
536            let action = Action::Cancel(value, true);
537            let cancel_object = Self::build_orders(action); // Assuming this returns the correct object structure
538            let built_cancels = json!(cancel_object);
539            requests_array.push(built_cancels);
540        }
541        parameters.insert("request".into(), Value::Array(requests_array));
542        let request = build_json_request(&parameters);
543        let response: BatchCancelResponse = self
544            .client
545            .post_signed(
546                API::Trade(Trade::BatchCancel),
547                self.recv_window.into(),
548                Some(request),
549            )
550            .await?;
551        Ok(response)
552    }
553
554    pub async fn get_borrow_quota_spot(&self) {
555        // TODO: Implement this function
556        todo!("This function has not yet been implemented");
557    }
558
559    pub async fn set_dcp_options(&self) {
560        // TODO: Implement this function
561        todo!("This function has not yet been implemented");
562    }
563
564    pub fn build_orders<'b>(action: Action<'_>) -> BTreeMap<String, Value> {
565        let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
566        match action {
567            Action::Order(req, batch) => {
568                if batch == false {
569                    parameters.insert("category".into(), req.category.as_str().into());
570                }
571                parameters.insert("symbol".into(), req.symbol.into());
572                if let Some(leverage) = req.is_leverage {
573                    if leverage {
574                        // Whether to borrow. Valid for Unified spot only. 0(default): false then spot trading, 1: true then margin trading
575                        parameters.insert("leverage".into(), 1.into());
576                    }
577                }
578                parameters.insert("side".into(), req.side.as_str().into());
579                parameters.insert("orderType".into(), req.order_type.as_str().into());
580
581                parameters.insert("qty".into(), req.qty.to_string().into());
582                if let Some(market_unit) = req.market_unit {
583                    parameters.insert("marketUnit".into(), market_unit.to_string().into());
584                }
585                if let Some(price) = req.price {
586                    parameters.insert("price".into(), price.to_string().into());
587                }
588                if let Some(trigger_direction) = req.trigger_direction {
589                    if trigger_direction {
590                        parameters.insert("triggerDirection".into(), 1.into());
591                    } else {
592                        parameters.insert("triggerDirection".into(), 2.into());
593                    }
594                }
595                if let Some(order_filter) = req.order_filter {
596                    parameters.insert("orderFilter".into(), order_filter.into());
597                }
598                if let Some(trigger_price) = req.trigger_price {
599                    parameters.insert("triggerPrice".into(), trigger_price.to_string().into());
600                }
601                if let Some(trigger) = req.trigger_by {
602                    parameters.insert("triggerBy".into(), trigger.into());
603                }
604                if let Some(iv) = req.order_iv {
605                    parameters.insert("orderIv".into(), iv.to_string().into());
606                }
607                if let Some(time_in_force) = req.time_in_force {
608                    parameters.insert("timeInForce".into(), time_in_force.into());
609                }
610                if let Some(v) = req.position_idx {
611                    match v {
612                        0 | 1 | 2 => {
613                            parameters.insert("positionIdx".into(), v.to_string().into());
614                        }
615                        _ => println!("Invalid position idx"),
616                    }
617                }
618                if let Some(order_link_id) = req.order_link_id {
619                    parameters.insert("orderLinkId".into(), order_link_id.into());
620                } else {
621                    let uuid = generate_random_uid(36);
622                    parameters.insert("orderLinkId".into(), uuid.into());
623                }
624                if let Some(price) = req.take_profit {
625                    parameters.insert("takeProfit".into(), price.to_string().into());
626                }
627                if let Some(price) = req.stop_loss {
628                    parameters.insert("stopLoss".into(), price.to_string().into());
629                }
630                if let Some(kind) = req.tp_trigger_by {
631                    parameters.insert("tpTriggerBy".into(), kind.into());
632                }
633                if let Some(kind) = req.sl_trigger_by {
634                    parameters.insert("slTriggerBy".into(), kind.into());
635                }
636                if let Some(reduce) = req.reduce_only {
637                    parameters.insert("reduceOnly".into(), reduce.into());
638                }
639                if let Some(close) = req.close_on_trigger {
640                    parameters.insert("closeOnTrigger".into(), close.into());
641                }
642                if let Some(v) = req.mmp {
643                    parameters.insert("mmp".into(), v.into());
644                }
645                if let Some(v) = req.tpsl_mode {
646                    parameters.insert("tpslMode".into(), v.into());
647                }
648                if let Some(v) = req.tp_limit_price {
649                    parameters.insert("tpTriggerPrice".into(), v.to_string().into());
650                }
651                if let Some(v) = req.sl_limit_price {
652                    parameters.insert("slTriggerPrice".into(), v.to_string().into());
653                }
654                if let Some(v) = req.tp_order_type {
655                    parameters.insert("tpOrderType".into(), v.into());
656                }
657                if let Some(v) = req.sl_order_type {
658                    parameters.insert("slOrderType".into(), v.into());
659                }
660            }
661            Action::Amend(req, batch) => {
662                if batch == false {
663                    parameters.insert("category".into(), req.category.as_str().into());
664                }
665                parameters.insert("symbol".into(), req.symbol.into());
666                if let Some(v) = req.order_id {
667                    parameters.insert("orderId".into(), v.into());
668                }
669                if let Some(v) = req.order_link_id {
670                    parameters.insert("orderLinkId".into(), v.into());
671                }
672                if let Some(v) = req.order_iv {
673                    parameters.insert("orderIv".into(), v.to_string().into());
674                }
675                if let Some(v) = req.trigger_price {
676                    parameters.insert("triggerPrice".into(), v.to_string().into());
677                }
678                parameters.insert("qty".into(), req.qty.into());
679                if let Some(v) = req.price {
680                    parameters.insert("price".into(), v.to_string().into());
681                }
682                if let Some(v) = req.tpsl_mode {
683                    parameters.insert("tpslMode".into(), v.into());
684                }
685                if let Some(v) = req.take_profit {
686                    parameters.insert("takeProfit".into(), v.to_string().into());
687                }
688                if let Some(v) = req.stop_loss {
689                    parameters.insert("stopLoss".into(), v.to_string().into());
690                }
691                if let Some(v) = req.tp_trigger_by {
692                    parameters.insert("tpTriggerBy".into(), v.into());
693                }
694                if let Some(v) = req.sl_trigger_by {
695                    parameters.insert("slTriggerBy".into(), v.into());
696                }
697                if let Some(v) = req.trigger_by {
698                    parameters.insert("triggerBy".into(), v.into());
699                }
700                if let Some(v) = req.tp_limit_price {
701                    parameters.insert("tpLimitPrice".into(), v.to_string().into());
702                }
703                if let Some(v) = req.sl_limit_price {
704                    parameters.insert("slLimitPrice".into(), v.to_string().into());
705                }
706            }
707            Action::Cancel(req, batch) => {
708                if batch == false {
709                    parameters.insert("category".into(), req.category.as_str().into());
710                }
711                parameters.insert("symbol".into(), req.symbol.into());
712                if let Some(v) = req.order_id {
713                    parameters.insert("orderId".into(), v.into());
714                }
715                if let Some(v) = req.order_link_id {
716                    parameters.insert("orderLinkId".into(), v.into());
717                }
718                if let Some(v) = req.order_filter {
719                    parameters.insert("orderFilter".into(), v.into());
720                }
721            }
722        }
723        parameters
724    }
725}
726
727pub fn build_ws_orders<'a>(orders: RequestType) -> Value {
728    let mut order_array = Vec::new();
729    match orders {
730        RequestType::Create(req) => {
731            for v in req.requests {
732                let action = Action::Order(v, false);
733                let order_object = Trader::build_orders(action); // Assuming this returns the correct object structure
734                let built_order = json!(order_object);
735                order_array.push(built_order);
736            }
737            Value::Array(order_array)
738        }
739        RequestType::Amend(req) => {
740            for v in req.requests {
741                let action = Action::Amend(v, false);
742                let order_object = Trader::build_orders(action); // Assuming this returns the correct object structure
743                let built_order = json!(order_object);
744                order_array.push(built_order);
745            }
746            Value::Array(order_array)
747        }
748        RequestType::Cancel(req) => {
749            for v in req.requests {
750                let action = Action::Cancel(v, false);
751                let order_object = Trader::build_orders(action); // Assuming this returns the correct object structure
752                let built_order = json!(order_object);
753                order_array.push(built_order);
754            }
755            Value::Array(order_array)
756        }
757    }
758}