unicex 0.14.9__tar.gz → 0.15.1__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 (114) hide show
  1. {unicex-0.14.9/unicex.egg-info → unicex-0.15.1}/PKG-INFO +2 -1
  2. {unicex-0.14.9 → unicex-0.15.1}/README.md +1 -0
  3. {unicex-0.14.9 → unicex-0.15.1}/pyproject.toml +1 -1
  4. {unicex-0.14.9 → unicex-0.15.1}/unicex/__init__.py +18 -0
  5. {unicex-0.14.9 → unicex-0.15.1}/unicex/_base/websocket.py +26 -9
  6. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/adapter.py +0 -12
  7. unicex-0.15.1/unicex/bingx/__init__.py +27 -0
  8. unicex-0.15.1/unicex/bingx/adapter.py +284 -0
  9. unicex-0.15.1/unicex/bingx/client.py +521 -0
  10. unicex-0.15.1/unicex/bingx/exchange_info.py +22 -0
  11. unicex-0.15.1/unicex/bingx/uni_client.py +191 -0
  12. unicex-0.15.1/unicex/bingx/uni_websocket_manager.py +283 -0
  13. unicex-0.15.1/unicex/bingx/user_websocket.py +7 -0
  14. unicex-0.15.1/unicex/bingx/websocket_manager.py +120 -0
  15. {unicex-0.14.9 → unicex-0.15.1}/unicex/enums.py +1 -0
  16. {unicex-0.14.9 → unicex-0.15.1}/unicex/extra.py +7 -0
  17. {unicex-0.14.9 → unicex-0.15.1}/unicex/mapper.py +59 -24
  18. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/__init__.py +335 -335
  19. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/websocket_manager.py +1 -1
  20. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/websocket_manager.py +3 -3
  21. {unicex-0.14.9 → unicex-0.15.1}/unicex/utils.py +17 -0
  22. {unicex-0.14.9 → unicex-0.15.1/unicex.egg-info}/PKG-INFO +2 -1
  23. {unicex-0.14.9 → unicex-0.15.1}/unicex.egg-info/SOURCES.txt +8 -0
  24. {unicex-0.14.9 → unicex-0.15.1}/LICENSE +0 -0
  25. {unicex-0.14.9 → unicex-0.15.1}/setup.cfg +0 -0
  26. {unicex-0.14.9 → unicex-0.15.1}/unicex/_abc/__init__.py +0 -0
  27. {unicex-0.14.9 → unicex-0.15.1}/unicex/_abc/exchange_info.py +0 -0
  28. {unicex-0.14.9 → unicex-0.15.1}/unicex/_abc/uni_client.py +0 -0
  29. {unicex-0.14.9 → unicex-0.15.1}/unicex/_abc/uni_websocket_manager.py +0 -0
  30. {unicex-0.14.9 → unicex-0.15.1}/unicex/_base/__init__.py +0 -0
  31. {unicex-0.14.9 → unicex-0.15.1}/unicex/_base/client.py +0 -0
  32. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/__init__.py +0 -0
  33. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/client.py +0 -0
  34. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/exchange_info.py +0 -0
  35. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/uni_client.py +0 -0
  36. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/uni_websocket_manager.py +0 -0
  37. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/user_websocket.py +0 -0
  38. {unicex-0.14.9 → unicex-0.15.1}/unicex/binance/websocket_manager.py +0 -0
  39. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/__init__.py +0 -0
  40. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/adapter.py +0 -0
  41. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/client.py +0 -0
  42. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/exchange_info.py +0 -0
  43. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/uni_client.py +0 -0
  44. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/uni_websocket_manager.py +0 -0
  45. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/user_websocket.py +0 -0
  46. {unicex-0.14.9 → unicex-0.15.1}/unicex/bitget/websocket_manager.py +0 -0
  47. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/__init__.py +0 -0
  48. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/adapter.py +0 -0
  49. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/client.py +0 -0
  50. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/exchange_info.py +0 -0
  51. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/uni_client.py +0 -0
  52. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/uni_websocket_manager.py +0 -0
  53. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/user_websocket.py +0 -0
  54. {unicex-0.14.9 → unicex-0.15.1}/unicex/bybit/websocket_manager.py +0 -0
  55. {unicex-0.14.9 → unicex-0.15.1}/unicex/exceptions.py +0 -0
  56. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/__init__.py +0 -0
  57. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/adapter.py +0 -0
  58. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/client.py +0 -0
  59. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/exchange_info.py +0 -0
  60. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/uni_client.py +0 -0
  61. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/uni_websocket_manager.py +0 -0
  62. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/user_websocket.py +0 -0
  63. {unicex-0.14.9 → unicex-0.15.1}/unicex/gate/websocket_manager.py +0 -0
  64. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/__init__.py +0 -0
  65. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/adapter.py +0 -0
  66. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/client.py +0 -0
  67. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/exchange_info.py +0 -0
  68. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/uni_client.py +0 -0
  69. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
  70. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/user_websocket.py +0 -0
  71. {unicex-0.14.9 → unicex-0.15.1}/unicex/hyperliquid/websocket_manager.py +0 -0
  72. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/__init__.py +0 -0
  73. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/adapter.py +0 -0
  74. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/client.py +0 -0
  75. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/exchange_info.py +0 -0
  76. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/uni_client.py +0 -0
  77. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/uni_websocket_manager.py +0 -0
  78. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/user_websocket.py +0 -0
  79. {unicex-0.14.9 → unicex-0.15.1}/unicex/kucoin/websocket_manager.py +0 -0
  80. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/__init__.py +0 -0
  81. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +0 -0
  82. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +0 -0
  83. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +0 -0
  84. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +0 -0
  85. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +0 -0
  86. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +0 -0
  87. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +0 -0
  88. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +0 -0
  89. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +0 -0
  90. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +0 -0
  91. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +0 -0
  92. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +0 -0
  93. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +0 -0
  94. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +0 -0
  95. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +0 -0
  96. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +0 -0
  97. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +0 -0
  98. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/adapter.py +0 -0
  99. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/client.py +0 -0
  100. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/exchange_info.py +0 -0
  101. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/uni_client.py +0 -0
  102. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/uni_websocket_manager.py +0 -0
  103. {unicex-0.14.9 → unicex-0.15.1}/unicex/mexc/user_websocket.py +0 -0
  104. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/__init__.py +0 -0
  105. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/adapter.py +0 -0
  106. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/client.py +0 -0
  107. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/exchange_info.py +0 -0
  108. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/uni_client.py +0 -0
  109. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/uni_websocket_manager.py +0 -0
  110. {unicex-0.14.9 → unicex-0.15.1}/unicex/okx/user_websocket.py +0 -0
  111. {unicex-0.14.9 → unicex-0.15.1}/unicex/types.py +0 -0
  112. {unicex-0.14.9 → unicex-0.15.1}/unicex.egg-info/dependency_links.txt +0 -0
  113. {unicex-0.14.9 → unicex-0.15.1}/unicex.egg-info/requires.txt +0 -0
  114. {unicex-0.14.9 → unicex-0.15.1}/unicex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unicex
3
- Version: 0.14.9
3
+ Version: 0.15.1
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -63,6 +63,7 @@ Dynamic: license-file
63
63
  | **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
64
64
  | **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
65
65
  | **Kucoin** | | | | | ✓ | | |
66
+ | **BingX** | | | | | ✓ | | |
66
67
  ---
67
68
 
68
69
 
@@ -14,6 +14,7 @@
14
14
  | **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
15
15
  | **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
16
16
  | **Kucoin** | | | | | ✓ | | |
17
+ | **BingX** | | | | | ✓ | | |
17
18
  ---
18
19
 
19
20
 
@@ -4,7 +4,7 @@ name = "unicex"
4
4
  # • PATCH (x.y.Z) → увеличивается при багфиксе, который не ломает совместимость.
5
5
  # • MINOR (x.Y.z) → увеличивается при добавлении новой функциональности, но без ломающих изменений (backward-compatible).
6
6
  # • MAJOR (X.y.z) → увеличивается при изменениях, которые ломают обратную совместимость.
7
- version = "0.14.9"
7
+ version = "0.15.1"
8
8
 
9
9
  description = "Unified Crypto Exchange API "
10
10
  readme = "README.md"
@@ -88,6 +88,13 @@ __all__ = [
88
88
  "KucoinWebsocketManager",
89
89
  "KucoinUserWebsocket",
90
90
  "KucoinExchangeInfo",
91
+ # BingX
92
+ "BingXClient",
93
+ "BingXUniClient",
94
+ "BingXUniWebsocketManager",
95
+ "BingXWebsocketManager",
96
+ "BingXUserWebsocket",
97
+ "BingXExchangeInfo",
91
98
  ]
92
99
 
93
100
  # ruff: noqa
@@ -189,6 +196,15 @@ from .kucoin import (
189
196
  ExchangeInfo as KucoinExchangeInfo,
190
197
  )
191
198
 
199
+ from .bingx import (
200
+ Client as BingXClient,
201
+ UniClient as BingXUniClient,
202
+ UniWebsocketManager as BingXUniWebsocketManager,
203
+ UserWebsocket as BingXUserWebsocket,
204
+ WebsocketManager as BingXWebsocketManager,
205
+ ExchangeInfo as BingXExchangeInfo,
206
+ )
207
+
192
208
 
193
209
  async def load_exchanges_info() -> None:
194
210
  """Единожды загружает информацию о тикерах на всех биржах."""
@@ -201,6 +217,7 @@ async def load_exchanges_info() -> None:
201
217
  MexcExchangeInfo.load_exchange_info(),
202
218
  OkxExchangeInfo.load_exchange_info(),
203
219
  KucoinExchangeInfo.load_exchange_info(),
220
+ BingXExchangeInfo.load_exchange_info(),
204
221
  )
205
222
 
206
223
 
@@ -215,4 +232,5 @@ async def start_exchanges_info(parse_interval_seconds: int = 60 * 60) -> None:
215
232
  MexcExchangeInfo.start(parse_interval_seconds),
216
233
  OkxExchangeInfo.start(parse_interval_seconds),
217
234
  KucoinExchangeInfo.start(parse_interval_seconds),
235
+ BingXExchangeInfo.start(parse_interval_seconds),
218
236
  )
@@ -3,7 +3,7 @@ __all__ = ["Websocket"]
3
3
  import asyncio
4
4
  import time
5
5
  from collections.abc import Awaitable, Callable
6
- from typing import Any, Protocol
6
+ from typing import Any, Literal, Protocol
7
7
 
8
8
  import orjson
9
9
  import websockets
@@ -23,12 +23,12 @@ class Websocket:
23
23
  class _DecoderProtocol(Protocol):
24
24
  """Протокол декодирования сообщений."""
25
25
 
26
- def decode(self, message: Any) -> dict: ...
26
+ def decode(self, message: Any) -> dict | Literal["ping"]: ...
27
27
 
28
28
  class _JsonDecoder:
29
29
  """Протокол декодирования сообщений в формате JSON."""
30
30
 
31
- def decode(self, message: Any) -> dict:
31
+ def decode(self, message: Any) -> dict | Literal["ping"]:
32
32
  return orjson.loads(message)
33
33
 
34
34
  def __init__(
@@ -114,7 +114,7 @@ class Websocket:
114
114
  # Цикл получения сообщений
115
115
  while self._running:
116
116
  message = await conn.recv()
117
- await self._handle_message(message)
117
+ await self._handle_message(message, conn)
118
118
 
119
119
  except websockets.exceptions.ConnectionClosed as e:
120
120
  self._logger.error(f"Websocket connection was closed unexpectedly: {e}")
@@ -128,7 +128,7 @@ class Websocket:
128
128
  else:
129
129
  return # Выходим из итератора, если вебсокет уже выключен
130
130
 
131
- async def _handle_message(self, message: str | bytes) -> None:
131
+ async def _handle_message(self, message: str | bytes, conn: ClientConnection) -> None:
132
132
  """Обрабатывает входящее сообщение вебсокета."""
133
133
  try:
134
134
  # Обновленяем время последнего сообщения
@@ -136,17 +136,22 @@ class Websocket:
136
136
 
137
137
  # Ложим сообщение в очередь, предварительно его сериализуя
138
138
  decoded_message = self._decoder.decode(message)
139
- await self._queue.put(decoded_message)
140
139
 
141
- # Проверяем размер очереди сообщений и выбрасываем ошибку, если он превышает максимальный размер
142
- self._check_queue_size()
140
+ # Проверяем - вдруг декордер вернул "ping"
141
+ if decoded_message == "ping":
142
+ await self._send_pong(conn)
143
+ else:
144
+ await self._queue.put(decoded_message)
145
+
146
+ # Проверяем размер очереди сообщений и выбрасываем ошибку, если он превышает максимальный размер
147
+ self._check_queue_size()
143
148
  except QueueOverflowError:
144
149
  self._logger.error("Message queue is overflow")
145
150
  except orjson.JSONDecodeError as e:
146
151
  if message in ["ping", "pong"]:
147
152
  self._logger.debug(f"Received ping message: {message}")
148
153
  else:
149
- self._logger.error(f"Failed to decode JSON message: {message}, error: {e}")
154
+ self._logger.error(f"Failed to decode message: {message}, error: {e}")
150
155
  except Exception as e:
151
156
  self._logger.error(f"Unexpected error: {e}")
152
157
 
@@ -256,6 +261,18 @@ class Websocket:
256
261
  return
257
262
  await asyncio.sleep(1)
258
263
 
264
+ async def _send_pong(self, conn: ClientConnection) -> None:
265
+ """Отправляет pong сообщение."""
266
+ if self._pong_message:
267
+ if isinstance(self._pong_message, Callable):
268
+ pong_message = self._pong_message()
269
+ else:
270
+ pong_message = self._pong_message
271
+ await conn.send(pong_message)
272
+ else:
273
+ await conn.pong()
274
+ self._logger.debug("Sent pong message")
275
+
259
276
  def __repr__(self) -> str:
260
277
  """Репрезентация вебсокета."""
261
278
  return f"<Websocket(url={self._url[:15]}...)>"
@@ -62,18 +62,6 @@ class Adapter:
62
62
  """
63
63
  return {item["symbol"]: float(item["price"]) for item in raw_data}
64
64
 
65
- @staticmethod
66
- def futures_last_price(raw_data: list[dict]) -> dict[str, float]:
67
- """Преобразует сырой ответ, в котором содержатся данные о тикере за последние 24 часа в унифицированный формат.
68
-
69
- Параметры:
70
- raw_data (list[dict]): Сырой ответ с биржи.
71
-
72
- Возвращает:
73
- dict[str, float]: Словарь, где ключ - тикер, а значение - последняя цена.
74
- """
75
- return Adapter.last_price(raw_data)
76
-
77
65
  @staticmethod
78
66
  def klines(raw_data: list[list], symbol: str) -> list[KlineDict]:
79
67
  """Преобразует сырой ответ, в котором содержатся данные о котировках тикеров в унифицированный формат.
@@ -0,0 +1,27 @@
1
+ """Пакет, содержащий реализации клиентов и менеджеров для работы с биржей BingX."""
2
+
3
+ __all__ = [
4
+ "Client",
5
+ "UniClient",
6
+ "UserWebsocket",
7
+ "WebsocketManager",
8
+ "UniWebsocketManager",
9
+ "ExchangeInfo",
10
+ ]
11
+
12
+ from .client import Client
13
+ from .exchange_info import ExchangeInfo
14
+ from .uni_client import UniClient
15
+ from .uni_websocket_manager import UniWebsocketManager
16
+ from .user_websocket import UserWebsocket
17
+ from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже BingX."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже BingX."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
@@ -0,0 +1,284 @@
1
+ __all__ = ["Adapter"]
2
+
3
+ from typing import Any
4
+
5
+ from unicex.types import (
6
+ KlineDict,
7
+ LiquidationDict,
8
+ OpenInterestItem,
9
+ TickerDailyDict,
10
+ TradeDict,
11
+ )
12
+ from unicex.utils import catch_adapter_errors, decorate_all_methods
13
+
14
+
15
+ @decorate_all_methods(catch_adapter_errors)
16
+ class Adapter:
17
+ """Адаптер для унификации данных с BingX API."""
18
+
19
+ @staticmethod
20
+ def tickers(raw_data: dict, only_usdt: bool) -> list[str]:
21
+ """Преобразует сырой ответ, в котором содержатся данные о тикерах в список тикеров.
22
+
23
+ Параметры:
24
+ raw_data (dict): Сырой ответ с биржи.
25
+ only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре к USDT.
26
+
27
+ Возвращает:
28
+ list[str]: Список тикеров.
29
+ """
30
+ data = raw_data["data"]
31
+ items = data["symbols"] if isinstance(data, dict) and "symbols" in data else data
32
+ return [
33
+ item["symbol"] for item in items if item["symbol"].endswith("USDT") or not only_usdt
34
+ ]
35
+
36
+ @staticmethod
37
+ def ticker_24hr(raw_data: dict) -> TickerDailyDict:
38
+ """Преобразует сырой ответ, в котором содержатся данные о тикере за последние 24 часа в унифицированный формат.
39
+
40
+ Параметры:
41
+ raw_data (dict): Сырой ответ с биржи.
42
+
43
+ Возвращает:
44
+ TickerDailyDict: Словарь, где ключ - тикер, а значение - статистика за последние 24 часа.
45
+ """
46
+ items = raw_data["data"]
47
+ result = {}
48
+ for item in items:
49
+ percent_raw = item["priceChangePercent"]
50
+ percent_str = str(percent_raw).strip()
51
+ if percent_str.endswith("%"):
52
+ percent_str = percent_str[:-1]
53
+ result[item["symbol"]] = {
54
+ "p": float(percent_str),
55
+ "v": float(item["volume"]),
56
+ "q": float(item["quoteVolume"]),
57
+ }
58
+ return result
59
+
60
+ @staticmethod
61
+ def open_interest(raw_data: dict) -> OpenInterestItem:
62
+ """Преобразует сырой ответ, в котором содержатся данные об открытом интересе, в унифицированный формат.
63
+
64
+ Параметры:
65
+ raw_data (dict): Сырой ответ с биржи.
66
+
67
+ Возвращает:
68
+ OpenInterestItem: Словарь со временем и объемом открытого интереса в монетах.
69
+ """
70
+ item = raw_data["data"]
71
+ return OpenInterestItem(t=int(item["time"]), v=float(item["openInterest"]))
72
+
73
+ @staticmethod
74
+ def funding_rate(raw_data: dict) -> dict[str, float]:
75
+ """Преобразует сырой ответ, в котором содержатся данные о ставках финансирования, в унифицированный формат.
76
+
77
+ Параметры:
78
+ raw_data (dict): Сырой ответ с биржи.
79
+
80
+ Возвращает:
81
+ dict[str, float]: Словарь, где ключ - тикер, а значение - ставка финансирования.
82
+ """
83
+ data = raw_data["data"]
84
+ items = data if isinstance(data, list) else [data]
85
+ result: dict[str, float] = {}
86
+ for item in items:
87
+ if "lastFundingRate" in item:
88
+ result[item["symbol"]] = float(item["lastFundingRate"]) * 100
89
+ elif "fundingRate" in item and item["fundingRate"] != "":
90
+ result[item["symbol"]] = float(item["fundingRate"]) * 100
91
+ return result
92
+
93
+ @staticmethod
94
+ def last_price(raw_data: dict) -> dict[str, float]:
95
+ """Преобразует сырой ответ, в котором содержатся данные о последней цене, в унифицированный формат.
96
+
97
+ Параметры:
98
+ raw_data (dict): Сырой ответ с биржи.
99
+
100
+ Возвращает:
101
+ dict[str, float]: Словарь, где ключ - тикер, а значение - последняя цена.
102
+ """
103
+ data = raw_data["data"]
104
+ if isinstance(data, list):
105
+ return {
106
+ item["symbol"]: float(item["lastPrice"] if "lastPrice" in item else item["price"])
107
+ for item in data
108
+ }
109
+ if isinstance(data, dict):
110
+ return {data["symbol"]: float(data["price"])}
111
+ return {}
112
+
113
+ @staticmethod
114
+ def klines(raw_data: dict) -> list[KlineDict]:
115
+ """Преобразует сырой ответ, в котором содержатся данные о свечах, в унифицированный формат.
116
+
117
+ Параметры:
118
+ raw_data (dict): Сырой ответ с биржи.
119
+
120
+ Возвращает:
121
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
122
+ """
123
+ data = raw_data["data"]
124
+ items = data["klines"] if isinstance(data, dict) and "klines" in data else data
125
+ if "symbol" in raw_data:
126
+ symbol = raw_data["symbol"]
127
+ elif isinstance(data, dict) and "symbol" in data:
128
+ symbol = data["symbol"]
129
+ else:
130
+ symbol = ""
131
+
132
+ if items and isinstance(items[0], dict):
133
+ return [
134
+ KlineDict(
135
+ s=symbol,
136
+ t=int(kline["time"]),
137
+ o=float(kline["open"]),
138
+ h=float(kline["high"]),
139
+ l=float(kline["low"]),
140
+ c=float(kline["close"]),
141
+ v=float(kline["volume"]),
142
+ q=0.0,
143
+ T=None,
144
+ x=None,
145
+ )
146
+ for kline in sorted(
147
+ items,
148
+ key=lambda x: int(x["time"]),
149
+ )
150
+ ]
151
+
152
+ return [
153
+ KlineDict(
154
+ s=symbol,
155
+ t=int(kline[0]),
156
+ o=float(kline[1]),
157
+ h=float(kline[2]),
158
+ l=float(kline[3]),
159
+ c=float(kline[4]),
160
+ v=float(kline[5]),
161
+ q=float(kline[7]) if len(kline) > 7 else 0.0,
162
+ T=int(kline[6]) if len(kline) > 6 else None,
163
+ x=None,
164
+ )
165
+ for kline in sorted(
166
+ items,
167
+ key=lambda x: int(x[0]),
168
+ )
169
+ ]
170
+
171
+ @staticmethod
172
+ def Klines_message(msg: Any) -> list[KlineDict]:
173
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
174
+ свече/свечах в унифицированный вид.
175
+
176
+ Параметры:
177
+ msg (Any): Сырое сообщение с вебсокета.
178
+
179
+ Возвращает:
180
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
181
+ """
182
+ ...
183
+
184
+ @staticmethod
185
+ def futures_klines_message(msg: Any) -> list[KlineDict]:
186
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
187
+ свече/свечах в унифицированный вид.
188
+
189
+ Параметры:
190
+ msg (Any): Сырое сообщение с вебсокета.
191
+
192
+ Возвращает:
193
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
194
+ """
195
+ ...
196
+
197
+ @staticmethod
198
+ def aggtrades_message(msg: Any) -> list[TradeDict]:
199
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
200
+ аггрегированных сделке/сделках в унифицированный вид.
201
+
202
+ Параметры:
203
+ msg (Any): Сырое сообщение с вебсокета.
204
+
205
+ Возвращает:
206
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о сделке.
207
+ """
208
+ ...
209
+
210
+ @staticmethod
211
+ def futures_aggtrades_message(msg: Any) -> list[TradeDict]:
212
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
213
+ аггрегированных сделке/сделках в унифицированный вид.
214
+
215
+ Параметры:
216
+ msg (Any): Сырое сообщение с вебсокета.
217
+
218
+ Возвращает:
219
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о сделке.
220
+ """
221
+ ...
222
+
223
+ @staticmethod
224
+ def trades_message(msg: Any) -> list[TradeDict]:
225
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
226
+ сделке/сделках в унифицированный вид.
227
+
228
+ Параметры:
229
+ msg (Any): Сырое сообщение с вебсокета.
230
+
231
+ Возвращает:
232
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о сделке.
233
+ """
234
+ return [
235
+ TradeDict(
236
+ t=int(trade["T"]),
237
+ s=str(trade["s"]),
238
+ S="SELL" if bool(trade["m"]) else "BUY",
239
+ p=float(trade["p"]),
240
+ v=float(trade["q"]),
241
+ )
242
+ for trade in sorted(
243
+ msg["data"],
244
+ key=lambda x: int(x["T"]),
245
+ )
246
+ ]
247
+
248
+ @staticmethod
249
+ def futures_trades_message(msg: Any) -> list[TradeDict]:
250
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
251
+ сделке/сделках в унифицированный вид.
252
+
253
+ Параметры:
254
+ msg (Any): Сырое сообщение с вебсокета.
255
+
256
+ Возвращает:
257
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о сделке.
258
+ """
259
+ return [
260
+ TradeDict(
261
+ t=int(trade["T"]),
262
+ s=str(trade["s"]),
263
+ S="SELL" if bool(trade["m"]) else "BUY",
264
+ p=float(trade["p"]),
265
+ v=float(trade["q"]),
266
+ )
267
+ for trade in sorted(
268
+ msg["data"],
269
+ key=lambda x: int(x["T"]),
270
+ )
271
+ ]
272
+
273
+ @staticmethod
274
+ def liquidations_message(msg: Any) -> list[LiquidationDict]:
275
+ """Преобразует сырое сообщение с вебсокета, в котором содержится информация о
276
+ ликвидации/ликвидациях в унифицированный вид.
277
+
278
+ Параметры:
279
+ msg (Any): Сырое сообщение с вебсокета.
280
+
281
+ Возвращает:
282
+ list[LiquidationDict]: Список словарей, где каждый словарь содержит данные о ликвидации.
283
+ """
284
+ ...