ccxt_exchanges/binance/rest/
spot.rs

1//! Binance spot trading operations.
2//!
3//! This module contains all spot trading methods including order creation,
4//! cancellation, and order management.
5
6use super::super::{Binance, constants::endpoints, parser, signed_request::HttpMethod};
7use ccxt_core::{
8    Error, ParseError, Result,
9    types::{Amount, Order, OrderRequest, OrderSide, OrderType, Price, TimeInForce},
10};
11use rust_decimal::Decimal;
12use std::collections::{BTreeMap, HashMap};
13use tracing::warn;
14
15impl Binance {
16    /// Create a new order using the builder pattern.
17    ///
18    /// This is the preferred method for creating orders. It accepts an [`OrderRequest`]
19    /// built using the builder pattern, which provides compile-time validation of
20    /// required fields and a more ergonomic API.
21    ///
22    /// # Arguments
23    ///
24    /// * `request` - Order request built via [`OrderRequest::builder()`]
25    ///
26    /// # Returns
27    ///
28    /// Returns the created [`Order`] structure with order details.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error if authentication fails, market is not found, or the API request fails.
33    ///
34    /// # Example
35    ///
36    /// ```no_run
37    /// use ccxt_exchanges::binance::Binance;
38    /// use ccxt_core::{ExchangeConfig, types::{OrderRequest, OrderSide, OrderType, Amount, Price}};
39    /// use rust_decimal_macros::dec;
40    ///
41    /// # async fn example() -> ccxt_core::Result<()> {
42    /// let binance = Binance::new(ExchangeConfig::default())?;
43    ///
44    /// // Create a market order using the builder
45    /// let request = OrderRequest::builder()
46    ///     .symbol("BTC/USDT")
47    ///     .side(OrderSide::Buy)
48    ///     .order_type(OrderType::Market)
49    ///     .amount(Amount::new(dec!(0.001)))
50    ///     .build();
51    ///
52    /// let order = binance.create_order_v2(request).await?;
53    /// println!("Order created: {:?}", order);
54    /// # Ok(())
55    /// # }
56    /// ```
57    ///
58    /// _Requirements: 2.2, 2.6_
59    pub async fn create_order_v2(&self, request: OrderRequest) -> Result<Order> {
60        let market = self.base().market(&request.symbol).await?;
61        let mut request_params = BTreeMap::new();
62
63        request_params.insert("symbol".to_string(), market.id.clone());
64        request_params.insert(
65            "side".to_string(),
66            match request.side {
67                OrderSide::Buy => "BUY".to_string(),
68                OrderSide::Sell => "SELL".to_string(),
69            },
70        );
71        request_params.insert(
72            "type".to_string(),
73            match request.order_type {
74                OrderType::Market => "MARKET".to_string(),
75                OrderType::Limit => "LIMIT".to_string(),
76                OrderType::StopLoss => "STOP_LOSS".to_string(),
77                OrderType::StopLossLimit => "STOP_LOSS_LIMIT".to_string(),
78                OrderType::TakeProfit => "TAKE_PROFIT".to_string(),
79                OrderType::TakeProfitLimit => "TAKE_PROFIT_LIMIT".to_string(),
80                OrderType::LimitMaker => "LIMIT_MAKER".to_string(),
81                OrderType::StopMarket => "STOP_MARKET".to_string(),
82                OrderType::StopLimit => "STOP_LIMIT".to_string(),
83                OrderType::TrailingStop => "TRAILING_STOP_MARKET".to_string(),
84            },
85        );
86        request_params.insert("quantity".to_string(), request.amount.to_string());
87
88        // Handle price
89        if let Some(p) = request.price {
90            request_params.insert("price".to_string(), p.to_string());
91        }
92
93        // Handle stop price
94        if let Some(sp) = request.stop_price {
95            request_params.insert("stopPrice".to_string(), sp.to_string());
96        }
97
98        // Handle time in force
99        if let Some(tif) = request.time_in_force {
100            let tif_str = match tif {
101                TimeInForce::GTC => "GTC",
102                TimeInForce::IOC => "IOC",
103                TimeInForce::FOK => "FOK",
104                TimeInForce::PO => "GTX", // Post-only maps to GTX on Binance
105            };
106            request_params.insert("timeInForce".to_string(), tif_str.to_string());
107        } else if request.order_type == OrderType::Limit
108            || request.order_type == OrderType::StopLossLimit
109            || request.order_type == OrderType::TakeProfitLimit
110        {
111            // Limit orders require timeInForce parameter
112            request_params.insert("timeInForce".to_string(), "GTC".to_string());
113        }
114
115        // Handle client order ID
116        if let Some(client_id) = request.client_order_id {
117            request_params.insert("newClientOrderId".to_string(), client_id);
118        }
119
120        // Handle reduce only (futures)
121        if let Some(reduce_only) = request.reduce_only {
122            request_params.insert("reduceOnly".to_string(), reduce_only.to_string());
123        }
124
125        // Handle post only
126        if request.post_only == Some(true) {
127            // For spot, post-only is handled via timeInForce=GTX
128            if !request_params.contains_key("timeInForce") {
129                request_params.insert("timeInForce".to_string(), "GTX".to_string());
130            }
131        }
132
133        // Handle trigger price (for conditional orders)
134        if let Some(trigger) = request.trigger_price {
135            if !request_params.contains_key("stopPrice") {
136                request_params.insert("stopPrice".to_string(), trigger.to_string());
137            }
138        }
139
140        // Handle take profit price
141        if let Some(tp) = request.take_profit_price {
142            request_params.insert("takeProfitPrice".to_string(), tp.to_string());
143            if !request_params.contains_key("stopPrice") {
144                request_params.insert("stopPrice".to_string(), tp.to_string());
145            }
146        }
147
148        // Handle stop loss price
149        if let Some(sl) = request.stop_loss_price {
150            request_params.insert("stopLossPrice".to_string(), sl.to_string());
151            if !request_params.contains_key("stopPrice") {
152                request_params.insert("stopPrice".to_string(), sl.to_string());
153            }
154        }
155
156        // Handle trailing delta
157        if let Some(delta) = request.trailing_delta {
158            request_params.insert("trailingDelta".to_string(), delta.to_string());
159        }
160
161        // Handle trailing percent (convert to basis points for spot)
162        if let Some(percent) = request.trailing_percent {
163            if market.is_spot() {
164                // Convert percentage to basis points (e.g., 2.0% -> 200)
165                let delta = (percent * Decimal::from(100)).to_string();
166                if let Ok(delta_int) = delta.parse::<i64>() {
167                    request_params.insert("trailingDelta".to_string(), delta_int.to_string());
168                }
169            } else {
170                request_params.insert("trailingPercent".to_string(), percent.to_string());
171            }
172        }
173
174        // Handle activation price (for trailing stop orders)
175        if let Some(activation) = request.activation_price {
176            request_params.insert("activationPrice".to_string(), activation.to_string());
177        }
178
179        // Handle callback rate (for futures trailing stop orders)
180        if let Some(rate) = request.callback_rate {
181            request_params.insert("callbackRate".to_string(), rate.to_string());
182        }
183
184        // Handle working type (for futures)
185        if let Some(working_type) = request.working_type {
186            request_params.insert("workingType".to_string(), working_type);
187        }
188
189        // Handle position side (for hedge mode futures)
190        if let Some(position_side) = request.position_side {
191            request_params.insert("positionSide".to_string(), position_side);
192        }
193
194        let url = format!("{}{}", self.urls().private, endpoints::ORDER);
195        let data = self
196            .signed_request(url)
197            .method(HttpMethod::Post)
198            .params(request_params)
199            .execute()
200            .await?;
201
202        parser::parse_order(&data, Some(&market))
203    }
204
205    /// Create a new order (deprecated).
206    ///
207    /// # Deprecated
208    ///
209    /// This method is deprecated. Use [`create_order_v2`](Self::create_order_v2) with
210    /// [`OrderRequest::builder()`] instead for a more ergonomic API.
211    ///
212    /// # Arguments
213    ///
214    /// * `symbol` - Trading pair symbol.
215    /// * `order_type` - Order type (Market, Limit, StopLoss, etc.).
216    /// * `side` - Order side (Buy or Sell).
217    /// * `amount` - Order quantity as [`Amount`] type.
218    /// * `price` - Optional price as [`Price`] type (required for limit orders).
219    /// * `params` - Additional parameters.
220    ///
221    /// # Returns
222    ///
223    /// Returns the created [`Order`] structure with order details.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if authentication fails, market is not found, or the API request fails.
228    #[deprecated(
229        since = "0.2.0",
230        note = "Use create_order_v2 with OrderRequest::builder() instead"
231    )]
232    pub async fn create_order(
233        &self,
234        symbol: &str,
235        order_type: OrderType,
236        side: OrderSide,
237        amount: Amount,
238        price: Option<Price>,
239        params: Option<HashMap<String, String>>,
240    ) -> Result<Order> {
241        let market = self.base().market(symbol).await?;
242        let mut request_params = BTreeMap::new();
243
244        request_params.insert("symbol".to_string(), market.id.clone());
245        request_params.insert(
246            "side".to_string(),
247            match side {
248                OrderSide::Buy => "BUY".to_string(),
249                OrderSide::Sell => "SELL".to_string(),
250            },
251        );
252        request_params.insert(
253            "type".to_string(),
254            match order_type {
255                OrderType::Market => "MARKET".to_string(),
256                OrderType::Limit => "LIMIT".to_string(),
257                OrderType::StopLoss => "STOP_LOSS".to_string(),
258                OrderType::StopLossLimit => "STOP_LOSS_LIMIT".to_string(),
259                OrderType::TakeProfit => "TAKE_PROFIT".to_string(),
260                OrderType::TakeProfitLimit => "TAKE_PROFIT_LIMIT".to_string(),
261                OrderType::LimitMaker => "LIMIT_MAKER".to_string(),
262                OrderType::StopMarket => "STOP_MARKET".to_string(),
263                OrderType::StopLimit => "STOP_LIMIT".to_string(),
264                OrderType::TrailingStop => "TRAILING_STOP_MARKET".to_string(),
265            },
266        );
267        request_params.insert("quantity".to_string(), amount.to_string());
268
269        if let Some(p) = price {
270            request_params.insert("price".to_string(), p.to_string());
271        }
272
273        // Limit orders require timeInForce parameter
274        if order_type == OrderType::Limit
275            || order_type == OrderType::StopLossLimit
276            || order_type == OrderType::TakeProfitLimit
277        {
278            if !request_params.contains_key("timeInForce") {
279                request_params.insert("timeInForce".to_string(), "GTC".to_string());
280            }
281        }
282
283        if let Some(extra) = params {
284            for (k, v) in extra {
285                request_params.insert(k, v);
286            }
287        }
288
289        // Handle cost parameter for market buy orders (quoteOrderQty)
290        if order_type == OrderType::Market && side == OrderSide::Buy {
291            if let Some(cost_str) = request_params.get("cost") {
292                request_params.insert("quoteOrderQty".to_string(), cost_str.clone());
293                request_params.remove("quantity");
294                request_params.remove("cost");
295            }
296        }
297
298        // Handle conditional order parameters
299        if matches!(
300            order_type,
301            OrderType::StopLoss
302                | OrderType::StopLossLimit
303                | OrderType::TakeProfit
304                | OrderType::TakeProfitLimit
305                | OrderType::StopMarket
306        ) {
307            if !request_params.contains_key("stopPrice") {
308                if let Some(stop_loss) = request_params.get("stopLossPrice") {
309                    request_params.insert("stopPrice".to_string(), stop_loss.clone());
310                } else if let Some(take_profit) = request_params.get("takeProfitPrice") {
311                    request_params.insert("stopPrice".to_string(), take_profit.clone());
312                }
313            }
314        }
315
316        // Trailing stop handling for spot market
317        if order_type == OrderType::TrailingStop {
318            if market.is_spot() {
319                if !request_params.contains_key("trailingDelta") {
320                    if let Some(percent_str) = request_params.get("trailingPercent") {
321                        if let Ok(percent) = percent_str.parse::<Decimal>() {
322                            // Convert percentage to basis points (e.g., 2.0% -> 200)
323                            let delta = (percent * Decimal::from(100)).to_string();
324                            // Parse as integer for the API
325                            if let Ok(delta_int) = delta.parse::<i64>() {
326                                request_params
327                                    .insert("trailingDelta".to_string(), delta_int.to_string());
328                                request_params.remove("trailingPercent");
329                            }
330                        }
331                    }
332                }
333            }
334        }
335
336        let url = format!("{}{}", self.urls().private, endpoints::ORDER);
337        let data = self
338            .signed_request(url)
339            .method(HttpMethod::Post)
340            .params(request_params)
341            .execute()
342            .await?;
343
344        parser::parse_order(&data, Some(&market))
345    }
346
347    /// Cancel an order.
348    ///
349    /// # Arguments
350    ///
351    /// * `id` - Order ID.
352    /// * `symbol` - Trading pair symbol.
353    ///
354    /// # Returns
355    ///
356    /// Returns the cancelled [`Order`] information.
357    ///
358    /// # Errors
359    ///
360    /// Returns an error if authentication fails, market is not found, or the API request fails.
361    pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
362        let market = self.base().market(symbol).await?;
363        let url = format!("{}{}", self.urls().private, endpoints::ORDER);
364
365        let data = self
366            .signed_request(url)
367            .method(HttpMethod::Delete)
368            .param("symbol", &market.id)
369            .param("orderId", id)
370            .execute()
371            .await?;
372
373        parser::parse_order(&data, Some(&market))
374    }
375
376    /// Fetch order details.
377    ///
378    /// # Arguments
379    ///
380    /// * `id` - Order ID.
381    /// * `symbol` - Trading pair symbol.
382    ///
383    /// # Returns
384    ///
385    /// Returns the [`Order`] information.
386    ///
387    /// # Errors
388    ///
389    /// Returns an error if authentication fails, market is not found, or the API request fails.
390    pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
391        let market = self.base().market(symbol).await?;
392        let url = format!("{}{}", self.urls().private, endpoints::ORDER);
393
394        let data = self
395            .signed_request(url)
396            .param("symbol", &market.id)
397            .param("orderId", id)
398            .execute()
399            .await?;
400
401        parser::parse_order(&data, Some(&market))
402    }
403
404    /// Fetch open (unfilled) orders.
405    ///
406    /// # Arguments
407    ///
408    /// * `symbol` - Optional trading pair symbol. If `None`, fetches all open orders.
409    ///
410    /// # Returns
411    ///
412    /// Returns a vector of open [`Order`] structures.
413    ///
414    /// # Errors
415    ///
416    /// Returns an error if authentication fails or the API request fails.
417    pub async fn fetch_open_orders(&self, symbol: Option<&str>) -> Result<Vec<Order>> {
418        let market = if let Some(sym) = symbol {
419            Some(self.base().market(sym).await?)
420        } else {
421            None
422        };
423
424        let url = format!("{}{}", self.urls().private, endpoints::OPEN_ORDERS);
425
426        let data = self
427            .signed_request(url)
428            .optional_param("symbol", market.as_ref().map(|m| &m.id))
429            .execute()
430            .await?;
431
432        let orders_array = data.as_array().ok_or_else(|| {
433            Error::from(ParseError::invalid_format(
434                "data",
435                "Expected array of orders",
436            ))
437        })?;
438
439        let mut orders = Vec::new();
440        for order_data in orders_array {
441            match parser::parse_order(order_data, market.as_deref()) {
442                Ok(order) => orders.push(order),
443                Err(e) => {
444                    warn!(error = %e, "Failed to parse order");
445                }
446            }
447        }
448
449        Ok(orders)
450    }
451
452    /// Fetch closed (completed) orders.
453    ///
454    /// # Arguments
455    ///
456    /// * `symbol` - Optional trading pair symbol.
457    /// * `since` - Optional start timestamp (milliseconds).
458    /// * `limit` - Optional limit on number of orders (default 500, max 1000).
459    ///
460    /// # Returns
461    ///
462    /// Returns a vector of closed [`Order`] structures.
463    ///
464    /// # Errors
465    ///
466    /// Returns an error if authentication fails or the API request fails.
467    pub async fn fetch_closed_orders(
468        &self,
469        symbol: Option<&str>,
470        since: Option<i64>,
471        limit: Option<u32>,
472    ) -> Result<Vec<Order>> {
473        let all_orders = self.fetch_orders(symbol, since, None).await?;
474
475        let mut closed_orders: Vec<Order> = all_orders
476            .into_iter()
477            .filter(|order| order.status == ccxt_core::types::OrderStatus::Closed)
478            .collect();
479
480        if let Some(l) = limit {
481            closed_orders.truncate(l as usize);
482        }
483
484        Ok(closed_orders)
485    }
486
487    /// Cancel all open orders.
488    ///
489    /// # Arguments
490    ///
491    /// * `symbol` - Trading pair symbol.
492    ///
493    /// # Returns
494    ///
495    /// Returns a vector of cancelled [`Order`] structures.
496    ///
497    /// # Errors
498    ///
499    /// Returns an error if authentication fails, market is not found, or the API request fails.
500    pub async fn cancel_all_orders(&self, symbol: &str) -> Result<Vec<Order>> {
501        let market = self.base().market(symbol).await?;
502        let url = format!("{}{}", self.urls().private, endpoints::OPEN_ORDERS);
503
504        let data = self
505            .signed_request(url)
506            .method(HttpMethod::Delete)
507            .param("symbol", &market.id)
508            .execute()
509            .await?;
510
511        let orders_array = data.as_array().ok_or_else(|| {
512            Error::from(ParseError::invalid_format(
513                "data",
514                "Expected array of orders",
515            ))
516        })?;
517
518        let mut orders = Vec::new();
519        for order_data in orders_array {
520            match parser::parse_order(order_data, Some(&market)) {
521                Ok(order) => orders.push(order),
522                Err(e) => {
523                    warn!(error = %e, "Failed to parse order");
524                }
525            }
526        }
527
528        Ok(orders)
529    }
530
531    /// Cancel multiple orders.
532    ///
533    /// # Arguments
534    ///
535    /// * `ids` - Vector of order IDs to cancel.
536    /// * `symbol` - Trading pair symbol.
537    ///
538    /// # Returns
539    ///
540    /// Returns a vector of cancelled [`Order`] structures.
541    ///
542    /// # Errors
543    ///
544    /// Returns an error if authentication fails, market is not found, or the API request fails.
545    pub async fn cancel_orders(&self, ids: Vec<String>, symbol: &str) -> Result<Vec<Order>> {
546        let market = self.base().market(symbol).await?;
547
548        let order_ids_json = serde_json::to_string(&ids).map_err(|e| {
549            Error::from(ParseError::invalid_format(
550                "data",
551                format!("Failed to serialize order IDs: {}", e),
552            ))
553        })?;
554
555        let url = format!("{}{}", self.urls().private, endpoints::OPEN_ORDERS);
556
557        let data = self
558            .signed_request(url)
559            .method(HttpMethod::Delete)
560            .param("symbol", &market.id)
561            .param("orderIdList", order_ids_json)
562            .execute()
563            .await?;
564
565        let orders_array = data.as_array().ok_or_else(|| {
566            Error::from(ParseError::invalid_format(
567                "data",
568                "Expected array of orders",
569            ))
570        })?;
571
572        let mut orders = Vec::new();
573        for order_data in orders_array {
574            match parser::parse_order(order_data, Some(&market)) {
575                Ok(order) => orders.push(order),
576                Err(e) => {
577                    warn!(error = %e, "Failed to parse order");
578                }
579            }
580        }
581
582        Ok(orders)
583    }
584
585    /// Fetch all orders (historical and current).
586    ///
587    /// # Arguments
588    ///
589    /// * `symbol` - Optional trading pair symbol.
590    /// * `since` - Optional start timestamp (milliseconds).
591    /// * `limit` - Optional limit on number of orders (default 500, max 1000).
592    ///
593    /// # Returns
594    ///
595    /// Returns a vector of [`Order`] structures.
596    ///
597    /// # Errors
598    ///
599    /// Returns an error if authentication fails or the API request fails.
600    pub async fn fetch_orders(
601        &self,
602        symbol: Option<&str>,
603        since: Option<i64>,
604        limit: Option<u32>,
605    ) -> Result<Vec<Order>> {
606        let market = if let Some(sym) = symbol {
607            Some(self.base().market(sym).await?)
608        } else {
609            None
610        };
611
612        let url = format!("{}{}", self.urls().private, endpoints::ALL_ORDERS);
613
614        let data = self
615            .signed_request(url)
616            .optional_param("symbol", market.as_ref().map(|m| &m.id))
617            .optional_param("startTime", since)
618            .optional_param("limit", limit)
619            .execute()
620            .await?;
621
622        let orders_array = data.as_array().ok_or_else(|| {
623            Error::from(ParseError::invalid_format(
624                "data",
625                "Expected array of orders",
626            ))
627        })?;
628
629        let mut orders = Vec::new();
630        for order_data in orders_array {
631            match parser::parse_order(order_data, market.as_deref()) {
632                Ok(order) => orders.push(order),
633                Err(e) => {
634                    warn!(error = %e, "Failed to parse order");
635                }
636            }
637        }
638
639        Ok(orders)
640    }
641
642    /// Create a stop-loss order.
643    ///
644    /// # Arguments
645    ///
646    /// * `symbol` - Trading pair symbol.
647    /// * `side` - Order side (Buy/Sell).
648    /// * `amount` - Order quantity as [`Amount`] type.
649    /// * `stop_price` - Stop-loss trigger price as [`Price`] type.
650    /// * `price` - Optional limit price as [`Price`] type (if `None`, creates market stop-loss order).
651    /// * `params` - Optional additional parameters.
652    ///
653    /// # Returns
654    ///
655    /// Returns the created stop-loss [`Order`].
656    ///
657    /// # Errors
658    ///
659    /// Returns an error if authentication fails or the API request fails.
660    #[allow(deprecated)]
661    pub async fn create_stop_loss_order(
662        &self,
663        symbol: &str,
664        side: OrderSide,
665        amount: Amount,
666        stop_price: Price,
667        price: Option<Price>,
668        params: Option<HashMap<String, String>>,
669    ) -> Result<Order> {
670        let mut request_params = params.unwrap_or_default();
671
672        request_params.insert("stopPrice".to_string(), stop_price.to_string());
673
674        let order_type = if price.is_some() {
675            OrderType::StopLossLimit
676        } else {
677            OrderType::StopLoss
678        };
679
680        self.create_order(
681            symbol,
682            order_type,
683            side,
684            amount,
685            price,
686            Some(request_params),
687        )
688        .await
689    }
690
691    /// Create a take-profit order.
692    ///
693    /// # Arguments
694    ///
695    /// * `symbol` - Trading pair symbol.
696    /// * `side` - Order side (Buy/Sell).
697    /// * `amount` - Order quantity as [`Amount`] type.
698    /// * `take_profit_price` - Take-profit trigger price as [`Price`] type.
699    /// * `price` - Optional limit price as [`Price`] type (if `None`, creates market take-profit order).
700    /// * `params` - Optional additional parameters.
701    ///
702    /// # Returns
703    ///
704    /// Returns the created take-profit [`Order`].
705    ///
706    /// # Errors
707    ///
708    /// Returns an error if authentication fails or the API request fails.
709    #[allow(deprecated)]
710    pub async fn create_take_profit_order(
711        &self,
712        symbol: &str,
713        side: OrderSide,
714        amount: Amount,
715        take_profit_price: Price,
716        price: Option<Price>,
717        params: Option<HashMap<String, String>>,
718    ) -> Result<Order> {
719        let mut request_params = params.unwrap_or_default();
720
721        request_params.insert("stopPrice".to_string(), take_profit_price.to_string());
722
723        let order_type = if price.is_some() {
724            OrderType::TakeProfitLimit
725        } else {
726            OrderType::TakeProfit
727        };
728
729        self.create_order(
730            symbol,
731            order_type,
732            side,
733            amount,
734            price,
735            Some(request_params),
736        )
737        .await
738    }
739
740    /// Create a trailing stop order.
741    ///
742    /// # Arguments
743    ///
744    /// * `symbol` - Trading pair symbol.
745    /// * `side` - Order side (Buy/Sell).
746    /// * `amount` - Order quantity as [`Amount`] type.
747    /// * `trailing_percent` - Trailing percentage as [`Decimal`] (e.g., 2.0 for 2%).
748    /// * `activation_price` - Optional activation price as [`Price`] type (not supported for spot markets).
749    /// * `params` - Optional additional parameters.
750    ///
751    /// # Returns
752    ///
753    /// Returns the created trailing stop [`Order`].
754    ///
755    /// # Errors
756    ///
757    /// Returns an error if authentication fails or the API request fails.
758    #[allow(deprecated)]
759    pub async fn create_trailing_stop_order(
760        &self,
761        symbol: &str,
762        side: OrderSide,
763        amount: Amount,
764        trailing_percent: Decimal,
765        activation_price: Option<Price>,
766        params: Option<HashMap<String, String>>,
767    ) -> Result<Order> {
768        let mut request_params = params.unwrap_or_default();
769
770        request_params.insert("trailingPercent".to_string(), trailing_percent.to_string());
771
772        if let Some(activation) = activation_price {
773            request_params.insert("activationPrice".to_string(), activation.to_string());
774        }
775
776        self.create_order(
777            symbol,
778            OrderType::TrailingStop,
779            side,
780            amount,
781            None,
782            Some(request_params),
783        )
784        .await
785    }
786}