exchanges-wrapper 2.1.11__tar.gz → 2.1.13__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.11 → exchanges_wrapper-2.1.13}/PKG-INFO +9 -4
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/README.md +5 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/client.py +4 -16
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/exch_srv.py +42 -35
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/web_sockets.py +15 -16
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/pyproject.toml +3 -3
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/http_client.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/bybit_parser.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/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.13
|
|
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,15 +11,15 @@ 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.12
|
|
15
15
|
Requires-Dist: grpcio==1.62.0
|
|
16
16
|
Requires-Dist: pyotp~=2.9.0
|
|
17
17
|
Requires-Dist: simplejson==3.19.2
|
|
18
|
-
Requires-Dist: aiohttp==3.9.
|
|
18
|
+
Requires-Dist: aiohttp==3.9.5
|
|
19
19
|
Requires-Dist: Pympler~=1.0.1
|
|
20
20
|
Requires-Dist: websockets~=12.0
|
|
21
21
|
Requires-Dist: expiringdict~=1.2.2
|
|
22
|
-
Requires-Dist: ujson~=5.
|
|
22
|
+
Requires-Dist: ujson~=5.10.0
|
|
23
23
|
Requires-Dist: betterproto==2.0.0b6
|
|
24
24
|
Requires-Dist: grpclib~=0.4.7
|
|
25
25
|
Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
|
|
@@ -108,6 +108,11 @@ pip install -U exchanges-wrapper
|
|
|
108
108
|
|
|
109
109
|
#### Start server
|
|
110
110
|
* Run in terminal window
|
|
111
|
+
```
|
|
112
|
+
exchanges-wrapper-init
|
|
113
|
+
```
|
|
114
|
+
and
|
|
115
|
+
|
|
111
116
|
```
|
|
112
117
|
exchanges-wrapper-srv
|
|
113
118
|
```
|
|
@@ -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.13"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -109,6 +109,8 @@ class Client:
|
|
|
109
109
|
self.ledgers_id = []
|
|
110
110
|
self.ts_start = {}
|
|
111
111
|
self.tasks = set()
|
|
112
|
+
self.request_event = asyncio.Event()
|
|
113
|
+
self.request_event.set()
|
|
112
114
|
|
|
113
115
|
def tasks_manage(self, coro):
|
|
114
116
|
_t = asyncio.create_task(coro)
|
|
@@ -377,24 +379,10 @@ class Client:
|
|
|
377
379
|
# MARKET DATA ENDPOINTS
|
|
378
380
|
|
|
379
381
|
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
|
|
380
|
-
async def fetch_order_book(self, symbol, precision='P0'
|
|
382
|
+
async def fetch_order_book(self, symbol, precision='P0'):
|
|
381
383
|
self.assert_symbol(symbol)
|
|
382
|
-
|
|
383
|
-
if self.exchange == 'binance':
|
|
384
|
-
valid_limits = [5, 10, 20, 50, 100, 500, 1000, 5000]
|
|
385
|
-
elif self.exchange == 'bitfinex':
|
|
386
|
-
valid_limits = [1, 25, 100]
|
|
387
|
-
elif self.exchange == 'huobi':
|
|
388
|
-
valid_limits = [5, 10, 20]
|
|
389
|
-
elif self.exchange == 'okx':
|
|
390
|
-
valid_limits = [1, 5, 10, 20, 50, 100, 400]
|
|
391
|
-
elif self.exchange == 'bybit':
|
|
392
|
-
valid_limits = range(1, 51)
|
|
384
|
+
limit = 1 if self.exchange in ('bitfinex', 'okx', 'bybit') else 5
|
|
393
385
|
binance_res = {}
|
|
394
|
-
if limit not in valid_limits:
|
|
395
|
-
raise ValueError(
|
|
396
|
-
f"{limit} is not a valid limit. Valid limits: {valid_limits}"
|
|
397
|
-
)
|
|
398
386
|
if self.exchange == 'binance':
|
|
399
387
|
binance_res = await self.http.send_api_call(
|
|
400
388
|
"/api/v3/depth",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
3
4
|
import grpclib.exceptions
|
|
4
5
|
|
|
5
|
-
from exchanges_wrapper import __version__ as
|
|
6
|
-
from crypto_ws_api import __version__ as
|
|
6
|
+
from exchanges_wrapper import __version__ as VER_EW
|
|
7
|
+
from crypto_ws_api import __version__ as VER_CW
|
|
7
8
|
import time
|
|
8
9
|
import weakref
|
|
9
10
|
import gc
|
|
@@ -148,10 +149,10 @@ class Martin(mr.MartinBase):
|
|
|
148
149
|
rate_limit_reached_time = None
|
|
149
150
|
rate_limiter = None
|
|
150
151
|
|
|
151
|
-
async def rate_limit_control(self,
|
|
152
|
-
if
|
|
153
|
-
rate_limit_interval = REST_RATE_LIMIT_INTERVAL.get(
|
|
154
|
-
ts_diff = time.time() -
|
|
152
|
+
async def rate_limit_control(self, exchange, ts_rlc, _call_from='default'):
|
|
153
|
+
if exchange == 'bitfinex':
|
|
154
|
+
rate_limit_interval = REST_RATE_LIMIT_INTERVAL.get(exchange, {}).get(_call_from, 0)
|
|
155
|
+
ts_diff = time.time() - ts_rlc
|
|
155
156
|
if ts_diff < rate_limit_interval:
|
|
156
157
|
sleep_duration = rate_limit_interval - ts_diff
|
|
157
158
|
await asyncio.sleep(sleep_duration)
|
|
@@ -201,7 +202,7 @@ class Martin(mr.MartinBase):
|
|
|
201
202
|
Martin.rate_limiter = max(Martin.rate_limiter or 0, request.rate_limiter)
|
|
202
203
|
return mr.OpenClientConnectionId(
|
|
203
204
|
client_id=client_id,
|
|
204
|
-
srv_version=f"{
|
|
205
|
+
srv_version=f"{VER_CW}:{VER_EW}",
|
|
205
206
|
exchange=open_client.client.exchange,
|
|
206
207
|
real_market=open_client.real_market
|
|
207
208
|
)
|
|
@@ -231,7 +232,10 @@ class Martin(mr.MartinBase):
|
|
|
231
232
|
if hasattr(request, 'client_order_id'):
|
|
232
233
|
msg_header += f"({request.client_order_id}):"
|
|
233
234
|
if rate_limit:
|
|
234
|
-
await self.rate_limit_control(open_client)
|
|
235
|
+
await self.rate_limit_control(client.exchange, open_client.ts_rlc)
|
|
236
|
+
if client.exchange == 'bitfinex':
|
|
237
|
+
await client.request_event.wait()
|
|
238
|
+
client.request_event.clear()
|
|
235
239
|
try:
|
|
236
240
|
res = await asyncio.wait_for(getattr(client, client_method_name)(**kwargs), timeout=HEARTBEAT * 60)
|
|
237
241
|
except asyncio.exceptions.CancelledError:
|
|
@@ -240,7 +244,7 @@ class Martin(mr.MartinBase):
|
|
|
240
244
|
except asyncio.exceptions.TimeoutError:
|
|
241
245
|
msg = f"{msg_header} timeout error"
|
|
242
246
|
logger.warning(msg)
|
|
243
|
-
raise GRPCError(status=Status.
|
|
247
|
+
raise GRPCError(status=Status.RESOURCE_EXHAUSTED, message=msg)
|
|
244
248
|
except (errors.RateLimitReached, errors.QueryCanceled) as ex:
|
|
245
249
|
Martin.rate_limit_reached_time = time.time()
|
|
246
250
|
msg = f"{msg_header} RateLimitReached: {ex}"
|
|
@@ -263,6 +267,8 @@ class Martin(mr.MartinBase):
|
|
|
263
267
|
if rate_limit:
|
|
264
268
|
open_client.ts_rlc = time.time()
|
|
265
269
|
return res, client, msg_header
|
|
270
|
+
finally:
|
|
271
|
+
client.request_event.set()
|
|
266
272
|
|
|
267
273
|
async def fetch_server_time(self, request: mr.OpenClientConnectionId) -> mr.FetchServerTimeResponse:
|
|
268
274
|
res, _, _ = await self.send_request('fetch_server_time', request, rate_limit=True)
|
|
@@ -432,36 +438,39 @@ class Martin(mr.MartinBase):
|
|
|
432
438
|
return response
|
|
433
439
|
|
|
434
440
|
async def fetch_order_book(self, request: mr.MarketRequest) -> mr.FetchOrderBookResponse:
|
|
435
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
436
|
-
client = open_client.client
|
|
437
441
|
response = mr.FetchOrderBookResponse()
|
|
438
|
-
await self.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
+
res, _, _ = await self.send_request(
|
|
443
|
+
'fetch_order_book',
|
|
444
|
+
request,
|
|
445
|
+
rate_limit=True,
|
|
446
|
+
symbol=request.symbol
|
|
447
|
+
)
|
|
448
|
+
|
|
442
449
|
res['bids'] = [json.dumps(v) for v in res.get('bids', [])]
|
|
443
450
|
res['asks'] = [json.dumps(v) for v in res.get('asks', [])]
|
|
444
451
|
return response.from_pydict(res)
|
|
445
452
|
|
|
446
453
|
async def fetch_symbol_price_ticker(self, request: mr.MarketRequest) -> mr.FetchSymbolPriceTickerResponse:
|
|
447
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
448
|
-
client = open_client.client
|
|
449
454
|
response = mr.FetchSymbolPriceTickerResponse()
|
|
450
|
-
await self.
|
|
451
|
-
|
|
452
|
-
|
|
455
|
+
res, _, _ = await self.send_request(
|
|
456
|
+
'fetch_symbol_price_ticker',
|
|
457
|
+
request,
|
|
458
|
+
rate_limit=True,
|
|
459
|
+
symbol=request.symbol
|
|
460
|
+
)
|
|
453
461
|
return response.from_pydict(res)
|
|
454
462
|
|
|
455
463
|
async def fetch_ticker_price_change_statistics(
|
|
456
464
|
self,
|
|
457
465
|
request: mr.MarketRequest
|
|
458
466
|
) -> mr.FetchTickerPriceChangeStatisticsResponse:
|
|
459
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
460
|
-
client = open_client.client
|
|
461
467
|
response = mr.FetchTickerPriceChangeStatisticsResponse()
|
|
462
|
-
await self.
|
|
463
|
-
|
|
464
|
-
|
|
468
|
+
res, _, _ = await self.send_request(
|
|
469
|
+
'fetch_ticker_price_change_statistics',
|
|
470
|
+
request,
|
|
471
|
+
rate_limit=True,
|
|
472
|
+
symbol=request.symbol
|
|
473
|
+
)
|
|
465
474
|
return response.from_pydict(res)
|
|
466
475
|
|
|
467
476
|
async def fetch_klines(self, request: mr.FetchKlinesRequest) -> mr.JsonResponse:
|
|
@@ -667,15 +676,13 @@ class Martin(mr.MartinBase):
|
|
|
667
676
|
_get_event_from_queue = False
|
|
668
677
|
|
|
669
678
|
if client.exchange in ('bitfinex', 'huobi', 'bybit'):
|
|
670
|
-
await self.
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
open_client.ts_rlc = time.time()
|
|
678
|
-
[_events.append(client.events.wrap_event(balance)) for balance in balances]
|
|
679
|
+
balances, _, _ = await self.send_request(
|
|
680
|
+
'fetch_ledgers',
|
|
681
|
+
request,
|
|
682
|
+
rate_limit=True,
|
|
683
|
+
symbol=request.symbol
|
|
684
|
+
)
|
|
685
|
+
[_events.append(client.events.wrap_event(balance)) for balance in balances]
|
|
679
686
|
|
|
680
687
|
for _event in _events:
|
|
681
688
|
if _event.asset in request.symbol:
|
|
@@ -851,7 +858,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
851
858
|
server = Server([Martin()])
|
|
852
859
|
with graceful_exit([server]):
|
|
853
860
|
await server.start(host, port)
|
|
854
|
-
logger.info(f"Starting server v:{
|
|
861
|
+
logger.info(f"Starting server v:{VER_CW}:{VER_EW} on {host}:{port}")
|
|
855
862
|
await server.wait_closed()
|
|
856
863
|
|
|
857
864
|
for oc in OpenClient.open_clients:
|
|
@@ -408,23 +408,22 @@ class HbpPrivateEventsDataStream(EventsDataStream):
|
|
|
408
408
|
|
|
409
409
|
async def _handle_event(self, msg_data, *args):
|
|
410
410
|
content = None
|
|
411
|
+
_ch = msg_data['ch']
|
|
411
412
|
_data = msg_data.get('data')
|
|
412
|
-
if _data.get('
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
self.client.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
self.client.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
self.client.active_orders[order_id]['executedQty'] += Decimal(_data['tradeVolume'])
|
|
427
|
-
content = hbp.on_order_update(self.client.active_orders[order_id])
|
|
413
|
+
if _ch == 'accounts.update#2' and _data.get('currency') in self.symbol.lower():
|
|
414
|
+
content = hbp.on_funds_update(_data)
|
|
415
|
+
elif _ch == f"orders#{self.symbol.lower()}" and _data['eventType'] in ('creation', 'cancellation'):
|
|
416
|
+
order_id = _data['orderId']
|
|
417
|
+
self.client.active_order(order_id, quantity=_data['orderSize'])
|
|
418
|
+
if _data.get('eventType') == 'cancellation':
|
|
419
|
+
self.client.active_orders[order_id]['cancelled'] = True
|
|
420
|
+
elif _ch == f"trade.clearing#{self.symbol.lower()}#0":
|
|
421
|
+
order_id = _data['orderId']
|
|
422
|
+
self.client.active_order(order_id, last_event=_data)
|
|
423
|
+
if _data['tradeId'] not in self.client.active_orders[order_id]["eventIds"]:
|
|
424
|
+
self.client.active_orders[order_id]["eventIds"].append(_data['tradeId'])
|
|
425
|
+
self.client.active_orders[order_id]['executedQty'] += Decimal(_data['tradeVolume'])
|
|
426
|
+
content = hbp.on_order_update(self.client.active_orders[order_id])
|
|
428
427
|
|
|
429
428
|
if content:
|
|
430
429
|
logger.debug(f"HTXPrivateEvents.content: {content}")
|
|
@@ -17,15 +17,15 @@ dynamic = ["version", "description"]
|
|
|
17
17
|
requires-python = ">=3.9"
|
|
18
18
|
|
|
19
19
|
dependencies = [
|
|
20
|
-
"crypto-ws-api==2.0.
|
|
20
|
+
"crypto-ws-api==2.0.12",
|
|
21
21
|
"grpcio==1.62.0",
|
|
22
22
|
"pyotp~=2.9.0",
|
|
23
23
|
"simplejson==3.19.2",
|
|
24
|
-
"aiohttp==3.9.
|
|
24
|
+
"aiohttp==3.9.5",
|
|
25
25
|
"Pympler~=1.0.1",
|
|
26
26
|
"websockets~=12.0",
|
|
27
27
|
"expiringdict~=1.2.2",
|
|
28
|
-
"ujson~=5.
|
|
28
|
+
"ujson~=5.10.0",
|
|
29
29
|
"betterproto==2.0.0b6",
|
|
30
30
|
"grpclib~=0.4.7"
|
|
31
31
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/bitfinex_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/bybit_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/huobi_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.11 → exchanges_wrapper-2.1.13}/exchanges_wrapper/parsers/okx_parser.py
RENAMED
|
File without changes
|
|
File without changes
|