def test_track_single_book_snapshot_message_with_past_diffs(self):
        snapshot_msg: OrderBookMessage = CoinflexOrderBook.snapshot_message_from_exchange(
            msg={
                "marketCode": self.trading_pair,
                "timestamp": 1,
                "bids": [
                    ["4.00000000", "431.00000000"]
                ],
                "asks": [
                    ["4.00000200", "12.00000000"]
                ]
            },
            timestamp=time.time(),
            metadata={"trading_pair": self.trading_pair}
        )
        past_diff_msg: OrderBookMessage = CoinflexOrderBook.diff_message_from_exchange(
            msg={
                "table": "depth",
                "data": [{
                    "timestamp": 2,
                    "instrumentId": "BNBBTC",
                    "seqNum": 123456789,
                    "bids": [
                        [
                            "0.0024",
                            "10"
                        ]
                    ],
                    "asks": [
                        [
                            "0.0026",
                            "100"
                        ]
                    ]
                }]
            },
            metadata={"trading_pair": self.trading_pair}
        )

        self.tracking_task = self.ev_loop.create_task(
            self.tracker._track_single_book(self.trading_pair)
        )

        self.ev_loop.run_until_complete(asyncio.sleep(0.5))

        self._simulate_message_enqueue(self.tracker._past_diffs_windows[self.trading_pair], past_diff_msg)
        self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg)

        self.ev_loop.run_until_complete(asyncio.sleep(0.5))

        self.assertEqual(1, self.tracker.order_books[self.trading_pair].snapshot_uid)
        self.assertEqual(2, self.tracker.order_books[self.trading_pair].last_diff_uid)
    def test_diff_message_from_exchange(self):
        diff_msg = CoinflexOrderBook.diff_message_from_exchange(
            msg={
                "table":
                "depth",
                "data": [{
                    "instrumentId": "COINALPHA-HBOT",
                    "seqNum": 1,
                    "timestamp": 2,
                    "bids": [["0.0024", "10"]],
                    "asks": [["0.0026", "100"]]
                }]
            },
            timestamp=1640000000.0,
            metadata={"trading_pair": "COINALPHA-HBOT"})

        self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair)
        self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type)
        self.assertEqual(1640000000.0, diff_msg.timestamp)
        self.assertEqual(2, diff_msg.update_id)
        self.assertEqual(1, diff_msg.first_update_id)
        self.assertEqual(-1, diff_msg.trade_id)
        self.assertEqual(1, len(diff_msg.bids))
        self.assertEqual(0.0024, diff_msg.bids[0].price)
        self.assertEqual(10.0, diff_msg.bids[0].amount)
        self.assertEqual(2, diff_msg.bids[0].update_id)
        self.assertEqual(1, len(diff_msg.asks))
        self.assertEqual(0.0026, diff_msg.asks[0].price)
        self.assertEqual(100.0, diff_msg.asks[0].amount)
        self.assertEqual(2, diff_msg.asks[0].update_id)
 async def listen_for_order_book_diffs(self,
                                       ev_loop: asyncio.AbstractEventLoop,
                                       output: asyncio.Queue):
     """
     Reads the order diffs events queue. For each event creates a diff message instance and adds it to the
     output queue
     :param ev_loop: the event loop the method will run in
     :param output: a queue to add the created diff messages
     """
     message_queue = self._message_queue[CONSTANTS.DIFF_EVENT_TYPE]
     while True:
         try:
             json_msg = await message_queue.get()
             if "success" in json_msg:
                 continue
             trading_pair = await CoinflexAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol(
                 symbol=json_msg["data"][0]["instrumentId"],
                 domain=self._domain,
                 api_factory=self._api_factory,
                 throttler=self._throttler)
             order_book_message: OrderBookMessage = CoinflexOrderBook.diff_message_from_exchange(
                 json_msg, time.time(), {"trading_pair": trading_pair})
             output.put_nowait(order_book_message)
         except asyncio.CancelledError:
             raise
         except Exception:
             self.logger().exception(
                 "Unexpected error when processing public order book updates from exchange"
             )
    async def listen_for_trades(self, ev_loop: asyncio.AbstractEventLoop,
                                output: asyncio.Queue):
        """
        Reads the trade events queue. For each event creates a trade message instance and adds it to the output queue
        :param ev_loop: the event loop the method will run in
        :param output: a queue to add the created trade messages
        """
        message_queue = self._message_queue[CONSTANTS.TRADE_EVENT_TYPE]
        while True:
            try:
                message_data = await message_queue.get()

                if "success" in message_data:
                    continue

                trades_data = message_data.get("data", [])

                for json_msg in trades_data:
                    trading_pair = await CoinflexAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol(
                        symbol=json_msg["marketCode"],
                        domain=self._domain,
                        api_factory=self._api_factory,
                        throttler=self._throttler)
                    trade_msg: OrderBookMessage = CoinflexOrderBook.trade_message_from_exchange(
                        json_msg, {"trading_pair": trading_pair})
                    output.put_nowait(trade_msg)

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().exception(
                    "Unexpected error when processing public trade updates from exchange"
                )
    def setUp(self) -> None:
        super().setUp()
        self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS)
        self.tracker: CoinflexOrderBookTracker = CoinflexOrderBookTracker(trading_pairs=[self.trading_pair],
                                                                          throttler=self.throttler)
        self.tracking_task: Optional[asyncio.Task] = None

        # Simulate start()
        self.tracker._order_books[self.trading_pair] = CoinflexOrderBook()
        self.tracker._tracking_message_queues[self.trading_pair] = asyncio.Queue()
        self.tracker._past_diffs_windows[self.trading_pair] = deque()
        self.tracker._order_books_initialized.set()
 async def get_new_order_book(self, trading_pair: str) -> OrderBook:
     """
     Creates a local instance of the exchange order book for a particular trading pair
     :param trading_pair: the trading pair for which the order book has to be retrieved
     :return: a local copy of the current order book in the exchange
     """
     snapshot: Dict[str, Any] = await self.get_snapshot(trading_pair, 1000)
     snapshot_timestamp: float = time.time()
     snapshot_msg: OrderBookMessage = CoinflexOrderBook.snapshot_message_from_exchange(
         snapshot,
         snapshot_timestamp,
         metadata={"trading_pair": trading_pair})
     order_book = self.order_book_create_function()
     order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks,
                               snapshot_msg.update_id)
     return order_book
    def test_trade_message_from_exchange(self):
        trade_update = {
            "timestamp": 1234567890123,
            "marketCode": "COINALPHA-HBOT",
            "tradeId": 12345,
            "side": "SELL",
            "price": "0.001",
            "quantity": "100",
        }

        trade_message = CoinflexOrderBook.trade_message_from_exchange(
            msg=trade_update, metadata={"trading_pair": "COINALPHA-HBOT"})

        self.assertEqual("COINALPHA-HBOT", trade_message.trading_pair)
        self.assertEqual(OrderBookMessageType.TRADE, trade_message.type)
        self.assertEqual(1234567890.123, trade_message.timestamp)
        self.assertEqual(-1, trade_message.update_id)
        self.assertEqual(-1, trade_message.first_update_id)
        self.assertEqual(12345, trade_message.trade_id)
    def test_track_single_book_snapshot_message_no_past_diffs(self):
        snapshot_msg: OrderBookMessage = CoinflexOrderBook.snapshot_message_from_exchange(
            msg={
                "marketCode": self.trading_pair,
                "timestamp": 1,
                "bids": [
                    ["4.00000000", "431.00000000"]
                ],
                "asks": [
                    ["4.00000200", "12.00000000"]
                ]
            },
            timestamp=time.time(),
            metadata={"trading_pair": self.trading_pair}
        )
        self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg)

        self.tracking_task = self.ev_loop.create_task(
            self.tracker._track_single_book(self.trading_pair)
        )
        self.ev_loop.run_until_complete(asyncio.sleep(0.5))
        self.assertEqual(1, self.tracker.order_books[self.trading_pair].snapshot_uid)
    def test_snapshot_message_from_exchange(self):
        snapshot_message = CoinflexOrderBook.snapshot_message_from_exchange(
            msg={
                "marketCode": "COINALPHA-HBOT",
                "timestamp": 1,
                "bids": [["4.00000000", "431.00000000"]],
                "asks": [["4.00000200", "12.00000000"]]
            },
            timestamp=1640000000.0,
            metadata={"trading_pair": "COINALPHA-HBOT"})

        self.assertEqual("COINALPHA-HBOT", snapshot_message.trading_pair)
        self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type)
        self.assertEqual(1640000000.0, snapshot_message.timestamp)
        self.assertEqual(1, snapshot_message.update_id)
        self.assertEqual(-1, snapshot_message.trade_id)
        self.assertEqual(1, len(snapshot_message.bids))
        self.assertEqual(4.0, snapshot_message.bids[0].price)
        self.assertEqual(431.0, snapshot_message.bids[0].amount)
        self.assertEqual(1, snapshot_message.bids[0].update_id)
        self.assertEqual(1, len(snapshot_message.asks))
        self.assertEqual(4.000002, snapshot_message.asks[0].price)
        self.assertEqual(12.0, snapshot_message.asks[0].amount)
        self.assertEqual(1, snapshot_message.asks[0].update_id)
 async def listen_for_order_book_snapshots(
         self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue):
     """
     This method runs continuously and request the full order book content from the exchange every hour.
     The method uses the REST API from the exchange because it does not provide an endpoint to get the full order
     book through websocket. With the information creates a snapshot messages that is added to the output queue
     :param ev_loop: the event loop the method will run in
     :param output: a queue to add the created snapshot messages
     """
     while True:
         try:
             for trading_pair in self._trading_pairs:
                 try:
                     snapshot: Dict[str, Any] = await self.get_snapshot(
                         trading_pair=trading_pair)
                     snapshot_timestamp: float = time.time()
                     snapshot_msg: OrderBookMessage = CoinflexOrderBook.snapshot_message_from_exchange(
                         snapshot,
                         snapshot_timestamp,
                         metadata={"trading_pair": trading_pair})
                     output.put_nowait(snapshot_msg)
                     self.logger().debug(
                         f"Saved order book snapshot for {trading_pair}")
                 except asyncio.CancelledError:
                     raise
                 except Exception:
                     self.logger().error(
                         f"Unexpected error fetching order book snapshot for {trading_pair}.",
                         exc_info=True)
                     await self._sleep(5.0)
             await self._sleep(self.ONE_HOUR)
         except asyncio.CancelledError:
             raise
         except Exception:
             self.logger().error("Unexpected error.", exc_info=True)
             await self._sleep(5.0)