ccxt_exchanges/binance/rest/
spot.rs1use super::super::{Binance, parser, signed_request::HttpMethod};
7use ccxt_core::{
8 Error, ParseError, Result,
9 types::{Order, OrderSide, OrderType},
10};
11use std::collections::{BTreeMap, HashMap};
12use tracing::warn;
13
14impl Binance {
15 pub async fn create_order(
34 &self,
35 symbol: &str,
36 order_type: OrderType,
37 side: OrderSide,
38 amount: f64,
39 price: Option<f64>,
40 params: Option<HashMap<String, String>>,
41 ) -> Result<Order> {
42 let market = self.base().market(symbol).await?;
43 let mut request_params = BTreeMap::new();
44
45 request_params.insert("symbol".to_string(), market.id.clone());
46 request_params.insert(
47 "side".to_string(),
48 match side {
49 OrderSide::Buy => "BUY".to_string(),
50 OrderSide::Sell => "SELL".to_string(),
51 },
52 );
53 request_params.insert(
54 "type".to_string(),
55 match order_type {
56 OrderType::Market => "MARKET".to_string(),
57 OrderType::Limit => "LIMIT".to_string(),
58 OrderType::StopLoss => "STOP_LOSS".to_string(),
59 OrderType::StopLossLimit => "STOP_LOSS_LIMIT".to_string(),
60 OrderType::TakeProfit => "TAKE_PROFIT".to_string(),
61 OrderType::TakeProfitLimit => "TAKE_PROFIT_LIMIT".to_string(),
62 OrderType::LimitMaker => "LIMIT_MAKER".to_string(),
63 OrderType::StopMarket => "STOP_MARKET".to_string(),
64 OrderType::StopLimit => "STOP_LIMIT".to_string(),
65 OrderType::TrailingStop => "TRAILING_STOP_MARKET".to_string(),
66 },
67 );
68 request_params.insert("quantity".to_string(), amount.to_string());
69
70 if let Some(p) = price {
71 request_params.insert("price".to_string(), p.to_string());
72 }
73
74 if order_type == OrderType::Limit
76 || order_type == OrderType::StopLossLimit
77 || order_type == OrderType::TakeProfitLimit
78 {
79 if !request_params.contains_key("timeInForce") {
80 request_params.insert("timeInForce".to_string(), "GTC".to_string());
81 }
82 }
83
84 if let Some(extra) = params {
85 for (k, v) in extra {
86 request_params.insert(k, v);
87 }
88 }
89
90 if order_type == OrderType::Market && side == OrderSide::Buy {
92 if let Some(cost_str) = request_params.get("cost") {
93 request_params.insert("quoteOrderQty".to_string(), cost_str.clone());
94 request_params.remove("quantity");
95 request_params.remove("cost");
96 }
97 }
98
99 if matches!(
101 order_type,
102 OrderType::StopLoss
103 | OrderType::StopLossLimit
104 | OrderType::TakeProfit
105 | OrderType::TakeProfitLimit
106 | OrderType::StopMarket
107 ) {
108 if !request_params.contains_key("stopPrice") {
109 if let Some(stop_loss) = request_params.get("stopLossPrice") {
110 request_params.insert("stopPrice".to_string(), stop_loss.clone());
111 } else if let Some(take_profit) = request_params.get("takeProfitPrice") {
112 request_params.insert("stopPrice".to_string(), take_profit.clone());
113 }
114 }
115 }
116
117 if order_type == OrderType::TrailingStop {
119 if market.is_spot() {
120 if !request_params.contains_key("trailingDelta") {
121 if let Some(percent_str) = request_params.get("trailingPercent") {
122 if let Ok(percent) = percent_str.parse::<f64>() {
123 let delta = (percent * 100.0) as i64;
124 request_params.insert("trailingDelta".to_string(), delta.to_string());
125 request_params.remove("trailingPercent");
126 }
127 }
128 }
129 }
130 }
131
132 let url = format!("{}/order", self.urls().private);
133 let data = self
134 .signed_request(url)
135 .method(HttpMethod::Post)
136 .params(request_params)
137 .execute()
138 .await?;
139
140 parser::parse_order(&data, Some(&market))
141 }
142
143 pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
158 let market = self.base().market(symbol).await?;
159 let url = format!("{}/order", self.urls().private);
160
161 let data = self
162 .signed_request(url)
163 .method(HttpMethod::Delete)
164 .param("symbol", &market.id)
165 .param("orderId", id)
166 .execute()
167 .await?;
168
169 parser::parse_order(&data, Some(&market))
170 }
171
172 pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
187 let market = self.base().market(symbol).await?;
188 let url = format!("{}/order", self.urls().private);
189
190 let data = self
191 .signed_request(url)
192 .param("symbol", &market.id)
193 .param("orderId", id)
194 .execute()
195 .await?;
196
197 parser::parse_order(&data, Some(&market))
198 }
199
200 pub async fn fetch_open_orders(&self, symbol: Option<&str>) -> Result<Vec<Order>> {
214 let market = if let Some(sym) = symbol {
215 Some(self.base().market(sym).await?)
216 } else {
217 None
218 };
219
220 let url = format!("{}/openOrders", self.urls().private);
221
222 let data = self
223 .signed_request(url)
224 .optional_param("symbol", market.as_ref().map(|m| &m.id))
225 .execute()
226 .await?;
227
228 let orders_array = data.as_array().ok_or_else(|| {
229 Error::from(ParseError::invalid_format(
230 "data",
231 "Expected array of orders",
232 ))
233 })?;
234
235 let mut orders = Vec::new();
236 for order_data in orders_array {
237 match parser::parse_order(order_data, market.as_ref().map(|v| &**v)) {
238 Ok(order) => orders.push(order),
239 Err(e) => {
240 warn!(error = %e, "Failed to parse order");
241 }
242 }
243 }
244
245 Ok(orders)
246 }
247
248 pub async fn fetch_closed_orders(
264 &self,
265 symbol: Option<&str>,
266 since: Option<i64>,
267 limit: Option<u32>,
268 ) -> Result<Vec<Order>> {
269 let all_orders = self.fetch_orders(symbol, since, None).await?;
270
271 let mut closed_orders: Vec<Order> = all_orders
272 .into_iter()
273 .filter(|order| order.status == ccxt_core::types::OrderStatus::Closed)
274 .collect();
275
276 if let Some(l) = limit {
277 closed_orders.truncate(l as usize);
278 }
279
280 Ok(closed_orders)
281 }
282
283 pub async fn cancel_all_orders(&self, symbol: &str) -> Result<Vec<Order>> {
297 let market = self.base().market(symbol).await?;
298 let url = format!("{}/openOrders", self.urls().private);
299
300 let data = self
301 .signed_request(url)
302 .method(HttpMethod::Delete)
303 .param("symbol", &market.id)
304 .execute()
305 .await?;
306
307 let orders_array = data.as_array().ok_or_else(|| {
308 Error::from(ParseError::invalid_format(
309 "data",
310 "Expected array of orders",
311 ))
312 })?;
313
314 let mut orders = Vec::new();
315 for order_data in orders_array {
316 match parser::parse_order(order_data, Some(&market)) {
317 Ok(order) => orders.push(order),
318 Err(e) => {
319 warn!(error = %e, "Failed to parse order");
320 }
321 }
322 }
323
324 Ok(orders)
325 }
326
327 pub async fn cancel_orders(&self, ids: Vec<String>, symbol: &str) -> Result<Vec<Order>> {
342 let market = self.base().market(symbol).await?;
343
344 let order_ids_json = serde_json::to_string(&ids).map_err(|e| {
345 Error::from(ParseError::invalid_format(
346 "data",
347 format!("Failed to serialize order IDs: {}", e),
348 ))
349 })?;
350
351 let url = format!("{}/openOrders", self.urls().private);
352
353 let data = self
354 .signed_request(url)
355 .method(HttpMethod::Delete)
356 .param("symbol", &market.id)
357 .param("orderIdList", order_ids_json)
358 .execute()
359 .await?;
360
361 let orders_array = data.as_array().ok_or_else(|| {
362 Error::from(ParseError::invalid_format(
363 "data",
364 "Expected array of orders",
365 ))
366 })?;
367
368 let mut orders = Vec::new();
369 for order_data in orders_array {
370 match parser::parse_order(order_data, Some(&market)) {
371 Ok(order) => orders.push(order),
372 Err(e) => {
373 warn!(error = %e, "Failed to parse order");
374 }
375 }
376 }
377
378 Ok(orders)
379 }
380
381 pub async fn fetch_orders(
397 &self,
398 symbol: Option<&str>,
399 since: Option<i64>,
400 limit: Option<u32>,
401 ) -> Result<Vec<Order>> {
402 let market = if let Some(sym) = symbol {
403 Some(self.base().market(sym).await?)
404 } else {
405 None
406 };
407
408 let url = format!("{}/allOrders", self.urls().private);
409
410 let data = self
411 .signed_request(url)
412 .optional_param("symbol", market.as_ref().map(|m| &m.id))
413 .optional_param("startTime", since)
414 .optional_param("limit", limit)
415 .execute()
416 .await?;
417
418 let orders_array = data.as_array().ok_or_else(|| {
419 Error::from(ParseError::invalid_format(
420 "data",
421 "Expected array of orders",
422 ))
423 })?;
424
425 let mut orders = Vec::new();
426 for order_data in orders_array {
427 match parser::parse_order(order_data, market.as_ref().map(|v| &**v)) {
428 Ok(order) => orders.push(order),
429 Err(e) => {
430 warn!(error = %e, "Failed to parse order");
431 }
432 }
433 }
434
435 Ok(orders)
436 }
437
438 pub async fn create_stop_loss_order(
457 &self,
458 symbol: &str,
459 side: OrderSide,
460 amount: f64,
461 stop_price: f64,
462 price: Option<f64>,
463 params: Option<HashMap<String, String>>,
464 ) -> Result<Order> {
465 let mut request_params = params.unwrap_or_default();
466
467 request_params.insert("stopPrice".to_string(), stop_price.to_string());
468
469 let order_type = if price.is_some() {
470 OrderType::StopLossLimit
471 } else {
472 OrderType::StopLoss
473 };
474
475 self.create_order(
476 symbol,
477 order_type,
478 side,
479 amount,
480 price,
481 Some(request_params),
482 )
483 .await
484 }
485
486 pub async fn create_take_profit_order(
505 &self,
506 symbol: &str,
507 side: OrderSide,
508 amount: f64,
509 take_profit_price: f64,
510 price: Option<f64>,
511 params: Option<HashMap<String, String>>,
512 ) -> Result<Order> {
513 let mut request_params = params.unwrap_or_default();
514
515 request_params.insert("stopPrice".to_string(), take_profit_price.to_string());
516
517 let order_type = if price.is_some() {
518 OrderType::TakeProfitLimit
519 } else {
520 OrderType::TakeProfit
521 };
522
523 self.create_order(
524 symbol,
525 order_type,
526 side,
527 amount,
528 price,
529 Some(request_params),
530 )
531 .await
532 }
533
534 pub async fn create_trailing_stop_order(
553 &self,
554 symbol: &str,
555 side: OrderSide,
556 amount: f64,
557 trailing_percent: f64,
558 activation_price: Option<f64>,
559 params: Option<HashMap<String, String>>,
560 ) -> Result<Order> {
561 let mut request_params = params.unwrap_or_default();
562
563 request_params.insert("trailingPercent".to_string(), trailing_percent.to_string());
564
565 if let Some(activation) = activation_price {
566 request_params.insert("activationPrice".to_string(), activation.to_string());
567 }
568
569 self.create_order(
570 symbol,
571 OrderType::TrailingStop,
572 side,
573 amount,
574 None,
575 Some(request_params),
576 )
577 .await
578 }
579}