nash_protocol/
convert.rs

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        // TODO: why this param?
79        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        // TODO: why is this required param for Nash?
97        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        //FIXME: Some issues with the graphql protocol for the market to be non nil
232        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            // Subscription::AccountOrders(account_orders) => Self::AccountOrders(
525            //     account_orders.into()
526            // ),
527            // Subscription::AccountTrades(market_name) => {
528            //     let market = MarketPair::from(market).0;
529            //     Self::AccountTrades(
530            //         nash_protocol::protocol::subscriptions::new_account_trades::SubscribeAccountTrades {
531            //             market_name
532            //         }
533            //     )
534            // },
535            // Subscription::AccountBalance(symbol) => Self::AccountBalances(
536            //     nash_protocol::protocol::subscriptions::updated_account_balances::SubscribeAccountBalances {
537            //         symbol
538            //     }
539            // ),
540        }
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}