exchanges-wrapper 2.1.7__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.7 → exchanges_wrapper-2.1.9}/PKG-INFO +2 -2
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/client.py +29 -20
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/events.py +2 -9
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv.py +8 -4
- {exchanges_wrapper-2.1.7/exchanges_wrapper → exchanges_wrapper-2.1.9/exchanges_wrapper/parsers}/bybit_parser.py +6 -1
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/web_sockets.py +14 -11
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/README.md +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/http_client.py +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.7 → exchanges_wrapper-2.1.9}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.7/exchanges_wrapper → exchanges_wrapper-2.1.9/exchanges_wrapper/parsers}/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.7/exchanges_wrapper → exchanges_wrapper-2.1.9/exchanges_wrapper/parsers}/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.7/exchanges_wrapper → exchanges_wrapper-2.1.9/exchanges_wrapper/parsers}/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.7 → 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
|
|
@@ -22,10 +22,10 @@ from exchanges_wrapper.web_sockets import UserEventsDataStream, \
|
|
|
22
22
|
BBTPrivateEventsDataStream
|
|
23
23
|
from exchanges_wrapper.definitions import OrderType
|
|
24
24
|
from exchanges_wrapper.events import Events
|
|
25
|
-
import exchanges_wrapper.bitfinex_parser as bfx
|
|
26
|
-
import exchanges_wrapper.huobi_parser as hbp
|
|
27
|
-
import exchanges_wrapper.okx_parser as okx
|
|
28
|
-
import exchanges_wrapper.bybit_parser as bbt
|
|
25
|
+
import exchanges_wrapper.parsers.bitfinex_parser as bfx
|
|
26
|
+
import exchanges_wrapper.parsers.huobi_parser as hbp
|
|
27
|
+
import exchanges_wrapper.parsers.okx_parser as okx
|
|
28
|
+
import exchanges_wrapper.parsers.bybit_parser as bbt
|
|
29
29
|
from crypto_ws_api.ws_session import UserWSSession
|
|
30
30
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
@@ -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
|
|
@@ -117,6 +123,7 @@ class Client:
|
|
|
117
123
|
return res
|
|
118
124
|
|
|
119
125
|
async def load(self, symbol):
|
|
126
|
+
logger.info(f"Try load {self.exchange}:{symbol} info...")
|
|
120
127
|
infos = await self.fetch_exchange_info(symbol)
|
|
121
128
|
if not infos.get('serverTime'):
|
|
122
129
|
raise UserWarning("Can't get exchange info, check availability and operational status of the exchange")
|
|
@@ -144,6 +151,7 @@ class Client:
|
|
|
144
151
|
# load rate limits
|
|
145
152
|
self.rate_limits = infos["rateLimits"]
|
|
146
153
|
self.loaded = True
|
|
154
|
+
logger.info(f"Info for {self.exchange}:{symbol} loaded successfully")
|
|
147
155
|
|
|
148
156
|
async def close(self):
|
|
149
157
|
await self.session.close()
|
|
@@ -174,7 +182,7 @@ class Client:
|
|
|
174
182
|
user_data_stream = BBTPrivateEventsDataStream(self, self.endpoint_ws_auth, self.exchange, _trade_id)
|
|
175
183
|
if user_data_stream:
|
|
176
184
|
self.data_streams[_trade_id] |= {user_data_stream}
|
|
177
|
-
|
|
185
|
+
self.tasks_manage(user_data_stream.start())
|
|
178
186
|
timeout = STATUS_TIMEOUT / 0.1
|
|
179
187
|
while not user_data_stream.wss_started:
|
|
180
188
|
timeout -= 1
|
|
@@ -188,8 +196,7 @@ class Client:
|
|
|
188
196
|
if self.exchange == 'binance':
|
|
189
197
|
market_data_stream = MarketEventsDataStream(self, self.endpoint_ws_public, self.exchange, _trade_id)
|
|
190
198
|
self.data_streams[_trade_id] |= {market_data_stream}
|
|
191
|
-
|
|
192
|
-
# start_list.append(market_data_stream.start())
|
|
199
|
+
self.tasks_manage(market_data_stream.start())
|
|
193
200
|
else:
|
|
194
201
|
for channel in _events:
|
|
195
202
|
# https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url
|
|
@@ -200,7 +207,7 @@ class Client:
|
|
|
200
207
|
#
|
|
201
208
|
market_data_stream = MarketEventsDataStream(self, _endpoint, self.exchange, _trade_id, channel)
|
|
202
209
|
self.data_streams[_trade_id] |= {market_data_stream}
|
|
203
|
-
|
|
210
|
+
self.tasks_manage(market_data_stream.start())
|
|
204
211
|
|
|
205
212
|
async def stop_events_listener(self, _trade_id):
|
|
206
213
|
logger.info(f"Stop events listener data streams for {_trade_id}")
|
|
@@ -1177,18 +1184,19 @@ class Client:
|
|
|
1177
1184
|
orders = await self.fetch_open_orders(trade_id, symbol, receive_window=receive_window, response_type=True)
|
|
1178
1185
|
orders_id = [order.get('orderId') for order in orders]
|
|
1179
1186
|
params = {'id': orders_id}
|
|
1180
|
-
res = (
|
|
1181
|
-
|
|
1182
|
-
|
|
1187
|
+
res = await self.user_wss_session.handle_request(trade_id, "oc_multi", _params=params)
|
|
1188
|
+
if res is None or (res and isinstance(res, list) and res[6] == 'ERROR'):
|
|
1189
|
+
logger.debug(f"cancel_all_orders.bitfinex {orders_id}: res1: {res}")
|
|
1190
|
+
res = await self.http.send_api_call(
|
|
1183
1191
|
"v2/auth/w/order/cancel/multi",
|
|
1184
1192
|
method="POST",
|
|
1185
1193
|
signed=True,
|
|
1186
|
-
**params
|
|
1187
|
-
|
|
1188
|
-
)
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1194
|
+
**params
|
|
1195
|
+
)
|
|
1196
|
+
if res and isinstance(res, list) and res[6] == 'SUCCESS':
|
|
1197
|
+
binance_res = bfx.orders(res[4], response_type=True, cancelled=True)
|
|
1198
|
+
else:
|
|
1199
|
+
logger.debug(f"bitfinex: cancel_all_orders.res: {res}")
|
|
1192
1200
|
|
|
1193
1201
|
elif self.exchange == 'huobi':
|
|
1194
1202
|
orders = await self.fetch_open_orders(trade_id, symbol, receive_window=receive_window, response_type=True)
|
|
@@ -1246,12 +1254,12 @@ class Client:
|
|
|
1246
1254
|
params = {'category': 'spot', 'symbol': symbol}
|
|
1247
1255
|
res, _ = await self.http.send_api_call("/v5/order/cancel-all", method="POST", signed=True, **params)
|
|
1248
1256
|
|
|
1249
|
-
tasks =
|
|
1257
|
+
tasks = set()
|
|
1250
1258
|
for order in res.get('list', []):
|
|
1251
1259
|
_id = order.get('orderId')
|
|
1252
|
-
task = asyncio.
|
|
1260
|
+
task = asyncio.create_task(self.fetch_object(f"oc-{_id}"))
|
|
1253
1261
|
task.set_name(f"{_id}")
|
|
1254
|
-
tasks.
|
|
1262
|
+
tasks.add(task)
|
|
1255
1263
|
|
|
1256
1264
|
if tasks:
|
|
1257
1265
|
done, pending = await asyncio.wait(tasks, timeout=STATUS_TIMEOUT)
|
|
@@ -1264,6 +1272,7 @@ class Client:
|
|
|
1264
1272
|
_res = await self.fetch_order(trade_id, symbol, order_id=_id, response_type=True)
|
|
1265
1273
|
binance_res.append(_res)
|
|
1266
1274
|
pending.clear()
|
|
1275
|
+
tasks.clear()
|
|
1267
1276
|
|
|
1268
1277
|
return binance_res
|
|
1269
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)})"
|
|
@@ -160,9 +160,11 @@ class Martin(mr.MartinBase):
|
|
|
160
160
|
logger.info(f"OpenClientConnection start trade: {request.account_name}:{request.trade_id}")
|
|
161
161
|
client_id = OpenClient.get_id(request.account_name)
|
|
162
162
|
if client_id:
|
|
163
|
+
logger.debug(f"OpenClientConnection: {request.account_name}:{request.trade_id}:{client_id}")
|
|
163
164
|
open_client = OpenClient.get_client(client_id)
|
|
164
165
|
open_client.client.http.rate_limit_reached = False
|
|
165
166
|
else:
|
|
167
|
+
logger.debug(f"OpenClientConnection: {request.account_name}:{request.trade_id}: set new client_id")
|
|
166
168
|
try:
|
|
167
169
|
open_client = OpenClient(request.account_name)
|
|
168
170
|
client_id = id(open_client)
|
|
@@ -183,14 +185,16 @@ class Martin(mr.MartinBase):
|
|
|
183
185
|
raise GRPCError(status=Status.FAILED_PRECONDITION, message=str(ex))
|
|
184
186
|
|
|
185
187
|
try:
|
|
186
|
-
await open_client.client.load(request.symbol)
|
|
188
|
+
await asyncio.wait_for(open_client.client.load(request.symbol), timeout=HEARTBEAT * 60)
|
|
187
189
|
except asyncio.CancelledError:
|
|
188
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")
|
|
189
195
|
except Exception as ex:
|
|
190
196
|
logger.warning(f"OpenClientConnection for '{open_client.name}' exception: {ex}")
|
|
191
197
|
logger.debug(f"Exception traceback: {traceback.format_exc()}")
|
|
192
|
-
await OpenClient.get_client(client_id).client.session.close()
|
|
193
|
-
OpenClient.remove_client(request.account_name)
|
|
194
198
|
raise GRPCError(status=Status.RESOURCE_EXHAUSTED, message=str(ex))
|
|
195
199
|
|
|
196
200
|
# Set rate_limiter
|
|
@@ -849,7 +853,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
849
853
|
for oc in OpenClient.open_clients:
|
|
850
854
|
await oc.client.session.close()
|
|
851
855
|
|
|
852
|
-
[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()]
|
|
853
857
|
|
|
854
858
|
|
|
855
859
|
def main():
|
|
@@ -374,7 +374,12 @@ def on_trade_update(res: dict) -> dict:
|
|
|
374
374
|
last_executed_price = res.get("execPrice", res.get("avgPrice"))
|
|
375
375
|
last_quote_asset_transacted = res.get("execValue", cumulative_quote_asset)
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
if leaves_qty == order_quantity:
|
|
378
|
+
status = 'NEW'
|
|
379
|
+
elif leaves_qty > 0:
|
|
380
|
+
status = 'PARTIALLY_FILLED'
|
|
381
|
+
else:
|
|
382
|
+
status = 'FILLED'
|
|
378
383
|
|
|
379
384
|
return {
|
|
380
385
|
"e": "executionReport",
|
|
@@ -10,10 +10,10 @@ from datetime import datetime, timezone
|
|
|
10
10
|
from urllib.parse import urlencode, urlparse
|
|
11
11
|
import websockets
|
|
12
12
|
|
|
13
|
-
import exchanges_wrapper.bitfinex_parser as bfx
|
|
14
|
-
import exchanges_wrapper.huobi_parser as hbp
|
|
15
|
-
import exchanges_wrapper.okx_parser as okx
|
|
16
|
-
import exchanges_wrapper.bybit_parser as bbt
|
|
13
|
+
import exchanges_wrapper.parsers.bitfinex_parser as bfx
|
|
14
|
+
import exchanges_wrapper.parsers.huobi_parser as hbp
|
|
15
|
+
import exchanges_wrapper.parsers.okx_parser as okx
|
|
16
|
+
import exchanges_wrapper.parsers.bybit_parser as bbt
|
|
17
17
|
from crypto_ws_api.ws_session import generate_signature
|
|
18
18
|
from exchanges_wrapper import LOG_PATH
|
|
19
19
|
|
|
@@ -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.7 → 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|