async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue):
        """
        *required
        Subscribe to user stream via web socket, and keep the connection open for incoming messages
        :param ev_loop: ev_loop to execute this function in
        :param output: an async queue where the incoming messages are stored
        """
        while True:
            try:
                async with websockets.connect(Constants.BAEE_WS_URL) as ws:
                    ws: websockets.WebSocketClientProtocol = ws

                    # Send a auth request first
                    auth_request: Dict[str, Any] = {
                        "event": Constants.WS_AUTH_REQUEST_EVENT,
                        "data": self._liquid_auth.get_ws_auth_data()
                    }
                    await ws.send(ujson.dumps(auth_request))

                    active_markets_df = await LiquidAPIOrderBookDataSource.get_active_exchange_markets()
                    quoted_currencies = [
                        active_markets_df.loc[trading_pair, 'quoted_currency']
                        for trading_pair in self._trading_pairs
                    ]

                    for trading_pair, quoted_currency in zip(self._trading_pairs, quoted_currencies):
                        subscribe_request: Dict[str, Any] = {
                            "event": Constants.WS_PUSHER_SUBSCRIBE_EVENT,
                            "data": {
                                "channel": Constants.WS_USER_ACCOUNTS_SUBSCRIPTION.format(
                                    quoted_currency=quoted_currency.lower()
                                )
                            }
                        }
                        await ws.send(ujson.dumps(subscribe_request))
                    async for raw_msg in self._inner_messages(ws):
                        diff_msg = ujson.loads(raw_msg)

                        event_type = diff_msg.get('event', None)
                        if event_type == 'updated':
                            # Channel example: 'user_executions_cash_ethusd'
                            trading_pair = diff_msg.get('channel').split('_')[-1].upper()
                            diff_timestamp: float = time.time()
                            diff_msg: OrderBookMessage = LiquidOrderBook.diff_message_from_exchange(
                                diff_msg,
                                diff_timestamp,
                                metadata={"trading_pair": trading_pair}
                            )
                            output.put_nowait(diff_msg)
                        elif not event_type:
                            raise ValueError(f"Liquid Websocket message does not contain an event type - {diff_msg}")
            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error("Unexpected error with Liquid WebSocket connection. "
                                    "Retrying after 30 seconds...", exc_info=True)
                await asyncio.sleep(30.0)
 async def listen_for_order_book_snapshots(self,
                                           ev_loop: asyncio.BaseEventLoop,
                                           output: asyncio.Queue):
     """
     Fetches order book snapshots for each trading pair, and use them to update the local order book
     :param ev_loop: ev_loop to execute this function in
     :param output: an async queue where the incoming messages are stored
     TODO: This method needs to be further break down, otherwise, whenever error occurs, the only message
     getting is something similar to `Unexpected error with WebSocket connection.`
     """
     while True:
         try:
             trading_pairs: List[str] = await self.get_trading_pairs()
             async with aiohttp.ClientSession() as client:
                 for trading_pair in trading_pairs:
                     try:
                         snapshot: Dict[str, any] = await self.get_snapshot(
                             client, trading_pair)
                         snapshot_timestamp: float = time.time()
                         snapshot['asks'] = snapshot.get(
                             'sell_price_levels')
                         snapshot['bids'] = snapshot.get('buy_price_levels')
                         snapshot_msg: OrderBookMessage = LiquidOrderBook.snapshot_message_from_exchange(
                             msg=snapshot,
                             timestamp=snapshot_timestamp,
                             metadata={'trading_pair': trading_pair})
                         output.put_nowait(snapshot_msg)
                         self.logger().debug(
                             f"Saved order book snapshot for {trading_pair}"
                         )
                         # Be careful not to go above API rate limits.
                         await asyncio.sleep(5.0)
                     except asyncio.CancelledError:
                         raise
                     except Exception as e:
                         print(e)
                         self.logger().network(
                             f"Unexpected error with WebSocket connection.",
                             exc_info=True,
                             app_warning_msg=
                             f"Unexpected error with WebSocket connection. Retrying in 5 seconds. "
                             f"Check network connection.")
                         await asyncio.sleep(5.0)
                 this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(
                     minute=0, second=0, microsecond=0)
                 next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1)
                 delta: float = next_hour.timestamp() - time.time()
                 await asyncio.sleep(delta)
         except asyncio.CancelledError:
             raise
         except Exception:
             self.logger().error("Unexpected error.", exc_info=True)
             await asyncio.sleep(5.0)
Exemple #3
0
    async def get_new_order_book(self, trading_pair: str) -> OrderBook:
        await self.get_trading_pairs()
        async with aiohttp.ClientSession() as client:
            snapshot: Dict[str, Any] = await self.get_snapshot(
                client, trading_pair, 1)
            snapshot_timestamp: float = time.time()
            snapshot_msg: OrderBookMessage = LiquidOrderBook.snapshot_message_from_exchange(
                snapshot,
                snapshot_timestamp,
                metadata={"trading_pair": trading_pair})

            order_book: OrderBook = self.order_book_create_function()
            order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks,
                                      snapshot_msg.update_id)
            return order_book
    async def get_tracking_pairs(
            self) -> Dict[str, LiquidOrderBookTrackerEntry]:
        """
        Create tracking pairs by using trading pairs (trading_pairs) fetched from
        active markets
        """
        # Get the currently active markets
        async with aiohttp.ClientSession() as client:

            trading_pairs: List[str] = await self.get_trading_pairs()

            retval: Dict[str, LiquidOrderBookTrackerEntry] = {}
            number_of_pairs: int = len(trading_pairs)

            for index, trading_pair in enumerate(trading_pairs):

                try:
                    snapshot: Dict[str, Any] = await self.get_snapshot(
                        client, trading_pair, 1)
                    snapshot_timestamp: float = time.time()
                    snapshot_msg: OrderBookMessage = LiquidOrderBook.snapshot_message_from_exchange(
                        snapshot,
                        snapshot_timestamp,
                        metadata={"trading_pair": trading_pair})

                    order_book: OrderBook = self.order_book_create_function()
                    order_book.apply_snapshot(snapshot_msg.bids,
                                              snapshot_msg.asks,
                                              snapshot_msg.update_id)

                    retval[trading_pair] = LiquidOrderBookTrackerEntry(
                        trading_pair, snapshot_timestamp, order_book)

                    self.logger().info(
                        f"Initialized order book for {trading_pair}."
                        f"{index*1}/{number_of_pairs} completed")
                    # Each 1000 limit snapshot costs ?? requests and Liquid rate limit is ?? requests per second.
                    await asyncio.sleep(1.0)  # Might need to be changed
                except Exception:
                    self.logger().error(
                        f"Error getting snapshot for {trading_pair}. ",
                        exc_info=True)
                    await asyncio.sleep(5)
            return retval
    async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop,
                                          output: asyncio.Queue):
        """
        Subscribe to diff channel via web socket, and keep the connection open for incoming messages
        :param ev_loop: ev_loop to execute this function in
        :param output: an async queue where the incoming messages are stored
        """

        # {old_trading_pair: new_trading_pair}
        old_trading_pair_conversions = {}

        while True:
            try:
                trading_pairs: List[str] = await self.get_trading_pairs()

                async with websockets.connect(Constants.BAEE_WS_URL) as ws:
                    ws: websockets.WebSocketClientProtocol = ws
                    for trading_pair in trading_pairs:

                        old_trading_pair = trading_pair.replace('-', '')
                        old_trading_pair_conversions[
                            old_trading_pair] = trading_pair

                        for side in [Constants.SIDE_BID, Constants.SIDE_ASK]:
                            subscribe_request: Dict[str, Any] = {
                                "event": Constants.WS_PUSHER_SUBSCRIBE_EVENT,
                                "data": {
                                    "channel":
                                    Constants.WS_ORDER_BOOK_DIFF_SUBSCRIPTION.
                                    format(currency_pair_code=old_trading_pair.
                                           lower(),
                                           side=side)
                                }
                            }

                            await ws.send(ujson.dumps(subscribe_request))

                    async for raw_msg in self._inner_messages(ws):
                        diff_msg: Dict[str, Any] = ujson.loads(raw_msg)

                        event_type = diff_msg.get('event', None)
                        if event_type == 'updated':

                            # Channel example: 'price_ladders_cash_ethusd_sell'
                            old_trading_pair = diff_msg.get('channel').split(
                                '_')[-2].upper()
                            trading_pair = old_trading_pair_conversions[
                                old_trading_pair]

                            buy_or_sell = diff_msg.get('channel').split(
                                '_')[-1].lower()
                            side = 'asks' if buy_or_sell == Constants.SIDE_ASK else 'bids'
                            diff_msg = {
                                '{0}'.format(side):
                                ujson.loads(diff_msg.get('data', [])),
                                'trading_pair':
                                trading_pair
                            }
                            diff_timestamp: float = time.time()
                            msg: OrderBookMessage = LiquidOrderBook.diff_message_from_exchange(
                                diff_msg,
                                diff_timestamp,
                                metadata={
                                    "trading_pair": trading_pair,
                                    "update_id": int(diff_timestamp * 1e-3)
                                })
                            output.put_nowait(msg)
                        elif not event_type:
                            raise ValueError(
                                f"Liquid Websocket message does not contain an event type - {diff_msg}"
                            )

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error(
                    "Unexpected error with WebSocket connection. Retrying after 30 seconds...",
                    exc_info=True)
                await asyncio.sleep(30.0)