async def fetch_trading_pairs() -> List[str]: async with aiohttp.ClientSession() as client: resp = await client.get(f"{REST_URL}/ticker") if resp.status != 200: # Do nothing if the request fails -- there will be no autocomplete for kucoin trading pairs return [] data: Dict[str, Dict[str, Any]] = await resp.json() return [convert_from_exchange_trading_pair(item["symbol"]) for item in data["data"]]
async def fetch_trading_pairs(client: Optional[aiohttp.ClientSession] = None, throttler: Optional[AsyncThrottler] = None) -> List[str]: client = client or AscendExAPIOrderBookDataSource._get_session_instance() throttler = throttler or AscendExAPIOrderBookDataSource._get_throttler_instance() async with throttler.execute_task(CONSTANTS.TICKER_PATH_URL): resp = await client.get(f"{CONSTANTS.REST_URL}/{CONSTANTS.TICKER_PATH_URL}") if resp.status != 200: # Do nothing if the request fails -- there will be no autocomplete for kucoin trading pairs return [] data: Dict[str, Dict[str, Any]] = await resp.json() return [convert_from_exchange_trading_pair(item["symbol"]) for item in data["data"]]
def _format_trading_rules( self, instruments_info: Dict[str, Any]) -> Dict[str, AscendExTradingRule]: """ Converts json API response into a dictionary of trading rules. :param instruments_info: The json API response :return A dictionary of trading rules. Response Example: { "code": 0, "data": [ { "symbol": "BTMX/USDT", "baseAsset": "BTMX", "quoteAsset": "USDT", "status": "Normal", "minNotional": "5", "maxNotional": "100000", "marginTradable": true, "commissionType": "Quote", "commissionReserveRate": "0.001", "tickSize": "0.000001", "lotSize": "0.001" } ] } """ trading_rules = {} for rule in instruments_info["data"]: try: trading_pair = ascend_ex_utils.convert_from_exchange_trading_pair( rule["symbol"]) trading_rules[trading_pair] = AscendExTradingRule( trading_pair, min_price_increment=Decimal(rule["tickSize"]), min_base_amount_increment=Decimal(rule["lotSize"]), min_notional_size=Decimal(rule["minNotional"]), max_notional_size=Decimal(rule["maxNotional"]), commission_type=AscendExCommissionType[ rule["commissionType"].upper()], commission_reserve_rate=Decimal( rule["commissionReserveRate"]), ) except Exception: self.logger().error( f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) return trading_rules
async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): while True: try: trading_pairs = ",".join( list( map( lambda trading_pair: convert_to_exchange_trading_pair(trading_pair), self._trading_pairs))) ch = f"depth:{trading_pairs}" payload = {"op": CONSTANTS.SUB_ENDPOINT_NAME, "ch": ch} async with websockets.connect(CONSTANTS.WS_URL) as ws: ws: websockets.WebSocketClientProtocol = ws async with self._throttler.execute_task( CONSTANTS.SUB_ENDPOINT_NAME): await ws.send(ujson.dumps(payload)) async for raw_msg in self._inner_messages(ws): msg = ujson.loads(raw_msg) if msg is None: continue if msg.get("m", '') == "ping": async with self._throttler.execute_task( CONSTANTS.PONG_ENDPOINT_NAME): await ws.send( ujson.dumps( dict(op=CONSTANTS.PONG_ENDPOINT_NAME))) if msg.get("m", '') == "depth": msg_timestamp: int = msg.get("data").get("ts") trading_pair: str = convert_from_exchange_trading_pair( msg.get("symbol")) order_book_message: OrderBookMessage = AscendExOrderBook.diff_message_from_exchange( msg.get("data"), msg_timestamp, metadata={"trading_pair": trading_pair}) output.put_nowait(order_book_message) except asyncio.CancelledError: raise except Exception as e: self.logger().debug(str(e)) self.logger().error( "Unexpected error with WebSocket connection. Retrying after 30 seconds...", exc_info=True) await asyncio.sleep(30.0)
async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): while True: try: trading_pairs = ",".join( list( map( lambda trading_pair: convert_to_exchange_trading_pair(trading_pair), self._trading_pairs))) payload = {"op": "sub", "ch": f"trades:{trading_pairs}"} async with websockets.connect(WS_URL) as ws: ws: websockets.WebSocketClientProtocol = ws await ws.send(ujson.dumps(payload)) async for raw_msg in self._inner_messages(ws): try: msg = ujson.loads(raw_msg) if (msg is None or msg.get("m") != "trades"): continue trading_pair: str = convert_from_exchange_trading_pair( msg.get("symbol")) for trade in msg.get("data"): trade_timestamp: int = trade.get("ts") trade_msg: OrderBookMessage = AscendExOrderBook.trade_message_from_exchange( trade, trade_timestamp, metadata={"trading_pair": trading_pair}) output.put_nowait(trade_msg) except Exception: raise except asyncio.CancelledError: raise except Exception as e: self.logger().debug(str(e)) self.logger().error( "Unexpected error with WebSocket connection. Retrying after 30 seconds...", exc_info=True) await asyncio.sleep(30.0)
async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): msg_queue = self._message_queue[self.DIFF_TOPIC_ID] while True: try: msg = await msg_queue.get() msg_timestamp: int = msg.get("data").get("ts") trading_pair: str = convert_from_exchange_trading_pair(msg.get("symbol")) order_book_message: OrderBookMessage = AscendExOrderBook.diff_message_from_exchange( msg.get("data"), msg_timestamp, metadata={"trading_pair": trading_pair} ) output.put_nowait(order_book_message) except asyncio.CancelledError: raise except Exception as e: self.logger().debug(str(e)) self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", exc_info=True) await self._sleep(30.0)
async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): msg_queue = self._message_queue[self.TRADE_TOPIC_ID] while True: try: msg = await msg_queue.get() trading_pair: str = convert_from_exchange_trading_pair(msg.get("symbol")) trades = msg.get("data") for trade in trades: trade_timestamp: int = trade.get("ts") trade_msg: OrderBookMessage = AscendExOrderBook.trade_message_from_exchange( trade, trade_timestamp, metadata={"trading_pair": trading_pair} ) output.put_nowait(trade_msg) except asyncio.CancelledError: raise except Exception: self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", exc_info=True)
async def get_open_orders(self) -> List[OpenOrder]: result = await self._api_request( method="get", path_url=CONSTANTS.ORDER_OPEN_PATH_URL, is_auth_required=True, force_auth_path_url="order/open" ) ret_val = [] for order in result["data"]: if order["orderType"].lower() != "limit": self.logger().debug(f"Unsupported orderType: {order['orderType']}. Order: {order}", exc_info=True) continue exchange_order_id = order["orderId"] client_order_id = None for in_flight_order in self._in_flight_orders.values(): if in_flight_order.exchange_order_id == exchange_order_id: client_order_id = in_flight_order.client_order_id if client_order_id is None: self.logger().debug(f"Unrecognized Order {exchange_order_id}: {order}") continue ret_val.append( OpenOrder( client_order_id=client_order_id, trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair(order["symbol"]), price=Decimal(str(order["price"])), amount=Decimal(str(order["orderQty"])), executed_amount=Decimal(str(order["cumFilledQty"])), status=order["status"], order_type=OrderType.LIMIT, is_buy=True if order["side"].lower() == "buy" else False, time=int(order["lastExecTime"]), exchange_order_id=exchange_order_id ) ) return ret_val
async def get_open_orders(self) -> List[OpenOrder]: result = await self._api_request( "get", "cash/order/open", {}, True, force_auth_path_url="order/open" ) ret_val = [] for order in result["data"]: if order["type"] != "LIMIT": raise Exception(f"Unsupported order type {order['type']}") exchange_order_id = order["orderId"] client_order_id = None for in_flight_order in self._in_flight_orders.values(): if in_flight_order.exchange_order_id == exchange_order_id: client_order_id = in_flight_order.client_order_id if client_order_id is None: raise Exception(f"Client order id for {exchange_order_id} not found.") ret_val.append( OpenOrder( client_order_id=client_order_id, trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair(order["symbol"]), price=Decimal(str(order["price"])), amount=Decimal(str(order["orderQty"])), executed_amount=Decimal(str(order["cumFilledQty"])), status=order["status"], order_type=OrderType.LIMIT, is_buy=True if order["side"].lower() == "buy" else False, time=int(order["lastExecTime"]), exchange_order_id=exchange_order_id ) ) return ret_val
def test_convert_from_exchange_trading_pair(self): trading_pair = "BTC/USDT" self.assertEqual("BTC-USDT", utils.convert_from_exchange_trading_pair(trading_pair))
async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ if len(self._in_flight_order_tracker.active_orders) == 0: return tracked_orders: List[InFlightOrder] = list(self._in_flight_order_tracker.active_orders.values()) ex_oid_to_c_oid_map: Dict[str, str] = {} # Check a second time the order is not done because it can be updated in other async process for order in (tracked_order for tracked_order in tracked_orders if not tracked_order.is_done): try: exchange_id = await order.get_exchange_order_id() ex_oid_to_c_oid_map[exchange_id] = order.client_order_id except asyncio.TimeoutError: self.logger().debug( f"Tracked order {order.client_order_id} does not have an exchange id. " f"Attempting fetch in next polling interval." ) self._stop_tracking_order_exceed_no_exchange_id_limit(tracked_order=order) continue if ex_oid_to_c_oid_map: exchange_order_ids_param_str: str = ",".join(list(ex_oid_to_c_oid_map.keys())) params = {"orderId": exchange_order_ids_param_str} try: resp = await self._api_request( method="get", path_url=CONSTANTS.ORDER_STATUS_PATH_URL, params=params, is_auth_required=True, force_auth_path_url="order/status", ) except Exception: self.logger().exception( f"There was an error requesting updates for the active orders ({ex_oid_to_c_oid_map})") raise self.logger().debug(f"Polling for order status updates of {len(ex_oid_to_c_oid_map)} orders.") self.logger().debug(f"cash/order/status?orderId={exchange_order_ids_param_str} response: {resp}") # The data returned from this end point can be either a list or a dict depending on number of orders resp_records: List = [] if isinstance(resp["data"], dict): resp_records.append(resp["data"]) elif isinstance(resp["data"], list): resp_records = resp["data"] order_updates: List[OrderUpdate] = [] try: for order_data in resp_records: exchange_order_id = order_data["orderId"] client_order_id = ex_oid_to_c_oid_map[exchange_order_id] new_state: OrderState = CONSTANTS.ORDER_STATE[order_data["status"]] order_updates.append( OrderUpdate( client_order_id=client_order_id, exchange_order_id=exchange_order_id, trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair(order_data["symbol"]), update_timestamp=order_data["lastExecTime"] * 1e-3, new_state=new_state, ) ) for update in order_updates: self._in_flight_order_tracker.process_order_update(update) except Exception: self.logger().info( f"Unexpected error during processing order status. The Ascend Ex Response: {resp}", exc_info=True )