exchanges-wrapper 2.1.3__tar.gz → 2.1.6__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.
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/PKG-INFO +2 -2
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/bybit_parser.py +3 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/client.py +8 -10
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/exch_srv.py +16 -14
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/http_client.py +31 -8
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/web_sockets.py +25 -13
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/README.md +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/proto/martin.proto +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: exchanges-wrapper
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: REST API and WebSocket asyncio wrapper with grpc powered multiplexer server
|
|
5
5
|
Author-email: Thomas Marchand <thomas.marchand@tuta.io>, Jerry Fedorenko <jerry.fedorenko@yahoo.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -11,7 +11,7 @@ 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: crypto-ws-api==2.0.
|
|
14
|
+
Requires-Dist: crypto-ws-api==2.0.8
|
|
15
15
|
Requires-Dist: grpcio==1.62.0
|
|
16
16
|
Requires-Dist: pyotp~=2.9.0
|
|
17
17
|
Requires-Dist: simplejson==3.19.2
|
|
@@ -12,7 +12,7 @@ __maintainer__ = "Jerry Fedorenko"
|
|
|
12
12
|
__contact__ = "https://github.com/DogsTailFarmer"
|
|
13
13
|
__email__ = "jerry.fedorenko@yahoo.com"
|
|
14
14
|
__credits__ = ["https://github.com/DanyaSWorlD"]
|
|
15
|
-
__version__ = "2.1.
|
|
15
|
+
__version__ = "2.1.6"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -450,7 +450,9 @@ def on_balance_update(data_in: list, ts: str, symbol: str, mode: str, uid=None)
|
|
|
450
450
|
|
|
451
451
|
elif mode == 'universal':
|
|
452
452
|
for i in data_in:
|
|
453
|
-
if i['coin'] in symbol and
|
|
453
|
+
if i['coin'] in symbol and \
|
|
454
|
+
((i['fromAccountType'] == 'UNIFIED' and i['fromMemberId'] == str(uid))
|
|
455
|
+
or (i['toAccountType'] == 'UNIFIED' and i['toMemberId'] == str(uid))):
|
|
454
456
|
data_out.append(
|
|
455
457
|
{
|
|
456
458
|
i['transferId']: {
|
|
@@ -1078,15 +1078,15 @@ class Client:
|
|
|
1078
1078
|
"This query requires an order_id on Bitfinex. Deletion by user number is not implemented."
|
|
1079
1079
|
)
|
|
1080
1080
|
params = {'id': order_id}
|
|
1081
|
-
res = (
|
|
1082
|
-
|
|
1083
|
-
|
|
1081
|
+
res = await self.user_wss_session.handle_request(trade_id, "oc", _params=params)
|
|
1082
|
+
if res is None or (res and isinstance(res, list) and res[6] == 'ERROR'):
|
|
1083
|
+
logger.debug(f"cancel_order.bitfinex {order_id}: res1: {res}")
|
|
1084
|
+
res = await self.http.send_api_call(
|
|
1084
1085
|
"v2/auth/w/order/cancel",
|
|
1085
1086
|
method="POST",
|
|
1086
1087
|
signed=True,
|
|
1087
1088
|
**params
|
|
1088
|
-
|
|
1089
|
-
)
|
|
1089
|
+
)
|
|
1090
1090
|
if res and isinstance(res, list) and res[6] == 'SUCCESS':
|
|
1091
1091
|
timeout = STATUS_TIMEOUT / 0.1
|
|
1092
1092
|
while timeout:
|
|
@@ -1095,9 +1095,8 @@ class Client:
|
|
|
1095
1095
|
binance_res = bfx.order(res[4], response_type=True, cancelled=True)
|
|
1096
1096
|
break
|
|
1097
1097
|
await asyncio.sleep(0.1)
|
|
1098
|
-
logger.debug(f"cancel_order.bitfinex {order_id}: timeout: {timeout}")
|
|
1099
1098
|
else:
|
|
1100
|
-
logger.
|
|
1099
|
+
logger.debug(f"cancel_order.bitfinex {order_id}: res2: {res}")
|
|
1101
1100
|
elif self.exchange == 'huobi':
|
|
1102
1101
|
res = await self.http.send_api_call(
|
|
1103
1102
|
f"v1/order/orders/{order_id}/submitcancel",
|
|
@@ -1110,7 +1109,6 @@ class Client:
|
|
|
1110
1109
|
timeout -= 1
|
|
1111
1110
|
await asyncio.sleep(0.1)
|
|
1112
1111
|
binance_res = await self.fetch_order(trade_id, symbol, order_id=res, response_type=True)
|
|
1113
|
-
|
|
1114
1112
|
elif self.exchange == 'okx':
|
|
1115
1113
|
_symbol = self.symbol_to_okx(symbol)
|
|
1116
1114
|
_queue = asyncio.Queue()
|
|
@@ -1188,9 +1186,10 @@ class Client:
|
|
|
1188
1186
|
**params,
|
|
1189
1187
|
)
|
|
1190
1188
|
)
|
|
1191
|
-
logger.debug(f"cancel_all_orders.res: {res}")
|
|
1192
1189
|
if res and res[6] == 'SUCCESS':
|
|
1193
1190
|
return bfx.orders(res[4], response_type=True, cancelled=True)
|
|
1191
|
+
logger.debug(f"bitfinex: cancel_all_orders.res: {res}")
|
|
1192
|
+
|
|
1194
1193
|
elif self.exchange == 'huobi':
|
|
1195
1194
|
orders = await self.fetch_open_orders(trade_id, symbol, receive_window=receive_window, response_type=True)
|
|
1196
1195
|
orders_id = [str(order.get('orderId')) for order in orders]
|
|
@@ -1296,7 +1295,6 @@ class Client:
|
|
|
1296
1295
|
method="POST",
|
|
1297
1296
|
signed=True
|
|
1298
1297
|
)
|
|
1299
|
-
# logger.debug(f"fetch_open_orders.res: {res}")
|
|
1300
1298
|
if res:
|
|
1301
1299
|
binance_res = bfx.orders(res)
|
|
1302
1300
|
elif self.exchange == 'huobi':
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
import grpclib.exceptions
|
|
3
4
|
|
|
4
|
-
from exchanges_wrapper import __version__
|
|
5
|
+
from exchanges_wrapper import __version__ as ver_ew
|
|
6
|
+
from crypto_ws_api import __version__ as ver_cw
|
|
5
7
|
import time
|
|
6
8
|
import weakref
|
|
7
9
|
import gc
|
|
@@ -129,9 +131,9 @@ class OpenClient:
|
|
|
129
131
|
)
|
|
130
132
|
|
|
131
133
|
@classmethod
|
|
132
|
-
def get_client(cls, _id
|
|
134
|
+
def get_client(cls, _id):
|
|
133
135
|
res = next((client for client in cls.open_clients if id(client) == _id), None)
|
|
134
|
-
if res is None
|
|
136
|
+
if res is None:
|
|
135
137
|
logger.warning(f"No client exist: {_id}")
|
|
136
138
|
raise GRPCError(status=Status.UNAVAILABLE, message="No client exist")
|
|
137
139
|
return res
|
|
@@ -172,8 +174,7 @@ class Martin(mr.MartinBase):
|
|
|
172
174
|
if main_client.account_uid and main_client.account_id:
|
|
173
175
|
open_client.client.main_account_uid = main_client.account_uid
|
|
174
176
|
open_client.client.main_account_id = main_client.account_id
|
|
175
|
-
logger.info(f"
|
|
176
|
-
f" UID: {main_client.account_uid} and account ID: {main_client.account_id}")
|
|
177
|
+
logger.info(f"Huobi UID: {main_client.account_uid} and account ID: {main_client.account_id}")
|
|
177
178
|
else:
|
|
178
179
|
logger.warning("No account IDs were received for the Huobi master account")
|
|
179
180
|
await main_client.close()
|
|
@@ -196,7 +197,7 @@ class Martin(mr.MartinBase):
|
|
|
196
197
|
Martin.rate_limiter = max(Martin.rate_limiter or 0, request.rate_limiter)
|
|
197
198
|
return mr.OpenClientConnectionId(
|
|
198
199
|
client_id=client_id,
|
|
199
|
-
srv_version=
|
|
200
|
+
srv_version=f"{ver_cw}:{ver_ew}",
|
|
200
201
|
exchange=open_client.client.exchange,
|
|
201
202
|
real_market=open_client.real_market
|
|
202
203
|
)
|
|
@@ -229,8 +230,9 @@ class Martin(mr.MartinBase):
|
|
|
229
230
|
await self.rate_limit_control(open_client)
|
|
230
231
|
try:
|
|
231
232
|
res = await getattr(client, client_method_name)(**kwargs)
|
|
232
|
-
except asyncio.CancelledError:
|
|
233
|
-
|
|
233
|
+
except (asyncio.CancelledError, asyncio.exceptions.CancelledError):
|
|
234
|
+
msg = f"{msg_header} Server Shutdown"
|
|
235
|
+
raise GRPCError(status=Status.UNAVAILABLE, message=msg)
|
|
234
236
|
except (errors.RateLimitReached, errors.QueryCanceled) as ex:
|
|
235
237
|
Martin.rate_limit_reached_time = time.time()
|
|
236
238
|
msg = f"{msg_header} RateLimitReached: {ex}"
|
|
@@ -653,7 +655,7 @@ class Martin(mr.MartinBase):
|
|
|
653
655
|
return
|
|
654
656
|
_events.append(_event)
|
|
655
657
|
_get_event_from_queue = True
|
|
656
|
-
except asyncio.TimeoutError:
|
|
658
|
+
except asyncio.exceptions.TimeoutError:
|
|
657
659
|
_get_event_from_queue = False
|
|
658
660
|
|
|
659
661
|
if client.exchange in ('bitfinex', 'huobi', 'bybit'):
|
|
@@ -700,7 +702,7 @@ class Martin(mr.MartinBase):
|
|
|
700
702
|
else:
|
|
701
703
|
event = vars(_event)
|
|
702
704
|
event.pop('handlers', None)
|
|
703
|
-
# logger.
|
|
705
|
+
# logger.info(f"OnOrderUpdate: {open_client.name}: {event}")
|
|
704
706
|
response.success = True
|
|
705
707
|
response.result = json.dumps(event)
|
|
706
708
|
yield response
|
|
@@ -800,11 +802,11 @@ class Martin(mr.MartinBase):
|
|
|
800
802
|
|
|
801
803
|
async def check_stream(self, request: mr.MarketRequest) -> mr.SimpleResponse:
|
|
802
804
|
response = mr.SimpleResponse()
|
|
803
|
-
|
|
805
|
+
response.success = False
|
|
806
|
+
if open_client := OpenClient.get_client(request.client_id):
|
|
804
807
|
client = open_client.client
|
|
805
808
|
response.success = bool(client.data_streams.get(request.trade_id))
|
|
806
809
|
else:
|
|
807
|
-
response.success = False
|
|
808
810
|
logger.warning(f"CheckStream request failed for {request.symbol}")
|
|
809
811
|
return response
|
|
810
812
|
|
|
@@ -841,7 +843,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
841
843
|
server = Server([Martin()])
|
|
842
844
|
with graceful_exit([server]):
|
|
843
845
|
await server.start(host, port)
|
|
844
|
-
logger.info(f"Starting server v:{
|
|
846
|
+
logger.info(f"Starting server v:{ver_cw}:{ver_ew} on {host}:{port}")
|
|
845
847
|
await server.wait_closed()
|
|
846
848
|
|
|
847
849
|
for oc in OpenClient.open_clients:
|
|
@@ -853,7 +855,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
853
855
|
def main():
|
|
854
856
|
try:
|
|
855
857
|
asyncio.run(amain())
|
|
856
|
-
except asyncio.exceptions.CancelledError:
|
|
858
|
+
except (asyncio.exceptions.CancelledError, grpclib.exceptions.StreamTerminatedError):
|
|
857
859
|
pass # # Task cancellation should not be logged as an error
|
|
858
860
|
except Exception as ex:
|
|
859
861
|
print(f"Exception: {ex}")
|
|
@@ -18,9 +18,15 @@ from exchanges_wrapper.errors import (
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
AJ = 'application/json'
|
|
21
|
+
STATUS_BAD_REQUEST = 400
|
|
22
|
+
STATUS_UNAUTHORIZED = 401
|
|
23
|
+
STATUS_FORBIDDEN = 403
|
|
24
|
+
STATUS_I_AM_A_TEAPOT = 418 # HTTP status code for IPAddressBanned
|
|
25
|
+
ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW = "Timestamp for this request is outside of the recvWindow"
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
class HttpClient:
|
|
29
|
+
|
|
24
30
|
def __init__(self, **kwargs):
|
|
25
31
|
self.api_key = kwargs.get('api_key')
|
|
26
32
|
self.api_secret = kwargs.get('api_secret')
|
|
@@ -48,19 +54,36 @@ class HttpClient:
|
|
|
48
54
|
payload = None
|
|
49
55
|
|
|
50
56
|
if response.status >= 400:
|
|
51
|
-
if response.status ==
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
if response.status == STATUS_BAD_REQUEST:
|
|
58
|
+
if payload:
|
|
59
|
+
if payload.get("error", "") == "ERR_RATE_LIMIT":
|
|
60
|
+
raise RateLimitReached(RateLimitReached.message)
|
|
61
|
+
elif self.exchange == 'binance' and payload.get('code', 0) == -1021:
|
|
62
|
+
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
63
|
+
else:
|
|
64
|
+
raise ExchangeError(f"ExchangeError: {payload}")
|
|
65
|
+
elif response.reason != "Bad Request":
|
|
66
|
+
raise ExchangeError(f"ExchangeError: {response.reason}:{response.text}")
|
|
67
|
+
|
|
68
|
+
elif (
|
|
69
|
+
response.status == STATUS_UNAUTHORIZED
|
|
70
|
+
and self.exchange == 'okx'
|
|
71
|
+
and payload
|
|
72
|
+
and payload.get('code', 0) == '50102'
|
|
73
|
+
):
|
|
74
|
+
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
75
|
+
elif response.status == STATUS_FORBIDDEN and self.exchange != 'okx':
|
|
56
76
|
raise WAFLimitViolated(WAFLimitViolated.message)
|
|
57
|
-
elif response.status ==
|
|
77
|
+
elif response.status == STATUS_I_AM_A_TEAPOT:
|
|
58
78
|
raise IPAddressBanned(IPAddressBanned.message)
|
|
59
79
|
else:
|
|
60
80
|
raise HTTPError(f"Malformed request: {payload}:{response.reason}:{response.text}")
|
|
61
81
|
|
|
62
|
-
if self.exchange == 'bybit' and payload
|
|
63
|
-
|
|
82
|
+
if self.exchange == 'bybit' and payload:
|
|
83
|
+
if payload.get('retCode') == 0:
|
|
84
|
+
return payload.get('result'), payload.get('time')
|
|
85
|
+
elif payload.get('retCode') == 10002:
|
|
86
|
+
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
64
87
|
elif self.exchange == 'huobi' and payload and (payload.get('status') == 'ok' or payload.get('ok')):
|
|
65
88
|
return payload.get('data', payload.get('tick'))
|
|
66
89
|
elif self.exchange == 'okx' and payload and payload.get('code') == '0':
|
|
@@ -8,8 +8,7 @@ from decimal import Decimal
|
|
|
8
8
|
import gzip
|
|
9
9
|
from datetime import datetime, timezone
|
|
10
10
|
from urllib.parse import urlencode, urlparse
|
|
11
|
-
import websockets
|
|
12
|
-
from websockets import ConnectionClosed
|
|
11
|
+
import websockets
|
|
13
12
|
|
|
14
13
|
import exchanges_wrapper.bitfinex_parser as bfx
|
|
15
14
|
import exchanges_wrapper.huobi_parser as hbp
|
|
@@ -53,10 +52,15 @@ class EventsDataStream:
|
|
|
53
52
|
self.wss_started = False
|
|
54
53
|
|
|
55
54
|
async def start(self):
|
|
56
|
-
|
|
55
|
+
ping_interval = None if self.exchange == 'huobi' else 20
|
|
56
|
+
async for self.websocket in websockets.connect(
|
|
57
|
+
self.endpoint,
|
|
58
|
+
logger=logger_ws,
|
|
59
|
+
ping_interval=ping_interval
|
|
60
|
+
):
|
|
57
61
|
try:
|
|
58
62
|
await self.start_wss()
|
|
59
|
-
except ConnectionClosed as ex:
|
|
63
|
+
except websockets.ConnectionClosed as ex:
|
|
60
64
|
self.tasks_cancel()
|
|
61
65
|
if ex.code == 4000:
|
|
62
66
|
logger.info(f"WSS closed for {self.exchange}:{self.trade_id}")
|
|
@@ -113,7 +117,7 @@ class EventsDataStream:
|
|
|
113
117
|
elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
|
|
114
118
|
and not msg_data.get("success")):
|
|
115
119
|
logger.warning(f"Reconnecting ByBit WSS: {symbol}: {ch_type}, msg_data: {msg_data}")
|
|
116
|
-
raise ConnectionClosed(None, None)
|
|
120
|
+
raise websockets.ConnectionClosed(None, None)
|
|
117
121
|
else:
|
|
118
122
|
logger.info(f"ByBit undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
|
|
119
123
|
elif self.exchange == 'okx':
|
|
@@ -129,7 +133,7 @@ class EventsDataStream:
|
|
|
129
133
|
self.wss_started = True
|
|
130
134
|
elif msg_data.get("event") in ("login", "error") and msg_data.get("code") != "0":
|
|
131
135
|
logger.warning(f"Reconnecting OKX WSS: {symbol}: {ch_type}, msg_data: {msg_data}")
|
|
132
|
-
raise ConnectionClosed(None, None)
|
|
136
|
+
raise websockets.ConnectionClosed(None, None)
|
|
133
137
|
else:
|
|
134
138
|
logger.debug(f"OKX undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
|
|
135
139
|
elif self.exchange == 'bitfinex':
|
|
@@ -140,12 +144,12 @@ class EventsDataStream:
|
|
|
140
144
|
if msg_data.get('platform') and msg_data.get('platform').get('status') != 1:
|
|
141
145
|
logger.warning(f"Exchange in maintenance mode, trying reconnect. Exchange info: {msg}")
|
|
142
146
|
await asyncio.sleep(60)
|
|
143
|
-
raise ConnectionClosed(None, None)
|
|
147
|
+
raise websockets.ConnectionClosed(None, None)
|
|
144
148
|
elif 'code' in msg_data:
|
|
145
149
|
code = msg_data.get('code')
|
|
146
150
|
if code == 10300:
|
|
147
151
|
logger.warning('WSS Subscription failed (generic)')
|
|
148
|
-
raise ConnectionClosed(None, None)
|
|
152
|
+
raise websockets.ConnectionClosed(None, None)
|
|
149
153
|
elif code == 10301:
|
|
150
154
|
logger.error('WSS Already subscribed')
|
|
151
155
|
elif code == 10302:
|
|
@@ -154,11 +158,11 @@ class EventsDataStream:
|
|
|
154
158
|
raise UserWarning('WSS Reached limit of open channels')
|
|
155
159
|
elif code == 20051:
|
|
156
160
|
logger.warning('WSS reconnection request received from exchange')
|
|
157
|
-
raise ConnectionClosed(None, None)
|
|
161
|
+
raise websockets.ConnectionClosed(None, None)
|
|
158
162
|
elif code == 20060:
|
|
159
163
|
logger.info('WSS entering in maintenance mode, trying reconnect after 120s')
|
|
160
164
|
await asyncio.sleep(120)
|
|
161
|
-
raise ConnectionClosed(None, None)
|
|
165
|
+
raise websockets.ConnectionClosed(None, None)
|
|
162
166
|
elif msg_data.get('event') == 'subscribed':
|
|
163
167
|
chan_id = msg_data.get('chanId')
|
|
164
168
|
logger.info(f"bitfinex, ch_type: {ch_type}, chan_id: {chan_id}")
|
|
@@ -178,8 +182,9 @@ class EventsDataStream:
|
|
|
178
182
|
else:
|
|
179
183
|
logger.debug(f"Bitfinex undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
|
|
180
184
|
elif self.exchange == 'huobi':
|
|
181
|
-
if msg_data.get('ping'):
|
|
182
|
-
await self.websocket.send(json.dumps({"pong":
|
|
185
|
+
if ping := msg_data.get('ping'):
|
|
186
|
+
await self.websocket.send(json.dumps({"pong": ping}))
|
|
187
|
+
await asyncio.sleep(0)
|
|
183
188
|
elif msg_data.get('action') == 'ping':
|
|
184
189
|
pong = {
|
|
185
190
|
"action": "pong",
|
|
@@ -188,6 +193,7 @@ class EventsDataStream:
|
|
|
188
193
|
}
|
|
189
194
|
}
|
|
190
195
|
await self.websocket.send(json.dumps(pong))
|
|
196
|
+
await asyncio.sleep(0)
|
|
191
197
|
elif msg_data.get('tick') or msg_data.get('data'):
|
|
192
198
|
if ch_type == 'ticker':
|
|
193
199
|
_price = msg_data.get('tick', {}).get('lastPrice', None)
|
|
@@ -203,13 +209,14 @@ class EventsDataStream:
|
|
|
203
209
|
msg_data.get('code') == 500 and
|
|
204
210
|
msg_data.get('message') == '系统异常:'):
|
|
205
211
|
logger.warning(f"Reconnecting Huobi user {ch_type} channel")
|
|
206
|
-
raise ConnectionClosed(None, None)
|
|
212
|
+
raise websockets.ConnectionClosed(None, None)
|
|
207
213
|
else:
|
|
208
214
|
logger.debug(f"Huobi undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
|
|
209
215
|
|
|
210
216
|
async def ws_listener(self, request=None, symbol=None, ch_type=str()):
|
|
211
217
|
if request:
|
|
212
218
|
await self.websocket.send(json.dumps(request))
|
|
219
|
+
await asyncio.sleep(0)
|
|
213
220
|
async for msg_data in self.websocket:
|
|
214
221
|
await self._handle_messages(msg_data, symbol, ch_type)
|
|
215
222
|
|
|
@@ -370,6 +377,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
|
|
|
370
377
|
"params": _params
|
|
371
378
|
}
|
|
372
379
|
await self.websocket.send(json.dumps(request))
|
|
380
|
+
await asyncio.sleep(0)
|
|
373
381
|
await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
|
|
374
382
|
#
|
|
375
383
|
request = {
|
|
@@ -377,6 +385,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
|
|
|
377
385
|
"ch": "accounts.update#2"
|
|
378
386
|
}
|
|
379
387
|
await self.websocket.send(json.dumps(request))
|
|
388
|
+
await asyncio.sleep(0)
|
|
380
389
|
await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
|
|
381
390
|
#
|
|
382
391
|
request = {
|
|
@@ -384,6 +393,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
|
|
|
384
393
|
"ch": f"orders#{self.symbol.lower()}"
|
|
385
394
|
}
|
|
386
395
|
await self.websocket.send(json.dumps(request))
|
|
396
|
+
await asyncio.sleep(0)
|
|
387
397
|
await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
|
|
388
398
|
#
|
|
389
399
|
request = {
|
|
@@ -489,6 +499,7 @@ class OkxPrivateEventsDataStream(EventsDataStream):
|
|
|
489
499
|
]
|
|
490
500
|
}
|
|
491
501
|
await self.websocket.send(json.dumps(request))
|
|
502
|
+
await asyncio.sleep(0)
|
|
492
503
|
await self._handle_messages(await self.websocket.recv())
|
|
493
504
|
# Channel subscription
|
|
494
505
|
request = {"op": 'subscribe',
|
|
@@ -539,6 +550,7 @@ class BBTPrivateEventsDataStream(EventsDataStream):
|
|
|
539
550
|
"args": [self.client.api_key, ts, signature]
|
|
540
551
|
}
|
|
541
552
|
await self.websocket.send(json.dumps(request))
|
|
553
|
+
await asyncio.sleep(0)
|
|
542
554
|
await self._handle_messages(await self.websocket.recv())
|
|
543
555
|
# Channel subscription
|
|
544
556
|
request = {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.6}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|