def test_trading_pair_convertion(self): hbot_trading_pair = "BTC-USDT" exchange_trading_pair = "BTC_USDT" self.assertEqual( exchange_trading_pair, utils.convert_to_exchange_trading_pair(hbot_trading_pair)) self.assertEqual( hbot_trading_pair, utils.convert_from_exchange_trading_pair(exchange_trading_pair))
def _format_trading_rules( self, symbols_details: Dict[str, Any]) -> Dict[str, TradingRule]: """ Converts json API response into a dictionary of trading rules. :param symbols_details: The json API response :return A dictionary of trading rules. Response Example: { "code": 1000, "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", "message": "OK", "data": { "symbols": [ { "symbol":"GXC_BTC", "symbol_id":1024, "base_currency":"GXC", "quote_currency":"BTC", "quote_increment":"1.00000000", "base_min_size":"1.00000000", "base_max_size":"10000000.00000000", "price_min_precision":6, "price_max_precision":8, "expiration":"NA", "min_buy_amount":"0.00010000", "min_sell_amount":"0.00010000" }, ... ] } } """ result = {} for rule in symbols_details["data"]["symbols"]: try: trading_pair = bitmart_utils.convert_from_exchange_trading_pair( rule["symbol"]) price_decimals = Decimal(str(rule["price_max_precision"])) # E.g. a price decimal of 2 means 0.01 incremental. price_step = Decimal("1") / Decimal( str(math.pow(10, price_decimals))) result[trading_pair] = TradingRule( trading_pair=trading_pair, min_order_size=Decimal(str(rule["base_min_size"])), max_order_size=Decimal(str(rule["base_max_size"])), min_order_value=Decimal(str(rule["min_buy_amount"])), min_base_amount_increment=Decimal( str(rule["quote_increment"])), min_price_increment=price_step) except Exception: self.logger().error( f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) return result
async def get_open_orders(self) -> List[OpenOrder]: if self._trading_pairs is None: raise Exception("get_open_orders can only be used when trading_pairs are specified.") page_len = 100 responses = [] for trading_pair in self._trading_pairs: page = 1 while True: response = await self._api_request("get", CONSTANTS.GET_OPEN_ORDERS_PATH_URL, {"symbol": bitmart_utils.convert_to_exchange_trading_pair(trading_pair), "offset": page, "limit": page_len, "status": "9"}, "KEYED") responses.append(response) count = len(response["data"]["orders"]) if count < page_len: break else: page += 1 for order in self._in_flight_orders.values(): await order.get_exchange_order_id() ret_val = [] for response in responses: for order in response["data"]["orders"]: exchange_order_id = str(order["order_id"]) tracked_orders = list(self._in_flight_orders.values()) tracked_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id] if not tracked_order: continue tracked_order = tracked_order[0] if order["type"] != "limit": raise Exception(f"Unsupported order type {order['type']}") ret_val.append( OpenOrder( client_order_id=tracked_order.client_order_id, trading_pair=bitmart_utils.convert_from_exchange_trading_pair(order["symbol"]), price=Decimal(str(order["price"])), amount=Decimal(str(order["size"])), executed_amount=Decimal(str(order["filled_size"])), status=CONSTANTS.ORDER_STATUS[int(order["status"])], order_type=OrderType.LIMIT, is_buy=True if order["side"].lower() == "buy" else False, time=int(order["create_time"]), exchange_order_id=str(order["order_id"]) ) ) return ret_val
async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): """ Listen for orderbook diffs using websocket book channel(all messages are snapshots) """ while True: try: ws: websockets.WebSocketClientProtocol = await self._create_websocket_connection( ) for trading_pair in self._trading_pairs: params: Dict[str, Any] = { "op": "subscribe", "args": [ f"spot/depth400:{bitmart_utils.convert_to_exchange_trading_pair(trading_pair)}" ] } await ws.send(ujson.dumps(params)) async for raw_msg in self._inner_messages(ws): messages = ujson.loads(raw_msg) if messages is None: continue if "errorCode" in messages or "data" not in messages: # Error/Unrecognized response from "depth400" channel continue for msg in messages["data"]: # data is a list msg_timestamp: float = float(msg["ms_t"]) t_pair = bitmart_utils.convert_from_exchange_trading_pair( msg["symbol"]) snapshot_msg: OrderBookMessage = BitmartOrderBook.snapshot_message_from_exchange( msg=msg, timestamp=msg_timestamp, metadata={"trading_pair": t_pair}) output.put_nowait(snapshot_msg) except asyncio.CancelledError: raise except Exception: self.logger().network( "Unexpected error with WebSocket connection.", exc_info=True, app_warning_msg= "Unexpected error with WebSocket connection. Retrying in 30 seconds. " "Check network connection.") await self._sleep(30.0) finally: await ws.close()
async def get_last_traded_prices( cls, trading_pairs: List[str]) -> Dict[str, float]: throttler = cls._get_throttler_instance() async with throttler.execute_task( CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL): result = {} async with aiohttp.ClientSession() as client: async with client.get( f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL}", timeout=10) as response: response_json = await response.json() for ticker in response_json["data"]["tickers"]: t_pair = bitmart_utils.convert_from_exchange_trading_pair( ticker["symbol"]) if t_pair in trading_pairs and ticker["last_price"]: result[t_pair] = float(ticker["last_price"]) return result
async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: throttler = cls._get_throttler_instance() async with throttler.execute_task(CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL): result = {} request = RESTRequest( method=RESTMethod.GET, url=f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL}", ) rest_assistant = await build_api_factory().get_rest_assistant() response = await rest_assistant.call(request=request, timeout=10) response_json = await response.json() for ticker in response_json["data"]["tickers"]: t_pair = convert_from_exchange_trading_pair(ticker["symbol"]) if t_pair in trading_pairs and ticker["last_price"]: result[t_pair] = float(ticker["last_price"]) return result
async def fetch_trading_pairs() -> List[str]: throttler = BitmartAPIOrderBookDataSource._get_throttler_instance() async with throttler.execute_task(CONSTANTS.GET_TRADING_PAIRS_PATH_URL): request = RESTRequest( method=RESTMethod.GET, url=f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_TRADING_PAIRS_PATH_URL}", ) rest_assistant = await build_api_factory().get_rest_assistant() response = await rest_assistant.call(request=request, timeout=10) if response.status == 200: try: response_json: Dict[str, Any] = await response.json() return [convert_from_exchange_trading_pair(symbol) for symbol in response_json["data"]["symbols"]] except Exception: pass # Do nothing if the request fails -- there will be no autocomplete for bitmart trading pairs return []
async def fetch_trading_pairs() -> List[str]: throttler = BitmartAPIOrderBookDataSource._get_throttler_instance() async with throttler.execute_task( CONSTANTS.GET_TRADING_PAIRS_PATH_URL): async with aiohttp.ClientSession() as client: async with client.get( f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_TRADING_PAIRS_PATH_URL}", timeout=10) as response: if response.status == 200: from hummingbot.connector.exchange.bitmart.bitmart_utils import \ convert_from_exchange_trading_pair try: response_json: Dict[str, Any] = await response.json() return [ convert_from_exchange_trading_pair(symbol) for symbol in response_json["data"]["symbols"] ] except Exception: pass # Do nothing if the request fails -- there will be no autocomplete for bitmart trading pairs return []
async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): """ Listen for orderbook diffs using websocket book channel(all messages are snapshots) """ while True: try: ws: WSAssistant = await self._api_factory.get_ws_assistant() await ws.connect(ws_url=CONSTANTS.WSS_URL, message_timeout=self.MESSAGE_TIMEOUT, ping_timeout=self.PING_TIMEOUT) for trading_pair in self._trading_pairs: ws_message: WSRequest = WSRequest({ "op": "subscribe", "args": [f"spot/depth400:{convert_to_exchange_trading_pair(trading_pair)}"] }) await ws.send(ws_message) while True: try: async for raw_msg in ws.iter_messages(): messages = decompress_ws_message(raw_msg.data) if messages is None: continue messages = ujson.loads(messages) if "errorCode" in messages.keys() or \ "data" not in messages.keys() or \ "table" not in messages.keys(): continue if messages["table"] != "spot/depth5": # Not an order book message continue for msg in messages["data"]: # data is a list msg_timestamp: float = float(msg["ms_t"]) t_pair = convert_from_exchange_trading_pair(msg["symbol"]) snapshot_msg: OrderBookMessage = BitmartOrderBook.snapshot_message_from_exchange( msg=msg, timestamp=msg_timestamp, metadata={"trading_pair": t_pair} ) output.put_nowait(snapshot_msg) break except asyncio.exceptions.TimeoutError: # Check whether connection is really dead await ws.ping() except asyncio.CancelledError: raise except asyncio.exceptions.TimeoutError: self.logger().warning("WebSocket ping timed out. Going to reconnect...") await ws.disconnect() await asyncio.sleep(30.0) except Exception: self.logger().network( "Unexpected error with WebSocket connection.", exc_info=True, app_warning_msg="Unexpected error with WebSocket connection. Retrying in 30 seconds. " "Check network connection." ) await ws.disconnect() await self._sleep(30.0) finally: await ws.disconnect()