unicex 0.15.2__tar.gz → 0.16.0__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.15.2/unicex.egg-info → unicex-0.16.0}/PKG-INFO +5 -5
  2. {unicex-0.15.2 → unicex-0.16.0}/README.md +4 -4
  3. {unicex-0.15.2 → unicex-0.16.0}/pyproject.toml +1 -1
  4. {unicex-0.15.2 → unicex-0.16.0}/unicex/_abc/exchange_info.py +1 -1
  5. {unicex-0.15.2 → unicex-0.16.0}/unicex/_abc/uni_client.py +1 -1
  6. {unicex-0.15.2 → unicex-0.16.0}/unicex/_abc/uni_websocket_manager.py +1 -1
  7. {unicex-0.15.2 → unicex-0.16.0}/unicex/_base/client.py +7 -0
  8. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/adapter.py +5 -1
  9. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/adapter.py +1 -1
  10. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/uni_websocket_manager.py +2 -2
  11. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/websocket_manager.py +2 -4
  12. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/adapter.py +1 -0
  13. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/uni_websocket_manager.py +21 -4
  14. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/websocket_manager.py +2 -4
  15. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/adapter.py +1 -0
  16. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/websocket_manager.py +7 -24
  17. {unicex-0.15.2 → unicex-0.16.0}/unicex/extra.py +23 -23
  18. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/adapter.py +103 -0
  19. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/uni_websocket_manager.py +41 -7
  20. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/adapter.py +1 -0
  21. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/adapter.py +4 -9
  22. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/client.py +13 -0
  23. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/uni_client.py +2 -14
  24. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/__init__.py +335 -335
  25. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/adapter.py +104 -0
  26. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/uni_websocket_manager.py +25 -7
  27. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/websocket_manager.py +26 -5
  28. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/adapter.py +51 -0
  29. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/uni_websocket_manager.py +44 -7
  30. {unicex-0.15.2 → unicex-0.16.0}/unicex/types.py +6 -3
  31. {unicex-0.15.2 → unicex-0.16.0}/unicex/utils.py +22 -1
  32. {unicex-0.15.2 → unicex-0.16.0/unicex.egg-info}/PKG-INFO +5 -5
  33. {unicex-0.15.2 → unicex-0.16.0}/LICENSE +0 -0
  34. {unicex-0.15.2 → unicex-0.16.0}/setup.cfg +0 -0
  35. {unicex-0.15.2 → unicex-0.16.0}/unicex/__init__.py +0 -0
  36. {unicex-0.15.2 → unicex-0.16.0}/unicex/_abc/__init__.py +0 -0
  37. {unicex-0.15.2 → unicex-0.16.0}/unicex/_base/__init__.py +0 -0
  38. {unicex-0.15.2 → unicex-0.16.0}/unicex/_base/websocket.py +0 -0
  39. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/__init__.py +0 -0
  40. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/client.py +0 -0
  41. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/exchange_info.py +0 -0
  42. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/uni_client.py +0 -0
  43. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/uni_websocket_manager.py +0 -0
  44. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/user_websocket.py +0 -0
  45. {unicex-0.15.2 → unicex-0.16.0}/unicex/binance/websocket_manager.py +0 -0
  46. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/__init__.py +0 -0
  47. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/client.py +0 -0
  48. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/exchange_info.py +0 -0
  49. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/uni_client.py +0 -0
  50. {unicex-0.15.2 → unicex-0.16.0}/unicex/bingx/user_websocket.py +0 -0
  51. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/__init__.py +0 -0
  52. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/client.py +0 -0
  53. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/exchange_info.py +0 -0
  54. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/uni_client.py +0 -0
  55. {unicex-0.15.2 → unicex-0.16.0}/unicex/bitget/user_websocket.py +0 -0
  56. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/__init__.py +0 -0
  57. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/client.py +0 -0
  58. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/exchange_info.py +0 -0
  59. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/uni_client.py +0 -0
  60. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/uni_websocket_manager.py +0 -0
  61. {unicex-0.15.2 → unicex-0.16.0}/unicex/bybit/user_websocket.py +0 -0
  62. {unicex-0.15.2 → unicex-0.16.0}/unicex/enums.py +0 -0
  63. {unicex-0.15.2 → unicex-0.16.0}/unicex/exceptions.py +0 -0
  64. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/__init__.py +0 -0
  65. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/client.py +0 -0
  66. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/exchange_info.py +0 -0
  67. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/uni_client.py +0 -0
  68. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/user_websocket.py +0 -0
  69. {unicex-0.15.2 → unicex-0.16.0}/unicex/gate/websocket_manager.py +0 -0
  70. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/__init__.py +0 -0
  71. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/client.py +0 -0
  72. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/exchange_info.py +0 -0
  73. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/uni_client.py +0 -0
  74. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
  75. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/user_websocket.py +0 -0
  76. {unicex-0.15.2 → unicex-0.16.0}/unicex/hyperliquid/websocket_manager.py +0 -0
  77. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/__init__.py +0 -0
  78. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/exchange_info.py +0 -0
  79. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/uni_websocket_manager.py +0 -0
  80. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/user_websocket.py +0 -0
  81. {unicex-0.15.2 → unicex-0.16.0}/unicex/kucoin/websocket_manager.py +0 -0
  82. {unicex-0.15.2 → unicex-0.16.0}/unicex/mapper.py +0 -0
  83. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/__init__.py +0 -0
  84. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +0 -0
  85. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +0 -0
  86. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +0 -0
  87. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +0 -0
  88. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +0 -0
  89. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +0 -0
  90. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +0 -0
  91. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +0 -0
  92. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +0 -0
  93. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +0 -0
  94. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +0 -0
  95. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +0 -0
  96. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +0 -0
  97. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +0 -0
  98. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +0 -0
  99. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +0 -0
  100. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +0 -0
  101. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/client.py +0 -0
  102. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/exchange_info.py +0 -0
  103. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/uni_client.py +0 -0
  104. {unicex-0.15.2 → unicex-0.16.0}/unicex/mexc/user_websocket.py +0 -0
  105. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/__init__.py +0 -0
  106. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/client.py +0 -0
  107. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/exchange_info.py +0 -0
  108. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/uni_client.py +0 -0
  109. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/user_websocket.py +0 -0
  110. {unicex-0.15.2 → unicex-0.16.0}/unicex/okx/websocket_manager.py +0 -0
  111. {unicex-0.15.2 → unicex-0.16.0}/unicex.egg-info/SOURCES.txt +0 -0
  112. {unicex-0.15.2 → unicex-0.16.0}/unicex.egg-info/dependency_links.txt +0 -0
  113. {unicex-0.15.2 → unicex-0.16.0}/unicex.egg-info/requires.txt +0 -0
  114. {unicex-0.15.2 → unicex-0.16.0}/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.15.2
3
+ Version: 0.16.0
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -56,12 +56,12 @@ Dynamic: license-file
56
56
  | Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
57
57
  |-----------------|--------|------|------------|---------|------------|----------------|--------------|
58
58
  | **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
59
- | **Bitget** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
59
+ | **Bitget** | ✓ | ✓ | ✓ | | ✓ || ✓ |
60
60
  | **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
61
- | **Gateio** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
61
+ | **Gateio** | ✓ | ✓ | ✓ | | ✓ || ✓ |
62
62
  | **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
63
- | **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
64
- | **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
63
+ | **Mexc** | ✓ | ✓ | ✓ | | ✓ || ✓ |
64
+ | **Okx** | ✓ | ✓ | ✓ | | ✓ || ✓ |
65
65
  | **Kucoin** | | | | | ✓ | | |
66
66
  | **BingX** | | | | | ✓ | | |
67
67
  ---
@@ -7,12 +7,12 @@
7
7
  | Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
8
8
  |-----------------|--------|------|------------|---------|------------|----------------|--------------|
9
9
  | **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
10
- | **Bitget** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
10
+ | **Bitget** | ✓ | ✓ | ✓ | | ✓ || ✓ |
11
11
  | **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
12
- | **Gateio** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
12
+ | **Gateio** | ✓ | ✓ | ✓ | | ✓ || ✓ |
13
13
  | **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
14
- | **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
15
- | **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
14
+ | **Mexc** | ✓ | ✓ | ✓ | | ✓ || ✓ |
15
+ | **Okx** | ✓ | ✓ | ✓ | | ✓ || ✓ |
16
16
  | **Kucoin** | | | | | ✓ | | |
17
17
  | **BingX** | | | | | ✓ | | |
18
18
  ---
@@ -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.15.2"
7
+ version = "0.16.0"
8
8
 
9
9
  description = "Unified Crypto Exchange API "
10
10
  readme = "README.md"
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
17
17
 
18
18
 
19
19
  class IExchangeInfo(ABC):
20
- """Интерфейс для наследников, которые предзагружают и обновляют информацию о бирже."""
20
+ """Интерфейс класса, который обновляет информацию о правилах торговли на бирже."""
21
21
 
22
22
  _loaded: bool
23
23
  """Флаг, указывающий, была ли информация о бирже загружена."""
@@ -15,7 +15,7 @@ TClient = TypeVar("TClient", bound="BaseClient")
15
15
 
16
16
 
17
17
  class IUniClient(ABC, Generic[TClient]):
18
- """Интерфейс для реализации асинхронного унифицированного клиента."""
18
+ """Интерфейс для унифицированного клиента."""
19
19
 
20
20
  def __init__(
21
21
  self,
@@ -16,7 +16,7 @@ type CallbackType = Callable[[Any], Awaitable[None]]
16
16
 
17
17
 
18
18
  class IUniWebsocketManager(ABC):
19
- """Интерфейс менеджера асинхронных унифицированных вебсокетов."""
19
+ """Интерфейс менеджера унифицированных вебсокетов."""
20
20
 
21
21
  def __init__(
22
22
  self, client: BaseClient | IUniClient | None = None, logger: LoggerLike | None = None
@@ -203,6 +203,9 @@ class BaseClient:
203
203
  response_json=response_json,
204
204
  ) from None
205
205
 
206
+ # Валидирование ответа в конерктной реализации клиента
207
+ self._validate_response(response_json)
208
+
206
209
  # Логирование ответа
207
210
  try:
208
211
  self._logger.debug(
@@ -212,3 +215,7 @@ class BaseClient:
212
215
  self._logger.error(f"Error while logging response: {e}")
213
216
 
214
217
  return response_json
218
+
219
+ def _validate_response(self, response_json: dict[str, Any]) -> None:
220
+ """Проверка ответа API на ошибки биржи. Переопределяется в клиентах конкретных бирж."""
221
+ return None
@@ -115,7 +115,11 @@ class Adapter:
115
115
  Возвращает:
116
116
  OpenInterestItem: Словарь со временем и объемом открытого интереса в монетах.
117
117
  """
118
- return OpenInterestItem(t=raw_data["time"], v=float(raw_data["openInterest"]))
118
+ return OpenInterestItem(
119
+ t=raw_data["time"],
120
+ v=float(raw_data["openInterest"]),
121
+ u="coins",
122
+ )
119
123
 
120
124
  @staticmethod
121
125
  def klines_message(raw_msg: dict) -> list[KlineDict]:
@@ -68,7 +68,7 @@ class Adapter:
68
68
  OpenInterestItem: Словарь со временем и объемом открытого интереса в монетах.
69
69
  """
70
70
  item = raw_data["data"]
71
- return OpenInterestItem(t=int(item["time"]), v=float(item["openInterest"]))
71
+ return OpenInterestItem(t=int(item["time"]), v=float(item["openInterest"]), u="usd")
72
72
 
73
73
  @staticmethod
74
74
  def funding_rate(raw_data: dict) -> dict[str, float]:
@@ -199,7 +199,7 @@ class UniWebsocketManager(IUniWebsocketManager):
199
199
  Возвращает:
200
200
  `Websocket`: Экземпляр вебсокета.
201
201
  """
202
- raise NotImplementedError()
202
+ return self.trades(callback, symbol=symbol, symbols=symbols) # type: ignore
203
203
 
204
204
  @overload
205
205
  def futures_trades(
@@ -280,4 +280,4 @@ class UniWebsocketManager(IUniWebsocketManager):
280
280
  Возвращает:
281
281
  `Websocket`: Экземпляр вебсокета.
282
282
  """
283
- raise NotImplementedError()
283
+ return self.futures_trades(callback, symbol=symbol, symbols=symbols) # type: ignore
@@ -9,6 +9,7 @@ from typing import Any, Literal
9
9
  import orjson
10
10
 
11
11
  from unicex._base import Websocket
12
+ from unicex.utils import validate_single_symbol_args
12
13
 
13
14
  from .client import Client
14
15
 
@@ -101,10 +102,7 @@ class WebsocketManager:
101
102
  Возвращает:
102
103
  `Websocket`: Объект для управления вебсокет соединением.
103
104
  """
104
- if symbol and symbols:
105
- raise ValueError("Parameters symbol and symbols cannot be used together")
106
- if not (symbol or symbols):
107
- raise ValueError("Either symbol or symbols must be provided")
105
+ validate_single_symbol_args(symbol, symbols)
108
106
 
109
107
  tickers = [symbol] if symbol else symbols
110
108
  data_types = [f"{ticker.upper()}@trade" for ticker in tickers] # type: ignore[arg-type]
@@ -183,6 +183,7 @@ class Adapter:
183
183
  i["symbol"]: OpenInterestItem(
184
184
  t=int(i["ts"]),
185
185
  v=float(i["holdingAmount"]),
186
+ u="coins",
186
187
  )
187
188
  for i in raw_data["data"]
188
189
  }
@@ -5,7 +5,7 @@ from typing import Any, overload
5
5
 
6
6
  from unicex._abc import IUniWebsocketManager
7
7
  from unicex._base import Websocket
8
- from unicex.enums import Timeframe
8
+ from unicex.enums import Exchange, MarketType, Timeframe
9
9
  from unicex.types import LoggerLike
10
10
 
11
11
  from .adapter import Adapter
@@ -76,7 +76,17 @@ class UniWebsocketManager(IUniWebsocketManager):
76
76
  Возвращает:
77
77
  `Websocket`: Экземпляр вебсокета для управления соединением.
78
78
  """
79
- raise NotImplementedError()
79
+ wrapper = self._make_wrapper(self._adapter.klines_message, callback)
80
+ return self._websocket_manager.candlestick(
81
+ callback=wrapper,
82
+ market_type="SPOT",
83
+ symbol=symbol,
84
+ symbols=symbols,
85
+ interval=timeframe.to_exchange_format(
86
+ Exchange.BITGET,
87
+ MarketType.FUTURES, # Тут пришлось поставить Futures, потому что:
88
+ ), # кто бы мог подумать, что у Bitget на споте для вебсокетов и HTTP запросов совершенно разные перечисления. Тупые ублюдки.
89
+ )
80
90
 
81
91
  @overload
82
92
  def futures_klines(
@@ -118,7 +128,14 @@ class UniWebsocketManager(IUniWebsocketManager):
118
128
  Возвращает:
119
129
  `Websocket`: Экземпляр вебсокета.
120
130
  """
121
- raise NotImplementedError()
131
+ wrapper = self._make_wrapper(self._adapter.klines_message, callback)
132
+ return self._websocket_manager.candlestick(
133
+ callback=wrapper,
134
+ market_type="USDT-FUTURES",
135
+ symbol=symbol,
136
+ symbols=symbols,
137
+ interval=timeframe.to_exchange_format(Exchange.BITGET, MarketType.FUTURES),
138
+ )
122
139
 
123
140
  @overload
124
141
  def trades(
@@ -276,4 +293,4 @@ class UniWebsocketManager(IUniWebsocketManager):
276
293
  Возвращает:
277
294
  `Websocket`: Экземпляр вебсокета.
278
295
  """
279
- raise NotImplementedError()
296
+ return self.futures_trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
@@ -6,6 +6,7 @@ from collections.abc import Awaitable, Callable, Sequence
6
6
  from typing import Any, Literal
7
7
 
8
8
  from unicex._base import Websocket
9
+ from unicex.utils import validate_single_symbol_args
9
10
 
10
11
  from .client import Client
11
12
 
@@ -46,10 +47,7 @@ class WebsocketManager:
46
47
  Возвращает:
47
48
  `str`: JSON-строка с сообщением для подписки на вебсокет.
48
49
  """
49
- if symbol and symbols:
50
- raise ValueError("Parameters symbol and symbols cannot be used together")
51
- if not (symbol or symbols):
52
- raise ValueError("Either symbol or symbols must be provided")
50
+ validate_single_symbol_args(symbol, symbols)
53
51
 
54
52
  tickers = [symbol] if symbol else symbols
55
53
  streams: list[dict] = [
@@ -68,6 +68,7 @@ class Adapter:
68
68
  item["symbol"]: OpenInterestItem(
69
69
  t=raw_data["time"],
70
70
  v=float(item["openInterest"]),
71
+ u="coins",
71
72
  )
72
73
  for item in raw_data["result"]["list"]
73
74
  }
@@ -6,6 +6,7 @@ from collections.abc import Awaitable, Callable, Sequence
6
6
  from typing import Any, Literal
7
7
 
8
8
  from unicex._base import Websocket
9
+ from unicex.utils import validate_single_symbol_args
9
10
 
10
11
  from .client import Client
11
12
 
@@ -108,10 +109,7 @@ class WebsocketManager:
108
109
  Возвращает:
109
110
  `Websocket`: Объект для управления вебсокет соединением.
110
111
  """
111
- if symbol and symbols:
112
- raise ValueError("Parameters symbol and symbols cannot be used together")
113
- if not (symbol or symbols):
114
- raise ValueError("Either symbol or symbols must be provided")
112
+ validate_single_symbol_args(symbol, symbols)
115
113
 
116
114
  tickers = [symbol] if symbol else symbols
117
115
  topics = [f"orderbook.{depth}.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -152,10 +150,7 @@ class WebsocketManager:
152
150
  Возвращает:
153
151
  `Websocket`: Объект для управления вебсокет соединением.
154
152
  """
155
- if symbol and symbols:
156
- raise ValueError("Parameters symbol and symbols cannot be used together")
157
- if not (symbol or symbols):
158
- raise ValueError("Either symbol or symbols must be provided")
153
+ validate_single_symbol_args(symbol, symbols)
159
154
 
160
155
  tickers = [symbol] if symbol else symbols
161
156
  topics = [f"kline.{interval}.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -192,10 +187,7 @@ class WebsocketManager:
192
187
  Возвращает:
193
188
  `Websocket`: Объект для управления вебсокет соединением.
194
189
  """
195
- if symbol and symbols:
196
- raise ValueError("Parameters symbol and symbols cannot be used together")
197
- if not (symbol or symbols):
198
- raise ValueError("Either symbol or symbols must be provided")
190
+ validate_single_symbol_args(symbol, symbols)
199
191
 
200
192
  tickers = [symbol] if symbol else symbols
201
193
  topics = [f"publicTrade.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -232,10 +224,7 @@ class WebsocketManager:
232
224
  Возвращает:
233
225
  `Websocket`: Объект для управления вебсокет соединением.
234
226
  """
235
- if symbol and symbols:
236
- raise ValueError("Parameters symbol and symbols cannot be used together")
237
- if not (symbol or symbols):
238
- raise ValueError("Either symbol or symbols must be provided")
227
+ validate_single_symbol_args(symbol, symbols)
239
228
 
240
229
  tickers = [symbol] if symbol else symbols
241
230
  topics = [f"tickers.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -280,10 +269,7 @@ class WebsocketManager:
280
269
  stacklevel=2,
281
270
  )
282
271
 
283
- if symbol and symbols:
284
- raise ValueError("Parameters symbol and symbols cannot be used together")
285
- if not (symbol or symbols):
286
- raise ValueError("Either symbol or symbols must be provided")
272
+ validate_single_symbol_args(symbol, symbols)
287
273
 
288
274
  tickers = [symbol] if symbol else symbols
289
275
  topics = [f"liquidation.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -320,10 +306,7 @@ class WebsocketManager:
320
306
  Возвращает:
321
307
  `Websocket`: Объект для управления вебсокет соединением.
322
308
  """
323
- if symbol and symbols:
324
- raise ValueError("Parameters symbol and symbols cannot be used together")
325
- if not (symbol or symbols):
326
- raise ValueError("Either symbol or symbols must be provided")
309
+ validate_single_symbol_args(symbol, symbols)
327
310
 
328
311
  tickers = [symbol] if symbol else symbols
329
312
  topics = [f"allLiquidation.{ticker.upper()}" for ticker in tickers] # type: ignore
@@ -179,23 +179,22 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
179
179
  Возвращает:
180
180
  `str`: Ссылка на биржу.
181
181
  """
182
- symbol = normalize_symbol(symbol)
183
182
  ticker = normalize_ticker(symbol)
184
183
  if exchange == Exchange.BINANCE:
185
184
  if market_type == MarketType.FUTURES:
186
- return f"https://www.binance.com/en/futures/{symbol}"
185
+ return f"https://www.binance.com/en/futures/{ticker}USDT"
187
186
  else:
188
187
  return f"https://www.binance.com/en/trade/{ticker}_USDT?type=spot"
189
188
  elif exchange == Exchange.BYBIT:
190
189
  if market_type == MarketType.FUTURES:
191
- return f"https://www.bybit.com/trade/usdt/{symbol}"
190
+ return f"https://www.bybit.com/trade/usdt/{ticker}USDT"
192
191
  else:
193
192
  return f"https://www.bybit.com/en/trade/spot/{ticker}/USDT"
194
193
  elif exchange == Exchange.BITGET:
195
194
  if market_type == MarketType.FUTURES:
196
- return f"https://www.bitget.com/ru/futures/usdt/{symbol}"
195
+ return f"https://www.bitget.com/ru/futures/usdt/{ticker}USDT"
197
196
  else:
198
- return f"https://www.bitget.com/ru/spot/{symbol}"
197
+ return f"https://www.bitget.com/ru/spot/{ticker}USDT"
199
198
  elif exchange == Exchange.OKX:
200
199
  if market_type == MarketType.FUTURES:
201
200
  return f"https://www.okx.com/ru/trade-swap/{ticker.lower()}-usdt-swap"
@@ -218,14 +217,14 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
218
217
  return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
219
218
  elif exchange == Exchange.KUCOIN:
220
219
  if market_type == MarketType.FUTURES:
221
- return f"https://www.kucoin.com/trade/futures/{symbol}M"
220
+ return f"https://www.kucoin.com/trade/futures/{ticker}USDTM"
222
221
  else:
223
222
  return f"https://www.kucoin.com/trade/{ticker}-USDT"
224
223
  elif exchange == Exchange.BINGX:
225
224
  if market_type == MarketType.FUTURES:
226
225
  return f"https://bingx.com/en/perpetual/{ticker}-USDT"
227
226
  else:
228
- return f"https://bingx.com/en/spot/{symbol}"
227
+ return f"https://bingx.com/en/spot/{ticker}USDT"
229
228
  else:
230
229
  raise NotSupported(f"Exchange {exchange} is not supported")
231
230
 
@@ -260,34 +259,35 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
260
259
  Возвращает:
261
260
  `str`: Ссылка для CoinGlass.
262
261
  """
263
- base_url = "https://www.coinglass.com/tv/ru"
264
-
265
- symbol = normalize_symbol(symbol)
262
+ ticker = normalize_ticker(symbol)
266
263
 
264
+ base_url = "https://www.coinglass.com/tv/ru"
267
265
  if market_type == MarketType.FUTURES:
268
266
  match exchange:
269
267
  case Exchange.OKX:
270
- return f"{base_url}/OKX_{symbol.replace('USDT', '-USDT')}-SWAP"
268
+ return f"{base_url}/OKX_{ticker}-USDT-SWAP"
271
269
  case Exchange.MEXC:
272
- return f"{base_url}/MEXC_{symbol.replace('USDT', '_USDT')}"
270
+ return f"{base_url}/MEXC_{ticker}_USDT"
273
271
  case Exchange.BITGET:
274
- return f"{base_url}/Bitget_{symbol}_UMCBL"
272
+ return f"{base_url}/Bitget_{ticker}USDT_UMCBL"
275
273
  case Exchange.GATE:
276
- return f"{base_url}/Gate_{symbol.replace('USDT', '_USDT')}"
274
+ return f"{base_url}/Gate_{ticker}_USDT"
277
275
  case Exchange.HYPERLIQUID:
278
- return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
276
+ return f"{base_url}/Hyperliquid_{ticker}-USD"
279
277
  case Exchange.KUCOIN:
280
- return f"https://www.coinglass.com/tv/ru/KuCoin_{symbol}M"
278
+ return f"https://www.coinglass.com/tv/ru/KuCoin_{ticker}USDTM"
281
279
  case Exchange.BINGX:
282
- return f"https://www.coinglass.com/tv/ru/BingX_{symbol.replace('USDT', '-USDT')}"
280
+ return f"https://www.coinglass.com/tv/ru/BingX_{ticker}-USDT"
283
281
  case _:
284
- return f"{base_url}/{exchange.capitalize()}_{symbol}"
282
+ return f"{base_url}/{exchange.capitalize()}_{ticker}USDT"
285
283
  else:
286
- # Для спота корректная ссылка есть только у OKX
287
- if exchange == Exchange.OKX:
288
- return f"{base_url}/SPOT_{exchange.upper()}_{symbol.replace('USDT', '-USDT')}"
289
- # Для остальных бирж ссылки нет → возвращаем заглушку
290
- return generate_cg_link(exchange, MarketType.FUTURES, symbol)
284
+ match exchange:
285
+ case Exchange.OKX:
286
+ return f"{base_url}/SPOT_{exchange.upper()}_{ticker}-USDT"
287
+ case Exchange.GATE:
288
+ return f"{base_url}/SPOT_{exchange.capitalize()}_{ticker}_USDT"
289
+ case _:
290
+ return f"{base_url}/SPOT_{exchange.capitalize()}_{ticker}USDT"
291
291
 
292
292
 
293
293
  def make_humanreadable(value: float, locale: Literal["ru", "en"] = "ru") -> str:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
+ from typing import Any
4
5
 
5
6
  __all__ = ["Adapter"]
6
7
 
@@ -10,6 +11,7 @@ from unicex.types import (
10
11
  OpenInterestItem,
11
12
  TickerDailyDict,
12
13
  TickerDailyItem,
14
+ TradeDict,
13
15
  )
14
16
  from unicex.utils import catch_adapter_errors, decorate_all_methods
15
17
 
@@ -173,6 +175,107 @@ class Adapter:
173
175
  item["contract"]: OpenInterestItem(
174
176
  t=int(time.time() * 1000),
175
177
  v=float(item["total_size"]) * float(item["quanto_multiplier"]),
178
+ u="coins",
176
179
  )
177
180
  for item in raw_data
178
181
  }
182
+
183
+ @staticmethod
184
+ def klines_message(raw_msg: Any) -> list[KlineDict]:
185
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
186
+
187
+ Параметры:
188
+ raw_msg (Any): Сырое сообщение с вебсокета.
189
+
190
+ Возвращает:
191
+ list[KlineDict]: Список свечей в унифицированном формате.
192
+ """
193
+ data = raw_msg["result"]
194
+ return [
195
+ KlineDict(
196
+ s=data["n"].split("_", 1)[1], # XRP_USDT
197
+ t=int(data["t"]) * 1000,
198
+ o=float(data["o"]),
199
+ h=float(data["h"]),
200
+ l=float(data["l"]),
201
+ c=float(data["c"]),
202
+ v=float(data["a"]),
203
+ q=float(data["v"]),
204
+ T=None,
205
+ x=not data["w"], # w=False → свеча закрыта
206
+ )
207
+ ]
208
+
209
+ @staticmethod
210
+ def futures_klines_message(raw_msg: Any) -> list[KlineDict]:
211
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
212
+
213
+ Параметры:
214
+ raw_msg (Any): Сырое сообщение с вебсокета.
215
+
216
+ Возвращает:
217
+ list[KlineDict]: Список свечей в унифицированном формате.
218
+ """
219
+ return [
220
+ KlineDict(
221
+ s=item["n"].split("_", 1)[1], # XRP_USDT
222
+ t=int(item["t"]) * 1000,
223
+ o=float(item["o"]),
224
+ h=float(item["h"]),
225
+ l=float(item["l"]),
226
+ c=float(item["c"]),
227
+ v=float(item["a"]),
228
+ q=float(item["v"]),
229
+ T=None,
230
+ x=not item["w"], # w=False → свеча закрыта
231
+ )
232
+ for item in sorted(
233
+ raw_msg["result"],
234
+ key=lambda x: int(x["t"]),
235
+ )
236
+ ]
237
+
238
+ @staticmethod
239
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
240
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
241
+
242
+ Параметры:
243
+ raw_msg (Any): Сырое сообщение с вебсокета.
244
+
245
+ Возвращает:
246
+ list[TradeDict]: Список сделок в унифицированном формате.
247
+ """
248
+ trade = raw_msg["result"]
249
+ return [
250
+ TradeDict(
251
+ t=trade["create_time_ms"],
252
+ s=trade["currency_pair"],
253
+ S=trade["side"].upper(),
254
+ p=float(trade["price"]),
255
+ v=float(trade["amount"]),
256
+ )
257
+ ]
258
+
259
+ @staticmethod
260
+ def futures_trades_message(raw_msg: Any) -> list[TradeDict]:
261
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
262
+
263
+ Параметры:
264
+ raw_msg (Any): Сырое сообщение с вебсокета.
265
+
266
+ Возвращает:
267
+ list[TradeDict]: Список сделок в унифицированном формате.
268
+ """
269
+ return [
270
+ TradeDict(
271
+ t=item["create_time_ms"],
272
+ s=item["contract"],
273
+ S="BUY" if float(item["size"]) > 0 else "SELL",
274
+ p=float(item["price"]),
275
+ v=abs(float(item["size"])),
276
+ )
277
+ for item in sorted(
278
+ raw_msg["result"],
279
+ key=lambda x: x["create_time_ms"],
280
+ )
281
+ ]
@@ -5,7 +5,7 @@ from typing import Any, overload
5
5
 
6
6
  from unicex._abc import IUniWebsocketManager
7
7
  from unicex._base import Websocket
8
- from unicex.enums import Timeframe
8
+ from unicex.enums import Exchange, MarketType, Timeframe
9
9
  from unicex.types import LoggerLike
10
10
 
11
11
  from .adapter import Adapter
@@ -36,6 +36,20 @@ class UniWebsocketManager(IUniWebsocketManager):
36
36
  self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
37
37
  self._adapter = Adapter()
38
38
 
39
+ def _normalize_symbols(
40
+ self,
41
+ symbol: str | None,
42
+ symbols: Sequence[str] | None,
43
+ ) -> list[str]:
44
+ """Преобразует параметры symbol/symbols в список тикеров."""
45
+ if symbol and symbols:
46
+ raise ValueError("Parameters symbol and symbols cannot be used together")
47
+ if symbol:
48
+ return [symbol]
49
+ if symbols:
50
+ return list(symbols)
51
+ raise ValueError("Either symbol or symbols must be provided")
52
+
39
53
  @overload
40
54
  def klines(
41
55
  self,
@@ -76,7 +90,14 @@ class UniWebsocketManager(IUniWebsocketManager):
76
90
  Возвращает:
77
91
  `Websocket`: Экземпляр вебсокета для управления соединением.
78
92
  """
79
- raise NotImplementedError()
93
+ tickers = self._normalize_symbols(symbol, symbols)
94
+
95
+ wrapper = self._make_wrapper(self._adapter.klines_message, callback)
96
+ return self._websocket_manager.candlesticks(
97
+ callback=wrapper,
98
+ interval=timeframe.to_exchange_format(Exchange.GATE, MarketType.SPOT),
99
+ symbols=tickers,
100
+ )
80
101
 
81
102
  @overload
82
103
  def futures_klines(
@@ -118,7 +139,14 @@ class UniWebsocketManager(IUniWebsocketManager):
118
139
  Возвращает:
119
140
  `Websocket`: Экземпляр вебсокета.
120
141
  """
121
- raise NotImplementedError()
142
+ tickers = self._normalize_symbols(symbol, symbols)
143
+
144
+ wrapper = self._make_wrapper(self._adapter.futures_klines_message, callback)
145
+ return self._websocket_manager.futures_candlesticks(
146
+ callback=wrapper,
147
+ interval=timeframe.to_exchange_format(Exchange.GATE, MarketType.FUTURES),
148
+ symbols=tickers,
149
+ )
122
150
 
123
151
  @overload
124
152
  def trades(
@@ -156,7 +184,10 @@ class UniWebsocketManager(IUniWebsocketManager):
156
184
  Возвращает:
157
185
  `Websocket`: Экземпляр вебсокета.
158
186
  """
159
- raise NotImplementedError()
187
+ tickers = self._normalize_symbols(symbol, symbols)
188
+
189
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
190
+ return self._websocket_manager.trades(callback=wrapper, symbols=tickers)
160
191
 
161
192
  @overload
162
193
  def aggtrades(
@@ -194,7 +225,7 @@ class UniWebsocketManager(IUniWebsocketManager):
194
225
  Возвращает:
195
226
  `Websocket`: Экземпляр вебсокета.
196
227
  """
197
- raise NotImplementedError()
228
+ return self.trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
198
229
 
199
230
  @overload
200
231
  def futures_trades(
@@ -232,7 +263,10 @@ class UniWebsocketManager(IUniWebsocketManager):
232
263
  Возвращает:
233
264
  `Websocket`: Экземпляр вебсокета.
234
265
  """
235
- raise NotImplementedError()
266
+ tickers = self._normalize_symbols(symbol, symbols)
267
+
268
+ wrapper = self._make_wrapper(self._adapter.futures_trades_message, callback)
269
+ return self._websocket_manager.futures_trades(callback=wrapper, symbols=tickers)
236
270
 
237
271
  @overload
238
272
  def futures_aggtrades(
@@ -270,4 +304,4 @@ class UniWebsocketManager(IUniWebsocketManager):
270
304
  Возвращает:
271
305
  `Websocket`: Экземпляр вебсокета.
272
306
  """
273
- raise NotImplementedError()
307
+ return self.futures_trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]