1pub use crate as nash_protocol;
2use openlimits_exchange::model::{OrderBookRequest, OrderBookResponse, AskBid, CancelOrderRequest, OrderCanceled, CancelAllOrdersRequest, OrderType, Order, TradeHistoryRequest, Trade, Side, Liquidity, GetHistoricRatesRequest, GetHistoricTradesRequest, Interval, Candle, GetOrderHistoryRequest, OrderStatus, GetPriceTickerRequest, Ticker, GetOrderRequest, TimeInForce, Paginator};
3use openlimits_exchange::{OpenLimitsError, MissingImplementationContent};
4use openlimits_exchange::model::websocket::{AccountOrders, Subscription, WebSocketResponse, OpenLimitsWebSocketMessage};
5use openlimits_exchange::shared::{Result, timestamp_to_utc_datetime};
6use rust_decimal::Decimal;
7use std::convert::{TryFrom, TryInto};
8use crate::types::{BuyOrSell, DateTimeRange};
9use crate::protocol::subscriptions::updated_account_orders::SubscribeAccountOrders;
10use chrono::Utc;
11use std::str::FromStr;
12use crate::types::market_pair::MarketPair;
13use crate::protocol::subscriptions::SubscriptionResponse;
14use crate::protocol::subscriptions::updated_orderbook::SubscribeOrderbookResponse;
15use crate::protocol::subscriptions::trades::TradesResponse;
16
17pub fn try_split_paginator(
18 paginator: Option<Paginator>,
19) -> Result<(
20 Option<String>,
21 Option<i64>,
22 Option<nash_protocol::types::DateTimeRange>,
23)> {
24 Ok(match paginator {
25 Some(paginator) => (
26 paginator.before,
27 match paginator.limit {
28 Some(v) => Some(i64::try_from(v).map_err(|_| {
29 OpenLimitsError::InvalidParameter(
30 "Couldn't convert paginator limit to i64".to_string(),
31 )
32 })?),
33 None => None,
34 },
35 if paginator.start_time.is_some() && paginator.end_time.is_some() {
36 Some(nash_protocol::types::DateTimeRange {
37 start: paginator.start_time.map(timestamp_to_utc_datetime).unwrap(),
38 stop: paginator.end_time.map(timestamp_to_utc_datetime).unwrap(),
39 })
40 } else {
41 None
42 },
43 ),
44 None => (None, None, None),
45 })
46}
47
48impl From<&OrderBookRequest> for nash_protocol::protocol::orderbook::OrderbookRequest {
49 fn from(req: &OrderBookRequest) -> Self {
50 let market = req.market_pair.clone();
51 let market = MarketPair::from(market).0;
52 Self { market }
53 }
54}
55
56impl From<nash_protocol::protocol::orderbook::OrderbookResponse> for OrderBookResponse {
57 fn from(book: nash_protocol::protocol::orderbook::OrderbookResponse) -> Self {
58 Self {
59 update_id: Some(book.update_id as u64),
60 last_update_id: Some(book.last_update_id as u64),
61 bids: book.bids.into_iter().map(Into::into).collect(),
62 asks: book.asks.into_iter().map(Into::into).collect(),
63 }
64 }
65}
66
67impl From<nash_protocol::types::OrderbookOrder> for AskBid {
68 fn from(resp: nash_protocol::types::OrderbookOrder) -> Self {
69 let price = Decimal::from_str(&resp.price).expect("Couldn't parse Decimal from string.");
70 let qty = Decimal::from_str(&resp.amount.to_string())
71 .expect("Couldn't parse Decimal from string.");
72 Self { price, qty }
73 }
74}
75
76impl From<&CancelOrderRequest> for nash_protocol::protocol::cancel_order::CancelOrderRequest {
77 fn from(req: &CancelOrderRequest) -> Self {
78 let market = req.market_pair.clone().expect("Couldn't get market_pair.");
80
81 Self {
82 market,
83 order_id: req.id.clone(),
84 }
85 }
86}
87
88impl From<nash_protocol::protocol::cancel_order::CancelOrderResponse> for OrderCanceled {
89 fn from(resp: nash_protocol::protocol::cancel_order::CancelOrderResponse) -> Self {
90 Self { id: resp.order_id }
91 }
92}
93
94impl From<&CancelAllOrdersRequest> for nash_protocol::protocol::cancel_all_orders::CancelAllOrders {
95 fn from(req: &CancelAllOrdersRequest) -> Self {
96 let market = req
98 .market_pair
99 .clone()
100 .expect("Market pair is a required param for Nash");
101 let market = MarketPair::from(market).0;
102 Self { market }
103 }
104}
105
106impl From<nash_protocol::types::OrderType> for OrderType {
107 fn from(order_type: nash_protocol::types::OrderType) -> Self {
108 match order_type {
109 nash_protocol::types::OrderType::Limit => OrderType::Limit,
110 nash_protocol::types::OrderType::Market => OrderType::Market,
111 nash_protocol::types::OrderType::StopLimit => OrderType::StopLimit,
112 nash_protocol::types::OrderType::StopMarket => OrderType::StopMarket,
113 }
114 }
115}
116
117impl From<nash_protocol::protocol::place_order::PlaceOrderResponse> for Order {
118 fn from(resp: nash_protocol::protocol::place_order::PlaceOrderResponse) -> Self {
119 Self {
120 id: resp.order_id,
121 market_pair: resp.market.name,
122 client_order_id: None,
123 created_at: Some(resp.placed_at.timestamp_millis() as u64),
124 order_type: resp.order_type.into(),
125 side: resp.buy_or_sell.into(),
126 status: resp.status.into(),
127 size: Decimal::from(0),
128 price: None,
129 remaining: None,
130 trades: Vec::new(),
131 }
132 }
133}
134
135impl TryFrom<&TradeHistoryRequest>
136for nash_protocol::protocol::list_account_trades::ListAccountTradesRequest
137{
138 type Error = OpenLimitsError;
139 fn try_from(req: &TradeHistoryRequest) -> openlimits_exchange::shared::Result<Self> {
140 let (before, limit, range) = try_split_paginator(req.paginator.clone())?;
141 let market = req.market_pair.clone();
142 let market = market.map(|market| MarketPair::from(market).0);
143 Ok(Self { market, before, limit, range })
144 }
145}
146
147impl From<nash_protocol::types::Trade> for Trade {
148 fn from(resp: nash_protocol::types::Trade) -> Self {
149 let qty = Decimal::from_str(&resp.amount.to_string())
150 .expect("Couldn't parse Decimal from string.");
151 let price = Decimal::from_str(&resp.limit_price.to_string())
152 .expect("Couldn't parse Decimal from string.");
153
154 let fees = match resp.account_side {
155 nash_protocol::types::AccountTradeSide::Taker => {
156 Decimal::from_str(&resp.taker_fee.to_string())
157 .expect("Couldn't parse Decimal from string.")
158 }
159 _ => Decimal::from(0),
160 };
161
162 let (buyer_order_id, seller_order_id) = match resp.direction {
163 nash_protocol::types::BuyOrSell::Buy => (resp.taker_order_id, resp.maker_order_id),
164 nash_protocol::types::BuyOrSell::Sell => (resp.maker_order_id, resp.taker_order_id),
165 };
166
167 Self {
168 id: resp.id,
169 created_at: (resp.executed_at.timestamp_millis() as u64).to_string(),
170 fees: Some(fees),
171 liquidity: Some(resp.account_side.into()),
172 market_pair: resp.market.clone(),
173 buyer_order_id: Some(buyer_order_id),
174 seller_order_id: Some(seller_order_id),
175 price,
176 qty,
177 side: resp.direction.into(),
178 }
179 }
180}
181
182impl From<nash_protocol::types::BuyOrSell> for Side {
183 fn from(side: nash_protocol::types::BuyOrSell) -> Self {
184 match side {
185 nash_protocol::types::BuyOrSell::Buy => Side::Buy,
186 nash_protocol::types::BuyOrSell::Sell => Side::Sell,
187 }
188 }
189}
190
191impl From<nash_protocol::types::AccountTradeSide> for Liquidity {
192 fn from(side: nash_protocol::types::AccountTradeSide) -> Self {
193 match side {
194 nash_protocol::types::AccountTradeSide::Taker => Liquidity::Taker,
195 _ => Liquidity::Maker,
196 }
197 }
198}
199
200impl TryFrom<&GetHistoricRatesRequest>
201for nash_protocol::protocol::list_candles::ListCandlesRequest
202{
203 type Error = OpenLimitsError;
204 fn try_from(req: &GetHistoricRatesRequest) -> openlimits_exchange::shared::Result<Self> {
205 let (before, limit, range) = try_split_paginator(req.paginator.clone())?;
206 let market = req.market_pair.clone();
207 let market = MarketPair::from(market).0;
208
209 Ok(Self {
210 market,
211 chronological: None,
212 before,
213 interval: Some(
214 req.interval
215 .try_into()
216 .expect("Couldn't convert Interval to CandleInterval."),
217 ),
218 limit,
219 range,
220 })
221 }
222}
223
224impl TryFrom<&GetHistoricTradesRequest>
225for nash_protocol::protocol::list_trades::ListTradesRequest
226{
227 type Error = OpenLimitsError;
228 fn try_from(req: &GetHistoricTradesRequest) -> openlimits_exchange::shared::Result<Self> {
229 let market = req.market_pair.clone();
230 let (before, limit, _) = try_split_paginator(req.paginator.clone())?;
231 Ok(Self {
233 market,
234 before,
235 limit,
236 })
237 }
238}
239
240impl TryFrom<Interval> for nash_protocol::types::CandleInterval {
241 type Error = OpenLimitsError;
242 fn try_from(interval: Interval) -> openlimits_exchange::shared::Result<Self> {
243 match interval {
244 Interval::OneMinute => Ok(nash_protocol::types::CandleInterval::OneMinute),
245 Interval::FiveMinutes => Ok(nash_protocol::types::CandleInterval::FiveMinute),
246 Interval::FifteenMinutes => Ok(nash_protocol::types::CandleInterval::FifteenMinute),
247 Interval::ThirtyMinutes => Ok(nash_protocol::types::CandleInterval::ThirtyMinute),
248 Interval::OneHour => Ok(nash_protocol::types::CandleInterval::OneHour),
249 Interval::SixHours => Ok(nash_protocol::types::CandleInterval::SixHour),
250 Interval::TwelveHours => Ok(nash_protocol::types::CandleInterval::TwelveHour),
251 Interval::OneDay => Ok(nash_protocol::types::CandleInterval::OneDay),
252 _ => {
253 let err = MissingImplementationContent {
254 message: String::from("Not supported interval"),
255 };
256 Err(OpenLimitsError::MissingImplementation(err))
257 }
258 }
259 }
260}
261
262impl From<nash_protocol::types::Candle> for Candle {
263 fn from(candle: nash_protocol::types::Candle) -> Self {
264 let close = Decimal::from_str(&candle.close_price.to_string())
265 .expect("Couldn't parse Decimal from string.");
266 let high = Decimal::from_str(&candle.high_price.to_string())
267 .expect("Couldn't parse Decimal from string.");
268 let low = Decimal::from_str(&candle.low_price.to_string())
269 .expect("Couldn't parse Decimal from string.");
270 let open = Decimal::from_str(&candle.open_price.to_string())
271 .expect("Couldn't parse Decimal from string.");
272 let volume = Decimal::from_str(&candle.a_volume.to_string())
273 .expect("Couldn't parse Decimal from string.");
274
275 Self {
276 close,
277 high,
278 low,
279 open,
280 time: candle.interval_start.timestamp_millis() as u64,
281 volume,
282 }
283 }
284}
285
286impl TryFrom<&GetOrderHistoryRequest>
287for nash_protocol::protocol::list_account_orders::ListAccountOrdersRequest
288{
289 type Error = OpenLimitsError;
290 fn try_from(req: &GetOrderHistoryRequest) -> openlimits_exchange::shared::Result<Self> {
291 let (before, limit, range) = try_split_paginator(req.paginator.clone())?;
292 let market = req.market_pair.clone();
293 let market = market.map(|market| MarketPair::from(market).0);
294
295 Ok(Self {
296 market,
297 before,
298 limit,
299 range,
300 buy_or_sell: None,
301 order_type: None,
302 status: match req.order_status.clone() {
303 Some(v) => Some(
304 v.into_iter()
305 .map(TryInto::try_into)
306 .collect::<Result<Vec<nash_protocol::types::OrderStatus>>>()?,
307 ),
308 None => None,
309 },
310 })
311 }
312}
313
314impl From<nash_protocol::types::Order> for Order {
315 fn from(order: nash_protocol::types::Order) -> Self {
316 let size = Decimal::from_str(&order.amount_placed.to_string())
317 .expect("Couldn't parse Decimal from string.");
318 let price = order
319 .limit_price
320 .map(|p| Decimal::from_str(&p.to_string()).unwrap());
321 let remaining = Some(
322 Decimal::from_str(&order.amount_remaining.to_string())
323 .expect("Couldn't parse Decimal from string."),
324 );
325
326 Self {
327 id: order.id,
328 market_pair: order.market.clone(),
329 client_order_id: None,
330 created_at: Some(order.placed_at.timestamp_millis() as u64),
331 order_type: order.order_type.into(),
332 side: order.buy_or_sell.into(),
333 status: order.status.into(),
334 size,
335 price,
336 remaining,
337 trades: order.trades.into_iter().map(Into::into).collect(),
338 }
339 }
340}
341
342impl From<nash_protocol::types::OrderStatus> for OrderStatus {
343 fn from(status: nash_protocol::types::OrderStatus) -> Self {
344 match status {
345 nash_protocol::types::OrderStatus::Filled => OrderStatus::Filled,
346 nash_protocol::types::OrderStatus::Open => OrderStatus::Open,
347 nash_protocol::types::OrderStatus::Canceled => OrderStatus::Canceled,
348 nash_protocol::types::OrderStatus::Pending => OrderStatus::Pending,
349 }
350 }
351}
352
353impl TryFrom<OrderStatus> for nash_protocol::types::OrderStatus {
354 type Error = OpenLimitsError;
355 fn try_from(status: OrderStatus) -> openlimits_exchange::shared::Result<Self> {
356 Ok(match status {
357 OrderStatus::Filled => nash_protocol::types::OrderStatus::Filled,
358 OrderStatus::Open => nash_protocol::types::OrderStatus::Open,
359 OrderStatus::Canceled => nash_protocol::types::OrderStatus::Canceled,
360 OrderStatus::Pending => nash_protocol::types::OrderStatus::Pending,
361 _ => {
362 return Err(OpenLimitsError::InvalidParameter(
363 "Had invalid order status for Nash".to_string(),
364 ))
365 }
366 })
367 }
368}
369
370impl From<&GetPriceTickerRequest> for nash_protocol::protocol::get_ticker::TickerRequest {
371 fn from(req: &GetPriceTickerRequest) -> Self {
372 let market = req.market_pair.clone();
373 let market = MarketPair::from(market).0;
374
375 Self { market }
376 }
377}
378
379impl From<nash_protocol::protocol::get_ticker::TickerResponse> for Ticker {
380 fn from(resp: nash_protocol::protocol::get_ticker::TickerResponse) -> Self {
381 let mut price = None;
382 if resp.best_ask_price.is_some() && resp.best_bid_price.is_some() {
383 let ask = Decimal::from_str(&resp.best_ask_price.unwrap().to_string())
384 .expect("Couldn't parse Decimal from string.");
385 let bid = Decimal::from_str(&resp.best_bid_price.unwrap().to_string())
386 .expect("Couldn't parse Decimal from string.");
387 price = Some((ask + bid) / Decimal::from(2));
388 }
389 let mut price_24h = None;
390 if resp.high_price_24h.is_some() && resp.low_price_24h.is_some() {
391 let day_high = Decimal::from_str(
392 &resp
393 .high_price_24h
394 .expect("Couldn't get high price 24h.")
395 .to_string(),
396 )
397 .expect("Couldn't parse Decimal from string.");
398 let day_low = Decimal::from_str(
399 &resp
400 .low_price_24h
401 .expect("Couldn't get low price 24h.")
402 .to_string(),
403 )
404 .expect("Couldn't parse Decimal from string.");
405 price_24h = Some((day_high + day_low) / Decimal::from(2));
406 }
407 Self { price, price_24h }
408 }
409}
410
411impl From<&GetOrderRequest> for nash_protocol::protocol::get_account_order::GetAccountOrderRequest {
412 fn from(req: &GetOrderRequest) -> Self {
413 Self {
414 order_id: req.id.clone(),
415 }
416 }
417}
418
419impl From<Side> for BuyOrSell {
420 fn from(side: Side) -> Self {
421 match side {
422 Side::Buy => BuyOrSell::Buy,
423 Side::Sell => BuyOrSell::Sell,
424 }
425 }
426}
427
428impl TryFrom<OrderType> for nash_protocol::types::OrderType {
429 type Error = OpenLimitsError;
430 fn try_from(order_type: OrderType) -> Result<Self> {
431 match order_type {
432 OrderType::Limit => Ok(Self::Limit),
433 OrderType::Market => Ok(Self::Market),
434 OrderType::StopLimit => Ok(Self::StopLimit),
435 OrderType::StopMarket => Ok(Self::StopMarket),
436 OrderType::Unknown => Err(OpenLimitsError::InvalidParameter(
437 "Had invalid order type for Nash".to_string(),
438 )),
439 }
440 }
441}
442
443impl From<AccountOrders> for SubscribeAccountOrders {
444 fn from(account_orders: AccountOrders) -> Self {
445 let market = account_orders.market.map(|market| MarketPair::from(market.clone()).0);
446 Self {
447 market,
448 order_type: account_orders.order_type.map(|x| {
449 x.iter()
450 .cloned()
451 .map(|x| x.try_into().ok())
452 .filter(|x| x.is_some())
453 .map(|x| x.unwrap())
454 .collect()
455 }),
456 range: account_orders.range.map(|range| DateTimeRange {
457 start: timestamp_to_utc_datetime(range.start),
458 stop: timestamp_to_utc_datetime(range.end),
459 }),
460 buy_or_sell: account_orders.buy_or_sell.map(|x| x.into()),
461 status: account_orders.status.map(|x| {
462 x.iter()
463 .cloned()
464 .map(|x| x.try_into().ok())
465 .filter(|x| x.is_some())
466 .map(|x| x.unwrap())
467 .collect()
468 }),
469 }
470 }
471}
472
473impl TryFrom<SubscriptionResponse> for WebSocketResponse<SubscriptionResponse> {
474 type Error = OpenLimitsError;
475
476 fn try_from(value: SubscriptionResponse) -> Result<Self> {
477 match value {
478 SubscriptionResponse::Orderbook(orders) => {
479 Ok(WebSocketResponse::Generic(orders.into()))
480 },
481 SubscriptionResponse::Trades(trades) => {
482 Ok(WebSocketResponse::Generic(trades.into()))
483 },
484 _ => Ok(WebSocketResponse::Raw(value))
485 }
486 }
487}
488
489impl From<TradesResponse> for OpenLimitsWebSocketMessage {
490 fn from(from: TradesResponse) -> Self {
491 let trades = from.trades.into_iter().map(|x| x.into()).collect();
492 Self::Trades(trades)
493 }
494}
495
496impl From<SubscribeOrderbookResponse> for OpenLimitsWebSocketMessage {
497 fn from(from: SubscribeOrderbookResponse) -> Self {
498 let asks = from.asks.into_iter().map(|orderbook| orderbook.into()).collect();
499 let bids = from.bids.into_iter().map(|orderbook| orderbook.into()).collect();
500 let last_update_id = Some(from.last_update_id as u64);
501 let update_id = Some(from.update_id as u64);
502 let orderbook = OrderBookResponse { asks, bids, last_update_id, update_id };
503 Self::OrderBook(orderbook)
504 }
505}
506
507impl From<Subscription> for nash_protocol::protocol::subscriptions::SubscriptionRequest {
508 fn from(sub: Subscription) -> Self {
509 match sub {
510 Subscription::OrderBookUpdates(market) => {
511 let market = MarketPair::from(market).0;
512 Self::Orderbook(
513 nash_protocol::protocol::subscriptions::updated_orderbook::SubscribeOrderbook {
514 market,
515 },
516 )
517 },
518 Subscription::Trades(market) => {
519 let market = MarketPair::from(market).0;
520 Self::Trades(
521 nash_protocol::protocol::subscriptions::trades::SubscribeTrades { market },
522 )
523 },
524 }
541 }
542}
543
544impl From<TimeInForce> for nash_protocol::types::OrderCancellationPolicy {
545 fn from(tif: TimeInForce) -> Self {
546 match tif {
547 TimeInForce::GoodTillCancelled => {
548 nash_protocol::types::OrderCancellationPolicy::GoodTilCancelled
549 }
550 TimeInForce::FillOrKill => nash_protocol::types::OrderCancellationPolicy::FillOrKill,
551 TimeInForce::ImmediateOrCancelled => {
552 nash_protocol::types::OrderCancellationPolicy::ImmediateOrCancel
553 }
554 TimeInForce::GoodTillTime(duration) => {
555 let expire_time = Utc::now() + duration;
556 nash_protocol::types::OrderCancellationPolicy::GoodTilTime(expire_time)
557 }
558 }
559 }
560}
561
562impl From<nash_protocol::errors::ProtocolError> for openlimits_exchange::OpenLimitsError {
563 fn from(error: nash_protocol::errors::ProtocolError) -> Self {
564 Self::Generic(Box::new(error))
565 }
566}