exchanges-wrapper 2.1.37__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.37 → exchanges_wrapper-2.1.41}/PKG-INFO +8 -6
  2. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/README.md +4 -2
  3. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/__init__.py +12 -2
  4. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/client.py +4 -6
  5. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/events.py +2 -8
  6. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/exch_srv.py +5 -5
  7. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/http_client.py +2 -2
  8. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/bitfinex.py +29 -11
  9. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/bybit.py +2 -2
  10. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/okx.py +9 -4
  11. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/pyproject.toml +3 -3
  12. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/LICENSE.md +0 -0
  13. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/definitions.py +0 -0
  14. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/errors.py +0 -0
  15. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/exch_srv_cfg.toml.template +0 -0
  16. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/lib.py +0 -0
  17. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/martin/__init__.py +0 -0
  18. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/parsers/huobi.py +0 -0
  19. {exchanges_wrapper-2.1.37 → exchanges_wrapper-2.1.41}/exchanges_wrapper/proto/martin.proto +0 -0
  20. {exchanges_wrapper-2.1.37 → 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.37
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,13 +12,13 @@ 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
- Requires-Dist: aiohttp~=3.11.18
18
+ Requires-Dist: aiohttp~=3.12.13
19
19
  Requires-Dist: expiringdict~=1.2.2
20
20
  Requires-Dist: betterproto==2.0.0b7
21
- Requires-Dist: grpclib~=0.4.7
21
+ Requires-Dist: grpclib~=0.4.8
22
22
  Project-URL: Source, https://github.com/DogsTailFarmer/exchanges-wrapper
23
23
 
24
24
  <h1 align="center"><img align="center" src="https://raw.githubusercontent.com/gist/DogsTailFarmer/167eaf65cebfe95d954082c7f181a2cc/raw/a67270de8663ad3de4733330ff64c9ba3153f87d/Logo%202v3.svg" width="75">Crypto exchanges API/WSS wrapper with grpc powered server</h1>
@@ -28,13 +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>
32
- <a href="https://codeclimate.com/github/DogsTailFarmer/exchanges-wrapper/maintainability"><img src="https://api.codeclimate.com/v1/badges/f333ab9b1f3024699e09/maintainability" /></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>
33
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>
34
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>
35
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>
36
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
+
37
38
  ***
39
+
38
40
  On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
39
41
 
40
42
  From `2.1.34` must be updated `exch_srv_cfg.toml` from `exchanges_wrapper/exch_srv_cfg.toml.template`
@@ -5,13 +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>
9
- <a href="https://codeclimate.com/github/DogsTailFarmer/exchanges-wrapper/maintainability"><img src="https://api.codeclimate.com/v1/badges/f333ab9b1f3024699e09/maintainability" /></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>
10
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>
11
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>
12
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>
13
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
+
14
15
  ***
16
+
15
17
  On `Binance`: now API key type [Ed25519](https://www.binance.com/en/support/faq/detail/6b9a63f1e3384cf48a2eedb82767a69a) is used instead of `HMAC`
16
18
 
17
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.37"
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
@@ -128,9 +128,7 @@ class Client:
128
128
  precision = symbol_infos["baseAssetPrecision"]
129
129
  if precision > self.highest_precision:
130
130
  self.highest_precision = precision
131
- symbol_infos["filters"] = dict(
132
- map(lambda x: (x.pop("filterType"), x), symbol_infos["filters"])
133
- )
131
+ symbol_infos["filters"] = {x.pop("filterType"): x for x in symbol_infos["filters"]}
134
132
  self.symbols[symbol] = symbol_infos
135
133
  decimal.getcontext().prec = (self.highest_precision + 4) # for operations and rounding
136
134
  if self.exchange == 'bybit':
@@ -186,7 +184,7 @@ class Client:
186
184
  break
187
185
  await asyncio.sleep(0.1)
188
186
 
189
- async def start_market_events_listener(self, _trade_id):
187
+ def start_market_events_listener(self, _trade_id):
190
188
  _events = self.events.registered_streams.get(self.exchange, {}).get(_trade_id, set())
191
189
  if self.exchange == 'binance':
192
190
  market_data_stream = MarketEventsDataStream(self, self.endpoint_ws_public, self.exchange, _trade_id)
@@ -589,7 +587,7 @@ class Client:
589
587
  "/api/v3/klines", params=params, signed=False
590
588
  )
591
589
  elif self.exchange == 'bitfinex':
592
- params = {'limit': limit, 'sort': -1}
590
+ params: Dict[str, Union[str, int]] = {'limit': limit, 'sort': -1}
593
591
  if start_time:
594
592
  params["start"] = str(start_time)
595
593
  if end_time:
@@ -241,9 +241,7 @@ class OutboundAccountPositionWrapper(EventWrapper):
241
241
  super().__init__(event_data, handlers)
242
242
  self.event_time = event_data["E"]
243
243
  self.last_update = event_data["u"]
244
- self.balances = dict(
245
- map(lambda x: (x["a"], {"free": x["f"], "locked": x["l"]}), event_data["B"])
246
- )
244
+ self.balances = {x["a"]: {"free": x["f"], "locked": x["l"]} for x in event_data["B"]}
247
245
 
248
246
 
249
247
  # BALANCE UPDATE
@@ -308,8 +306,4 @@ class ListStatus(EventWrapper):
308
306
  self.list_order_status = event_data["L"]
309
307
  self.list_reject_reason = event_data["r"]
310
308
  self.list_client_order_id = event_data["C"]
311
- # noinspection PyArgumentList
312
- self.orders = dict(
313
- map(lambda x: (x["s"], {"orderId": x["i"], "clientOrderId": x["c"]})),
314
- event_data["O"],
315
- )
309
+ self.orders = {x["s"]: {"orderId": x["i"], "clientOrderId": x["c"]} for x in event_data["O"]}
@@ -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")
@@ -753,7 +753,7 @@ class Martin(mr.MartinBase):
753
753
  len(v[request.trade_id]) for v in client.events.registered_streams.values() if request.trade_id in v
754
754
  )
755
755
  logger.info(f"Start WS streams for {open_client.name}")
756
- await client.start_market_events_listener(request.trade_id)
756
+ client.start_market_events_listener(request.trade_id)
757
757
  await client.start_user_events_listener(request.trade_id, request.symbol)
758
758
  response.success = True
759
759
  return response
@@ -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,13 +17,13 @@ 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
- "aiohttp~=3.11.18",
23
+ "aiohttp~=3.12.13",
24
24
  "expiringdict~=1.2.2",
25
25
  "betterproto==2.0.0b7",
26
- "grpclib~=0.4.7"
26
+ "grpclib~=0.4.8"
27
27
  ]
28
28
 
29
29
  [tool.flit.module]