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.
Files changed (20) hide show
  1. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/PKG-INFO +2 -2
  2. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/__init__.py +1 -1
  3. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/bybit_parser.py +3 -1
  4. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/client.py +0 -1
  5. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/exch_srv.py +12 -10
  6. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/http_client.py +29 -6
  7. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/pyproject.toml +1 -1
  8. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/LICENSE.md +0 -0
  9. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/README.md +0 -0
  10. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/bitfinex_parser.py +0 -0
  11. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/definitions.py +0 -0
  12. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/errors.py +0 -0
  13. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/events.py +0 -0
  14. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
  15. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/huobi_parser.py +0 -0
  16. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/lib.py +0 -0
  17. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/martin/__init__.py +0 -0
  18. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/okx_parser.py +0 -0
  19. {exchanges_wrapper-2.1.3 → exchanges_wrapper-2.1.5}/exchanges_wrapper/proto/martin.proto +0 -0
  20. {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
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.6
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.3"
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 'UNIFIED' in (i['fromAccountType'], i['toAccountType']):
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, raise_ex=True):
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 and raise_ex:
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=__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.debug(f"OnOrderUpdate: {open_client.name}: {event}")
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
- if open_client := OpenClient.get_client(request.client_id, raise_ex=False):
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:{__version__} on {host}:{port}")
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 == 400 and payload and payload.get("error", str()) == "ERR_RATE_LIMIT":
52
- raise RateLimitReached(RateLimitReached.message)
53
- elif response.status == 400 and response.reason != "Bad Request":
54
- raise ExchangeError(f"ExchangeError: {payload}:{response.reason}:{response.text}")
55
- elif response.status == 403 and self.exchange != 'okx':
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 == 418:
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}")
@@ -17,7 +17,7 @@ dynamic = ["version", "description"]
17
17
  requires-python = ">=3.9"
18
18
 
19
19
  dependencies = [
20
- "crypto-ws-api==2.0.6",
20
+ "crypto-ws-api==2.0.7",
21
21
  "grpcio==1.62.0",
22
22
  "pyotp~=2.9.0",
23
23
  "simplejson==3.19.2",