openlimits_binance/
lib.rs

1//! This module provides functionality for communicating with the openlimits-binance API.
2
3pub mod model;
4
5pub use openlimits_exchange::shared;
6
7use async_trait::async_trait;
8use model::KlineSummaries;
9use transport::Transport;
10use client::BaseClient;
11use std::convert::TryFrom;
12use model::{websocket::TradeMessage, SymbolFilter, ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET};
13use openlimits_exchange::{
14    errors::OpenLimitsError,
15    model::{
16        AskBid, Balance, CancelAllOrdersRequest, CancelOrderRequest, Candle,
17        GetHistoricRatesRequest, GetHistoricTradesRequest, GetOrderHistoryRequest, GetOrderRequest,
18        GetPriceTickerRequest, Liquidity, OpenLimitOrderRequest, OpenMarketOrderRequest,
19        Order, OrderBookRequest, OrderBookResponse, OrderCanceled, OrderStatus, OrderType,
20        Paginator, Side, Ticker, TimeInForce, Trade, TradeHistoryRequest, Transaction,
21    }
22};
23
24use openlimits_exchange::Result;
25
26mod binance_content_error;
27mod binance_credentials;
28mod binance_parameters;
29mod transport;
30
31pub use binance_content_error::*;
32pub use binance_credentials::*;
33pub use binance_parameters::*;
34pub use transport::*;
35
36pub mod client;
37
38pub use client::stream::BinanceWebsocket;
39use openlimits_exchange::traits::info::{ExchangeInfo, ExchangeInfoRetrieval, MarketPairInfo, MarketPairHandle};
40use openlimits_exchange::traits::{Exchange, ExchangeMarketData, ExchangeAccount};
41use openlimits_exchange::exchange::Environment;
42use openlimits_exchange::model::market_pair::MarketPair;
43
44/// The main struct of the openlimits-binance module
45#[derive(Clone)]
46pub struct Binance {
47    pub exchange_info: ExchangeInfo,
48    pub client: BaseClient,
49}
50
51#[async_trait]
52impl Exchange for Binance {
53    type InitParams = BinanceParameters;
54    type InnerClient = BaseClient;
55
56    async fn new(parameters: Self::InitParams) -> Result<Self> {
57        let binance = match parameters.credentials {
58            Some(credentials) => Binance {
59                exchange_info: ExchangeInfo::new(),
60                client: BaseClient {
61                    transport: Transport::with_credential(
62                        &credentials.api_key,
63                        &credentials.api_secret,
64                        parameters.environment == Environment::Sandbox,
65                    )?,
66                },
67            },
68            None => Binance {
69                exchange_info: ExchangeInfo::new(),
70                client: BaseClient {
71                    transport: Transport::new(parameters.environment == Environment::Sandbox)?,
72                },
73            },
74        };
75
76        binance.refresh_market_info().await?;
77        Ok(binance)
78    }
79
80    fn inner_client(&self) -> Option<&Self::InnerClient> {
81        Some(&self.client)
82    }
83}
84
85#[async_trait]
86impl ExchangeInfoRetrieval for Binance {
87    async fn retrieve_pairs(&self) -> Result<Vec<MarketPairInfo>> {
88        self.client.get_exchange_info().await.map(|v| {
89            v.symbols
90                .into_iter()
91                .map(|symbol| {
92                    let lot_size = symbol
93                        .filters
94                        .iter()
95                        .find_map(|f| match f {
96                            SymbolFilter::LotSize {
97                                max_qty: _,
98                                min_qty: _,
99                                step_size,
100                            } => Some(step_size),
101                            _ => None,
102                        })
103                        .expect("Couldn't find lot size.");
104
105                    let tick_size = symbol
106                        .filters
107                        .iter()
108                        .find_map(|f| match f {
109                            SymbolFilter::PriceFilter {
110                                min_price: _,
111                                max_price: _,
112                                tick_size,
113                            } => Some(tick_size),
114                            _ => None,
115                        })
116                        .expect("Couldn't find tick size.");
117
118                    MarketPairInfo {
119                        base: symbol.base_asset,
120                        quote: symbol.quote_asset,
121                        symbol: symbol.symbol,
122                        base_increment: *lot_size,
123                        quote_increment: *tick_size,
124                        min_base_trade_size: None,
125                        min_quote_trade_size: None,
126                    }
127                })
128                .collect()
129        })
130    }
131
132    async fn refresh_market_info(&self) -> Result<Vec<MarketPairHandle>> {
133        self.exchange_info
134            .refresh(self as &dyn ExchangeInfoRetrieval)
135            .await
136    }
137
138    async fn get_pair(&self, market_pair: &MarketPair) -> Result<MarketPairHandle> {
139        let name = crate::model::MarketPair::from(market_pair.clone()).0;
140        self.exchange_info.get_pair(&name)
141    }
142}
143
144#[async_trait]
145impl ExchangeMarketData for Binance {
146    async fn order_book(&self, req: &OrderBookRequest) -> Result<OrderBookResponse> {
147        self.client
148            .get_depth(req.market_pair.clone(), None)
149            .await
150            .map(Into::into)
151    }
152
153    async fn get_price_ticker(&self, req: &GetPriceTickerRequest) -> Result<Ticker> {
154        self.client
155            .get_price(req.market_pair.clone())
156            .await
157            .map(Into::into)
158    }
159
160    async fn get_historic_rates(&self, req: &GetHistoricRatesRequest) -> Result<Vec<Candle>> {
161        let params = req.into();
162
163        self.client
164            .get_klines(&params)
165            .await
166            .map(|KlineSummaries::AllKlineSummaries(v)| v.into_iter().map(Into::into).collect())
167    }
168
169    async fn get_historic_trades(&self, _req: &GetHistoricTradesRequest) -> Result<Vec<Trade>> {
170        unimplemented!("Only implemented for Nash right now");
171    }
172}
173
174#[async_trait]
175impl ExchangeAccount for Binance {
176    async fn limit_buy(&self, req: &OpenLimitOrderRequest) -> Result<Order> {
177        let pair = self.get_pair(&req.market_pair).await?.read()?;
178        self.client
179            .limit_buy(
180                pair,
181                req.size,
182                req.price,
183                model::TimeInForce::from(req.time_in_force),
184                req.post_only,
185            )
186            .await
187            .map(Into::into)
188    }
189    async fn limit_sell(&self, req: &OpenLimitOrderRequest) -> Result<Order> {
190        let pair = self.get_pair(&req.market_pair).await?.read()?;
191        self.client
192            .limit_sell(
193                pair,
194                req.size,
195                req.price,
196                model::TimeInForce::from(req.time_in_force),
197                req.post_only,
198            )
199            .await
200            .map(Into::into)
201    }
202
203    async fn market_buy(&self, req: &OpenMarketOrderRequest) -> Result<Order> {
204        let pair = self.get_pair(&req.market_pair).await?.read()?;
205        self.client.market_buy(pair, req.size).await.map(Into::into)
206    }
207    async fn market_sell(&self, req: &OpenMarketOrderRequest) -> Result<Order> {
208        let pair = self.get_pair(&req.market_pair).await?.read()?;
209        self.client
210            .market_sell(pair, req.size)
211            .await
212            .map(Into::into)
213    }
214    async fn cancel_order(&self, req: &CancelOrderRequest) -> Result<OrderCanceled> {
215        if let Some(pair) = req.market_pair.as_ref() {
216            let u64_id = req
217                .id
218                .parse::<u64>()
219                .expect("openlimits-binance order id did not parse as u64");
220            self.client
221                .cancel_order(pair.as_ref(), u64_id)
222                .await
223                .map(Into::into)
224        } else {
225            Err(OpenLimitsError::MissingParameter(
226                "pair parameter is required.".to_string(),
227            ))
228        }
229    }
230    async fn cancel_all_orders(&self, req: &CancelAllOrdersRequest) -> Result<Vec<OrderCanceled>> {
231        if let Some(pair) = req.market_pair.as_ref() {
232            self.client
233                .cancel_all_orders(pair.clone())
234                .await
235                .map(|v| v.into_iter().map(Into::into).collect())
236        } else {
237            Err(OpenLimitsError::MissingParameter(
238                "pair parameter is required.".to_string(),
239            ))
240        }
241    }
242    async fn get_all_open_orders(&self) -> Result<Vec<Order>> {
243        self.client
244            .get_all_open_orders()
245            .await
246            .map(|v| v.into_iter().map(Into::into).collect())
247    }
248
249    async fn get_order_history(&self, req: &GetOrderHistoryRequest) -> Result<Vec<Order>> {
250        let req = model::AllOrderReq::try_from(req)?;
251        self.client
252            .get_all_orders(&req)
253            .await
254            .map(|v| v.into_iter().map(Into::into).collect())
255    }
256
257    async fn get_trade_history(&self, req: &TradeHistoryRequest) -> Result<Vec<Trade>> {
258        let req = model::TradeHistoryReq::try_from(req)?;
259        self.client
260            .trade_history(&req)
261            .await
262            .map(|v| v.into_iter().map(Into::into).collect())
263    }
264
265    async fn get_account_balances(&self, _paginator: Option<Paginator>) -> Result<Vec<Balance>> {
266        self.client
267            .get_account()
268            .await
269            .map(|v| v.balances.into_iter().map(Into::into).collect())
270    }
271
272    async fn get_order(&self, req: &GetOrderRequest) -> Result<Order> {
273        let pair = req.market_pair.clone().ok_or_else(|| {
274            OpenLimitsError::MissingParameter("market_pair parameter is required.".to_string())
275        })?;
276        let u64_id = req
277            .id
278            .parse::<u64>()
279            .expect("openlimits-binance order id did not parse as u64");
280        self.client.get_order(&pair, u64_id).await.map(Into::into)
281    }
282}
283
284impl From<model::OrderBook> for OrderBookResponse {
285    fn from(book: model::OrderBook) -> Self {
286        Self {
287            last_update_id: None,
288            update_id: Some(book.last_update_id),
289            bids: book.bids.into_iter().map(Into::into).collect(),
290            asks: book.asks.into_iter().map(Into::into).collect(),
291        }
292    }
293}
294
295impl From<model::websocket::Depth> for OrderBookResponse {
296    fn from(depth: model::websocket::Depth) -> Self {
297        Self {
298            last_update_id: Some(depth.first_update_id),
299            update_id: Some(depth.final_update_id),
300            bids: depth.bids.into_iter().map(Into::into).collect(),
301            asks: depth.asks.into_iter().map(Into::into).collect(),
302        }
303    }
304}
305
306impl From<model::websocket::TradeMessage> for Vec<Trade> {
307    fn from(trade_message: model::websocket::TradeMessage) -> Self {
308        vec![Trade {
309            id: trade_message.trade_id.to_string(),
310            buyer_order_id: Some(trade_message.buyer_order_id.to_string()),
311            seller_order_id: Some(trade_message.buyer_order_id.to_string()),
312            market_pair: trade_message.symbol,
313            price: trade_message.price,
314            qty: trade_message.qty,
315            fees: None,
316            side: match trade_message.is_buyer_maker {
317                true => Side::Buy,
318                false => Side::Sell,
319            },
320            liquidity: None,
321            created_at: trade_message.event_time.to_string(),
322        }]
323    }
324}
325
326impl From<TradeMessage> for Trade {
327    fn from(trade: TradeMessage) -> Self {
328        Self {
329            id: trade.trade_id.to_string(),
330            buyer_order_id: Some(trade.buyer_order_id.to_string()),
331            seller_order_id: Some(trade.seller_order_id.to_string()),
332            market_pair: trade.symbol,
333            price: trade.price,
334            qty: trade.qty,
335            fees: None, // Binance does not return fee on trades over WS stream
336            // https://money.stackexchange.com/questions/90686/what-does-buyer-is-maker-mean/102005#102005
337            side: match trade.is_buyer_maker {
338                true => Side::Sell,
339                false => Side::Buy,
340            },
341            liquidity: None,
342            created_at: trade.trade_order_time.to_string(),
343        }
344    }
345}
346
347impl From<model::AskBid> for AskBid {
348    fn from(bids: model::AskBid) -> Self {
349        Self {
350            price: bids.price,
351            qty: bids.qty,
352        }
353    }
354}
355
356impl From<model::Transaction> for Transaction<u64> {
357    fn from(order: model::Transaction) -> Self {
358        Self {
359            id: order.order_id,
360            market_pair: order.symbol,
361            client_order_id: Some(order.client_order_id),
362            created_at: order.transact_time,
363        }
364    }
365}
366
367impl From<model::Order> for Order {
368    fn from(order: model::Order) -> Self {
369        let order_type = match order.type_name.as_str() {
370            ORDER_TYPE_LIMIT => OrderType::Limit,
371            ORDER_TYPE_MARKET => OrderType::Market,
372            _ => OrderType::Unknown,
373        };
374
375        Self {
376            id: order.order_id.to_string(),
377            market_pair: order.symbol,
378            client_order_id: Some(order.client_order_id),
379            created_at: order.time,
380            order_type,
381            side: order.side.into(),
382            status: order.status.into(),
383            size: order.orig_qty,
384            price: Some(order.price),
385            remaining: Some(order.orig_qty - order.executed_qty),
386            trades: Vec::new(),
387        }
388    }
389}
390
391impl From<model::OrderCanceled> for OrderCanceled {
392    fn from(order: model::OrderCanceled) -> Self {
393        Self {
394            id: order.order_id.to_string(),
395        }
396    }
397}
398
399impl From<model::Balance> for Balance {
400    fn from(balance: model::Balance) -> Self {
401        Self {
402            asset: balance.asset,
403            free: balance.free,
404            total: balance.locked + balance.free,
405        }
406    }
407}
408
409impl From<model::TradeHistory> for Trade {
410    fn from(trade_history: model::TradeHistory) -> Self {
411        let (buyer_order_id, seller_order_id) = match trade_history.is_buyer {
412            true => (Some(trade_history.order_id.to_string()), None),
413            false => (None, Some(trade_history.order_id.to_string())),
414        };
415        Self {
416            id: trade_history.id.to_string(),
417            buyer_order_id,
418            seller_order_id,
419            market_pair: trade_history.symbol,
420            price: trade_history.price,
421            qty: trade_history.qty,
422            fees: Some(trade_history.commission),
423            side: match trade_history.is_buyer {
424                true => Side::Buy,
425                false => Side::Sell,
426            },
427            liquidity: match trade_history.is_maker {
428                true => Some(Liquidity::Maker),
429                false => Some(Liquidity::Taker),
430            },
431            created_at: trade_history.time.to_string(),
432        }
433    }
434}
435
436impl From<model::SymbolPrice> for Ticker {
437    fn from(ticker: model::SymbolPrice) -> Self {
438        Self {
439            price: Some(ticker.price),
440            price_24h: None,
441        }
442    }
443}
444
445impl TryFrom<&GetOrderHistoryRequest> for model::AllOrderReq {
446    type Error = OpenLimitsError;
447    fn try_from(req: &GetOrderHistoryRequest) -> Result<Self> {
448        Ok(Self {
449            paginator: req.paginator.clone().map(|p| p.into()),
450            symbol: req.market_pair
451                .clone()
452                .map(|market| crate::model::MarketPair::from(market).0)
453                .ok_or_else(|| OpenLimitsError::MissingParameter("market_pair parameter is required.".to_string()))?,
454        })
455    }
456}
457
458impl TryFrom<&TradeHistoryRequest> for model::TradeHistoryReq {
459    type Error = OpenLimitsError;
460    fn try_from(trade_history: &TradeHistoryRequest) -> Result<Self> {
461        Ok(Self {
462            paginator: trade_history.paginator.clone().map(|p| p.into()),
463            symbol: trade_history.market_pair
464                .clone()
465                .map(|market| crate::model::MarketPair::from(market).0)
466                .ok_or_else(|| OpenLimitsError::MissingParameter("market_pair parameter is required.".to_string()))?,
467        })
468    }
469}
470
471impl From<&GetHistoricRatesRequest> for model::KlineParams {
472    fn from(req: &GetHistoricRatesRequest) -> Self {
473        let interval: &str = req.interval.into();
474        let symbol = crate::model::MarketPair::from(req.market_pair.clone()).0;
475        Self {
476            interval: String::from(interval),
477            paginator: req.paginator.clone().map(|d| d.into()),
478            symbol,
479        }
480    }
481}
482
483impl From<model::KlineSummary> for Candle {
484    fn from(kline_summary: model::KlineSummary) -> Self {
485        Self {
486            time: kline_summary.open_time as u64,
487            low: kline_summary.low,
488            high: kline_summary.high,
489            open: kline_summary.open,
490            close: kline_summary.close,
491            volume: kline_summary.volume,
492        }
493    }
494}
495
496impl From<TimeInForce> for model::TimeInForce {
497    fn from(tif: TimeInForce) -> Self {
498        match tif {
499            TimeInForce::GoodTillCancelled => model::TimeInForce::GTC,
500            TimeInForce::FillOrKill => model::TimeInForce::FOK,
501            TimeInForce::ImmediateOrCancelled => model::TimeInForce::IOC,
502            _ => panic!("Binance does not support GoodTillTime policy"),
503        }
504    }
505}
506
507impl From<Paginator> for model::Paginator {
508    fn from(paginator: Paginator) -> Self {
509        Self {
510            from_id: paginator
511                .after
512                .as_ref()
513                .map(|s| s.parse().expect("openlimits-binance page id did not parse as u64")),
514            // TODO: what is this, and why do we reuse "after"?
515            order_id: paginator
516                .after
517                .map(|s| s.parse().expect("openlimits-binance order id did not parse as u64")),
518            end_time: paginator.end_time,
519            start_time: paginator.start_time,
520            limit: paginator.limit,
521        }
522    }
523}
524
525
526impl From<model::OrderStatus> for OrderStatus {
527    fn from(status: model::OrderStatus) -> OrderStatus {
528        match status {
529            model::OrderStatus::Canceled => OrderStatus::Canceled,
530            model::OrderStatus::Expired => OrderStatus::Expired,
531            model::OrderStatus::Filled => OrderStatus::Filled,
532            model::OrderStatus::New => OrderStatus::New,
533            model::OrderStatus::PartiallyFilled => OrderStatus::PartiallyFilled,
534            model::OrderStatus::PendingCancel => OrderStatus::PendingCancel,
535            model::OrderStatus::Rejected => OrderStatus::Rejected,
536        }
537    }
538}