exchanges-wrapper 2.1.10__tar.gz → 2.1.12__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.10 → exchanges_wrapper-2.1.12}/PKG-INFO +7 -2
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/README.md +5 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/client.py +4 -16
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/exch_srv.py +47 -36
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/http_client.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/bybit_parser.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/proto/martin.proto +0 -0
- {exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/web_sockets.py +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.12
|
|
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.11
|
|
15
15
|
Requires-Dist: grpcio==1.62.0
|
|
16
16
|
Requires-Dist: pyotp~=2.9.0
|
|
17
17
|
Requires-Dist: simplejson==3.19.2
|
|
@@ -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.12"
|
|
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,12 +232,19 @@ 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
|
-
res = await getattr(client, client_method_name)(**kwargs)
|
|
237
|
-
except
|
|
240
|
+
res = await asyncio.wait_for(getattr(client, client_method_name)(**kwargs), timeout=HEARTBEAT * 60)
|
|
241
|
+
except asyncio.exceptions.CancelledError:
|
|
238
242
|
msg = f"{msg_header} Server Shutdown"
|
|
239
243
|
raise GRPCError(status=Status.UNAVAILABLE, message=msg)
|
|
244
|
+
except asyncio.exceptions.TimeoutError:
|
|
245
|
+
msg = f"{msg_header} timeout error"
|
|
246
|
+
logger.warning(msg)
|
|
247
|
+
raise GRPCError(status=Status.RESOURCE_EXHAUSTED, message=msg)
|
|
240
248
|
except (errors.RateLimitReached, errors.QueryCanceled) as ex:
|
|
241
249
|
Martin.rate_limit_reached_time = time.time()
|
|
242
250
|
msg = f"{msg_header} RateLimitReached: {ex}"
|
|
@@ -259,6 +267,8 @@ class Martin(mr.MartinBase):
|
|
|
259
267
|
if rate_limit:
|
|
260
268
|
open_client.ts_rlc = time.time()
|
|
261
269
|
return res, client, msg_header
|
|
270
|
+
finally:
|
|
271
|
+
client.request_event.set()
|
|
262
272
|
|
|
263
273
|
async def fetch_server_time(self, request: mr.OpenClientConnectionId) -> mr.FetchServerTimeResponse:
|
|
264
274
|
res, _, _ = await self.send_request('fetch_server_time', request, rate_limit=True)
|
|
@@ -428,36 +438,39 @@ class Martin(mr.MartinBase):
|
|
|
428
438
|
return response
|
|
429
439
|
|
|
430
440
|
async def fetch_order_book(self, request: mr.MarketRequest) -> mr.FetchOrderBookResponse:
|
|
431
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
432
|
-
client = open_client.client
|
|
433
441
|
response = mr.FetchOrderBookResponse()
|
|
434
|
-
await self.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
442
|
+
res, _, _ = await self.send_request(
|
|
443
|
+
'fetch_order_book',
|
|
444
|
+
request,
|
|
445
|
+
rate_limit=True,
|
|
446
|
+
symbol=request.symbol
|
|
447
|
+
)
|
|
448
|
+
|
|
438
449
|
res['bids'] = [json.dumps(v) for v in res.get('bids', [])]
|
|
439
450
|
res['asks'] = [json.dumps(v) for v in res.get('asks', [])]
|
|
440
451
|
return response.from_pydict(res)
|
|
441
452
|
|
|
442
453
|
async def fetch_symbol_price_ticker(self, request: mr.MarketRequest) -> mr.FetchSymbolPriceTickerResponse:
|
|
443
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
444
|
-
client = open_client.client
|
|
445
454
|
response = mr.FetchSymbolPriceTickerResponse()
|
|
446
|
-
await self.
|
|
447
|
-
|
|
448
|
-
|
|
455
|
+
res, _, _ = await self.send_request(
|
|
456
|
+
'fetch_symbol_price_ticker',
|
|
457
|
+
request,
|
|
458
|
+
rate_limit=True,
|
|
459
|
+
symbol=request.symbol
|
|
460
|
+
)
|
|
449
461
|
return response.from_pydict(res)
|
|
450
462
|
|
|
451
463
|
async def fetch_ticker_price_change_statistics(
|
|
452
464
|
self,
|
|
453
465
|
request: mr.MarketRequest
|
|
454
466
|
) -> mr.FetchTickerPriceChangeStatisticsResponse:
|
|
455
|
-
open_client = OpenClient.get_client(request.client_id)
|
|
456
|
-
client = open_client.client
|
|
457
467
|
response = mr.FetchTickerPriceChangeStatisticsResponse()
|
|
458
|
-
await self.
|
|
459
|
-
|
|
460
|
-
|
|
468
|
+
res, _, _ = await self.send_request(
|
|
469
|
+
'fetch_ticker_price_change_statistics',
|
|
470
|
+
request,
|
|
471
|
+
rate_limit=True,
|
|
472
|
+
symbol=request.symbol
|
|
473
|
+
)
|
|
461
474
|
return response.from_pydict(res)
|
|
462
475
|
|
|
463
476
|
async def fetch_klines(self, request: mr.FetchKlinesRequest) -> mr.JsonResponse:
|
|
@@ -663,15 +676,13 @@ class Martin(mr.MartinBase):
|
|
|
663
676
|
_get_event_from_queue = False
|
|
664
677
|
|
|
665
678
|
if client.exchange in ('bitfinex', 'huobi', 'bybit'):
|
|
666
|
-
await self.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
open_client.ts_rlc = time.time()
|
|
674
|
-
[_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]
|
|
675
686
|
|
|
676
687
|
for _event in _events:
|
|
677
688
|
if _event.asset in request.symbol:
|
|
@@ -847,7 +858,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
847
858
|
server = Server([Martin()])
|
|
848
859
|
with graceful_exit([server]):
|
|
849
860
|
await server.start(host, port)
|
|
850
|
-
logger.info(f"Starting server v:{
|
|
861
|
+
logger.info(f"Starting server v:{VER_CW}:{VER_EW} on {host}:{port}")
|
|
851
862
|
await server.wait_closed()
|
|
852
863
|
|
|
853
864
|
for oc in OpenClient.open_clients:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/exch_srv_cfg.toml.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/bitfinex_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/bybit_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/huobi_parser.py
RENAMED
|
File without changes
|
{exchanges_wrapper-2.1.10 → exchanges_wrapper-2.1.12}/exchanges_wrapper/parsers/okx_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|