exchanges-wrapper 2.1.3__tar.gz → 2.1.5__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.3 → exchanges_wrapper-2.1.5}/PKG-INFO +2 -2
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/__init__.py +1 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/bybit_parser.py +3 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/client.py +0 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/exch_srv.py +12 -10
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/http_client.py +29 -6
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/pyproject.toml +1 -1
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/LICENSE.md +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/README.md +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/bitfinex_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/definitions.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/errors.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/events.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/huobi_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/lib.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/martin/__init__.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/okx_parser.py +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/proto/martin.proto +0 -0
- {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/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.5
|
|
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.7
|
|
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.5"
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import shutil
|
|
@@ -450,7 +450,9 @@ def on_balance_update(data_in: list, ts: str, symbol: str, mode: str, uid=None)
|
|
|
450
450
|
|
|
451
451
|
elif mode == 'universal':
|
|
452
452
|
for i in data_in:
|
|
453
|
-
if i['coin'] in symbol and
|
|
453
|
+
if i['coin'] in symbol and \
|
|
454
|
+
((i['fromAccountType'] == 'UNIFIED' and i['fromMemberId'] == uid)
|
|
455
|
+
or (i['toAccountType'] == 'UNIFIED' and i['toMemberId'] == uid)):
|
|
454
456
|
data_out.append(
|
|
455
457
|
{
|
|
456
458
|
i['transferId']: {
|
|
@@ -1095,7 +1095,6 @@ class Client:
|
|
|
1095
1095
|
binance_res = bfx.order(res[4], response_type=True, cancelled=True)
|
|
1096
1096
|
break
|
|
1097
1097
|
await asyncio.sleep(0.1)
|
|
1098
|
-
logger.debug(f"cancel_order.bitfinex {order_id}: timeout: {timeout}")
|
|
1099
1098
|
else:
|
|
1100
1099
|
logger.warning(f"cancel_order.bitfinex {order_id}: res: {res}")
|
|
1101
1100
|
elif self.exchange == 'huobi':
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
import grpclib.exceptions
|
|
3
4
|
|
|
4
|
-
from exchanges_wrapper import __version__
|
|
5
|
+
from exchanges_wrapper import __version__ as ver_ew
|
|
6
|
+
from crypto_ws_api import __version__ as ver_cw
|
|
5
7
|
import time
|
|
6
8
|
import weakref
|
|
7
9
|
import gc
|
|
@@ -129,9 +131,9 @@ class OpenClient:
|
|
|
129
131
|
)
|
|
130
132
|
|
|
131
133
|
@classmethod
|
|
132
|
-
def get_client(cls, _id
|
|
134
|
+
def get_client(cls, _id):
|
|
133
135
|
res = next((client for client in cls.open_clients if id(client) == _id), None)
|
|
134
|
-
if res is None
|
|
136
|
+
if res is None:
|
|
135
137
|
logger.warning(f"No client exist: {_id}")
|
|
136
138
|
raise GRPCError(status=Status.UNAVAILABLE, message="No client exist")
|
|
137
139
|
return res
|
|
@@ -196,7 +198,7 @@ class Martin(mr.MartinBase):
|
|
|
196
198
|
Martin.rate_limiter = max(Martin.rate_limiter or 0, request.rate_limiter)
|
|
197
199
|
return mr.OpenClientConnectionId(
|
|
198
200
|
client_id=client_id,
|
|
199
|
-
srv_version=
|
|
201
|
+
srv_version=f"{ver_cw}:{ver_ew}",
|
|
200
202
|
exchange=open_client.client.exchange,
|
|
201
203
|
real_market=open_client.real_market
|
|
202
204
|
)
|
|
@@ -229,7 +231,7 @@ class Martin(mr.MartinBase):
|
|
|
229
231
|
await self.rate_limit_control(open_client)
|
|
230
232
|
try:
|
|
231
233
|
res = await getattr(client, client_method_name)(**kwargs)
|
|
232
|
-
except asyncio.CancelledError:
|
|
234
|
+
except (asyncio.CancelledError, asyncio.exceptions.CancelledError):
|
|
233
235
|
pass # Task cancellation should not be logged as an error
|
|
234
236
|
except (errors.RateLimitReached, errors.QueryCanceled) as ex:
|
|
235
237
|
Martin.rate_limit_reached_time = time.time()
|
|
@@ -700,7 +702,7 @@ class Martin(mr.MartinBase):
|
|
|
700
702
|
else:
|
|
701
703
|
event = vars(_event)
|
|
702
704
|
event.pop('handlers', None)
|
|
703
|
-
# logger.
|
|
705
|
+
# logger.info(f"OnOrderUpdate: {open_client.name}: {event}")
|
|
704
706
|
response.success = True
|
|
705
707
|
response.result = json.dumps(event)
|
|
706
708
|
yield response
|
|
@@ -800,11 +802,11 @@ class Martin(mr.MartinBase):
|
|
|
800
802
|
|
|
801
803
|
async def check_stream(self, request: mr.MarketRequest) -> mr.SimpleResponse:
|
|
802
804
|
response = mr.SimpleResponse()
|
|
803
|
-
|
|
805
|
+
response.success = False
|
|
806
|
+
if open_client := OpenClient.get_client(request.client_id):
|
|
804
807
|
client = open_client.client
|
|
805
808
|
response.success = bool(client.data_streams.get(request.trade_id))
|
|
806
809
|
else:
|
|
807
|
-
response.success = False
|
|
808
810
|
logger.warning(f"CheckStream request failed for {request.symbol}")
|
|
809
811
|
return response
|
|
810
812
|
|
|
@@ -841,7 +843,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
841
843
|
server = Server([Martin()])
|
|
842
844
|
with graceful_exit([server]):
|
|
843
845
|
await server.start(host, port)
|
|
844
|
-
logger.info(f"Starting server v:{
|
|
846
|
+
logger.info(f"Starting server v:{ver_cw}:{ver_ew} on {host}:{port}")
|
|
845
847
|
await server.wait_closed()
|
|
846
848
|
|
|
847
849
|
for oc in OpenClient.open_clients:
|
|
@@ -853,7 +855,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
|
|
|
853
855
|
def main():
|
|
854
856
|
try:
|
|
855
857
|
asyncio.run(amain())
|
|
856
|
-
except asyncio.exceptions.CancelledError:
|
|
858
|
+
except (asyncio.exceptions.CancelledError, grpclib.exceptions.StreamTerminatedError):
|
|
857
859
|
pass # # Task cancellation should not be logged as an error
|
|
858
860
|
except Exception as ex:
|
|
859
861
|
print(f"Exception: {ex}")
|
|
@@ -18,9 +18,15 @@ from exchanges_wrapper.errors import (
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
AJ = 'application/json'
|
|
21
|
+
STATUS_BAD_REQUEST = 400
|
|
22
|
+
STATUS_UNAUTHORIZED = 401
|
|
23
|
+
STATUS_FORBIDDEN = 403
|
|
24
|
+
STATUS_I_AM_A_TEAPOT = 418 # HTTP status code for IPAddressBanned
|
|
25
|
+
ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW = "Timestamp for this request is outside of the recvWindow"
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
class HttpClient:
|
|
29
|
+
|
|
24
30
|
def __init__(self, **kwargs):
|
|
25
31
|
self.api_key = kwargs.get('api_key')
|
|
26
32
|
self.api_secret = kwargs.get('api_secret')
|
|
@@ -48,13 +54,30 @@ class HttpClient:
|
|
|
48
54
|
payload = None
|
|
49
55
|
|
|
50
56
|
if response.status >= 400:
|
|
51
|
-
if response.status ==
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
if response.status == STATUS_BAD_REQUEST:
|
|
58
|
+
if payload:
|
|
59
|
+
if payload.get("error", "") == "ERR_RATE_LIMIT":
|
|
60
|
+
raise RateLimitReached(RateLimitReached.message)
|
|
61
|
+
elif (
|
|
62
|
+
(self.exchange == 'binance' and payload.get('code', 0) == -1021)
|
|
63
|
+
or (self.exchange == 'bybit' and payload.get('retCode', 0) == 10002)
|
|
64
|
+
):
|
|
65
|
+
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
66
|
+
else:
|
|
67
|
+
raise ExchangeError(f"ExchangeError: {payload}")
|
|
68
|
+
elif response.reason != "Bad Request":
|
|
69
|
+
raise ExchangeError(f"ExchangeError: {response.reason}:{response.text}")
|
|
70
|
+
|
|
71
|
+
elif (
|
|
72
|
+
response.status == STATUS_UNAUTHORIZED
|
|
73
|
+
and self.exchange == 'okx'
|
|
74
|
+
and payload
|
|
75
|
+
and payload.get('code', 0) == '50102'
|
|
76
|
+
):
|
|
77
|
+
raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
|
|
78
|
+
elif response.status == STATUS_FORBIDDEN and self.exchange != 'okx':
|
|
56
79
|
raise WAFLimitViolated(WAFLimitViolated.message)
|
|
57
|
-
elif response.status ==
|
|
80
|
+
elif response.status == STATUS_I_AM_A_TEAPOT:
|
|
58
81
|
raise IPAddressBanned(IPAddressBanned.message)
|
|
59
82
|
else:
|
|
60
83
|
raise HTTPError(f"Malformed request: {payload}:{response.reason}:{response.text}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/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
|