1use 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 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 if let Some(p) = request.price {
90 request_params.insert("price".to_string(), p.to_string());
91 }
92
93 if let Some(sp) = request.stop_price {
95 request_params.insert("stopPrice".to_string(), sp.to_string());
96 }
97
98 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", };
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 request_params.insert("timeInForce".to_string(), "GTC".to_string());
113 }
114
115 if let Some(client_id) = request.client_order_id {
117 request_params.insert("newClientOrderId".to_string(), client_id);
118 }
119
120 if let Some(reduce_only) = request.reduce_only {
122 request_params.insert("reduceOnly".to_string(), reduce_only.to_string());
123 }
124
125 if request.post_only == Some(true) {
127 if !request_params.contains_key("timeInForce") {
129 request_params.insert("timeInForce".to_string(), "GTX".to_string());
130 }
131 }
132
133 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 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 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 if let Some(delta) = request.trailing_delta {
158 request_params.insert("trailingDelta".to_string(), delta.to_string());
159 }
160
161 if let Some(percent) = request.trailing_percent {
163 if market.is_spot() {
164 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 if let Some(activation) = request.activation_price {
176 request_params.insert("activationPrice".to_string(), activation.to_string());
177 }
178
179 if let Some(rate) = request.callback_rate {
181 request_params.insert("callbackRate".to_string(), rate.to_string());
182 }
183
184 if let Some(working_type) = request.working_type {
186 request_params.insert("workingType".to_string(), working_type);
187 }
188
189 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 #[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 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 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 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 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 let delta = (percent * Decimal::from(100)).to_string();
324 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 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 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 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 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 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 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 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 #[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 #[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 #[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}