exchanges-wrapper 2.1.8__tar.gz → 2.1.10__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.10}/PKG-INFO +2 -2
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/client.py +46 -30
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/events.py +2 -9
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/exch_srv.py +6 -4
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/bitfinex_parser.py +17 -4
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/web_sockets.py +10 -7
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/README.md +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/http_client.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/bybit_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/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.10
|
|
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.10"
|
|
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}")
|
|
@@ -339,7 +344,7 @@ class Client:
|
|
|
339
344
|
"v2/tickers",
|
|
340
345
|
send_api_key=False,
|
|
341
346
|
endpoint=self.endpoint_api_public,
|
|
342
|
-
symbols=bfx.get_symbols(symbols_details)
|
|
347
|
+
symbols=bfx.get_symbols(symbols_details, symbol)
|
|
343
348
|
)
|
|
344
349
|
if symbols_details and tickers:
|
|
345
350
|
binance_res = bfx.exchange_info(symbols_details, tickers, symbol)
|
|
@@ -874,18 +879,22 @@ class Client:
|
|
|
874
879
|
if new_client_order_id:
|
|
875
880
|
params["cid"] = new_client_order_id
|
|
876
881
|
self.active_order(new_client_order_id, quantity)
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
882
|
+
|
|
883
|
+
res = await self.user_wss_session.handle_request(trade_id, "on", _params=params)
|
|
884
|
+
|
|
885
|
+
if not res or (res and isinstance(res, list) and res[6] != 'SUCCESS'):
|
|
886
|
+
logger.debug(f"create_order.bitfinex {new_client_order_id}: {res}")
|
|
887
|
+
res = await self.http.send_api_call(
|
|
888
|
+
"v2/auth/w/order/submit",
|
|
889
|
+
method="POST",
|
|
890
|
+
signed=True,
|
|
891
|
+
**params
|
|
892
|
+
)
|
|
886
893
|
if res and isinstance(res, list) and res[6] == 'SUCCESS':
|
|
887
894
|
self.active_order(res[4][0][0], quantity)
|
|
888
895
|
binance_res = bfx.order(res[4][0], response_type=False)
|
|
896
|
+
else:
|
|
897
|
+
logger.debug(f"create_order.bitfinex {new_client_order_id}: {res}")
|
|
889
898
|
elif self.exchange == 'huobi':
|
|
890
899
|
params = {
|
|
891
900
|
'account-id': str(self.account_id),
|
|
@@ -961,9 +970,7 @@ class Client:
|
|
|
961
970
|
response_type=None,
|
|
962
971
|
):
|
|
963
972
|
self.assert_symbol(symbol)
|
|
964
|
-
if
|
|
965
|
-
raise ValueError("This query requires an order_id")
|
|
966
|
-
elif self.exchange in ('binance', 'huobi', 'okx', 'bybit') and not order_id and not origin_client_order_id:
|
|
973
|
+
if not order_id and not origin_client_order_id:
|
|
967
974
|
raise ValueError("This query requires an order_id or an origin_client_order_id")
|
|
968
975
|
|
|
969
976
|
b_res = {}
|
|
@@ -990,20 +997,28 @@ class Client:
|
|
|
990
997
|
)
|
|
991
998
|
)
|
|
992
999
|
elif self.exchange == 'bitfinex':
|
|
993
|
-
params = {
|
|
994
|
-
|
|
1000
|
+
params = {}
|
|
1001
|
+
if order_id:
|
|
1002
|
+
params['id'] = [order_id]
|
|
1003
|
+
_res = await self.http.send_api_call(
|
|
995
1004
|
f"v2/auth/r/orders/{self.symbol_to_bfx(symbol)}",
|
|
996
1005
|
method="POST",
|
|
997
1006
|
signed=True,
|
|
998
1007
|
**params
|
|
999
|
-
) or await self.http.send_api_call(
|
|
1000
|
-
f"v2/auth/r/orders/{self.symbol_to_bfx(symbol)}/hist",
|
|
1001
|
-
method="POST",
|
|
1002
|
-
signed=True,
|
|
1003
|
-
**params
|
|
1004
1008
|
)
|
|
1009
|
+
res = bfx.find_order(_res, order_id, origin_client_order_id)
|
|
1010
|
+
if not res:
|
|
1011
|
+
if not order_id:
|
|
1012
|
+
params['start'] = int(time.time() * 1000) - 600000 # 10 mins
|
|
1013
|
+
_res = await self.http.send_api_call(
|
|
1014
|
+
f"v2/auth/r/orders/{self.symbol_to_bfx(symbol)}/hist",
|
|
1015
|
+
method="POST",
|
|
1016
|
+
signed=True,
|
|
1017
|
+
**params
|
|
1018
|
+
)
|
|
1019
|
+
res = bfx.find_order(_res, order_id, origin_client_order_id)
|
|
1005
1020
|
if res:
|
|
1006
|
-
b_res = bfx.order(res
|
|
1021
|
+
b_res = bfx.order(res, response_type=response_type)
|
|
1007
1022
|
self.active_order(b_res['orderId'], b_res['origQty'], b_res['executedQty'])
|
|
1008
1023
|
elif self.exchange == 'huobi':
|
|
1009
1024
|
if origin_client_order_id:
|
|
@@ -1180,7 +1195,7 @@ class Client:
|
|
|
1180
1195
|
orders_id = [order.get('orderId') for order in orders]
|
|
1181
1196
|
params = {'id': orders_id}
|
|
1182
1197
|
res = await self.user_wss_session.handle_request(trade_id, "oc_multi", _params=params)
|
|
1183
|
-
if res
|
|
1198
|
+
if not res or (res and isinstance(res, list) and res[6] != 'SUCCESS'):
|
|
1184
1199
|
logger.debug(f"cancel_all_orders.bitfinex {orders_id}: res1: {res}")
|
|
1185
1200
|
res = await self.http.send_api_call(
|
|
1186
1201
|
"v2/auth/w/order/cancel/multi",
|
|
@@ -1249,12 +1264,12 @@ class Client:
|
|
|
1249
1264
|
params = {'category': 'spot', 'symbol': symbol}
|
|
1250
1265
|
res, _ = await self.http.send_api_call("/v5/order/cancel-all", method="POST", signed=True, **params)
|
|
1251
1266
|
|
|
1252
|
-
tasks =
|
|
1267
|
+
tasks = set()
|
|
1253
1268
|
for order in res.get('list', []):
|
|
1254
1269
|
_id = order.get('orderId')
|
|
1255
|
-
task = asyncio.
|
|
1270
|
+
task = asyncio.create_task(self.fetch_object(f"oc-{_id}"))
|
|
1256
1271
|
task.set_name(f"{_id}")
|
|
1257
|
-
tasks.
|
|
1272
|
+
tasks.add(task)
|
|
1258
1273
|
|
|
1259
1274
|
if tasks:
|
|
1260
1275
|
done, pending = await asyncio.wait(tasks, timeout=STATUS_TIMEOUT)
|
|
@@ -1267,6 +1282,7 @@ class Client:
|
|
|
1267
1282
|
_res = await self.fetch_order(trade_id, symbol, order_id=_id, response_type=True)
|
|
1268
1283
|
binance_res.append(_res)
|
|
1269
1284
|
pending.clear()
|
|
1285
|
+
tasks.clear()
|
|
1270
1286
|
|
|
1271
1287
|
return binance_res
|
|
1272
1288
|
|
|
@@ -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():
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/bitfinex_parser.py
RENAMED
|
@@ -50,13 +50,13 @@ class OrderBook:
|
|
|
50
50
|
return self
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def get_symbols(symbols_details: []) -> str:
|
|
53
|
+
def get_symbols(symbols_details: [], symbol) -> str:
|
|
54
54
|
symbols = []
|
|
55
55
|
res = ",t"
|
|
56
56
|
for symbol_details in symbols_details:
|
|
57
|
-
|
|
58
|
-
if 'f0' not in symbol:
|
|
59
|
-
symbols.append(
|
|
57
|
+
_symbol = symbol_details['pair']
|
|
58
|
+
if 'f0' not in _symbol and _symbol.replace(":", "").upper() == symbol:
|
|
59
|
+
symbols.append(_symbol.upper())
|
|
60
60
|
return f"t{res.join(symbols)}"
|
|
61
61
|
|
|
62
62
|
|
|
@@ -623,3 +623,16 @@ def funding_wallet(res: []) -> []:
|
|
|
623
623
|
balances.append(_binance_res)
|
|
624
624
|
|
|
625
625
|
return balances
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def find_order(_res, order_id, origin_client_order_id):
|
|
629
|
+
res = []
|
|
630
|
+
if _res:
|
|
631
|
+
if order_id:
|
|
632
|
+
res = _res[0]
|
|
633
|
+
else:
|
|
634
|
+
for i in _res:
|
|
635
|
+
if i[2] == int(origin_client_order_id):
|
|
636
|
+
res = i
|
|
637
|
+
break
|
|
638
|
+
return res
|
|
@@ -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.10}/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.10}/exchanges_wrapper/parsers/bybit_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/huobi_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.8 → exchanges_wrapper-2.1.10}/exchanges_wrapper/parsers/okx_parser.py
RENAMED
|
File without changes
|
|
File without changes
|