ccxt_exchanges/binance/parser/
order.rs

1#![allow(dead_code)]
2
3use super::{parse_decimal, value_to_hashmap};
4use ccxt_core::{
5    Result,
6    error::{Error, ParseError},
7    types::{
8        Market, OcoOrder, OcoOrderInfo, Order, OrderReport, OrderSide, OrderStatus, OrderType,
9        TimeInForce,
10    },
11};
12use serde_json::Value;
13
14/// Parse order data from Binance order response.
15pub fn parse_order(data: &Value, market: Option<&Market>) -> Result<Order> {
16    let symbol = if let Some(m) = market {
17        m.symbol.clone()
18    } else {
19        data["symbol"]
20            .as_str()
21            .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
22            .to_string()
23    };
24
25    let id = data["orderId"]
26        .as_u64()
27        .ok_or_else(|| Error::from(ParseError::missing_field("orderId")))?
28        .to_string();
29
30    let timestamp = data["time"]
31        .as_i64()
32        .or_else(|| data["transactTime"].as_i64());
33
34    let status_str = data["status"]
35        .as_str()
36        .ok_or_else(|| Error::from(ParseError::missing_field("status")))?;
37
38    let status = match status_str {
39        "FILLED" => OrderStatus::Closed,
40        "CANCELED" => OrderStatus::Cancelled,
41        "EXPIRED" => OrderStatus::Expired,
42        "REJECTED" => OrderStatus::Rejected,
43        _ => OrderStatus::Open,
44    };
45
46    let side = match data["side"].as_str() {
47        Some("BUY") => OrderSide::Buy,
48        Some("SELL") => OrderSide::Sell,
49        _ => return Err(Error::from(ParseError::invalid_format("data", "side"))),
50    };
51
52    let order_type = match data["type"].as_str() {
53        Some("MARKET") => OrderType::Market,
54        Some("STOP_LOSS") => OrderType::StopLoss,
55        Some("STOP_LOSS_LIMIT") => OrderType::StopLossLimit,
56        Some("TAKE_PROFIT") => OrderType::TakeProfit,
57        Some("TAKE_PROFIT_LIMIT" | "TAKE_PROFIT_MARKET") => OrderType::TakeProfitLimit,
58        Some("STOP_MARKET" | "STOP") => OrderType::StopMarket,
59        Some("TRAILING_STOP_MARKET") => OrderType::TrailingStop,
60        Some("LIMIT_MAKER") => OrderType::LimitMaker,
61        _ => OrderType::Limit,
62    };
63
64    let time_in_force = match data["timeInForce"].as_str() {
65        Some("GTC") => Some(TimeInForce::GTC),
66        Some("IOC") => Some(TimeInForce::IOC),
67        Some("FOK") => Some(TimeInForce::FOK),
68        Some("GTX") => Some(TimeInForce::PO),
69        _ => None,
70    };
71
72    let price = parse_decimal(data, "price");
73    let amount = parse_decimal(data, "origQty");
74    let filled = parse_decimal(data, "executedQty");
75    let remaining = match (&amount, &filled) {
76        (Some(a), Some(f)) => Some(*a - *f),
77        _ => None,
78    };
79
80    let cost = parse_decimal(data, "cummulativeQuoteQty");
81
82    let average = match (&cost, &filled) {
83        (Some(c), Some(f)) if !f.is_zero() => Some(*c / *f),
84        _ => None,
85    };
86
87    Ok(Order {
88        id,
89        client_order_id: data["clientOrderId"].as_str().map(ToString::to_string),
90        timestamp,
91        datetime: timestamp.map(|t| {
92            chrono::DateTime::from_timestamp(t / 1000, 0)
93                .map(|dt| dt.to_rfc3339())
94                .unwrap_or_default()
95        }),
96        last_trade_timestamp: data["updateTime"].as_i64(),
97        status,
98        symbol,
99        order_type,
100        time_in_force: time_in_force.map(|t| t.to_string()),
101        side,
102        price,
103        average,
104        amount: amount.ok_or_else(|| Error::from(ParseError::missing_field("amount")))?,
105        filled,
106        remaining,
107        cost,
108        trades: None,
109        fee: None,
110        post_only: None,
111        reduce_only: data["reduceOnly"].as_bool(),
112        trigger_price: parse_decimal(data, "triggerPrice"),
113        stop_price: parse_decimal(data, "stopPrice"),
114        take_profit_price: parse_decimal(data, "takeProfitPrice"),
115        stop_loss_price: parse_decimal(data, "stopLossPrice"),
116        trailing_delta: parse_decimal(data, "trailingDelta"),
117        trailing_percent: super::parse_decimal_multi(data, &["trailingPercent", "callbackRate"]),
118        activation_price: super::parse_decimal_multi(data, &["activationPrice", "activatePrice"]),
119        callback_rate: parse_decimal(data, "callbackRate"),
120        working_type: data["workingType"].as_str().map(ToString::to_string),
121        fees: Some(Vec::new()),
122        info: value_to_hashmap(data),
123    })
124}
125
126/// Parse OCO (One-Cancels-the-Other) order data from Binance.
127pub fn parse_oco_order(data: &Value) -> Result<OcoOrder> {
128    let order_list_id = data["orderListId"]
129        .as_i64()
130        .ok_or_else(|| Error::from(ParseError::missing_field("orderListId")))?;
131
132    let list_client_order_id = data["listClientOrderId"].as_str().map(ToString::to_string);
133
134    let symbol = data["symbol"]
135        .as_str()
136        .ok_or_else(|| Error::from(ParseError::missing_field("symbol")))?
137        .to_string();
138
139    let list_status = data["listStatusType"]
140        .as_str()
141        .ok_or_else(|| Error::from(ParseError::missing_field("listStatusType")))?
142        .to_string();
143
144    let list_order_status = data["listOrderStatus"]
145        .as_str()
146        .ok_or_else(|| Error::from(ParseError::missing_field("listOrderStatus")))?
147        .to_string();
148
149    let transaction_time = data["transactionTime"]
150        .as_i64()
151        .ok_or_else(|| Error::from(ParseError::missing_field("transactionTime")))?;
152
153    let datetime = chrono::DateTime::from_timestamp(transaction_time / 1000, 0)
154        .map(|dt| dt.to_rfc3339())
155        .unwrap_or_default();
156
157    let mut orders = Vec::new();
158    if let Some(orders_array) = data["orders"].as_array() {
159        for order in orders_array {
160            let order_info = OcoOrderInfo {
161                symbol: order["symbol"].as_str().unwrap_or(&symbol).to_string(),
162                order_id: order["orderId"]
163                    .as_i64()
164                    .ok_or_else(|| Error::from(ParseError::missing_field("orderId")))?,
165                client_order_id: order["clientOrderId"].as_str().map(ToString::to_string),
166            };
167            orders.push(order_info);
168        }
169    }
170
171    let order_reports = if let Some(reports_array) = data["orderReports"].as_array() {
172        let mut reports = Vec::new();
173        for report in reports_array {
174            let order_report = OrderReport {
175                symbol: report["symbol"].as_str().unwrap_or(&symbol).to_string(),
176                order_id: report["orderId"]
177                    .as_i64()
178                    .ok_or_else(|| Error::from(ParseError::missing_field("orderId")))?,
179                order_list_id: report["orderListId"].as_i64().unwrap_or(order_list_id),
180                client_order_id: report["clientOrderId"].as_str().map(ToString::to_string),
181                transact_time: report["transactTime"].as_i64().unwrap_or(transaction_time),
182                price: report["price"].as_str().unwrap_or("0").to_string(),
183                orig_qty: report["origQty"].as_str().unwrap_or("0").to_string(),
184                executed_qty: report["executedQty"].as_str().unwrap_or("0").to_string(),
185                cummulative_quote_qty: report["cummulativeQuoteQty"]
186                    .as_str()
187                    .unwrap_or("0")
188                    .to_string(),
189                status: report["status"].as_str().unwrap_or("NEW").to_string(),
190                time_in_force: report["timeInForce"].as_str().unwrap_or("GTC").to_string(),
191                type_: report["type"].as_str().unwrap_or("LIMIT").to_string(),
192                side: report["side"].as_str().unwrap_or("SELL").to_string(),
193                stop_price: report["stopPrice"].as_str().map(ToString::to_string),
194            };
195            reports.push(order_report);
196        }
197        Some(reports)
198    } else {
199        None
200    };
201
202    Ok(OcoOrder {
203        info: Some(data.clone()),
204        order_list_id,
205        list_client_order_id,
206        symbol,
207        list_status,
208        list_order_status,
209        transaction_time,
210        datetime,
211        orders,
212        order_reports,
213    })
214}
215
216/// Parse edit order response from Binance cancelReplace endpoint.
217pub fn parse_edit_order_result(data: &Value, market: Option<&Market>) -> Result<Order> {
218    let new_order_data = data.get("newOrderResponse").ok_or_else(|| {
219        Error::from(ParseError::invalid_format(
220            "data",
221            "Missing newOrderResponse field",
222        ))
223    })?;
224
225    parse_order(new_order_data, market)
226}