crypto-ws-api 2.0.5.post3__tar.gz → 2.0.7__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.
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/CHANGELOG.md +8 -0
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/PKG-INFO +4 -5
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/README.md +2 -4
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/crypto_ws_api/__init__.py +1 -1
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/crypto_ws_api/ws_session.py +97 -72
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/pyproject.toml +1 -0
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/requirements.txt +2 -1
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/.deepsource.toml +0 -0
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/LICENSE.md +0 -0
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/crypto_ws_api/demo.py +0 -0
- {crypto-ws-api-2.0.5.post3 → crypto-ws-api-2.0.7}/crypto_ws_api/ws_api.toml.template +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: crypto-ws-api
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.7
|
|
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
|
|
@@ -15,6 +15,7 @@ Requires-Dist: shortuuid~=1.0.11
|
|
|
15
15
|
Requires-Dist: platformdirs~=3.10.0
|
|
16
16
|
Requires-Dist: toml~=0.10.2
|
|
17
17
|
Requires-Dist: websockets~=12.0
|
|
18
|
+
Requires-Dist: ujson~=5.9.0
|
|
18
19
|
Project-URL: Source, https://github.com/DogsTailFarmer/crypto-ws-api
|
|
19
20
|
|
|
20
21
|
<h1 align="center"><img align="center" src="https://user-images.githubusercontent.com/77513676/250364389-cbedc171-a930-4467-a0cd-21627a6a41ed.svg" width="75">Crypto WS API connector for ASYNC requests</h1>
|
|
@@ -36,7 +37,7 @@ For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex
|
|
|
36
37
|
|
|
37
38
|
## Features
|
|
38
39
|
Lightweight and efficient solution to utilize of all available methods** provided through the:
|
|
39
|
-
* [Binance Websocket API v3](https://
|
|
40
|
+
* [Binance Websocket API v3](https://binance-docs.github.io/apidocs/websocket_api/en)
|
|
40
41
|
* [OKX Websocket API v5](https://www.okx.com/docs-v5/en/#overview-websocket)
|
|
41
42
|
* [Bitfinex Websocket Inputs](https://docs.bitfinex.com/reference/ws-auth-input)
|
|
42
43
|
|
|
@@ -221,7 +222,5 @@ If you send a request in this state, the answer will be `None`
|
|
|
221
222
|
*In any case, you are protected from exceeding limits and blocking for this reason*
|
|
222
223
|
|
|
223
224
|
## Donate
|
|
224
|
-
*
|
|
225
|
-
|
|
226
|
-
*USDT* (TRC20) TP1Y43dpY7rrRyTSLaSKDZmFirqvRcpopC
|
|
225
|
+
*USDT* (TRC20) TU3kagV9kxbjuUmEi6bUym5MTXjeM7Tm8K
|
|
227
226
|
|
|
@@ -17,7 +17,7 @@ For :heavy_check_mark:Binance, :heavy_check_mark:OKX, :heavy_check_mark:Bitfinex
|
|
|
17
17
|
|
|
18
18
|
## Features
|
|
19
19
|
Lightweight and efficient solution to utilize of all available methods** provided through the:
|
|
20
|
-
* [Binance Websocket API v3](https://
|
|
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
23
|
|
|
@@ -202,6 +202,4 @@ If you send a request in this state, the answer will be `None`
|
|
|
202
202
|
*In any case, you are protected from exceeding limits and blocking for this reason*
|
|
203
203
|
|
|
204
204
|
## Donate
|
|
205
|
-
*
|
|
206
|
-
|
|
207
|
-
*USDT* (TRC20) TP1Y43dpY7rrRyTSLaSKDZmFirqvRcpopC
|
|
205
|
+
*USDT* (TRC20) TU3kagV9kxbjuUmEi6bUym5MTXjeM7Tm8K
|
|
@@ -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.
|
|
16
|
+
__version__ = "2.0.7"
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
import shutil
|
|
@@ -5,7 +5,7 @@ import gc
|
|
|
5
5
|
import sys
|
|
6
6
|
import time
|
|
7
7
|
import logging
|
|
8
|
-
import json
|
|
8
|
+
import ujson as json
|
|
9
9
|
import hmac
|
|
10
10
|
import hashlib
|
|
11
11
|
import base64
|
|
@@ -176,7 +176,6 @@ class UserWSS:
|
|
|
176
176
|
except asyncio.TimeoutError:
|
|
177
177
|
logger.warning(f"UserWSS: get response timeout error: {self.ws_id}")
|
|
178
178
|
await self.stop()
|
|
179
|
-
return None
|
|
180
179
|
except asyncio.CancelledError:
|
|
181
180
|
pass # Task cancellation should not be logged as an error
|
|
182
181
|
else:
|
|
@@ -193,53 +192,59 @@ class UserWSS:
|
|
|
193
192
|
return self._response_pool.pop(f"NoneResponse{self.ws_id}", None)
|
|
194
193
|
|
|
195
194
|
def compose_request(self, _id, api_key, method, params, signed):
|
|
196
|
-
req = None
|
|
197
195
|
if self.exchange == "binance":
|
|
198
|
-
|
|
199
|
-
if (api_key or signed) and not params:
|
|
200
|
-
params = {}
|
|
201
|
-
if api_key:
|
|
202
|
-
params["apiKey"] = self._api_key
|
|
203
|
-
if signed:
|
|
204
|
-
params["timestamp"] = int(time.time() * 1000)
|
|
205
|
-
payload = '&'.join(f"{key}={value}" for key, value in dict(sorted(params.items())).items())
|
|
206
|
-
params["signature"] = generate_signature('binance_ws', self._api_secret, payload)
|
|
207
|
-
if params:
|
|
208
|
-
req["params"] = params
|
|
196
|
+
return self._compose_binance_request(_id, api_key, method, params, signed)
|
|
209
197
|
elif self.exchange == "okx":
|
|
210
|
-
|
|
211
|
-
# https://www.okx.com/docs-v5/en/?python#overview-websocket-connect
|
|
212
|
-
ts = int(time.time())
|
|
213
|
-
signature_payload = f"{ts}GET/users/self/verify"
|
|
214
|
-
signature = generate_signature(self.exchange, self._api_secret, signature_payload)
|
|
215
|
-
# Login on account
|
|
216
|
-
req = {"op": 'login',
|
|
217
|
-
"args": [{"apiKey": self._api_key,
|
|
218
|
-
"passphrase": self._passphrase,
|
|
219
|
-
"timestamp": ts,
|
|
220
|
-
"sign": signature}
|
|
221
|
-
]
|
|
222
|
-
}
|
|
223
|
-
else:
|
|
224
|
-
req = {"id": _id, "op": method, "args": params if isinstance(params, list) else [params]}
|
|
198
|
+
return self._compose_okx_request(_id, method, params)
|
|
225
199
|
elif self.exchange == 'bitfinex':
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
200
|
+
return self._compose_bitfinex_request(_id, method, params)
|
|
201
|
+
else:
|
|
202
|
+
raise ValueError(f"Unsupported exchange: {self.exchange}")
|
|
203
|
+
|
|
204
|
+
def _compose_binance_request(self, _id, api_key, method, params, signed):
|
|
205
|
+
req = {"id": _id, "method": method}
|
|
206
|
+
params = params or {}
|
|
207
|
+
if api_key:
|
|
208
|
+
params["apiKey"] = self._api_key
|
|
209
|
+
if signed:
|
|
210
|
+
params["timestamp"] = int(time.time() * 1000)
|
|
211
|
+
payload = '&'.join(f"{key}={value}" for key, value in sorted(params.items()))
|
|
212
|
+
params["signature"] = generate_signature('binance_ws', self._api_secret, payload)
|
|
213
|
+
if params:
|
|
214
|
+
req["params"] = params
|
|
241
215
|
return req
|
|
242
216
|
|
|
217
|
+
def _compose_okx_request(self, _id, method, params):
|
|
218
|
+
if method == CONST_3:
|
|
219
|
+
ts = int(time.time())
|
|
220
|
+
signature_payload = f"{ts}GET/users/self/verify"
|
|
221
|
+
signature = generate_signature(self.exchange, self._api_secret, signature_payload)
|
|
222
|
+
return {
|
|
223
|
+
"op": 'login',
|
|
224
|
+
"args": [
|
|
225
|
+
{"apiKey": self._api_key, "passphrase": self._passphrase, "timestamp": ts, "sign": signature}
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
else:
|
|
229
|
+
return {"id": _id, "op": method, "args": params if isinstance(params, list) else [params]}
|
|
230
|
+
|
|
231
|
+
def _compose_bitfinex_request(self, _id, method, params):
|
|
232
|
+
if method == CONST_3:
|
|
233
|
+
ts = int(time.time() * 1000)
|
|
234
|
+
data = f"AUTH{ts}"
|
|
235
|
+
return {
|
|
236
|
+
'event': "auth",
|
|
237
|
+
'apiKey': self._api_key,
|
|
238
|
+
'authSig': generate_signature(self.exchange, self._api_secret, data),
|
|
239
|
+
'authPayload': data,
|
|
240
|
+
'authNonce': ts,
|
|
241
|
+
'filter': ['trading']
|
|
242
|
+
}
|
|
243
|
+
else:
|
|
244
|
+
if method == 'on':
|
|
245
|
+
params.update({"meta": {"aff_code": "v_4az2nCP"}})
|
|
246
|
+
return [0, method, _id, params]
|
|
247
|
+
|
|
243
248
|
async def _keepalive(self, interval=10):
|
|
244
249
|
while self.operational_status is not None:
|
|
245
250
|
if self.request_limit_reached and (int(time.time() * 1000) - self._retry_after >= 0):
|
|
@@ -291,40 +296,60 @@ class UserWSS:
|
|
|
291
296
|
elif self.exchange == 'bitfinex':
|
|
292
297
|
return await self.bitfinex_error_handle(msg)
|
|
293
298
|
|
|
299
|
+
#region BitfinexErrorHandle
|
|
294
300
|
async def bitfinex_error_handle(self, msg):
|
|
295
301
|
if isinstance(msg, dict):
|
|
296
|
-
|
|
297
|
-
logger.warning(f"UserWSS Bitfinex platform in maintenance mode: {msg}")
|
|
298
|
-
await self.stop()
|
|
299
|
-
if msg.get('event') == 'auth' and msg.get('status') == "OK":
|
|
300
|
-
return msg
|
|
301
|
-
if msg.get('event') == 'info' and msg.get('version') != 2:
|
|
302
|
-
logger.critical('Bitfinex WSS platform: version change detected')
|
|
303
|
-
# sourcery skip: merge-nested-ifs
|
|
304
|
-
if msg.get('code'):
|
|
305
|
-
if msg.get('code') == 10305:
|
|
306
|
-
logger.warning('UserWSS Bitfinex: Reached limit of open channels')
|
|
307
|
-
self._retry_after = int((time.time() + TIMEOUT) * 1000)
|
|
308
|
-
self.request_limit_reached = True
|
|
309
|
-
logger.warning(f"Malformed request for {self.ws_id}: {msg}")
|
|
310
|
-
return None
|
|
311
|
-
return 'pass'
|
|
302
|
+
return await self._handle_dict_message(msg)
|
|
312
303
|
elif isinstance(msg, list) and msg[1] == 'n' and msg[2][1] in ('on-req', 'oc-req', 'oc_multi-req'):
|
|
313
|
-
|
|
314
|
-
"data": [msg[2][0],
|
|
315
|
-
msg[2][1],
|
|
316
|
-
None,
|
|
317
|
-
None,
|
|
318
|
-
[msg[2][4]] if msg[2][1] == 'on-req' else msg[2][4],
|
|
319
|
-
None,
|
|
320
|
-
msg[2][6],
|
|
321
|
-
msg[2][7]
|
|
322
|
-
]
|
|
323
|
-
}
|
|
324
|
-
return msg
|
|
304
|
+
return self._transform_list_message(msg)
|
|
325
305
|
else:
|
|
326
306
|
return 'pass'
|
|
327
307
|
|
|
308
|
+
async def _handle_dict_message(self, msg):
|
|
309
|
+
event = msg.get('event')
|
|
310
|
+
if event == 'info':
|
|
311
|
+
return await self._handle_info_event(msg)
|
|
312
|
+
elif event == 'auth':
|
|
313
|
+
return msg if msg.get('status') == "OK" else None
|
|
314
|
+
elif msg.get('code'):
|
|
315
|
+
return await self._handle_error_code(msg)
|
|
316
|
+
return 'pass'
|
|
317
|
+
|
|
318
|
+
async def _handle_info_event(self, msg):
|
|
319
|
+
if not msg.get('platform', {}).get('status'):
|
|
320
|
+
logger.warning(f"UserWSS Bitfinex platform in maintenance mode: {msg}")
|
|
321
|
+
await self.stop()
|
|
322
|
+
elif msg.get('version') != 2:
|
|
323
|
+
logger.critical('Bitfinex WSS platform: version change detected')
|
|
324
|
+
return 'pass'
|
|
325
|
+
|
|
326
|
+
async def _handle_error_code(self, msg):
|
|
327
|
+
code = msg.get('code')
|
|
328
|
+
if code == 10305:
|
|
329
|
+
logger.warning('UserWSS Bitfinex: Reached limit of open channels')
|
|
330
|
+
self._retry_after = int((time.time() + TIMEOUT) * 1000)
|
|
331
|
+
self.request_limit_reached = True
|
|
332
|
+
else:
|
|
333
|
+
logger.warning(f"Malformed request for {self.ws_id}: {msg}")
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def _transform_list_message(msg):
|
|
338
|
+
return {
|
|
339
|
+
"id": msg[2][2],
|
|
340
|
+
"data": [
|
|
341
|
+
msg[2][0],
|
|
342
|
+
msg[2][1],
|
|
343
|
+
None,
|
|
344
|
+
None,
|
|
345
|
+
[msg[2][4]] if msg[2][1] == 'on-req' else msg[2][4],
|
|
346
|
+
None,
|
|
347
|
+
msg[2][6],
|
|
348
|
+
msg[2][7]
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
#endregion
|
|
352
|
+
|
|
328
353
|
async def okx_error_handle(self, msg):
|
|
329
354
|
if msg.get('code') == '1':
|
|
330
355
|
logger.warning(f"Operation failed: {msg}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|