exchanges-wrapper 2.1.8__tar.gz → 2.1.9__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.8 → exchanges_wrapper-2.1.9}/PKG-INFO +2 -2
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/client.py +14 -8
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/events.py +2 -9
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv.py +6 -4
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/web_sockets.py +10 -7
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/README.md +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/http_client.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bybit_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/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.9
|
|
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.9
|
|
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.9"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -108,6 +108,12 @@ class Client:
|
|
|
108
108
|
self.main_account_uid = None
|
|
109
109
|
self.ledgers_id = []
|
|
110
110
|
self.ts_start = {}
|
|
111
|
+
self.tasks = set()
|
|
112
|
+
|
|
113
|
+
def tasks_manage(self, coro):
|
|
114
|
+
_t = asyncio.create_task(coro)
|
|
115
|
+
self.tasks.add(_t)
|
|
116
|
+
_t.add_done_callback(self.tasks.discard)
|
|
111
117
|
|
|
112
118
|
async def fetch_object(self, key):
|
|
113
119
|
res = None
|
|
@@ -145,7 +151,7 @@ class Client:
|
|
|
145
151
|
# load rate limits
|
|
146
152
|
self.rate_limits = infos["rateLimits"]
|
|
147
153
|
self.loaded = True
|
|
148
|
-
logger.info(f"Info for {self.exchange}:{symbol} loaded
|
|
154
|
+
logger.info(f"Info for {self.exchange}:{symbol} loaded successfully")
|
|
149
155
|
|
|
150
156
|
async def close(self):
|
|
151
157
|
await self.session.close()
|
|
@@ -176,7 +182,7 @@ class Client:
|
|
|
176
182
|
user_data_stream = BBTPrivateEventsDataStream(self, self.endpoint_ws_auth, self.exchange, _trade_id)
|
|
177
183
|
if user_data_stream:
|
|
178
184
|
self.data_streams[_trade_id] |= {user_data_stream}
|
|
179
|
-
|
|
185
|
+
self.tasks_manage(user_data_stream.start())
|
|
180
186
|
timeout = STATUS_TIMEOUT / 0.1
|
|
181
187
|
while not user_data_stream.wss_started:
|
|
182
188
|
timeout -= 1
|
|
@@ -190,8 +196,7 @@ class Client:
|
|
|
190
196
|
if self.exchange == 'binance':
|
|
191
197
|
market_data_stream = MarketEventsDataStream(self, self.endpoint_ws_public, self.exchange, _trade_id)
|
|
192
198
|
self.data_streams[_trade_id] |= {market_data_stream}
|
|
193
|
-
|
|
194
|
-
# start_list.append(market_data_stream.start())
|
|
199
|
+
self.tasks_manage(market_data_stream.start())
|
|
195
200
|
else:
|
|
196
201
|
for channel in _events:
|
|
197
202
|
# https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url
|
|
@@ -202,7 +207,7 @@ class Client:
|
|
|
202
207
|
#
|
|
203
208
|
market_data_stream = MarketEventsDataStream(self, _endpoint, self.exchange, _trade_id, channel)
|
|
204
209
|
self.data_streams[_trade_id] |= {market_data_stream}
|
|
205
|
-
|
|
210
|
+
self.tasks_manage(market_data_stream.start())
|
|
206
211
|
|
|
207
212
|
async def stop_events_listener(self, _trade_id):
|
|
208
213
|
logger.info(f"Stop events listener data streams for {_trade_id}")
|
|
@@ -1249,12 +1254,12 @@ class Client:
|
|
|
1249
1254
|
params = {'category': 'spot', 'symbol': symbol}
|
|
1250
1255
|
res, _ = await self.http.send_api_call("/v5/order/cancel-all", method="POST", signed=True, **params)
|
|
1251
1256
|
|
|
1252
|
-
tasks =
|
|
1257
|
+
tasks = set()
|
|
1253
1258
|
for order in res.get('list', []):
|
|
1254
1259
|
_id = order.get('orderId')
|
|
1255
|
-
task = asyncio.
|
|
1260
|
+
task = asyncio.create_task(self.fetch_object(f"oc-{_id}"))
|
|
1256
1261
|
task.set_name(f"{_id}")
|
|
1257
|
-
tasks.
|
|
1262
|
+
tasks.add(task)
|
|
1258
1263
|
|
|
1259
1264
|
if tasks:
|
|
1260
1265
|
done, pending = await asyncio.wait(tasks, timeout=STATUS_TIMEOUT)
|
|
@@ -1267,6 +1272,7 @@ class Client:
|
|
|
1267
1272
|
_res = await self.fetch_order(trade_id, symbol, order_id=_id, response_type=True)
|
|
1268
1273
|
binance_res.append(_res)
|
|
1269
1274
|
pending.clear()
|
|
1275
|
+
tasks.clear()
|
|
1270
1276
|
|
|
1271
1277
|
return binance_res
|
|
1272
1278
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import functools
|
|
1
|
+
|
|
3
2
|
from collections import defaultdict
|
|
4
3
|
import logging
|
|
5
4
|
|
|
@@ -11,7 +10,6 @@ logger = logging.getLogger(__name__)
|
|
|
11
10
|
# based on: https://stackoverflow.com/a/2022629/10144963
|
|
12
11
|
class Handlers(list):
|
|
13
12
|
async def __call__(self, *args, **kwargs):
|
|
14
|
-
loop = asyncio.get_running_loop()
|
|
15
13
|
trade_id = kwargs.pop('trade_id', None)
|
|
16
14
|
_trade_id = None
|
|
17
15
|
for func in self:
|
|
@@ -20,12 +18,7 @@ class Handlers(list):
|
|
|
20
18
|
except Exception as ex: # skipcq: PYL-W0703
|
|
21
19
|
logger.warning(f"Handlers error when try get trade_id: {ex}")
|
|
22
20
|
if trade_id is None or trade_id == _trade_id:
|
|
23
|
-
|
|
24
|
-
await func(*args, **kwargs)
|
|
25
|
-
continue
|
|
26
|
-
if kwargs:
|
|
27
|
-
func = functools.partial(func, **kwargs)
|
|
28
|
-
loop.run_in_executor(None, func, *args)
|
|
21
|
+
await func(*args, **kwargs)
|
|
29
22
|
|
|
30
23
|
def __repr__(self):
|
|
31
24
|
return f"Handlers({list.__repr__(self)})"
|
|
@@ -185,14 +185,16 @@ class Martin(mr.MartinBase):
|
|
|
185
185
|
raise GRPCError(status=Status.FAILED_PRECONDITION, message=str(ex))
|
|
186
186
|
|
|
187
187
|
try:
|
|
188
|
-
await open_client.client.load(request.symbol)
|
|
188
|
+
await asyncio.wait_for(open_client.client.load(request.symbol), timeout=HEARTBEAT * 60)
|
|
189
189
|
except asyncio.CancelledError:
|
|
190
190
|
pass # Task cancellation should not be logged as an error
|
|
191
|
+
except asyncio.exceptions.TimeoutError:
|
|
192
|
+
await OpenClient.get_client(client_id).client.session.close()
|
|
193
|
+
OpenClient.remove_client(request.account_name)
|
|
194
|
+
raise GRPCError(status=Status.UNAVAILABLE, message=f"'{open_client.name}' timeout error")
|
|
191
195
|
except Exception as ex:
|
|
192
196
|
logger.warning(f"OpenClientConnection for '{open_client.name}' exception: {ex}")
|
|
193
197
|
logger.debug(f"Exception traceback: {traceback.format_exc()}")
|
|
194
|
-
await OpenClient.get_client(client_id).client.session.close()
|
|
195
|
-
OpenClient.remove_client(request.account_name)
|
|
196
198
|
raise GRPCError(status=Status.RESOURCE_EXHAUSTED, message=str(ex))
|
|
197
199
|
|
|
198
200
|
# Set rate_limiter
|
|
@@ -851,7 +853,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
851
853
|
for oc in OpenClient.open_clients:
|
|
852
854
|
await oc.client.session.close()
|
|
853
855
|
|
|
854
|
-
[task.cancel() for task in asyncio.all_tasks() if not task.done()]
|
|
856
|
+
[task.cancel() for task in asyncio.all_tasks() if not task.done() and task is not asyncio.current_task()]
|
|
855
857
|
|
|
856
858
|
|
|
857
859
|
def main():
|
|
@@ -48,7 +48,7 @@ class EventsDataStream:
|
|
|
48
48
|
self.wss_event_buffer = {}
|
|
49
49
|
self._order_book = None
|
|
50
50
|
self._price = None
|
|
51
|
-
self.
|
|
51
|
+
self.tasks = set()
|
|
52
52
|
self.wss_started = False
|
|
53
53
|
|
|
54
54
|
async def start(self):
|
|
@@ -84,8 +84,13 @@ class EventsDataStream:
|
|
|
84
84
|
await self.websocket.close(code=4000)
|
|
85
85
|
|
|
86
86
|
def tasks_cancel(self):
|
|
87
|
-
[task.cancel() for task in self.
|
|
88
|
-
self.
|
|
87
|
+
[task.cancel() for task in self.tasks if not task.done()]
|
|
88
|
+
self.tasks.clear()
|
|
89
|
+
|
|
90
|
+
def tasks_manage(self, coro):
|
|
91
|
+
_t = asyncio.create_task(coro)
|
|
92
|
+
self.tasks.add(_t)
|
|
93
|
+
_t.add_done_callback(self.tasks.discard)
|
|
89
94
|
|
|
90
95
|
async def _handle_event(self, *args):
|
|
91
96
|
pass # meant to be overridden in a subclass
|
|
@@ -110,8 +115,7 @@ class EventsDataStream:
|
|
|
110
115
|
return
|
|
111
116
|
elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
|
|
112
117
|
and msg_data.get("success")):
|
|
113
|
-
|
|
114
|
-
self.tasks_list.append(_task)
|
|
118
|
+
self.tasks_manage(self.bybit_heartbeat(ch_type or "private"))
|
|
115
119
|
if msg_data["op"] == "subscribe" and msg_data["success"] and not msg_data["ret_msg"]:
|
|
116
120
|
self.wss_started = True
|
|
117
121
|
elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
|
|
@@ -602,8 +606,7 @@ class UserEventsDataStream(EventsDataStream):
|
|
|
602
606
|
|
|
603
607
|
async def start_wss(self):
|
|
604
608
|
logger.info(f"Start User WSS for {self.exchange}")
|
|
605
|
-
|
|
606
|
-
self.tasks_list.append(_task)
|
|
609
|
+
self.tasks_manage(self._heartbeat(self.listen_key))
|
|
607
610
|
try:
|
|
608
611
|
await self.ws_listener()
|
|
609
612
|
finally:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bitfinex_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/bybit_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.9}/exchanges_wrapper/parsers/huobi_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|