exchanges-wrapper 2.1.40__tar.gz → 2.1.41__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.40 → exchanges_wrapper-2.1.41}/PKG-INFO +6 -3
  2. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/README.md +4 -1
  3. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/__init__.py +12 -2
  4. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/client.py +2 -2
  5. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/exch_srv.py +4 -4
  6. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/http_client.py +2 -2
  7. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/bitfinex.py +29 -11
  8. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/bybit.py +2 -2
  9. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/okx.py +9 -4
  10. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/pyproject.toml +1 -1
  11. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/LICENSE.md +0 -0
  12. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/definitions.py +0 -0
  13. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/errors.py +0 -0
  14. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/events.py +0 -0
  15. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
  16. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/lib.py +0 -0
  17. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/martin/__init__.py +0 -0
  18. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/huobi.py +0 -0
  19. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/proto/martin.proto +0 -0
  20. {exchanges_wrapper-2.1.40 → exchanges_wrapper-2.1.41}/exchanges_wrapper/web_sockets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exchanges-wrapper
3
- Version: 2.1.40
3
+ Version: 2.1.41
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.10
@@ -12,7 +12,7 @@ Classifier: Operating System :: Unix
12
12
  Classifier: Operating System :: Microsoft :: Windows
13
13
  Classifier: Operating System :: MacOS
14
14
  License-File: LICENSE.md
15
- Requires-Dist: crypto-ws-api==2.0.20
15
+ Requires-Dist: crypto-ws-api==2.1.0
16
16
  Requires-Dist: pyotp==2.9.0
17
17
  Requires-Dist: simplejson==3.20.1
18
18
  Requires-Dist: aiohttp~=3.12.13
@@ -28,12 +28,15 @@ Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
28
28
  <h3 align="center">For SPOT markets</h3>
29
29
 
30
30
  ***
31
- <a href="https://pypi.org/project/exchanges-wrapper/"><img src="https://img.shields.io/pypi/v/exchanges-wrapper" alt="PyPI version"></a>
31
+ <h1 align="center"><a href="https://pypi.org/project/exchanges-wrapper/"><img src="https://img.shields.io/pypi/v/exchanges-wrapper" alt="PyPI version"></a>
32
32
  <a href="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper.svg/?label=resolved+issues&token=XuG5PmzMiKlDL921-qREIuX_"/></a>
33
33
  <a href="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper.svg/?label=active+issues&token=XuG5PmzMiKlDL921-qREIuX_"/></a>
34
34
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
35
35
  <a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
36
+ </h1>
37
+
36
38
  ***
39
+
37
40
  On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
38
41
 
39
42
  From `2.1.34` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
@@ -5,12 +5,15 @@
5
5
  <h3 align="center">For SPOT markets</h3>
6
6
 
7
7
  ***
8
- <a href="https://pypi.org/project/exchanges-wrapper/"><img src="https://img.shields.io/pypi/v/exchanges-wrapper" alt="PyPI version"></a>
8
+ <h1 align="center"><a href="https://pypi.org/project/exchanges-wrapper/"><img src="https://img.shields.io/pypi/v/exchanges-wrapper" alt="PyPI version"></a>
9
9
  <a href="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper.svg/?label=resolved+issues&token=XuG5PmzMiKlDL921-qREIuX_"/></a>
10
10
  <a href="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper/?ref=repository-badge}" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://deepsource.io/gh/DogsTailFarmer/exchanges-wrapper.svg/?label=active+issues&token=XuG5PmzMiKlDL921-qREIuX_"/></a>
11
11
  <a href="https://sonarcloud.io/summary/new_code?id=DogsTailFarmer_exchanges-wrapper" target="_blank"><img alt="sonarcloud" title="sonarcloud" src="https://sonarcloud.io/api/project_badges/measure?project=DogsTailFarmer_exchanges-wrapper&metric=alert_status"/></a>
12
12
  <a href="https://pepy.tech/project/exchanges-wrapper" target="_blank"><img alt="Downloads" title="Downloads" src="https://static.pepy.tech/badge/exchanges-wrapper/month"/></a>
13
+ </h1>
14
+
13
15
  ***
16
+
14
17
  On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
15
18
 
16
19
  From `2.1.34` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
@@ -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.40"
15
+ __version__ = "2.1.41"
16
16
 
17
17
  from pathlib import Path
18
18
  import shutil
@@ -23,7 +23,17 @@ from grpclib.utils import graceful_exit
23
23
  from grpclib import exceptions
24
24
 
25
25
  __all__ = [
26
- 'Server', 'GRPCError', 'Status', 'Channel', 'graceful_exit', 'exceptions'
26
+ '__version__',
27
+ 'Server',
28
+ 'GRPCError',
29
+ 'Status',
30
+ 'Channel',
31
+ 'graceful_exit',
32
+ 'exceptions',
33
+ 'LOG_PATH',
34
+ 'WORK_PATH',
35
+ 'LOG_FILE',
36
+ 'CONFIG_FILE'
27
37
  ]
28
38
 
29
39
  WORK_PATH = Path(Path.home(), ".MartinBinance")
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Union
2
+ from typing import Union, Dict
3
3
  import decimal
4
4
  import math
5
5
  import asyncio
@@ -587,7 +587,7 @@ class Client:
587
587
  "/api/v3/klines", params=params, signed=False
588
588
  )
589
589
  elif self.exchange == 'bitfinex':
590
- params = {'limit': limit, 'sort': -1}
590
+ params: Dict[str, Union[str, int]] = {'limit': limit, 'sort': -1}
591
591
  if start_time:
592
592
  params["start"] = str(start_time)
593
593
  if end_time:
@@ -4,7 +4,9 @@ from typing import Any, AsyncGenerator
4
4
 
5
5
  import grpclib.exceptions
6
6
 
7
+ # noinspection PyPep8Naming
7
8
  from exchanges_wrapper import __version__ as VER_EW
9
+ # noinspection PyPep8Naming
8
10
  from crypto_ws_api import __version__ as VER_CW
9
11
  import time
10
12
  import weakref
@@ -136,8 +138,6 @@ class Martin(mr.MartinBase):
136
138
 
137
139
  try:
138
140
  await asyncio.wait_for(open_client.client.load(request.symbol), timeout=HEARTBEAT * 60)
139
- except asyncio.CancelledError:
140
- pass # Task cancellation should not be logged as an error
141
141
  except asyncio.exceptions.TimeoutError:
142
142
  await OpenClient.get_client(client_id).client.http.session.close()
143
143
  OpenClient.remove_client(request.account_name)
@@ -186,7 +186,7 @@ class Martin(mr.MartinBase):
186
186
 
187
187
  try:
188
188
  res = await asyncio.wait_for(getattr(client, client_method_name)(**kwargs), timeout=90)
189
- except asyncio.exceptions.CancelledError:
189
+ except KeyboardInterrupt:
190
190
  raise GRPCError(status=Status.UNAVAILABLE, message=f"{msg_header} Server Shutdown")
191
191
  except asyncio.exceptions.TimeoutError:
192
192
  self.log_and_raise_grpc_error(msg_header, Status.DEADLINE_EXCEEDED, "timeout error")
@@ -834,7 +834,7 @@ async def amain(host: str = '127.0.0.1', port: int = 50051):
834
834
  def main():
835
835
  try:
836
836
  asyncio.run(amain())
837
- except (asyncio.exceptions.CancelledError, grpclib.exceptions.StreamTerminatedError):
837
+ except grpclib.exceptions.StreamTerminatedError:
838
838
  pass # Task cancellation should not be logged as an error
839
839
  except Exception as ex:
840
840
  print(f"Exception: {ex}")
@@ -56,7 +56,7 @@ class HttpClient:
56
56
  async def _create_session_if_required(self):
57
57
  if self.session is None or self.session.closed:
58
58
  async with self._session_mutex:
59
- self.session = aiohttp.ClientSession(timeout=TIMEOUT)
59
+ self.session = aiohttp.ClientSession(trust_env=True, timeout=TIMEOUT)
60
60
 
61
61
  async def handle_errors(self, response, path=None):
62
62
  if response.status >= 500:
@@ -170,7 +170,7 @@ class HttpClient:
170
170
  async def _bitfinex_request(self, path, method, signed, send_api_key, endpoint, timeout, **kwargs):
171
171
  _endpoint = endpoint or self.endpoint
172
172
  bfx_post = (method == 'POST' and kwargs) or "params" in kwargs
173
- _params = json.dumps(kwargs) if bfx_post else None
173
+ _params = json.dumps(kwargs) if bfx_post else {}
174
174
  url = f'{_endpoint}/{path}'
175
175
  query_kwargs = {"headers": {"Accept": AJ}}
176
176
  if kwargs and not bfx_post:
@@ -4,6 +4,7 @@ Parser for convert Bitfinex REST API/WSS response to Binance like result
4
4
  import time
5
5
  from decimal import Decimal
6
6
  import logging
7
+ from typing import Dict, List, Union
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
@@ -21,9 +22,9 @@ class OrderBook:
21
22
  self.asks[str(i[0])] = str(abs(i[2]))
22
23
 
23
24
  def get_book(self) -> dict:
24
- bids = list(map(list, self.bids.items()))
25
+ bids = [list(item) for item in self.bids.items()]
25
26
  bids.sort(key=lambda x: float(x[0]), reverse=True)
26
- asks = list(map(list, self.asks.items()))
27
+ asks = [list(item) for item in self.asks.items()]
27
28
  asks.sort(key=lambda x: float(x[0]), reverse=False)
28
29
  return {
29
30
  'stream': f"{self.symbol}@depth5",
@@ -270,15 +271,32 @@ def orders(res: list, response_type=None, cancelled=False) -> list:
270
271
  return binance_orders
271
272
 
272
273
 
273
- def order_book(res: list) -> dict:
274
- binance_order_book = {"lastUpdateId": int(time.time() * 1000)}
275
- bids = []
276
- asks = []
274
+ def order_book(res: List[List[float]]) -> Dict[str, Union[int, List[List[str]]]]:
275
+ """
276
+ Processes a list of raw order book entries into a structured dictionary.
277
+
278
+ Args:
279
+ res: A list of order entries, where each entry is a list like
280
+ [price (float), quantity (float), side_indicator (float)].
281
+ side_indicator > 0 for bids, < 0 for asks.
282
+
283
+ Returns:
284
+ A dictionary representing the order book with 'lastUpdateId', 'bids', and 'asks'.
285
+ """
286
+ binance_order_book: Dict[str, Union[int, List[List[str]]]] = {
287
+ "lastUpdateId": int(time.time() * 1000)
288
+ }
289
+
290
+ bids: List[List[str]] = []
291
+ asks: List[List[str]] = []
292
+
277
293
  for i in res:
278
- if i[2] > 0:
279
- bids.append([str(i[0]), str(i[2])])
280
- else:
281
- asks.append([str(i[0]), str(abs(i[2]))])
294
+ # Assuming i[2] determines bid/ask side and its absolute value is the quantity
295
+ if i[2] > 0: # This means it's a bid (positive quantity indicator)
296
+ bids.append([str(i[0]), str(i[2])]) # price, quantity
297
+ else: # This means it's an ask (negative quantity indicator or zero)
298
+ asks.append([str(i[0]), str(abs(i[2]))]) # price, absolute quantity
299
+
282
300
  binance_order_book['bids'] = bids
283
301
  binance_order_book['asks'] = asks
284
302
  return binance_order_book
@@ -435,7 +453,7 @@ def ticker(res: list, symbol: str = None) -> dict:
435
453
 
436
454
 
437
455
  def on_funds_update(res: list) -> dict:
438
- binance_funds = {
456
+ binance_funds: Dict[str, Union[str, int, List]] = {
439
457
  'e': 'outboundAccountPosition',
440
458
  'E': int(time.time() * 1000),
441
459
  'u': int(time.time() * 1000),
@@ -23,8 +23,8 @@ class OrderBook:
23
23
  'stream': f"{self.symbol}@depth5",
24
24
  'data': {
25
25
  'lastUpdateId': self.last_update_id,
26
- 'bids': list(map(list, self.bids.items()))[:5],
27
- 'asks': list(map(list, self.asks.items()))[:5],
26
+ 'bids': [list(item) for item in self.bids.items()][:5],
27
+ 'asks': [list(item) for item in self.asks.items()][:5],
28
28
  },
29
29
  }
30
30
 
@@ -3,6 +3,7 @@ Parser for convert OKX REST API/WSS V5 response to Binance like result
3
3
  """
4
4
  import time
5
5
  from decimal import Decimal
6
+ from typing import Dict, List, Union
6
7
  import logging
7
8
 
8
9
  logger = logging.getLogger(__name__)
@@ -214,10 +215,14 @@ def account_balances(res: list) -> dict:
214
215
  return {"balances": balances}
215
216
 
216
217
 
217
- def order_book(res: dict) -> dict:
218
- asks = []
219
- bids = []
220
- binance_order_book = {"lastUpdateId": int(res.get('ts'))}
218
+ def order_book(res: dict) -> Dict[str, Union[int, List[List[str]]]]:
219
+ binance_order_book: Dict[str, Union[int, List[List[str]]]] = {
220
+ "lastUpdateId": int(time.time() * 1000)
221
+ }
222
+
223
+ bids: List[List[str]] = []
224
+ asks: List[List[str]] = []
225
+
221
226
  [asks.append(ask[:2]) for ask in res.get('asks')]
222
227
  binance_order_book['asks'] = asks
223
228
  [bids.append(bid[:2]) for bid in res.get('bids')]
@@ -17,7 +17,7 @@ dynamic = ["version", "description"]
17
17
  requires-python = ">=3.10"
18
18
 
19
19
  dependencies = [
20
- "crypto-ws-api==2.0.20",
20
+ "crypto-ws-api==2.1.0",
21
21
  "pyotp==2.9.0",
22
22
  "simplejson==3.20.1",
23
23
  "aiohttp~=3.12.13",