exc_binance/types/adaptations/
trading.rs

1use std::{collections::HashMap, ops::Neg};
2
3use exc_core::{types, Adaptor, ExchangeError, Str};
4use futures::{FutureExt, StreamExt, TryStreamExt};
5use rust_decimal::Decimal;
6use time::OffsetDateTime;
7
8use crate::{
9    http::{
10        request::trading::{CancelOrder, GetOrder, GetOrderInner, PlaceOrder},
11        response::trading::Order,
12    },
13    types::{
14        trading::{self, OrderSide, Status, TimeInForce},
15        Name,
16    },
17    websocket::protocol::frame::account::OrderUpdateFrame,
18    Request,
19};
20
21impl Adaptor<types::SubscribeOrders> for Request {
22    fn from_request(req: types::SubscribeOrders) -> Result<Self, ExchangeError> {
23        Ok(Self::subscribe(Name::order_trade_update(&req.instrument)))
24    }
25
26    fn into_response(resp: Self::Response) -> Result<types::OrderStream, ExchangeError> {
27        let stream = resp.into_stream::<OrderUpdateFrame>()?;
28        Ok(stream
29            .map_err(ExchangeError::from)
30            .and_then(|update| async move { update.try_into() })
31            .boxed())
32    }
33}
34
35impl TryFrom<Order> for types::Order {
36    type Error = ExchangeError;
37
38    fn try_from(order: Order) -> Result<Self, Self::Error> {
39        match order {
40            Order::UsdMarginFutures(order) => {
41                let mut filled = order.executed_qty.abs();
42                let mut size = order.orig_qty.abs();
43                match order.side {
44                    OrderSide::Buy => {
45                        filled.set_sign_positive(true);
46                        size.set_sign_positive(true);
47                    }
48                    OrderSide::Sell => {
49                        filled.set_sign_positive(false);
50                        size.set_sign_positive(false);
51                    }
52                }
53                let kind = match order.order_type {
54                    trading::OrderType::Limit => match order.time_in_force {
55                        TimeInForce::Gtc => types::OrderKind::Limit(
56                            order.price,
57                            types::TimeInForce::GoodTilCancelled,
58                        ),
59                        TimeInForce::Fok => {
60                            types::OrderKind::Limit(order.price, types::TimeInForce::FillOrKill)
61                        }
62                        TimeInForce::Ioc => types::OrderKind::Limit(
63                            order.price,
64                            types::TimeInForce::ImmediateOrCancel,
65                        ),
66                        TimeInForce::Gtx => types::OrderKind::PostOnly(order.price),
67                    },
68                    trading::OrderType::Market => types::OrderKind::Market,
69                    other => {
70                        return Err(ExchangeError::Other(anyhow!(
71                            "unsupported order type: {other:?}"
72                        )));
73                    }
74                };
75                let status = match order.status {
76                    Status::New | Status::PartiallyFilled => types::OrderStatus::Pending,
77                    Status::Canceled | Status::Expired | Status::Filled => {
78                        types::OrderStatus::Finished
79                    }
80                    Status::NewAdl | Status::NewInsurance => types::OrderStatus::Pending,
81                };
82                Ok(types::Order {
83                    id: types::OrderId::from(order.client_order_id),
84                    target: types::Place { size, kind },
85                    state: types::OrderState {
86                        filled,
87                        cost: if filled.is_zero() {
88                            Decimal::ONE
89                        } else {
90                            order.avg_price
91                        },
92                        status,
93                        fees: HashMap::default(),
94                    },
95                    trade: None,
96                })
97            }
98            Order::Spot(order) => {
99                let ack = order.ack;
100                if let Some(result) = order.result {
101                    let mut filled = result.executed_qty.abs().normalize();
102                    let mut size = result.orig_qty.abs().normalize();
103                    match result.side {
104                        OrderSide::Buy => {
105                            filled.set_sign_positive(true);
106                            size.set_sign_positive(true);
107                        }
108                        OrderSide::Sell => {
109                            filled.set_sign_positive(false);
110                            size.set_sign_positive(false);
111                        }
112                    }
113                    let kind = match result.order_type {
114                        trading::OrderType::Limit => match result.time_in_force {
115                            TimeInForce::Gtc | TimeInForce::Gtx => types::OrderKind::Limit(
116                                result.price.normalize(),
117                                types::TimeInForce::GoodTilCancelled,
118                            ),
119                            TimeInForce::Fok => types::OrderKind::Limit(
120                                result.price.normalize(),
121                                types::TimeInForce::FillOrKill,
122                            ),
123                            TimeInForce::Ioc => types::OrderKind::Limit(
124                                result.price.normalize(),
125                                types::TimeInForce::ImmediateOrCancel,
126                            ),
127                        },
128                        trading::OrderType::Market => types::OrderKind::Market,
129                        trading::OrderType::LimitMaker => {
130                            types::OrderKind::PostOnly(result.price.normalize())
131                        }
132                        other => {
133                            return Err(ExchangeError::Other(anyhow!(
134                                "unsupported order type: {other:?}"
135                            )));
136                        }
137                    };
138                    let status = match result.status {
139                        Status::New | Status::PartiallyFilled => types::OrderStatus::Pending,
140                        Status::Canceled | Status::Expired | Status::Filled => {
141                            types::OrderStatus::Finished
142                        }
143                        Status::NewAdl | Status::NewInsurance => types::OrderStatus::Pending,
144                    };
145                    let mut fees = HashMap::default();
146                    let mut last_trade = None;
147                    for fill in order.fills {
148                        let fee = fees.entry(fill.commission_asset.clone()).or_default();
149                        *fee -= fill.commission.normalize();
150                        last_trade = Some(types::OrderTrade {
151                            price: fill.price.normalize(),
152                            size: fill.qty.normalize(),
153                            fee: -fill.commission.normalize(),
154                            fee_asset: Some(fill.commission_asset),
155                        });
156                    }
157                    Ok(types::Order {
158                        id: types::OrderId::from(ack.client_order_id().to_string()),
159                        target: types::Place { size, kind },
160                        state: types::OrderState {
161                            filled,
162                            cost: if result.executed_qty.is_zero() {
163                                Decimal::ONE
164                            } else {
165                                (result.cummulative_quote_qty / result.executed_qty).normalize()
166                            },
167                            status,
168                            fees,
169                        },
170                        trade: last_trade,
171                    })
172                } else {
173                    Err(ExchangeError::Other(anyhow::anyhow!(
174                        "order result is missing"
175                    )))
176                }
177            }
178            Order::EuropeanOptions(order) => {
179                let id = order
180                    .client_order_id
181                    .unwrap_or_else(|| Str::new(order.order_id.to_string()));
182                let mut size = order.quantity.normalize().abs();
183                match order.side {
184                    OrderSide::Buy => size.set_sign_positive(true),
185                    OrderSide::Sell => size.set_sign_positive(false),
186                }
187                let state = order
188                    .state
189                    .ok_or_else(|| ExchangeError::Other(anyhow::anyhow!("order is not ready")))?;
190                let kind = match order.order_type {
191                    trading::OrderType::Limit => match (order.post_only, state.time_in_force) {
192                        (false, TimeInForce::Gtc) => types::OrderKind::Limit(
193                            order.price.normalize(),
194                            types::TimeInForce::GoodTilCancelled,
195                        ),
196                        (false, TimeInForce::Fok) => types::OrderKind::Limit(
197                            order.price.normalize(),
198                            types::TimeInForce::FillOrKill,
199                        ),
200                        (false, TimeInForce::Ioc) => types::OrderKind::Limit(
201                            order.price.normalize(),
202                            types::TimeInForce::ImmediateOrCancel,
203                        ),
204                        (true, _) => types::OrderKind::PostOnly(order.price.normalize()),
205                        kind => {
206                            return Err(ExchangeError::Other(anyhow::anyhow!(
207                                "unsupported order kind: {kind:?}"
208                            )));
209                        }
210                    },
211                    trading::OrderType::Market => types::OrderKind::Market,
212                    other => {
213                        return Err(ExchangeError::Other(anyhow!(
214                            "unsupported order type: {other:?}"
215                        )));
216                    }
217                };
218                let mut filled = state.executed_qty.normalize().abs();
219                match order.side {
220                    OrderSide::Buy => filled.set_sign_positive(true),
221                    OrderSide::Sell => filled.set_sign_positive(false),
222                }
223                let state = types::OrderState {
224                    filled,
225                    cost: state.avg_price.normalize(),
226                    status: state.status.try_into()?,
227                    fees: HashMap::from([(state.quote_asset, state.fee.normalize().neg())]),
228                };
229                Ok(types::Order {
230                    id: types::OrderId::from(id),
231                    target: types::Place { size, kind },
232                    state,
233                    trade: None,
234                })
235            }
236        }
237    }
238}
239
240impl Adaptor<types::PlaceOrder> for Request {
241    fn from_request(req: types::PlaceOrder) -> Result<Self, ExchangeError> {
242        Ok(Self::with_rest_payload(PlaceOrder { inner: req }))
243    }
244
245    fn into_response(
246        resp: Self::Response,
247    ) -> Result<<types::PlaceOrder as exc_core::Request>::Response, ExchangeError> {
248        Ok(async move {
249            let order = resp.into_response::<Order>()?;
250            let id = types::OrderId::from(order.client_id().to_string());
251            Ok(types::Placed {
252                ts: order
253                    .updated()
254                    .map(super::from_timestamp)
255                    .unwrap_or_else(|| Ok(OffsetDateTime::now_utc()))?,
256                id,
257                order: order
258                    .try_into()
259                    .map_err(|err| {
260                        tracing::warn!(%err, "failed to convert order");
261                    })
262                    .ok(),
263            })
264        }
265        .boxed())
266    }
267}
268
269impl Adaptor<types::CancelOrder> for Request {
270    fn from_request(req: types::CancelOrder) -> Result<Self, ExchangeError> {
271        Ok(Self::with_rest_payload(CancelOrder {
272            inner: GetOrderInner {
273                symbol: req.instrument.to_uppercase(),
274                order_id: None,
275                orig_client_order_id: Some(req.id.as_str().to_string()),
276                client_order_id: None,
277            },
278        }))
279    }
280
281    fn into_response(
282        resp: Self::Response,
283    ) -> Result<<types::CancelOrder as exc_core::Request>::Response, ExchangeError> {
284        Ok(async move {
285            let order = resp.into_response::<Order>()?;
286            Ok(types::Canceled {
287                ts: order
288                    .updated()
289                    .map(super::from_timestamp)
290                    .unwrap_or_else(|| Ok(OffsetDateTime::now_utc()))?,
291                order: Some(order.try_into()?),
292            })
293        }
294        .boxed())
295    }
296}
297
298impl Adaptor<types::GetOrder> for Request {
299    fn from_request(req: types::GetOrder) -> Result<Self, ExchangeError> {
300        Ok(Self::with_rest_payload(GetOrder {
301            inner: GetOrderInner {
302                symbol: req.instrument.to_uppercase(),
303                order_id: None,
304                orig_client_order_id: Some(req.id.as_str().to_string()),
305                client_order_id: None,
306            },
307        }))
308    }
309
310    fn into_response(
311        resp: Self::Response,
312    ) -> Result<<types::GetOrder as exc_core::Request>::Response, ExchangeError> {
313        Ok(async move {
314            let order = resp.into_response::<Order>()?;
315            Ok(types::OrderUpdate {
316                ts: order
317                    .updated()
318                    .map(super::from_timestamp)
319                    .unwrap_or_else(|| Ok(OffsetDateTime::now_utc()))?,
320                order: order.try_into()?,
321            })
322        }
323        .boxed())
324    }
325}