crypto-ws-api 2.0.13__tar.gz → 2.0.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,16 @@
1
+ ## 2.0.15 - 2024-12-16
2
+ ### Added for new features
3
+ * `HTX` implemented
4
+
5
+ ### Update
6
+ * reused WS connection grouped by private and public methods
7
+ * `websockets`: bump to v14.1
8
+ * some minor improvements
9
+
10
+ ## 2.0.14 - 2024-09-13
11
+ ### Update
12
+ * `pyproject.toml`
13
+
1
14
  ## 2.0.13 - 2024-09-13
2
15
  ### Update
3
16
  * Dependencies
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: crypto-ws-api
3
- Version: 2.0.13
3
+ Version: 2.0.15
4
4
  Summary: Crypto WS API connector for ASYNC requests
5
5
  Author-email: Jerry Fedorenko <jerry.fedorenko@yahoo.com>
6
6
  Requires-Python: >=3.8
@@ -11,10 +11,10 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: Unix
12
12
  Classifier: Operating System :: Microsoft :: Windows
13
13
  Classifier: Operating System :: MacOS
14
- Requires-Dist: shortuuid~=1.0.11
15
- Requires-Dist: platformdirs~=3.10.0
14
+ Requires-Dist: shortuuid~=1.0.13
15
+ Requires-Dist: platformdirs==3.11.0
16
16
  Requires-Dist: toml~=0.10.2
17
- Requires-Dist: websockets~=12.0
17
+ Requires-Dist: websockets==14.1
18
18
  Requires-Dist: ujson~=5.10.0
19
19
  Project-URL: Source, https://github.com/DogsTailFarmer/crypto-ws-api
20
20
 
@@ -32,7 +32,7 @@ Project-URL: Source, https://github.com/DogsTailFarmer/crypto-ws-api
32
32
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_crypto-ws-api" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_crypto-ws-api&metric=alert_status"/></a>
33
33
  <a href="https://pepy.tech/project/crypto-ws-api" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/crypto-ws-api"/></a>
34
34
  ***
35
- For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex
35
+ For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex, :heavy_check_mark:HTX
36
36
  ***
37
37
 
38
38
  ## Features
@@ -40,6 +40,7 @@ Lightweight and efficient solution to utilize of all available methods** provide
40
40
  * [Binance Websocket API v3](https://binance-docs.github.io/apidocs/websocket_api/en)
41
41
  * [OKX Websocket API v5](https://www.okx.com/docs-v5/en/#overview-websocket)
42
42
  * [Bitfinex Websocket Inputs](https://docs.bitfinex.com/reference/ws-auth-input)
43
+ * [HTX WS Trade](https://www.htx.com/en-us/opend/newApiPages/?id=8cb89359-77b5-11ed-9966-1928f079ab6)
43
44
 
44
45
  ** Not for channel by one-way subscription, for **_request <-> response_** mode only
45
46
 
@@ -12,7 +12,7 @@
12
12
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_crypto-ws-api" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_crypto-ws-api&metric=alert_status"/></a>
13
13
  <a href="https://pepy.tech/project/crypto-ws-api" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/crypto-ws-api"/></a>
14
14
  ***
15
- For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex
15
+ For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex, :heavy_check_mark:HTX
16
16
  ***
17
17
 
18
18
  ## Features
@@ -20,6 +20,7 @@ Lightweight and efficient solution to utilize of all available methods** provide
20
20
  * [Binance Websocket API v3](https://binance-docs.github.io/apidocs/websocket_api/en)
21
21
  * [OKX Websocket API v5](https://www.okx.com/docs-v5/en/#overview-websocket)
22
22
  * [Bitfinex Websocket Inputs](https://docs.bitfinex.com/reference/ws-auth-input)
23
+ * [HTX WS Trade](https://www.htx.com/en-us/opend/newApiPages/?id=8cb89359-77b5-11ed-9966-1928f079ab6)
23
24
 
24
25
  ** Not for channel by one-way subscription, for **_request <-> response_** mode only
25
26
 
@@ -13,7 +13,7 @@ __maintainer__ = "Jerry Fedorenko"
13
13
  __contact__ = "https://github.com/DogsTailFarmer"
14
14
  __email__ = "jerry.fedorenko@yahoo.com"
15
15
  __credits__ = ["https://github.com/DanyaSWorlD"]
16
- __version__ = "2.0.13"
16
+ __version__ = "2.0.15"
17
17
 
18
18
  from pathlib import Path
19
19
  import shutil
@@ -101,7 +101,7 @@ async def account_information(user_session: UserWSSession, _trade_id):
101
101
  res = await user_session.handle_request(
102
102
  _trade_id,
103
103
  "account.status",
104
- _api_key=True,
104
+ send_api_key=True,
105
105
  _signed=True
106
106
  )
107
107
  if res is None:
@@ -6,39 +6,59 @@ import sys
6
6
  import time
7
7
  import logging
8
8
  import ujson as json
9
+ from ujson import JSONDecodeError
9
10
  import hmac
10
11
  import hashlib
11
12
  import base64
12
13
  import string
13
14
  import random
14
- import websockets.client
15
+ from datetime import datetime, timezone
16
+ from urllib.parse import urlencode, urlparse
15
17
 
18
+ from websockets.asyncio.client import connect
16
19
  from websockets import ConnectionClosed
17
20
 
18
21
  from enum import Enum
19
22
  from crypto_ws_api import TIMEOUT, ID_LEN_LIMIT, DELAY
20
23
 
21
- logger_ws = logger = logging.getLogger(__name__)
22
- logger_ws.level = logging.INFO
24
+ logger = logging.getLogger(__name__)
25
+ logger.level = logging.INFO
23
26
  sys.tracebacklimit = 0
24
27
  ALPHABET = string.ascii_letters + string.digits
25
- CONST_3 = "userDataStream.start"
26
-
27
-
28
- def generate_signature(exchange, api_secret, data):
29
- if exchange == 'bitfinex':
30
- sig = hmac.new(api_secret.encode("utf-8"), data.encode("utf-8"), hashlib.sha384).hexdigest()
31
- elif exchange in ('huobi', 'okx'):
32
- sig = hmac.new(api_secret.encode("utf-8"), data.encode("utf-8"), hashlib.sha256).digest()
33
- sig = base64.b64encode(sig).decode()
34
- elif exchange == 'binance_ws':
35
- sig = hmac.new(api_secret.encode("ascii"), data.encode("ascii"), hashlib.sha256).hexdigest()
36
- elif exchange == 'bybit':
37
- sig = hmac.new(bytes(api_secret.encode("utf-8")), data.encode("utf-8"), hashlib.sha256).hexdigest()
38
- else:
39
- sig = hmac.new(api_secret.encode("utf-8"), data.encode("utf-8"), hashlib.sha256).hexdigest()
40
- return sig
28
+ CONST_WS_START = "userDataStream.start"
29
+
41
30
 
31
+ def generate_signature(exchange: str, secret: str, data: str) -> str:
32
+ digest_func = hashlib.sha384 if exchange == 'bitfinex' else hashlib.sha256
33
+ encoding = "ascii" if exchange == 'binance_ws' else "utf-8"
34
+ key = secret.encode(encoding=encoding)
35
+ signature = hmac.new(
36
+ bytes(key) if exchange == 'bybit' else key,
37
+ data.encode(encoding=encoding),
38
+ digest_func
39
+ )
40
+ if exchange in {"huobi", "okx"}:
41
+ return base64.b64encode(signature.digest()).decode()
42
+ else:
43
+ return signature.hexdigest()
44
+
45
+ def compose_htx_ws_auth(endpoint, exchange, api_key, api_secret):
46
+ _params = {
47
+ "accessKey": api_key,
48
+ "signatureMethod": "HmacSHA256",
49
+ "signatureVersion": "2.1",
50
+ "timestamp": str(datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S"))
51
+ }
52
+ signature_payload = (f"GET\n{urlparse(endpoint).hostname}\n{urlparse(endpoint).path}\n"
53
+ f"{urlencode(_params)}")
54
+ signature = generate_signature(exchange, api_secret, signature_payload)
55
+ _params["authType"] = "api"
56
+ _params["signature"] = signature
57
+ return {
58
+ "action": "req",
59
+ "ch": "auth",
60
+ "params": _params
61
+ }
42
62
 
43
63
  # https://binance-docs.github.io/apidocs/websocket_api/en/#rate-limits
44
64
  class RateLimitInterval(Enum):
@@ -51,11 +71,11 @@ class RateLimitInterval(Enum):
51
71
  class UserWSS:
52
72
  __slots__ = (
53
73
  "init",
54
- "method",
55
74
  "exchange",
56
75
  "endpoint",
57
76
  "_api_key",
58
77
  "_api_secret",
78
+ "signed",
59
79
  "_passphrase",
60
80
  "_ws",
61
81
  "_listen_key",
@@ -67,16 +87,17 @@ class UserWSS:
67
87
  "in_event",
68
88
  "_response_pool",
69
89
  "tasks",
90
+ "ping"
70
91
  )
71
92
 
72
- def __init__(self, method, ws_id, exchange, endpoint, api_key, api_secret, passphrase=None):
93
+ def __init__(self, ws_id, exchange, endpoint, api_key, api_secret, signed, passphrase=None):
73
94
  self.init = True
74
- self.method = method
75
95
  self.exchange = exchange
76
96
  self.endpoint = endpoint
77
97
  #
78
98
  self._api_key = api_key
79
99
  self._api_secret = api_secret
100
+ self.signed = signed
80
101
  self._passphrase = passphrase
81
102
  self._ws = None
82
103
  self._listen_key = None
@@ -88,6 +109,7 @@ class UserWSS:
88
109
  self.request_limit_reached = False
89
110
  self.in_event = asyncio.Event()
90
111
  self.tasks = set()
112
+ self.ping = 0
91
113
 
92
114
  def tasks_manage(self, coro, name=None):
93
115
  _t = asyncio.create_task(coro, name=name)
@@ -99,27 +121,39 @@ class UserWSS:
99
121
  async for msg in self._ws:
100
122
  # logger.info(f"_ws_listener: msg: {self.ws_id}: {msg}")
101
123
  if isinstance(msg, str):
102
- res = await self._handle_msg(json.loads(msg))
103
- # logger.info(f"_ws_listener: res: {self.ws_id}: {res}")
104
- if res != 'pass':
105
- if res is None:
106
- self._response_pool[f"NoneResponse{self.ws_id}"] = None
107
- elif self.exchange == 'binance':
108
- self._response_pool[res.get('id')] = res.get('result')
109
- elif self.exchange in ['okx', 'bitfinex']:
110
- self._response_pool[res.get('id') or self.ws_id] = res.get('data') or res
111
- self.in_event.set()
112
- await asyncio.sleep(0)
124
+ try:
125
+ _msg = json.loads(msg)
126
+ except JSONDecodeError:
127
+ logger.warning(f"UserWSS: {self.ws_id}: {msg}")
128
+ else:
129
+ res = await self._handle_msg(_msg)
130
+ # logger.info(f"_ws_listener: res: {self.ws_id}: {res}")
131
+ if res != 'pass':
132
+ if res is None:
133
+ self._response_pool[f"NoneResponse{self.ws_id}"] = None
134
+ elif self.exchange == 'binance':
135
+ self._response_pool[res.get('id')] = res.get('result')
136
+ elif self.exchange in {'okx', 'bitfinex'}:
137
+ self._response_pool[res.get('id') or self.ws_id] = res.get('data') or res
138
+ elif self.exchange == 'huobi':
139
+ self._response_pool[res.get('cid') or self.ws_id] = res.get('data')
140
+ self.in_event.set()
141
+ # logger.info(f"_ws_listener: _response_pool: {self._response_pool}")
142
+ await asyncio.sleep(0)
113
143
  else:
114
144
  logger.warning(f"UserWSS: {self.ws_id}: {msg}")
115
145
  await self.stop()
116
146
 
117
147
  async def start_wss(self):
118
- async for self._ws in websockets.client.connect(self.endpoint, logger=logger_ws):
148
+ async for self._ws in connect(
149
+ self.endpoint,
150
+ logger=logger,
151
+ ping_interval=None if self.exchange == 'huobi' else 20
152
+ ):
119
153
  try:
120
154
  await self._ws_listener()
121
155
  except ConnectionClosed as ex:
122
- if ex.code == 4000:
156
+ if ex.rcvd and ex.rcvd.code == 4000:
123
157
  logger.info(f"WSS closed for {self.ws_id}")
124
158
  break
125
159
  else:
@@ -129,10 +163,10 @@ class UserWSS:
129
163
  logger.warning(f"Restart WSS for {self.ws_id}")
130
164
  continue
131
165
  except Exception as ex:
132
- logger.error(f"WSS start_wss() other exception: {ex}")
166
+ logger.error(f"WSS start other exception: {ex}")
133
167
 
134
168
  async def ws_login(self):
135
- res = await self.request('userDataStream.start', _api_key=True)
169
+ res = await self.request(CONST_WS_START, send_api_key=True)
136
170
  if res is None:
137
171
  logger.warning(f"UserWSS: Not 'logged in' for {self.ws_id}")
138
172
  raise ConnectionClosed(None, None)
@@ -140,37 +174,37 @@ class UserWSS:
140
174
  if self.exchange == 'binance':
141
175
  self._listen_key = res.get('listenKey')
142
176
  self.tasks_manage(self.heartbeat(), f"heartbeat-{self.ws_id}")
143
- else:
144
- self._listen_key = f"{int(time.time() * 1000)}{self.ws_id}"
177
+ elif self.exchange == 'huobi':
178
+ self.tasks_manage(self.htx_keepalive(), f"htx_keepalive-{self.ws_id}")
179
+
145
180
  self.operational_status = True
146
181
  self.order_handling = True
147
182
  self.tasks_manage(self._keepalive(), f"keepalive-{self.ws_id}")
148
183
  logger.info(f"UserWSS: 'logged in' for {self.ws_id}")
149
184
 
150
- async def request(self, _method=None, _params=None, _api_key=False, _signed=False):
185
+ async def request(self, method, _params=None, send_api_key=False, _signed=False):
151
186
  """
152
187
  Construct and handling request/response to WS API endpoint, use a description of the methods on
153
188
  https://developers.binance.com/docs/binance-trading-api/websocket_api#request-format
154
189
  :return: result: {} or None if temporary Out-of-Service state
155
190
  """
156
- method = _method or self.method
157
191
  if self.request_limit_reached:
158
192
  logger.warning(f"UserWSS {self.ws_id}: request limit reached, try later")
159
193
  return None
160
- if method != 'userDataStream.start' and not self.operational_status:
194
+ if method != CONST_WS_START and not self.operational_status:
161
195
  logger.warning("UserWSS temporary in Out-of-Service state")
162
196
  return None
163
- if method in ('order.place', 'order.cancelReplace', 'order') and not self.order_handling:
197
+ if method in ('order.place', 'order.cancelReplace', 'order', 'create-order') and not self.order_handling:
164
198
  logger.warning("UserWSS: exceeded order placement limit, try later")
165
199
  return None
166
200
  params = _params.copy() if _params else None
167
201
  r_id = f"{self.exchange}{method}{''.join(random.choices(ALPHABET, k=8))}"
168
- if self.exchange in ("okx", "bitfinex") and method == CONST_3:
202
+ if self.exchange in ("okx", "bitfinex", "huobi") and method == CONST_WS_START:
169
203
  _id = self.ws_id
170
204
  else:
171
- _id = ''.join(e for e in r_id if e.isalnum())[-ID_LEN_LIMIT[self.exchange]:]
205
+ _id = ''.join(e for e in r_id if e.isalnum())[-ID_LEN_LIMIT.get(self.exchange, 64):]
172
206
  await self._ws.send(
173
- json.dumps(self.compose_request(_id, _api_key, method, params, _signed))
207
+ json.dumps(self.compose_request(_id, send_api_key, method, params, _signed))
174
208
  )
175
209
  await asyncio.sleep(0)
176
210
  try:
@@ -193,20 +227,28 @@ class UserWSS:
193
227
  elif f"NoneResponse{self.ws_id}" in self._response_pool:
194
228
  return self._response_pool.pop(f"NoneResponse{self.ws_id}", None)
195
229
 
196
- def compose_request(self, _id, api_key, method, params, signed):
230
+ def compose_request(self, _id, send_api_key, method, params, signed):
197
231
  if self.exchange == "binance":
198
- return self._compose_binance_request(_id, api_key, method, params, signed)
232
+ return self._compose_binance_request(_id, send_api_key, method, params, signed)
199
233
  elif self.exchange == "okx":
200
234
  return self._compose_okx_request(_id, method, params)
201
- elif self.exchange == 'bitfinex':
235
+ elif self.exchange == "bitfinex":
202
236
  return self._compose_bitfinex_request(_id, method, params)
237
+ elif self.exchange == "huobi":
238
+ return self._compose_htx_request(_id, method, params)
203
239
  else:
204
240
  raise ValueError(f"Unsupported exchange: {self.exchange}")
205
241
 
206
- def _compose_binance_request(self, _id, api_key, method, params, signed):
242
+ def _compose_htx_request(self, _id, method, params):
243
+ if method == CONST_WS_START:
244
+ return compose_htx_ws_auth(self.endpoint, self.exchange, self._api_key, self._api_secret)
245
+ else:
246
+ return {"cid": _id, "ch": method, "params": params}
247
+
248
+ def _compose_binance_request(self, _id, send_api_key, method, params, signed):
207
249
  req = {"id": _id, "method": method}
208
250
  params = params or {}
209
- if api_key:
251
+ if send_api_key:
210
252
  params["apiKey"] = self._api_key
211
253
  if signed:
212
254
  params["timestamp"] = int(time.time() * 1000)
@@ -217,7 +259,7 @@ class UserWSS:
217
259
  return req
218
260
 
219
261
  def _compose_okx_request(self, _id, method, params):
220
- if method == CONST_3:
262
+ if method == CONST_WS_START:
221
263
  ts = int(time.time())
222
264
  signature_payload = f"{ts}GET/users/self/verify"
223
265
  signature = generate_signature(self.exchange, self._api_secret, signature_payload)
@@ -231,7 +273,7 @@ class UserWSS:
231
273
  return {"id": _id, "op": method, "args": params if isinstance(params, list) else [params]}
232
274
 
233
275
  def _compose_bitfinex_request(self, _id, method, params):
234
- if method == CONST_3:
276
+ if method == CONST_WS_START:
235
277
  ts = int(time.time() * 1000)
236
278
  data = f"AUTH{ts}"
237
279
  return {
@@ -265,10 +307,21 @@ class UserWSS:
265
307
  await self.request(
266
308
  "userDataStream.ping",
267
309
  params,
268
- _api_key=True,
310
+ send_api_key=True,
269
311
  )
270
312
  await asyncio.sleep(interval)
271
313
 
314
+ async def htx_keepalive(self, interval=60):
315
+ await asyncio.sleep(interval * 10)
316
+ while True:
317
+ await asyncio.sleep(interval)
318
+ if self.ping:
319
+ break
320
+ else:
321
+ self.ping = 1
322
+ logger.warning("From HTX server PING timeout exceeded")
323
+ await self.stop()
324
+
272
325
  async def stop(self):
273
326
  """
274
327
  Stop data stream
@@ -278,7 +331,7 @@ class UserWSS:
278
331
  self.init = True
279
332
  [task.cancel() for task in self.tasks if not task.done()]
280
333
  self.tasks.clear()
281
- if self._ws and not self._ws.closed:
334
+ if self._ws:
282
335
  await self._ws.close(code=4000)
283
336
  gc.collect()
284
337
  logger.info(f"User WSS for {self.ws_id} stopped")
@@ -292,11 +345,37 @@ class UserWSS:
292
345
  return msg
293
346
  elif self.exchange == 'okx':
294
347
  if msg.get('code') != '0':
295
- await self.okx_error_handle(msg)
348
+ self.okx_error_handle(msg)
296
349
  msg = None
297
350
  return msg
298
351
  elif self.exchange == 'bitfinex':
299
352
  return await self.bitfinex_error_handle(msg)
353
+ elif self.exchange == 'huobi':
354
+ if msg.get('action') == 'ping':
355
+ pong = {
356
+ "action": "pong",
357
+ "data": {
358
+ "ts": msg.get('data').get('ts')
359
+ }
360
+ }
361
+ self.ping = 0
362
+ await self._ws.send(json.dumps(pong))
363
+ return 'pass'
364
+
365
+ if msg.get('code') == 200 or msg.get('status') == 'ok':
366
+ return msg
367
+
368
+ await self.htx_error_handle(msg)
369
+
370
+ async def htx_error_handle(self, msg):
371
+ if msg.get('code') == 500:
372
+ logger.warning(f"An issue occurred on exchange's side: {msg}")
373
+ elif msg.get('code') in {429, 4000}:
374
+ self._retry_after = int((time.time() + TIMEOUT) * 1000)
375
+ self.request_limit_reached = True
376
+ logger.warning(f"HTX WSS exceed limit: {msg}")
377
+ else:
378
+ logger.warning(f"Malformed request: status: {msg}")
300
379
 
301
380
  # region BitfinexErrorHandle
302
381
  async def bitfinex_error_handle(self, msg):
@@ -314,7 +393,7 @@ class UserWSS:
314
393
  elif event == 'auth':
315
394
  return msg if msg.get('status') == "OK" else None
316
395
  elif msg.get('code'):
317
- return await self._handle_error_code(msg)
396
+ return self._handle_error_code(msg)
318
397
  return 'pass'
319
398
 
320
399
  async def _handle_info_event(self, msg):
@@ -325,7 +404,7 @@ class UserWSS:
325
404
  logger.critical('Bitfinex WSS platform: version change detected')
326
405
  return 'pass'
327
406
 
328
- async def _handle_error_code(self, msg):
407
+ def _handle_error_code(self, msg):
329
408
  code = msg.get('code')
330
409
  if code == 10305:
331
410
  logger.warning('UserWSS Bitfinex: Reached limit of open channels')
@@ -333,7 +412,6 @@ class UserWSS:
333
412
  self.request_limit_reached = True
334
413
  else:
335
414
  logger.warning(f"Malformed request for {self.ws_id}: {msg}")
336
- return None
337
415
 
338
416
  @staticmethod
339
417
  def _transform_list_message(msg):
@@ -352,15 +430,16 @@ class UserWSS:
352
430
  }
353
431
  # endregion
354
432
 
355
- async def okx_error_handle(self, msg):
433
+ def okx_error_handle(self, msg):
356
434
  if msg.get('code') == '1':
357
- logger.warning(f"Operation failed: {msg}")
435
+ logger.warning(f"OKX User WSS operation failed: {msg}")
358
436
  elif msg.get('code') == '63999':
359
437
  logger.warning(f"An issue occurred on exchange's side: {msg}")
360
438
  elif msg.get('code') == '60014':
361
439
  self._retry_after = int((time.time() + TIMEOUT) * 1000)
362
440
  self.request_limit_reached = True
363
- logger.warning(f"Malformed request: status: {msg}")
441
+ else:
442
+ logger.warning(f"Malformed request: status: {msg}")
364
443
 
365
444
  async def binance_error_handle(self, msg):
366
445
  error_msg = msg.get('error')
@@ -396,7 +475,7 @@ class UserWSSession:
396
475
  )
397
476
 
398
477
  def __init__(self, exchange, endpoint, api_key, api_secret, passphrase=None):
399
- if exchange not in ('binance', 'okx', 'bitfinex'):
478
+ if exchange not in ('binance', 'okx', 'bitfinex', 'huobi'):
400
479
  raise UserWarning(f"UserWSSession: exchange {exchange} not serviced")
401
480
  self.exchange = exchange
402
481
  self.endpoint = endpoint
@@ -412,19 +491,20 @@ class UserWSSession:
412
491
  trade_id: str,
413
492
  method: str,
414
493
  _params=None,
415
- _api_key=False,
494
+ send_api_key=False,
416
495
  _signed=False,
417
496
  ):
418
497
  ws_id = f"{self.exchange}-{trade_id}-{method}"
498
+
419
499
  user_wss = self.user_wss.setdefault(
420
500
  ws_id,
421
501
  UserWSS(
422
- method,
423
502
  ws_id,
424
503
  self.exchange,
425
504
  self.endpoint,
426
505
  self._api_key,
427
506
  self._api_secret,
507
+ _signed,
428
508
  self._passphrase
429
509
  )
430
510
  )
@@ -444,7 +524,7 @@ class UserWSSession:
444
524
  duration += DELAY
445
525
 
446
526
  try:
447
- return await user_wss.request(_params=_params, _api_key=_api_key, _signed=_signed)
527
+ return await user_wss.request(method=method, _params=_params, send_api_key=send_api_key, _signed=_signed)
448
528
  except asyncio.CancelledError:
449
529
  pass # Task cancellation should not be logged as an error
450
530
  except Exception as ex:
@@ -17,10 +17,10 @@ dynamic = ["version", "description"]
17
17
  requires-python = ">=3.8"
18
18
 
19
19
  dependencies = [
20
- "shortuuid~=1.0.11",
21
- "platformdirs~=3.10.0",
20
+ "shortuuid~=1.0.13",
21
+ "platformdirs==3.11.0",
22
22
  "toml~=0.10.2",
23
- "websockets~=12.0",
23
+ "websockets==14.1",
24
24
  "ujson~=5.10.0"
25
25
  ]
26
26
 
@@ -1,5 +1,5 @@
1
1
  shortuuid~=1.0.13
2
2
  platformdirs==3.11.0
3
3
  toml~=0.10.2
4
- websockets==13.0.1
4
+ websockets==14.1
5
5
  ujson~=5.10.0