diff --git a/.run/exchanges-wrapper-image.run.xml b/.run/exchanges-wrapper-image.run.xml
new file mode 100644
index 0000000..1639b9f
--- /dev/null
+++ b/.run/exchanges-wrapper-image.run.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/exchanges-wrapper.run.xml b/.run/exchanges-wrapper.run.xml
new file mode 100644
index 0000000..81abc30
--- /dev/null
+++ b/.run/exchanges-wrapper.run.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fba887b..64b2a92 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
-## 2.1.11.post1 2024-04-xx
+## 2.1.12 2024-04-30
+### Fix
+* `Docker`: [#49 ImportError: cannot import name 'version' from 'exchanges_wrapper'](https://github.com/DogsTailFarmer/exchanges-wrapper/issues/49#issue-2272432093)
+
### Update
* `send_request`: controlling rate_limit by changing exception handling
+* `Bitfinex`: sync `nonce` for connections group
+* Dependency: Up requirements for crypto-ws-api==2.0.11
## 2.1.11 2024-04-19
### Update
diff --git a/Dockerfile b/Dockerfile
index c947f71..a220949 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@ COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
-COPY ./exchanges_wrapper/* /home/appuser/.local/lib/python3.10/site-packages/exchanges_wrapper/
+COPY ./exchanges_wrapper /home/appuser/.local/lib/python3.10/site-packages/exchanges_wrapper/
WORKDIR "/home/appuser/.local/lib/python3.10/site-packages"
diff --git a/README.md b/README.md
index ec915f9..6b9aa0c 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,11 @@ pip install -U exchanges-wrapper
#### Start server
* Run in terminal window
+ ```
+ exchanges-wrapper-init
+ ```
+and
+
```
exchanges-wrapper-srv
```
diff --git a/exchanges_wrapper/__init__.py b/exchanges_wrapper/__init__.py
index 44d5f18..ef188f3 100755
--- a/exchanges_wrapper/__init__.py
+++ b/exchanges_wrapper/__init__.py
@@ -12,7 +12,7 @@
__contact__ = "https://github.com/DogsTailFarmer"
__email__ = "jerry.fedorenko@yahoo.com"
__credits__ = ["https://github.com/DanyaSWorlD"]
-__version__ = "2.1.11.post1"
+__version__ = "2.1.12"
from pathlib import Path
import shutil
diff --git a/exchanges_wrapper/client.py b/exchanges_wrapper/client.py
index 0db105f..d08b53a 100644
--- a/exchanges_wrapper/client.py
+++ b/exchanges_wrapper/client.py
@@ -109,6 +109,8 @@ def __init__(self, *acc):
self.ledgers_id = []
self.ts_start = {}
self.tasks = set()
+ self.request_event = asyncio.Event()
+ self.request_event.set()
def tasks_manage(self, coro):
_t = asyncio.create_task(coro)
@@ -377,24 +379,10 @@ async def fetch_exchange_info(self, symbol):
# MARKET DATA ENDPOINTS
# https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
- async def fetch_order_book(self, symbol, precision='P0', limit=100):
+ async def fetch_order_book(self, symbol, precision='P0'):
self.assert_symbol(symbol)
- valid_limits = []
- if self.exchange == 'binance':
- valid_limits = [5, 10, 20, 50, 100, 500, 1000, 5000]
- elif self.exchange == 'bitfinex':
- valid_limits = [1, 25, 100]
- elif self.exchange == 'huobi':
- valid_limits = [5, 10, 20]
- elif self.exchange == 'okx':
- valid_limits = [1, 5, 10, 20, 50, 100, 400]
- elif self.exchange == 'bybit':
- valid_limits = range(1, 51)
+ limit = 1 if self.exchange in ('bitfinex', 'okx', 'bybit') else 5
binance_res = {}
- if limit not in valid_limits:
- raise ValueError(
- f"{limit} is not a valid limit. Valid limits: {valid_limits}"
- )
if self.exchange == 'binance':
binance_res = await self.http.send_api_call(
"/api/v3/depth",
diff --git a/exchanges_wrapper/exch_srv.py b/exchanges_wrapper/exch_srv.py
index f45f32a..0357272 100755
--- a/exchanges_wrapper/exch_srv.py
+++ b/exchanges_wrapper/exch_srv.py
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+
import grpclib.exceptions
-from exchanges_wrapper import __version__ as ver_ew
-from crypto_ws_api import __version__ as ver_cw
+from exchanges_wrapper import __version__ as VER_EW
+from crypto_ws_api import __version__ as VER_CW
import time
import weakref
import gc
@@ -148,10 +149,10 @@ class Martin(mr.MartinBase):
rate_limit_reached_time = None
rate_limiter = None
- async def rate_limit_control(self, client, _call_from='default'):
- if client.client.exchange == 'bitfinex':
- rate_limit_interval = REST_RATE_LIMIT_INTERVAL.get(client.client.exchange, {}).get(_call_from, 0)
- ts_diff = time.time() - client.ts_rlc
+ async def rate_limit_control(self, exchange, ts_rlc, _call_from='default'):
+ if exchange == 'bitfinex':
+ rate_limit_interval = REST_RATE_LIMIT_INTERVAL.get(exchange, {}).get(_call_from, 0)
+ ts_diff = time.time() - ts_rlc
if ts_diff < rate_limit_interval:
sleep_duration = rate_limit_interval - ts_diff
await asyncio.sleep(sleep_duration)
@@ -201,7 +202,7 @@ async def open_client_connection(self, request: mr.OpenClientConnectionRequest)
Martin.rate_limiter = max(Martin.rate_limiter or 0, request.rate_limiter)
return mr.OpenClientConnectionId(
client_id=client_id,
- srv_version=f"{ver_cw}:{ver_ew}",
+ srv_version=f"{VER_CW}:{VER_EW}",
exchange=open_client.client.exchange,
real_market=open_client.real_market
)
@@ -231,7 +232,10 @@ async def send_request(self, client_method_name, request, rate_limit=False, **kw
if hasattr(request, 'client_order_id'):
msg_header += f"({request.client_order_id}):"
if rate_limit:
- await self.rate_limit_control(open_client)
+ await self.rate_limit_control(client.exchange, open_client.ts_rlc)
+ if client.exchange == 'bitfinex':
+ await client.request_event.wait()
+ client.request_event.clear()
try:
res = await asyncio.wait_for(getattr(client, client_method_name)(**kwargs), timeout=HEARTBEAT * 60)
except asyncio.exceptions.CancelledError:
@@ -263,6 +267,8 @@ async def send_request(self, client_method_name, request, rate_limit=False, **kw
if rate_limit:
open_client.ts_rlc = time.time()
return res, client, msg_header
+ finally:
+ client.request_event.set()
async def fetch_server_time(self, request: mr.OpenClientConnectionId) -> mr.FetchServerTimeResponse:
res, _, _ = await self.send_request('fetch_server_time', request, rate_limit=True)
@@ -432,36 +438,39 @@ async def fetch_funding_wallet(self, request: mr.FetchFundingWalletRequest) -> m
return response
async def fetch_order_book(self, request: mr.MarketRequest) -> mr.FetchOrderBookResponse:
- open_client = OpenClient.get_client(request.client_id)
- client = open_client.client
response = mr.FetchOrderBookResponse()
- await self.rate_limit_control(open_client)
- limit = 1 if client.exchange in ('bitfinex', 'okx') else 5
- res = await client.fetch_order_book(symbol=request.symbol, limit=limit)
- open_client.ts_rlc = time.time()
+ res, _, _ = await self.send_request(
+ 'fetch_order_book',
+ request,
+ rate_limit=True,
+ symbol=request.symbol
+ )
+
res['bids'] = [json.dumps(v) for v in res.get('bids', [])]
res['asks'] = [json.dumps(v) for v in res.get('asks', [])]
return response.from_pydict(res)
async def fetch_symbol_price_ticker(self, request: mr.MarketRequest) -> mr.FetchSymbolPriceTickerResponse:
- open_client = OpenClient.get_client(request.client_id)
- client = open_client.client
response = mr.FetchSymbolPriceTickerResponse()
- await self.rate_limit_control(open_client)
- res = await client.fetch_symbol_price_ticker(symbol=request.symbol)
- open_client.ts_rlc = time.time()
+ res, _, _ = await self.send_request(
+ 'fetch_symbol_price_ticker',
+ request,
+ rate_limit=True,
+ symbol=request.symbol
+ )
return response.from_pydict(res)
async def fetch_ticker_price_change_statistics(
self,
request: mr.MarketRequest
) -> mr.FetchTickerPriceChangeStatisticsResponse:
- open_client = OpenClient.get_client(request.client_id)
- client = open_client.client
response = mr.FetchTickerPriceChangeStatisticsResponse()
- await self.rate_limit_control(open_client)
- res = await client.fetch_ticker_price_change_statistics(symbol=request.symbol)
- open_client.ts_rlc = time.time()
+ res, _, _ = await self.send_request(
+ 'fetch_ticker_price_change_statistics',
+ request,
+ rate_limit=True,
+ symbol=request.symbol
+ )
return response.from_pydict(res)
async def fetch_klines(self, request: mr.FetchKlinesRequest) -> mr.JsonResponse:
@@ -667,15 +676,13 @@ async def on_balance_update(self, request: mr.MarketRequest) -> mr.StreamRespons
_get_event_from_queue = False
if client.exchange in ('bitfinex', 'huobi', 'bybit'):
- await self.rate_limit_control(open_client)
- try:
- balances = await client.fetch_ledgers(request.symbol)
- except Exception as _ex:
- logger.warning(f"OnBalanceUpdate: for {open_client.name}:{request.symbol}: {_ex}")
- logger.debug(f"OnBalanceUpdate: {traceback.format_exc()}")
- else:
- open_client.ts_rlc = time.time()
- [_events.append(client.events.wrap_event(balance)) for balance in balances]
+ balances, _, _ = await self.send_request(
+ 'fetch_ledgers',
+ request,
+ rate_limit=True,
+ symbol=request.symbol
+ )
+ [_events.append(client.events.wrap_event(balance)) for balance in balances]
for _event in _events:
if _event.asset in request.symbol:
@@ -851,7 +858,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
server = Server([Martin()])
with graceful_exit([server]):
await server.start(host, port)
- logger.info(f"Starting server v:{ver_cw}:{ver_ew} on {host}:{port}")
+ logger.info(f"Starting server v:{VER_CW}:{VER_EW} on {host}:{port}")
await server.wait_closed()
for oc in OpenClient.open_clients:
diff --git a/pyproject.toml b/pyproject.toml
index b3db409..0611ed5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,7 @@ dynamic = ["version", "description"]
requires-python = ">=3.9"
dependencies = [
- "crypto-ws-api==2.0.10",
+ "crypto-ws-api==2.0.11",
"grpcio==1.62.0",
"pyotp~=2.9.0",
"simplejson==3.19.2",
diff --git a/requirements.txt b/requirements.txt
index 9675089..8de6164 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-crypto-ws-api==2.0.10
+crypto-ws-api==2.0.11
pyotp==2.9.0
simplejson==3.19.2
toml~=0.10.2