1use 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 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 if let Some(p) = request.price {
92 request_params.insert("price".to_string(), p.to_string());
93 }
94
95 if let Some(sp) = request.stop_price {
97 request_params.insert("stopPrice".to_string(), sp.to_string());
98 }
99
100 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", };
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 request_params.insert("timeInForce".to_string(), "GTC".to_string());
115 }
116
117 if let Some(client_id) = request.client_order_id {
119 request_params.insert("newClientOrderId".to_string(), client_id);
120 }
121
122 if let Some(reduce_only) = request.reduce_only {
124 request_params.insert("reduceOnly".to_string(), reduce_only.to_string());
125 }
126
127 if request.post_only == Some(true) {
129 if !request_params.contains_key("timeInForce") {
131 request_params.insert("timeInForce".to_string(), "GTX".to_string());
132 }
133 }
134
135 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 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 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 if let Some(delta) = request.trailing_delta {
164 request_params.insert("trailingDelta".to_string(), delta.to_string());
165 }
166
167 if let Some(percent) = request.trailing_percent {
169 if market.is_spot() {
170 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 if let Some(activation) = request.activation_price {
187 request_params.insert("activationPrice".to_string(), activation.to_string());
188 }
189
190 if let Some(rate) = request.callback_rate {
192 request_params.insert("callbackRate".to_string(), rate.to_string());
193 }
194
195 if let Some(working_type) = request.working_type {
197 request_params.insert("workingType".to_string(), working_type);
198 }
199
200 if let Some(position_side) = request.position_side {
202 request_params.insert("positionSide".to_string(), position_side);
203 }
204
205 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 #[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 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 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 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 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 let delta = (percent * Decimal::from(100)).to_string();
337 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 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 pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
377 let market = self.base().market(symbol).await?;
378 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 pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
408 let market = self.base().market(symbol).await?;
409 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 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 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 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 pub async fn cancel_all_orders(&self, symbol: &str) -> Result<Vec<Order>> {
526 let market = self.base().market(symbol).await?;
527 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 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 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 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 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 #[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 #[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 #[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}