Skip to main content

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