exchanges-wrapper 2.1.5__tar.gz → 2.1.7__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.5 → exchanges_wrapper-2.1.7}/PKG-INFO +3 -3
  2. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/README.md +1 -1
  3. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/__init__.py +1 -1
  4. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/bybit_parser.py +2 -2
  5. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/client.py +8 -9
  6. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/exch_srv.py +4 -4
  7. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/http_client.py +10 -8
  8. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/web_sockets.py +25 -13
  9. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/pyproject.toml +1 -1
  10. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/LICENSE.md +0 -0
  11. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/bitfinex_parser.py +0 -0
  12. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/definitions.py +0 -0
  13. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/errors.py +0 -0
  14. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/events.py +0 -0
  15. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
  16. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/huobi_parser.py +0 -0
  17. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/lib.py +0 -0
  18. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/martin/__init__.py +0 -0
  19. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/exchanges_wrapper/okx_parser.py +0 -0
  20. {exchanges_wrapper-2.1.5 → exchanges_wrapper-2.1.7}/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.5
3
+ Version: 2.1.7
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.7
14
+ Requires-Dist: crypto-ws-api==2.0.8
15
15
  Requires-Dist: grpcio==1.62.0
16
16
  Requires-Dist: pyotp~=2.9.0
17
17
  Requires-Dist: simplejson==3.19.2
@@ -142,7 +142,7 @@ docker run -itP \
142
142
 
143
143
  ### Documentations
144
144
  * Served methods and examples of their use are described at ```example/exch_client.py```
145
- * For [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) serializing structured data see ```proto/exchanges_wrapper/api.proto```
145
+ * For [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) serializing structured data see ```exchanges_wrapper/proto/martin.proto```
146
146
 
147
147
  ## Donate
148
148
  *USDT* (TRC20) TN8F3Dz8BU8VwECRh3LTKi7FrsU8eWfsZz
@@ -116,7 +116,7 @@ docker run -itP \
116
116
 
117
117
  ### Documentations
118
118
  * Served methods and examples of their use are described at ```example/exch_client.py```
119
- * For [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) serializing structured data see ```proto/exchanges_wrapper/api.proto```
119
+ * For [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) serializing structured data see ```exchanges_wrapper/proto/martin.proto```
120
120
 
121
121
  ## Donate
122
122
  *USDT* (TRC20) TN8F3Dz8BU8VwECRh3LTKi7FrsU8eWfsZz
@@ -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.5"
15
+ __version__ = "2.1.7"
16
16
 
17
17
  from pathlib import Path
18
18
  import shutil
@@ -451,8 +451,8 @@ def on_balance_update(data_in: list, ts: str, symbol: str, mode: str, uid=None)
451
451
  elif mode == 'universal':
452
452
  for i in data_in:
453
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
+ ((i['fromAccountType'] == 'UNIFIED' and i['fromMemberId'] == str(uid))
455
+ or (i['toAccountType'] == 'UNIFIED' and i['toMemberId'] == str(uid))):
456
456
  data_out.append(
457
457
  {
458
458
  i['transferId']: {
@@ -1078,15 +1078,15 @@ class Client:
1078
1078
  "This query requires an order_id on Bitfinex. Deletion by user number is not implemented."
1079
1079
  )
1080
1080
  params = {'id': order_id}
1081
- res = (
1082
- await self.user_wss_session.handle_request(trade_id, "oc", _params=params)
1083
- or await self.http.send_api_call(
1081
+ res = await self.user_wss_session.handle_request(trade_id, "oc", _params=params)
1082
+ if res is None or (res and isinstance(res, list) and res[6] == 'ERROR'):
1083
+ logger.debug(f"cancel_order.bitfinex {order_id}: res1: {res}")
1084
+ res = await self.http.send_api_call(
1084
1085
  "v2/auth/w/order/cancel",
1085
1086
  method="POST",
1086
1087
  signed=True,
1087
1088
  **params
1088
- )
1089
- )
1089
+ )
1090
1090
  if res and isinstance(res, list) and res[6] == 'SUCCESS':
1091
1091
  timeout = STATUS_TIMEOUT / 0.1
1092
1092
  while timeout:
@@ -1096,7 +1096,7 @@ class Client:
1096
1096
  break
1097
1097
  await asyncio.sleep(0.1)
1098
1098
  else:
1099
- logger.warning(f"cancel_order.bitfinex {order_id}: res: {res}")
1099
+ logger.debug(f"cancel_order.bitfinex {order_id}: res2: {res}")
1100
1100
  elif self.exchange == 'huobi':
1101
1101
  res = await self.http.send_api_call(
1102
1102
  f"v1/order/orders/{order_id}/submitcancel",
@@ -1109,7 +1109,6 @@ class Client:
1109
1109
  timeout -= 1
1110
1110
  await asyncio.sleep(0.1)
1111
1111
  binance_res = await self.fetch_order(trade_id, symbol, order_id=res, response_type=True)
1112
-
1113
1112
  elif self.exchange == 'okx':
1114
1113
  _symbol = self.symbol_to_okx(symbol)
1115
1114
  _queue = asyncio.Queue()
@@ -1187,9 +1186,10 @@ class Client:
1187
1186
  **params,
1188
1187
  )
1189
1188
  )
1190
- logger.debug(f"cancel_all_orders.res: {res}")
1191
1189
  if res and res[6] == 'SUCCESS':
1192
1190
  return bfx.orders(res[4], response_type=True, cancelled=True)
1191
+ logger.debug(f"bitfinex: cancel_all_orders.res: {res}")
1192
+
1193
1193
  elif self.exchange == 'huobi':
1194
1194
  orders = await self.fetch_open_orders(trade_id, symbol, receive_window=receive_window, response_type=True)
1195
1195
  orders_id = [str(order.get('orderId')) for order in orders]
@@ -1295,7 +1295,6 @@ class Client:
1295
1295
  method="POST",
1296
1296
  signed=True
1297
1297
  )
1298
- # logger.debug(f"fetch_open_orders.res: {res}")
1299
1298
  if res:
1300
1299
  binance_res = bfx.orders(res)
1301
1300
  elif self.exchange == 'huobi':
@@ -174,8 +174,7 @@ class Martin(mr.MartinBase):
174
174
  if main_client.account_uid and main_client.account_id:
175
175
  open_client.client.main_account_uid = main_client.account_uid
176
176
  open_client.client.main_account_id = main_client.account_id
177
- logger.info(f"The values for main Huobi account were received and set:"
178
- f" UID: {main_client.account_uid} and account ID: {main_client.account_id}")
177
+ logger.info(f"Huobi UID: {main_client.account_uid} and account ID: {main_client.account_id}")
179
178
  else:
180
179
  logger.warning("No account IDs were received for the Huobi master account")
181
180
  await main_client.close()
@@ -232,7 +231,8 @@ class Martin(mr.MartinBase):
232
231
  try:
233
232
  res = await getattr(client, client_method_name)(**kwargs)
234
233
  except (asyncio.CancelledError, asyncio.exceptions.CancelledError):
235
- pass # Task cancellation should not be logged as an error
234
+ msg = f"{msg_header} Server Shutdown"
235
+ raise GRPCError(status=Status.UNAVAILABLE, message=msg)
236
236
  except (errors.RateLimitReached, errors.QueryCanceled) as ex:
237
237
  Martin.rate_limit_reached_time = time.time()
238
238
  msg = f"{msg_header} RateLimitReached: {ex}"
@@ -655,7 +655,7 @@ class Martin(mr.MartinBase):
655
655
  return
656
656
  _events.append(_event)
657
657
  _get_event_from_queue = True
658
- except asyncio.TimeoutError:
658
+ except asyncio.exceptions.TimeoutError:
659
659
  _get_event_from_queue = False
660
660
 
661
661
  if client.exchange in ('bitfinex', 'huobi', 'bybit'):
@@ -58,10 +58,7 @@ class HttpClient:
58
58
  if payload:
59
59
  if payload.get("error", "") == "ERR_RATE_LIMIT":
60
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
- ):
61
+ elif self.exchange == 'binance' and payload.get('code', 0) == -1021:
65
62
  raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
66
63
  else:
67
64
  raise ExchangeError(f"ExchangeError: {payload}")
@@ -82,14 +79,19 @@ class HttpClient:
82
79
  else:
83
80
  raise HTTPError(f"Malformed request: {payload}:{response.reason}:{response.text}")
84
81
 
85
- if self.exchange == 'bybit' and payload and payload.get('retCode') == 0:
86
- return payload.get('result'), payload.get('time')
82
+ if self.exchange == 'bybit' and payload:
83
+ if payload.get('retCode') == 0:
84
+ return payload.get('result'), payload.get('time')
85
+ elif payload.get('retCode') == 10002:
86
+ raise ExchangeError(ERR_TIMESTAMP_OUTSIDE_RECV_WINDOW)
87
+ else:
88
+ raise ExchangeError(f"API request failed: {response.status}:{response.reason}:{payload}")
87
89
  elif self.exchange == 'huobi' and payload and (payload.get('status') == 'ok' or payload.get('ok')):
88
90
  return payload.get('data', payload.get('tick'))
89
91
  elif self.exchange == 'okx' and payload and payload.get('code') == '0':
90
92
  return payload.get('data', [])
91
- elif (self.exchange not in ('binance', 'bitfinex') or
92
- (self.exchange == 'binance' and payload and "code" in payload)):
93
+ elif self.exchange not in ('binance', 'bitfinex') \
94
+ or (self.exchange == 'binance' and payload and "code" in payload):
93
95
  raise ExchangeError(f"API request failed: {response.status}:{response.reason}:{payload}")
94
96
  else:
95
97
  return payload
@@ -8,8 +8,7 @@ from decimal import Decimal
8
8
  import gzip
9
9
  from datetime import datetime, timezone
10
10
  from urllib.parse import urlencode, urlparse
11
- import websockets.client
12
- from websockets import ConnectionClosed
11
+ import websockets
13
12
 
14
13
  import exchanges_wrapper.bitfinex_parser as bfx
15
14
  import exchanges_wrapper.huobi_parser as hbp
@@ -53,10 +52,15 @@ class EventsDataStream:
53
52
  self.wss_started = False
54
53
 
55
54
  async def start(self):
56
- async for self.websocket in websockets.client.connect(self.endpoint, logger=logger_ws):
55
+ ping_interval = None if self.exchange == 'huobi' else 20
56
+ async for self.websocket in websockets.connect(
57
+ self.endpoint,
58
+ logger=logger_ws,
59
+ ping_interval=ping_interval
60
+ ):
57
61
  try:
58
62
  await self.start_wss()
59
- except ConnectionClosed as ex:
63
+ except websockets.ConnectionClosed as ex:
60
64
  self.tasks_cancel()
61
65
  if ex.code == 4000:
62
66
  logger.info(f"WSS closed for {self.exchange}:{self.trade_id}")
@@ -113,7 +117,7 @@ class EventsDataStream:
113
117
  elif ((msg_data.get("ret_msg") == "subscribe" or msg_data.get("op") in ("auth", "subscribe"))
114
118
  and not msg_data.get("success")):
115
119
  logger.warning(f"Reconnecting ByBit WSS: {symbol}: {ch_type}, msg_data: {msg_data}")
116
- raise ConnectionClosed(None, None)
120
+ raise websockets.ConnectionClosed(None, None)
117
121
  else:
118
122
  logger.info(f"ByBit undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
119
123
  elif self.exchange == 'okx':
@@ -129,7 +133,7 @@ class EventsDataStream:
129
133
  self.wss_started = True
130
134
  elif msg_data.get("event") in ("login", "error") and msg_data.get("code") != "0":
131
135
  logger.warning(f"Reconnecting OKX WSS: {symbol}: {ch_type}, msg_data: {msg_data}")
132
- raise ConnectionClosed(None, None)
136
+ raise websockets.ConnectionClosed(None, None)
133
137
  else:
134
138
  logger.debug(f"OKX undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
135
139
  elif self.exchange == 'bitfinex':
@@ -140,12 +144,12 @@ class EventsDataStream:
140
144
  if msg_data.get('platform') and msg_data.get('platform').get('status') != 1:
141
145
  logger.warning(f"Exchange in maintenance mode, trying reconnect. Exchange info: {msg}")
142
146
  await asyncio.sleep(60)
143
- raise ConnectionClosed(None, None)
147
+ raise websockets.ConnectionClosed(None, None)
144
148
  elif 'code' in msg_data:
145
149
  code = msg_data.get('code')
146
150
  if code == 10300:
147
151
  logger.warning('WSS Subscription failed (generic)')
148
- raise ConnectionClosed(None, None)
152
+ raise websockets.ConnectionClosed(None, None)
149
153
  elif code == 10301:
150
154
  logger.error('WSS Already subscribed')
151
155
  elif code == 10302:
@@ -154,11 +158,11 @@ class EventsDataStream:
154
158
  raise UserWarning('WSS Reached limit of open channels')
155
159
  elif code == 20051:
156
160
  logger.warning('WSS reconnection request received from exchange')
157
- raise ConnectionClosed(None, None)
161
+ raise websockets.ConnectionClosed(None, None)
158
162
  elif code == 20060:
159
163
  logger.info('WSS entering in maintenance mode, trying reconnect after 120s')
160
164
  await asyncio.sleep(120)
161
- raise ConnectionClosed(None, None)
165
+ raise websockets.ConnectionClosed(None, None)
162
166
  elif msg_data.get('event') == 'subscribed':
163
167
  chan_id = msg_data.get('chanId')
164
168
  logger.info(f"bitfinex, ch_type: {ch_type}, chan_id: {chan_id}")
@@ -178,8 +182,9 @@ class EventsDataStream:
178
182
  else:
179
183
  logger.debug(f"Bitfinex undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
180
184
  elif self.exchange == 'huobi':
181
- if msg_data.get('ping'):
182
- await self.websocket.send(json.dumps({"pong": msg_data.get('ping')}))
185
+ if ping := msg_data.get('ping'):
186
+ await self.websocket.send(json.dumps({"pong": ping}))
187
+ await asyncio.sleep(0)
183
188
  elif msg_data.get('action') == 'ping':
184
189
  pong = {
185
190
  "action": "pong",
@@ -188,6 +193,7 @@ class EventsDataStream:
188
193
  }
189
194
  }
190
195
  await self.websocket.send(json.dumps(pong))
196
+ await asyncio.sleep(0)
191
197
  elif msg_data.get('tick') or msg_data.get('data'):
192
198
  if ch_type == 'ticker':
193
199
  _price = msg_data.get('tick', {}).get('lastPrice', None)
@@ -203,13 +209,14 @@ class EventsDataStream:
203
209
  msg_data.get('code') == 500 and
204
210
  msg_data.get('message') == '系统异常:'):
205
211
  logger.warning(f"Reconnecting Huobi user {ch_type} channel")
206
- raise ConnectionClosed(None, None)
212
+ raise websockets.ConnectionClosed(None, None)
207
213
  else:
208
214
  logger.debug(f"Huobi undefined WSS: symbol: {symbol}, ch_type: {ch_type}, msg_data: {msg_data}")
209
215
 
210
216
  async def ws_listener(self, request=None, symbol=None, ch_type=str()):
211
217
  if request:
212
218
  await self.websocket.send(json.dumps(request))
219
+ await asyncio.sleep(0)
213
220
  async for msg_data in self.websocket:
214
221
  await self._handle_messages(msg_data, symbol, ch_type)
215
222
 
@@ -370,6 +377,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
370
377
  "params": _params
371
378
  }
372
379
  await self.websocket.send(json.dumps(request))
380
+ await asyncio.sleep(0)
373
381
  await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
374
382
  #
375
383
  request = {
@@ -377,6 +385,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
377
385
  "ch": "accounts.update#2"
378
386
  }
379
387
  await self.websocket.send(json.dumps(request))
388
+ await asyncio.sleep(0)
380
389
  await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
381
390
  #
382
391
  request = {
@@ -384,6 +393,7 @@ class HbpPrivateEventsDataStream(EventsDataStream):
384
393
  "ch": f"orders#{self.symbol.lower()}"
385
394
  }
386
395
  await self.websocket.send(json.dumps(request))
396
+ await asyncio.sleep(0)
387
397
  await self._handle_messages(await self.websocket.recv(), symbol=self.symbol)
388
398
  #
389
399
  request = {
@@ -489,6 +499,7 @@ class OkxPrivateEventsDataStream(EventsDataStream):
489
499
  ]
490
500
  }
491
501
  await self.websocket.send(json.dumps(request))
502
+ await asyncio.sleep(0)
492
503
  await self._handle_messages(await self.websocket.recv())
493
504
  # Channel subscription
494
505
  request = {"op": 'subscribe',
@@ -539,6 +550,7 @@ class BBTPrivateEventsDataStream(EventsDataStream):
539
550
  "args": [self.client.api_key, ts, signature]
540
551
  }
541
552
  await self.websocket.send(json.dumps(request))
553
+ await asyncio.sleep(0)
542
554
  await self._handle_messages(await self.websocket.recv())
543
555
  # Channel subscription
544
556
  request = {
@@ -17,7 +17,7 @@ dynamic = ["version", "description"]
17
17
  requires-python = ">=3.9"
18
18
 
19
19
  dependencies = [
20
- "crypto-ws-api==2.0.7",
20
+ "crypto-ws-api==2.0.8",
21
21
  "grpcio==1.62.0",
22
22
  "pyotp~=2.9.0",
23
23
  "simplejson==3.19.2",