Skip to main content

binance_rs_plus/
account.rs

1use crate::util::{build_signed_request, is_start_time_valid};
2use crate::model::{
3    AccountInformation, Balance, Empty, Order, OrderCanceled, TradeHistory, Transaction,
4};
5use crate::client::Client;
6use crate::errors::{Result, Error};
7use std::collections::BTreeMap;
8use std::fmt::Display;
9use crate::api::API;
10use crate::api::Spot;
11
12#[derive(Clone)]
13pub struct Account {
14    pub client: Client,
15    pub recv_window: u64,
16}
17
18struct OrderRequest {
19    pub symbol: String,
20    pub qty: f64,
21    pub price: f64,
22    pub stop_price: Option<f64>,
23    pub order_side: OrderSide,
24    pub order_type: OrderType,
25    pub time_in_force: TimeInForce,
26    pub new_client_order_id: Option<String>,
27}
28
29struct OrderQuoteQuantityRequest {
30    pub symbol: String,
31    pub quote_order_qty: f64,
32    pub price: f64,
33    pub order_side: OrderSide,
34    pub order_type: OrderType,
35    pub time_in_force: TimeInForce,
36    pub new_client_order_id: Option<String>,
37}
38
39#[derive(PartialEq, Eq, Debug, Clone, Copy)]
40pub enum OrderType {
41    Limit,
42    Market,
43    StopLossLimit,
44}
45
46impl OrderType {
47    pub fn from_int(value: i32) -> Option<Self> {
48        match value {
49            1 => Some(OrderType::Limit),
50            2 => Some(OrderType::Market),
51            3 => Some(OrderType::StopLossLimit),
52            _ => None,
53        }
54    }
55}
56
57impl Display for OrderType {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            Self::Limit => write!(f, "LIMIT"),
61            Self::Market => write!(f, "MARKET"),
62            Self::StopLossLimit => write!(f, "STOP_LOSS_LIMIT"),
63        }
64    }
65}
66
67#[derive(PartialEq, Eq, Debug, Clone, Copy)]
68pub enum OrderSide {
69    Buy,
70    Sell,
71}
72
73impl OrderSide {
74    pub fn from_int(value: i32) -> Option<Self> {
75        match value {
76            1 => Some(OrderSide::Buy),
77            2 => Some(OrderSide::Sell),
78            _ => None,
79        }
80    }
81}
82
83impl Display for OrderSide {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Self::Buy => write!(f, "BUY"),
87            Self::Sell => write!(f, "SELL"),
88        }
89    }
90}
91
92#[allow(clippy::all)]
93#[derive(PartialEq, Eq, Debug, Clone, Copy)]
94pub enum TimeInForce {
95    GTC,
96    IOC,
97    FOK,
98}
99
100impl TimeInForce {
101    pub fn from_int(value: i32) -> Option<Self> {
102        match value {
103            1 => Some(TimeInForce::GTC),
104            2 => Some(TimeInForce::IOC),
105            3 => Some(TimeInForce::FOK),
106            _ => None,
107        }
108    }
109}
110
111impl Display for TimeInForce {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            Self::GTC => write!(f, "GTC"),
115            Self::IOC => write!(f, "IOC"),
116            Self::FOK => write!(f, "FOK"),
117        }
118    }
119}
120
121impl Account {
122    // Account Information
123    pub async fn get_account(&self) -> Result<AccountInformation> {
124        let request = build_signed_request(BTreeMap::new(), self.recv_window)?;
125        self.client
126            .get_signed(API::Spot(Spot::Account), Some(request))
127            .await
128    }
129
130    // Balance for a single Asset
131    pub async fn get_balance<S>(&self, asset: S) -> Result<Balance>
132    where
133        S: Into<String>,
134    {
135        match self.get_account().await {
136            Ok(account) => {
137                let cmp_asset = asset.into();
138                for balance in account.balances {
139                    if balance.asset == cmp_asset {
140                        return Ok(balance);
141                    }
142                }
143                Err(Error::Custom("Asset not found".to_string()))
144            }
145            Err(e) => Err(e),
146        }
147    }
148
149    // Current open orders for ONE symbol
150    pub async fn get_open_orders<S>(&self, symbol: S) -> Result<Vec<Order>>
151    where
152        S: Into<String>,
153    {
154        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
155        parameters.insert("symbol".into(), symbol.into());
156
157        let request = build_signed_request(parameters, self.recv_window)?;
158        self.client
159            .get_signed(API::Spot(Spot::OpenOrders), Some(request))
160            .await
161    }
162
163    // All current open orders
164    pub async fn get_all_open_orders(&self) -> Result<Vec<Order>> {
165        let parameters: BTreeMap<String, String> = BTreeMap::new();
166
167        let request = build_signed_request(parameters, self.recv_window)?;
168        self.client
169            .get_signed(API::Spot(Spot::OpenOrders), Some(request))
170            .await
171    }
172
173    // Cancel all open orders for a single symbol
174    pub async fn cancel_all_open_orders<S>(&self, symbol: S) -> Result<Vec<OrderCanceled>>
175    where
176        S: Into<String>,
177    {
178        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
179        parameters.insert("symbol".into(), symbol.into());
180        let request = build_signed_request(parameters, self.recv_window)?;
181        self.client
182            .delete_signed(API::Spot(Spot::OpenOrders), Some(request))
183            .await
184    }
185
186    // Check an order's status
187    pub async fn order_status<S>(&self, symbol: S, order_id: u64) -> Result<Order>
188    where
189        S: Into<String>,
190    {
191        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
192        parameters.insert("symbol".into(), symbol.into());
193        parameters.insert("orderId".into(), order_id.to_string());
194
195        let request = build_signed_request(parameters, self.recv_window)?;
196        self.client
197            .get_signed(API::Spot(Spot::Order), Some(request))
198            .await
199    }
200
201    /// Place a test status order
202    ///
203    /// This order is sandboxed: it is validated, but not sent to the matching engine.
204    pub async fn test_order_status<S>(&self, symbol: S, order_id: u64) -> Result<()>
205    where
206        S: Into<String>,
207    {
208        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
209        parameters.insert("symbol".into(), symbol.into());
210        parameters.insert("orderId".into(), order_id.to_string());
211
212        let request = build_signed_request(parameters, self.recv_window)?;
213        self.client
214            .get_signed::<Empty>(API::Spot(Spot::OrderTest), Some(request))
215            .await?;
216        Ok(())
217    }
218
219    // Place a LIMIT order - BUY
220    pub async fn limit_buy<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<Transaction>
221    where
222        S: Into<String>,
223        F: Into<f64>,
224    {
225        let buy = OrderRequest {
226            symbol: symbol.into(),
227            qty: qty.into(),
228            price,
229            stop_price: None,
230            order_side: OrderSide::Buy,
231            order_type: OrderType::Limit,
232            time_in_force: TimeInForce::GTC,
233            new_client_order_id: None,
234        };
235        let order = self.build_order(buy);
236        let request = build_signed_request(order, self.recv_window)?;
237        self.client
238            .post_signed(API::Spot(Spot::Order), request)
239            .await
240    }
241
242    /// Place a test limit order - BUY
243    ///
244    /// This order is sandboxed: it is validated, but not sent to the matching engine.
245    pub async fn test_limit_buy<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<()>
246    where
247        S: Into<String>,
248        F: Into<f64>,
249    {
250        let buy = OrderRequest {
251            symbol: symbol.into(),
252            qty: qty.into(),
253            price,
254            stop_price: None,
255            order_side: OrderSide::Buy,
256            order_type: OrderType::Limit,
257            time_in_force: TimeInForce::GTC,
258            new_client_order_id: None,
259        };
260        let order = self.build_order(buy);
261        let request = build_signed_request(order, self.recv_window)?;
262        self.client
263            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
264            .await?;
265        Ok(())
266    }
267
268    // Place a LIMIT order - SELL
269    pub async fn limit_sell<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<Transaction>
270    where
271        S: Into<String>,
272        F: Into<f64>,
273    {
274        let sell = OrderRequest {
275            symbol: symbol.into(),
276            qty: qty.into(),
277            price,
278            stop_price: None,
279            order_side: OrderSide::Sell,
280            order_type: OrderType::Limit,
281            time_in_force: TimeInForce::GTC,
282            new_client_order_id: None,
283        };
284        let order = self.build_order(sell);
285        let request = build_signed_request(order, self.recv_window)?;
286        self.client
287            .post_signed(API::Spot(Spot::Order), request)
288            .await
289    }
290
291    /// Place a test LIMIT order - SELL
292    ///
293    /// This order is sandboxed: it is validated, but not sent to the matching engine.
294    pub async fn test_limit_sell<S, F>(&self, symbol: S, qty: F, price: f64) -> Result<()>
295    where
296        S: Into<String>,
297        F: Into<f64>,
298    {
299        let sell = OrderRequest {
300            symbol: symbol.into(),
301            qty: qty.into(),
302            price,
303            stop_price: None,
304            order_side: OrderSide::Sell,
305            order_type: OrderType::Limit,
306            time_in_force: TimeInForce::GTC,
307            new_client_order_id: None,
308        };
309        let order = self.build_order(sell);
310        let request = build_signed_request(order, self.recv_window)?;
311        self.client
312            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
313            .await?;
314        Ok(())
315    }
316
317    // Place a MARKET order - BUY
318    pub async fn market_buy<S, F>(&self, symbol: S, qty: F) -> Result<Transaction>
319    where
320        S: Into<String>,
321        F: Into<f64>,
322    {
323        let buy = OrderRequest {
324            symbol: symbol.into(),
325            qty: qty.into(),
326            price: 0.0,
327            stop_price: None,
328            order_side: OrderSide::Buy,
329            order_type: OrderType::Market,
330            time_in_force: TimeInForce::GTC,
331            new_client_order_id: None,
332        };
333        let order = self.build_order(buy);
334        let request = build_signed_request(order, self.recv_window)?;
335        self.client
336            .post_signed(API::Spot(Spot::Order), request)
337            .await
338    }
339
340    /// Place a test MARKET order - BUY
341    ///
342    /// This order is sandboxed: it is validated, but not sent to the matching engine.
343    pub async fn test_market_buy<S, F>(&self, symbol: S, qty: F) -> Result<()>
344    where
345        S: Into<String>,
346        F: Into<f64>,
347    {
348        let buy = OrderRequest {
349            symbol: symbol.into(),
350            qty: qty.into(),
351            price: 0.0,
352            stop_price: None,
353            order_side: OrderSide::Buy,
354            order_type: OrderType::Market,
355            time_in_force: TimeInForce::GTC,
356            new_client_order_id: None,
357        };
358        let order = self.build_order(buy);
359        let request = build_signed_request(order, self.recv_window)?;
360        self.client
361            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
362            .await?;
363        Ok(())
364    }
365
366    // Place a MARKET order with quote quantity - BUY
367    pub async fn market_buy_using_quote_quantity<S, F>(
368        &self, symbol: S, quote_order_qty: F,
369    ) -> Result<Transaction>
370    where
371        S: Into<String>,
372        F: Into<f64>,
373    {
374        let buy = OrderQuoteQuantityRequest {
375            symbol: symbol.into(),
376            quote_order_qty: quote_order_qty.into(),
377            price: 0.0,
378            order_side: OrderSide::Buy,
379            order_type: OrderType::Market,
380            time_in_force: TimeInForce::GTC,
381            new_client_order_id: None,
382        };
383        let order = self.build_quote_quantity_order(buy);
384        let request = build_signed_request(order, self.recv_window)?;
385        self.client
386            .post_signed(API::Spot(Spot::Order), request)
387            .await
388    }
389
390    /// Place a test MARKET order with quote quantity - BUY
391    ///
392    /// This order is sandboxed: it is validated, but not sent to the matching engine.
393    pub async fn test_market_buy_using_quote_quantity<S, F>(
394        &self, symbol: S, quote_order_qty: F,
395    ) -> Result<()>
396    where
397        S: Into<String>,
398        F: Into<f64>,
399    {
400        let buy = OrderQuoteQuantityRequest {
401            symbol: symbol.into(),
402            quote_order_qty: quote_order_qty.into(),
403            price: 0.0,
404            order_side: OrderSide::Buy,
405            order_type: OrderType::Market,
406            time_in_force: TimeInForce::GTC,
407            new_client_order_id: None,
408        };
409        let order = self.build_quote_quantity_order(buy);
410        let request = build_signed_request(order, self.recv_window)?;
411        self.client
412            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
413            .await?;
414        Ok(())
415    }
416
417    // Place a MARKET order - SELL
418    pub async fn market_sell<S, F>(&self, symbol: S, qty: F) -> Result<Transaction>
419    where
420        S: Into<String>,
421        F: Into<f64>,
422    {
423        let sell = OrderRequest {
424            symbol: symbol.into(),
425            qty: qty.into(),
426            price: 0.0,
427            stop_price: None,
428            order_side: OrderSide::Sell,
429            order_type: OrderType::Market,
430            time_in_force: TimeInForce::GTC,
431            new_client_order_id: None,
432        };
433        let order = self.build_order(sell);
434        let request = build_signed_request(order, self.recv_window)?;
435        self.client
436            .post_signed(API::Spot(Spot::Order), request)
437            .await
438    }
439
440    /// Place a test MARKET order - SELL
441    ///
442    /// This order is sandboxed: it is validated, but not sent to the matching engine.
443    pub async fn test_market_sell<S, F>(&self, symbol: S, qty: F) -> Result<()>
444    where
445        S: Into<String>,
446        F: Into<f64>,
447    {
448        let sell = OrderRequest {
449            symbol: symbol.into(),
450            qty: qty.into(),
451            price: 0.0,
452            stop_price: None,
453            order_side: OrderSide::Sell,
454            order_type: OrderType::Market,
455            time_in_force: TimeInForce::GTC,
456            new_client_order_id: None,
457        };
458        let order = self.build_order(sell);
459        let request = build_signed_request(order, self.recv_window)?;
460        self.client
461            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
462            .await?;
463        Ok(())
464    }
465
466    // Place a MARKET order with quote quantity - SELL
467    pub async fn market_sell_using_quote_quantity<S, F>(
468        &self, symbol: S, quote_order_qty: F,
469    ) -> Result<Transaction>
470    where
471        S: Into<String>,
472        F: Into<f64>,
473    {
474        let sell = OrderQuoteQuantityRequest {
475            symbol: symbol.into(),
476            quote_order_qty: quote_order_qty.into(),
477            price: 0.0,
478            order_side: OrderSide::Sell,
479            order_type: OrderType::Market,
480            time_in_force: TimeInForce::GTC,
481            new_client_order_id: None,
482        };
483        let order = self.build_quote_quantity_order(sell);
484        let request = build_signed_request(order, self.recv_window)?;
485        self.client
486            .post_signed(API::Spot(Spot::Order), request)
487            .await
488    }
489
490    /// Place a test MARKET order with quote quantity - SELL
491    ///
492    /// This order is sandboxed: it is validated, but not sent to the matching engine.
493    pub async fn test_market_sell_using_quote_quantity<S, F>(
494        &self, symbol: S, quote_order_qty: F,
495    ) -> Result<()>
496    where
497        S: Into<String>,
498        F: Into<f64>,
499    {
500        let sell = OrderQuoteQuantityRequest {
501            symbol: symbol.into(),
502            quote_order_qty: quote_order_qty.into(),
503            price: 0.0,
504            order_side: OrderSide::Sell,
505            order_type: OrderType::Market,
506            time_in_force: TimeInForce::GTC,
507            new_client_order_id: None,
508        };
509        let order = self.build_quote_quantity_order(sell);
510        let request = build_signed_request(order, self.recv_window)?;
511        self.client
512            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
513            .await?;
514        Ok(())
515    }
516
517    /// Create a stop limit buy order for the given symbol, price and stop price.
518    /// Returning a `Transaction` value with the same parameters sent on the order.
519    ///
520    ///```no_run
521    /// use binance_rs_plus::api::Binance;
522    /// use binance_rs_plus::account::*;
523    ///
524    /// #[tokio::main]
525    /// async fn main() -> anyhow::Result<()> {
526    ///     let api_key = Some("api_key".into());
527    ///     let secret_key = Some("secret_key".into());
528    ///     let account: Account = Binance::new(api_key, secret_key);
529    ///
530    ///     match account.stop_limit_buy_order("BNBBTC", 1.0, 0.001, 0.0009).await {
531    ///         Ok(answer) => println!("{:#?}", answer),
532    ///         Err(e) => println!("Error: {:#?}", e),
533    ///     }
534    ///     Ok(())
535    /// }
536    /// ```
537    pub async fn stop_limit_buy_order<S, F>(
538        &self, symbol: S, qty: F, price: f64, stop_price: f64,
539    ) -> Result<Transaction>
540    where
541        S: Into<String>,
542        F: Into<f64>,
543    {
544        let buy = OrderRequest {
545            symbol: symbol.into(),
546            qty: qty.into(),
547            price,
548            stop_price: Some(stop_price),
549            order_side: OrderSide::Buy,
550            order_type: OrderType::StopLossLimit,
551            time_in_force: TimeInForce::GTC,
552            new_client_order_id: None,
553        };
554        let order = self.build_order(buy);
555        let request = build_signed_request(order, self.recv_window)?;
556        self.client
557            .post_signed(API::Spot(Spot::Order), request)
558            .await
559    }
560
561    /// Place a test Stop Limit Buy order
562    ///
563    /// This order is sandboxed: it is validated, but not sent to the matching engine.
564    ///
565    ///```no_run
566    /// use binance_rs_plus::api::Binance;
567    /// use binance_rs_plus::account::*;
568    ///
569    /// #[tokio::main]
570    /// async fn main() -> anyhow::Result<()> {
571    ///     let api_key = Some("api_key".into());
572    ///     let secret_key = Some("secret_key".into());
573    ///     let account: Account = Binance::new(api_key, secret_key);
574    ///
575    ///     match account.test_stop_limit_buy_order("BNBBTC", 1.0, 0.001, 0.0009).await {
576    ///         Ok(_answer) => println!("Test stop limit buy order placed successfully."),
577    ///         Err(e) => println!("Error: {:#?}", e),
578    ///     }
579    ///     Ok(())
580    /// }
581    /// ```
582    pub async fn test_stop_limit_buy_order<S, F>(
583        &self, symbol: S, qty: F, price: f64, stop_price: f64,
584    ) -> Result<()>
585    where
586        S: Into<String>,
587        F: Into<f64>,
588    {
589        let buy = OrderRequest {
590            symbol: symbol.into(),
591            qty: qty.into(),
592            price,
593            stop_price: Some(stop_price),
594            order_side: OrderSide::Buy,
595            order_type: OrderType::StopLossLimit,
596            time_in_force: TimeInForce::GTC,
597            new_client_order_id: None,
598        };
599        let order = self.build_order(buy);
600        let request = build_signed_request(order, self.recv_window)?;
601        self.client
602            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
603            .await?;
604        Ok(())
605    }
606
607    /// Create a stop limit sell order for the given symbol, price and stop price.
608    /// Returning a `Transaction` value with the same parameters sent on the order.
609    ///
610    ///```no_run
611    /// use binance_rs_plus::api::Binance;
612    /// use binance_rs_plus::account::*;
613    ///
614    /// #[tokio::main]
615    /// async fn main() -> anyhow::Result<()> {
616    ///     let api_key = Some("api_key".into());
617    ///     let secret_key = Some("secret_key".into());
618    ///     let account: Account = Binance::new(api_key, secret_key);
619    ///
620    ///     match account.stop_limit_sell_order("BNBBTC", 1.0, 0.001, 0.0009).await {
621    ///         Ok(answer) => println!("{:#?}", answer),
622    ///         Err(e) => println!("Error: {:#?}", e),
623    ///     }
624    ///     Ok(())
625    /// }
626    /// ```
627    pub async fn stop_limit_sell_order<S, F>(
628        &self, symbol: S, qty: F, price: f64, stop_price: f64,
629    ) -> Result<Transaction>
630    where
631        S: Into<String>,
632        F: Into<f64>,
633    {
634        let sell = OrderRequest {
635            symbol: symbol.into(),
636            qty: qty.into(),
637            price,
638            stop_price: Some(stop_price),
639            order_side: OrderSide::Sell,
640            order_type: OrderType::StopLossLimit,
641            time_in_force: TimeInForce::GTC,
642            new_client_order_id: None,
643        };
644        let order = self.build_order(sell);
645        let request = build_signed_request(order, self.recv_window)?;
646        self.client
647            .post_signed(API::Spot(Spot::Order), request)
648            .await
649    }
650
651    /// Place a test Stop Limit Sell order
652    ///
653    /// This order is sandboxed: it is validated, but not sent to the matching engine.
654    ///
655    ///```no_run
656    /// use binance_rs_plus::api::Binance;
657    /// use binance_rs_plus::account::*;
658    ///
659    /// #[tokio::main]
660    /// async fn main() -> anyhow::Result<()> {
661    ///     let api_key = Some("api_key".into());
662    ///     let secret_key = Some("secret_key".into());
663    ///     let account: Account = Binance::new(api_key, secret_key);
664    ///
665    ///     match account.test_stop_limit_sell_order("BNBBTC", 1.0, 0.001, 0.0009).await {
666    ///         Ok(_answer) => println!("Test stop limit sell order placed successfully."),
667    ///         Err(e) => println!("Error: {:#?}", e),
668    ///     }
669    ///     Ok(())
670    /// }
671    /// ```
672    pub async fn test_stop_limit_sell_order<S, F>(
673        &self, symbol: S, qty: F, price: f64, stop_price: f64,
674    ) -> Result<()>
675    where
676        S: Into<String>,
677        F: Into<f64>,
678    {
679        let sell = OrderRequest {
680            symbol: symbol.into(),
681            qty: qty.into(),
682            price,
683            stop_price: Some(stop_price),
684            order_side: OrderSide::Sell,
685            order_type: OrderType::StopLossLimit,
686            time_in_force: TimeInForce::GTC,
687            new_client_order_id: None,
688        };
689        let order = self.build_order(sell);
690        let request = build_signed_request(order, self.recv_window)?;
691        self.client
692            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
693            .await?;
694        Ok(())
695    }
696
697    /// Create a custom order
698    pub async fn custom_order<S, F>(
699        &self, symbol: S, qty: F, price: f64, order_side: OrderSide, order_type: OrderType,
700        time_in_force: TimeInForce, new_client_order_id: Option<String>,
701    ) -> Result<Transaction>
702    where
703        S: Into<String>,
704        F: Into<f64>,
705    {
706        let custom_order = OrderRequest {
707            symbol: symbol.into(),
708            qty: qty.into(),
709            price,
710            stop_price: None,
711            order_side,
712            order_type,
713            time_in_force,
714            new_client_order_id,
715        };
716        let order = self.build_order(custom_order);
717        let request = build_signed_request(order, self.recv_window)?;
718        self.client
719            .post_signed(API::Spot(Spot::Order), request)
720            .await
721    }
722
723    /// Place a test custom order
724    ///
725    /// This order is sandboxed: it is validated, but not sent to the matching engine.
726    pub async fn test_custom_order<S, F>(
727        &self, symbol: S, qty: F, price: f64, order_side: OrderSide, order_type: OrderType,
728        time_in_force: TimeInForce, new_client_order_id: Option<String>,
729    ) -> Result<()>
730    where
731        S: Into<String>,
732        F: Into<f64>,
733    {
734        let custom_order = OrderRequest {
735            symbol: symbol.into(),
736            qty: qty.into(),
737            price,
738            stop_price: None,
739            order_side,
740            order_type,
741            time_in_force,
742            new_client_order_id,
743        };
744        let order = self.build_order(custom_order);
745        let request = build_signed_request(order, self.recv_window)?;
746        self.client
747            .post_signed::<Empty>(API::Spot(Spot::OrderTest), request)
748            .await?;
749        Ok(())
750    }
751
752    // Cancel an order
753    pub async fn cancel_order<S>(&self, symbol: S, order_id: u64) -> Result<OrderCanceled>
754    where
755        S: Into<String>,
756    {
757        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
758        parameters.insert("symbol".into(), symbol.into());
759        parameters.insert("orderId".into(), order_id.to_string());
760
761        let request = build_signed_request(parameters, self.recv_window)?;
762        self.client
763            .delete_signed(API::Spot(Spot::Order), Some(request))
764            .await
765    }
766    pub async fn cancel_order_with_client_id<S>(
767        &self, symbol: S, client_order_id: String,
768    ) -> Result<OrderCanceled>
769    where
770        S: Into<String>,
771    {
772        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
773        parameters.insert("symbol".into(), symbol.into());
774        parameters.insert("origClientOrderId".into(), client_order_id);
775
776        let request = build_signed_request(parameters, self.recv_window)?;
777        self.client
778            .delete_signed(API::Spot(Spot::Order), Some(request))
779            .await
780    }
781
782    /// Place a test cancel order
783    ///
784    /// This order is sandboxed: it is validated, but not sent to the matching engine.
785    pub async fn test_cancel_order<S>(&self, symbol: S, order_id: u64) -> Result<()>
786    where
787        S: Into<String>,
788    {
789        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
790        parameters.insert("symbol".into(), symbol.into());
791        parameters.insert("orderId".into(), order_id.to_string());
792
793        let request = build_signed_request(parameters, self.recv_window)?;
794        self.client
795            .delete_signed::<Empty>(API::Spot(Spot::OrderTest), Some(request))
796            .await?;
797        Ok(())
798    }
799
800    // Trade History
801    pub async fn trade_history<S>(&self, symbol: S) -> Result<Vec<TradeHistory>>
802    where
803        S: Into<String>,
804    {
805        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
806        parameters.insert("symbol".into(), symbol.into());
807
808        let request = build_signed_request(parameters, self.recv_window)?;
809        self.client
810            .get_signed(API::Spot(Spot::MyTrades), Some(request))
811            .await
812    }
813
814    // Trade History from a start time
815    pub async fn trade_history_from<S>(
816        &self, symbol: S, start_time: u64,
817    ) -> Result<Vec<TradeHistory>>
818    where
819        S: Into<String>,
820    {
821        let mut parameters: BTreeMap<String, String> = BTreeMap::new();
822        parameters.insert("symbol".into(), symbol.into());
823        parameters.insert("startTime".into(), start_time.to_string());
824
825        let request = build_signed_request(parameters, self.recv_window)?;
826        self.client
827            .get_signed(API::Spot(Spot::MyTrades), Some(request))
828            .await
829    }
830
831    // Trade History from a start time to an end time
832    pub async fn trade_history_from_to<S>(
833        &self, symbol: S, start_time: u64, end_time: u64,
834    ) -> Result<Vec<TradeHistory>>
835    where
836        S: Into<String>,
837    {
838        if is_start_time_valid(&start_time) && (start_time < end_time) {
839            self.get_trades(symbol.into(), start_time, end_time).await
840        } else {
841            Err(Error::Custom(
842                "The given start or end time is invalid.".to_string(),
843            ))
844        }
845    }
846    async fn get_trades<S>(
847        &self, symbol: S, start_time: u64, end_time: u64,
848    ) -> Result<Vec<TradeHistory>>
849    where
850        S: Into<String>,
851    {
852        let symbol_string = symbol.into(); // Convert once
853        let mut trades = match self
854            .trade_history_from(symbol_string.clone(), start_time)
855            .await
856        {
857            Ok(trades_vec) => trades_vec,
858            Err(e) => return Err(e),
859        };
860
861        trades.retain(|trade| trade.time <= end_time);
862        Ok(trades)
863    }
864
865    fn build_order(&self, order: OrderRequest) -> BTreeMap<String, String> {
866        let mut open_order: BTreeMap<String, String> = BTreeMap::new();
867
868        open_order.insert("symbol".into(), order.symbol);
869        open_order.insert("quantity".into(), format!("{}", order.qty));
870        open_order.insert("side".into(), order.order_side.to_string());
871        open_order.insert("type".into(), order.order_type.to_string());
872        if order.order_type == OrderType::Limit || order.order_type == OrderType::StopLossLimit {
873            open_order.insert("timeInForce".into(), order.time_in_force.to_string());
874            open_order.insert("price".into(), format!("{}", order.price));
875        }
876        if order.order_type == OrderType::StopLossLimit {
877            if let Some(stop_price) = order.stop_price {
878                open_order.insert("stopPrice".into(), format!("{}", stop_price));
879            }
880        }
881        if let Some(ref new_client_order_id) = order.new_client_order_id {
882            open_order.insert("newClientOrderId".into(), new_client_order_id.clone());
883        }
884
885        open_order
886    }
887
888    fn build_quote_quantity_order(
889        &self, order: OrderQuoteQuantityRequest,
890    ) -> BTreeMap<String, String> {
891        let mut open_order: BTreeMap<String, String> = BTreeMap::new();
892
893        open_order.insert("symbol".into(), order.symbol);
894        open_order.insert("quoteOrderQty".into(), format!("{}", order.quote_order_qty));
895        open_order.insert("side".into(), order.order_side.to_string());
896        open_order.insert("type".into(), order.order_type.to_string());
897        if order.order_type == OrderType::Limit {
898            open_order.insert("timeInForce".into(), order.time_in_force.to_string());
899            open_order.insert("price".into(), format!("{}", order.price));
900        }
901        if let Some(ref new_client_order_id) = order.new_client_order_id {
902            open_order.insert("newClientOrderId".into(), new_client_order_id.clone());
903        }
904
905        open_order
906    }
907}