def test_convert_to_exchange_trading_pair(self): # Test (1) Regular asset self.assertEqual(self.trading_pair, convert_to_exchange_trading_pair(self.trading_pair)) # Test (2) Matching asset matching_asset, asset_name = next(iter(NAME_TO_ASSET_MAPPING.items())) trading_pair = f"{matching_asset}-{self.quote_asset}" expected_trading_pair = f"{asset_name}-{self.quote_asset}" self.assertEqual(expected_trading_pair, convert_to_exchange_trading_pair(trading_pair))
async def get_snapshot( cls, client: aiohttp.ClientSession, trading_pair: str, auth: KucoinAuth = None, throttler: Optional[AsyncThrottler] = None, ) -> Dict[str, Any]: throttler = throttler or cls._get_throttler_instance() params: Dict = { "symbol": convert_to_exchange_trading_pair(trading_pair) } if auth is not None: url = CONSTANTS.BASE_PATH_URL + CONSTANTS.SNAPSHOT_PATH_URL limit_id = CONSTANTS.SNAPSHOT_PATH_URL else: url = CONSTANTS.BASE_PATH_URL + CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL limit_id = CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL path_url = f"{URL(url).path}?{urlencode(params)}" headers = auth.add_auth_to_params("get", path_url) if auth else None async with throttler.execute_task(limit_id): async with client.get(url, params=params, headers=headers) as response: response: aiohttp.ClientResponse = response if response.status != 200: raise IOError( f"Error fetching Kucoin market snapshot for {trading_pair}. " f"HTTP status is {response.status}.") data: Dict[str, Any] = await response.json() return data
async def _collect_and_decode_messages_loop(self, stream_type: StreamType, task_index: int, output: asyncio.Queue): while True: try: kucoin_msg_iterator: KucoinWSConnectionIterator = KucoinWSConnectionIterator( stream_type, self._tasks[stream_type][task_index].trading_pairs, self._throttler) self._tasks[stream_type][ task_index].message_iterator = kucoin_msg_iterator async for raw_msg in kucoin_msg_iterator: msg_type: str = raw_msg.get("type", "") if msg_type in {"ack", "welcome", "pong"}: pass elif msg_type == "message": if stream_type == StreamType.Depth: order_book_message: OrderBookMessage = KucoinOrderBook.diff_message_from_exchange( raw_msg) else: trading_pair: str = convert_to_exchange_trading_pair( raw_msg["data"]["symbol"]) data = raw_msg["data"] order_book_message: OrderBookMessage = \ KucoinOrderBook.trade_message_from_exchange( data, metadata={"trading_pair": trading_pair} ) output.put_nowait(order_book_message) elif msg_type == "error": self.logger().error( f"WS error message from Kucoin: {raw_msg}") else: self.logger().warning( f"Unrecognized message type from Kucoin: {msg_type}. " f"Message = {raw_msg}.") except asyncio.CancelledError: raise except asyncio.TimeoutError: self.logger().error( "Timeout error with WebSocket connection. Retrying after 5 seconds...", exc_info=True) await asyncio.sleep(5.0) except Exception: self.logger().error( "Unexpected exception with WebSocket connection. Retrying after 5 seconds...", exc_info=True) await asyncio.sleep(5.0) finally: if stream_type in self._tasks: if task_index in self._tasks: self._tasks[stream_type][ task_index].message_iterator = None
async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str, auth: KucoinAuth) -> Dict[str, Any]: params: Dict = {"symbol": convert_to_exchange_trading_pair(trading_pair)} path_url = f"/api/v3/market/orderbook/level2?{urlencode(params)}" headers = auth.add_auth_to_params("get", path_url) async with client.get(SNAPSHOT_REST_URL, params=params, headers=headers) as response: response: aiohttp.ClientResponse = response if response.status != 200: raise IOError(f"Error fetching Kucoin market snapshot for {trading_pair}. " f"HTTP status is {response.status}.") data: Dict[str, Any] = await response.json() return data
async def update_subscription( cls, ws: websockets.WebSocketClientProtocol, stream_type: StreamType, trading_pairs: Set[str], subscribe: bool, throttler: Optional[AsyncThrottler] = None, ): trading_pairs = { convert_to_exchange_trading_pair(t) for t in trading_pairs } it = iter(trading_pairs) trading_pair_chunks: List[Tuple[str]] = list( iter(lambda: tuple(islice(it, 100)), ())) subscribe_requests: List[Dict[str, Any]] = [] if stream_type == StreamType.Depth: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/level2:{market_str}", "response": True }) else: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/match:{market_str}", "privateChannel": False, "response": True }) throttler = throttler or cls._get_throttler_instance() for i, subscribe_request in enumerate(subscribe_requests): async with throttler.execute_task(CONSTANTS.WS_REQUEST_LIMIT_ID): await ws.send(json.dumps(subscribe_request))
async def update_subscription(ws: websockets.WebSocketClientProtocol, stream_type: StreamType, trading_pairs: Set[str], subscribe: bool): # Kucoin has a limit of 100 subscription per 10 seconds trading_pairs = { convert_to_exchange_trading_pair(t) for t in trading_pairs } it = iter(trading_pairs) trading_pair_chunks: List[Tuple[str]] = list( iter(lambda: tuple(islice(it, 100)), ())) subscribe_requests: List[Dict[str, Any]] = [] if stream_type == StreamType.Depth: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/level2:{market_str}", "response": True }) else: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/match:{market_str}", "privateChannel": False, "response": True }) for i, subscribe_request in enumerate(subscribe_requests): await ws.send(json.dumps(subscribe_request)) if i != len(subscribe_requests) - 1: # only sleep between requests await asyncio.sleep(10) await asyncio.sleep(0.2) # watch out for the rate limit
async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str, auth: KucoinAuth = None) -> Dict[str, Any]: params: Dict = { "symbol": convert_to_exchange_trading_pair(trading_pair) } url = SNAPSHOT_REST_URL if auth else SNAPSHOT_REST_URL_NO_AUTH path_url = f"{URL(url).path}?{urlencode(params)}" headers = auth.add_auth_to_params("get", path_url) if auth else None async with client.get(url, params=params, headers=headers) as response: response: aiohttp.ClientResponse = response if response.status != 200: raise IOError( f"Error fetching Kucoin market snapshot for {trading_pair}. " f"HTTP status is {response.status}.") data: Dict[str, Any] = await response.json() return data
async def update_subscription(self, stream_type: StreamType, trading_pairs: Set[str], subscribe: bool): trading_pairs = { convert_to_exchange_trading_pair(t) for t in trading_pairs } it = iter(trading_pairs) trading_pair_chunks: List[Tuple[str]] = list( iter(lambda: tuple(islice(it, 100)), ())) subscribe_requests: List[Dict[str, Any]] = [] if stream_type == StreamType.Depth: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/level2:{market_str}", "response": True }) else: for trading_pair_chunk in trading_pair_chunks: market_str: str = ",".join(sorted(trading_pair_chunk)) subscribe_requests.append({ "id": int(time.time()), "type": "subscribe" if subscribe else "unsubscribe", "topic": f"/market/match:{market_str}", "privateChannel": False, "response": True }) for i, subscribe_request in enumerate(subscribe_requests): async with self._throttler.execute_task( CONSTANTS.WS_REQUEST_LIMIT_ID): await self._websocket.send_json(subscribe_request)