/hummingbot/connector/exchange/crypto_com/crypto_com_websocket.py

https://github.com/CoinAlpha/hummingbot · Python · 127 lines · 91 code · 23 blank · 13 comment · 20 complexity · 6c3d58f28efdad34fcacdadf281abf57 MD5 · raw file

  1. #!/usr/bin/env python
  2. import asyncio
  3. import copy
  4. import logging
  5. import websockets
  6. import ujson
  7. import hummingbot.connector.exchange.crypto_com.crypto_com_constants as constants
  8. from hummingbot.core.utils.async_utils import safe_ensure_future
  9. from typing import Optional, AsyncIterable, Any, List
  10. from websockets.exceptions import ConnectionClosed
  11. from hummingbot.logger import HummingbotLogger
  12. from hummingbot.connector.exchange.crypto_com.crypto_com_auth import CryptoComAuth
  13. from hummingbot.connector.exchange.crypto_com.crypto_com_utils import RequestId, get_ms_timestamp
  14. # reusable websocket class
  15. class CryptoComWebsocket(RequestId):
  16. MESSAGE_TIMEOUT = 30.0
  17. PING_TIMEOUT = 10.0
  18. _logger: Optional[HummingbotLogger] = None
  19. @classmethod
  20. def logger(cls) -> HummingbotLogger:
  21. if cls._logger is None:
  22. cls._logger = logging.getLogger(__name__)
  23. return cls._logger
  24. def __init__(self, auth: Optional[CryptoComAuth] = None):
  25. self._auth: Optional[CryptoComAuth] = auth
  26. self._isPrivate = True if self._auth is not None else False
  27. self._WS_URL = constants.WSS_PRIVATE_URL if self._isPrivate else constants.WSS_PUBLIC_URL
  28. self._client: Optional[websockets.WebSocketClientProtocol] = None
  29. # connect to exchange
  30. async def connect(self):
  31. try:
  32. self._client = await websockets.connect(self._WS_URL)
  33. # if auth class was passed into websocket class
  34. # we need to emit authenticated requests
  35. if self._isPrivate:
  36. await self._emit("public/auth", None)
  37. # TODO: wait for response
  38. await asyncio.sleep(1)
  39. return self._client
  40. except Exception as e:
  41. self.logger().error(f"Websocket error: '{str(e)}'", exc_info=True)
  42. # disconnect from exchange
  43. async def disconnect(self):
  44. if self._client is None:
  45. return
  46. await self._client.close()
  47. # receive & parse messages
  48. async def _messages(self) -> AsyncIterable[Any]:
  49. try:
  50. while True:
  51. try:
  52. raw_msg_str: str = await asyncio.wait_for(self._client.recv(), timeout=self.MESSAGE_TIMEOUT)
  53. raw_msg = ujson.loads(raw_msg_str)
  54. if "method" in raw_msg and raw_msg["method"] == "public/heartbeat":
  55. payload = {"id": raw_msg["id"], "method": "public/respond-heartbeat"}
  56. safe_ensure_future(self._client.send(ujson.dumps(payload)))
  57. yield raw_msg
  58. except asyncio.TimeoutError:
  59. await asyncio.wait_for(self._client.ping(), timeout=self.PING_TIMEOUT)
  60. except asyncio.TimeoutError:
  61. self.logger().warning("WebSocket ping timed out. Going to reconnect...")
  62. return
  63. except ConnectionClosed:
  64. return
  65. finally:
  66. await self.disconnect()
  67. # emit messages
  68. async def _emit(self, method: str, data: Optional[Any] = {}) -> int:
  69. id = self.generate_request_id()
  70. nonce = get_ms_timestamp()
  71. payload = {
  72. "id": id,
  73. "method": method,
  74. "nonce": nonce,
  75. "params": copy.deepcopy(data),
  76. }
  77. if self._isPrivate:
  78. auth = self._auth.generate_auth_dict(
  79. method,
  80. request_id=id,
  81. nonce=nonce,
  82. data=data,
  83. )
  84. payload["sig"] = auth["sig"]
  85. payload["api_key"] = auth["api_key"]
  86. await self._client.send(ujson.dumps(payload))
  87. return id
  88. # request via websocket
  89. async def request(self, method: str, data: Optional[Any] = {}) -> int:
  90. return await self._emit(method, data)
  91. # subscribe to a method
  92. async def subscribe(self, channels: List[str]) -> int:
  93. return await self.request("subscribe", {
  94. "channels": channels
  95. })
  96. # unsubscribe to a method
  97. async def unsubscribe(self, channels: List[str]) -> int:
  98. return await self.request("unsubscribe", {
  99. "channels": channels
  100. })
  101. # listen to messages by method
  102. async def on_message(self) -> AsyncIterable[Any]:
  103. async for msg in self._messages():
  104. yield msg