1pub 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#[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(¶ms)
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, 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 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}