unicex 0.15.2__tar.gz → 0.15.4__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.15.4}/PKG-INFO +1 -1
  2. {unicex-0.15.2 → unicex-0.15.4}/pyproject.toml +1 -1
  3. {unicex-0.15.2 → unicex-0.15.4}/unicex/_abc/exchange_info.py +1 -1
  4. {unicex-0.15.2 → unicex-0.15.4}/unicex/_abc/uni_client.py +1 -1
  5. {unicex-0.15.2 → unicex-0.15.4}/unicex/_abc/uni_websocket_manager.py +1 -1
  6. {unicex-0.15.2 → unicex-0.15.4}/unicex/_base/client.py +7 -0
  7. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/adapter.py +5 -1
  8. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/adapter.py +1 -1
  9. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/websocket_manager.py +2 -4
  10. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/adapter.py +1 -0
  11. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/websocket_manager.py +2 -4
  12. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/adapter.py +1 -0
  13. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/websocket_manager.py +7 -24
  14. {unicex-0.15.2 → unicex-0.15.4}/unicex/extra.py +23 -23
  15. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/adapter.py +1 -0
  16. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/adapter.py +1 -0
  17. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/adapter.py +4 -9
  18. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/client.py +13 -0
  19. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/uni_client.py +2 -14
  20. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/__init__.py +335 -335
  21. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/adapter.py +1 -0
  22. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/websocket_manager.py +2 -4
  23. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/adapter.py +1 -0
  24. {unicex-0.15.2 → unicex-0.15.4}/unicex/types.py +4 -1
  25. {unicex-0.15.2 → unicex-0.15.4}/unicex/utils.py +22 -1
  26. {unicex-0.15.2 → unicex-0.15.4/unicex.egg-info}/PKG-INFO +1 -1
  27. {unicex-0.15.2 → unicex-0.15.4}/LICENSE +0 -0
  28. {unicex-0.15.2 → unicex-0.15.4}/README.md +0 -0
  29. {unicex-0.15.2 → unicex-0.15.4}/setup.cfg +0 -0
  30. {unicex-0.15.2 → unicex-0.15.4}/unicex/__init__.py +0 -0
  31. {unicex-0.15.2 → unicex-0.15.4}/unicex/_abc/__init__.py +0 -0
  32. {unicex-0.15.2 → unicex-0.15.4}/unicex/_base/__init__.py +0 -0
  33. {unicex-0.15.2 → unicex-0.15.4}/unicex/_base/websocket.py +0 -0
  34. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/__init__.py +0 -0
  35. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/client.py +0 -0
  36. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/exchange_info.py +0 -0
  37. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/uni_client.py +0 -0
  38. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/uni_websocket_manager.py +0 -0
  39. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/user_websocket.py +0 -0
  40. {unicex-0.15.2 → unicex-0.15.4}/unicex/binance/websocket_manager.py +0 -0
  41. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/__init__.py +0 -0
  42. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/client.py +0 -0
  43. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/exchange_info.py +0 -0
  44. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/uni_client.py +0 -0
  45. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/uni_websocket_manager.py +0 -0
  46. {unicex-0.15.2 → unicex-0.15.4}/unicex/bingx/user_websocket.py +0 -0
  47. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/__init__.py +0 -0
  48. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/client.py +0 -0
  49. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/exchange_info.py +0 -0
  50. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/uni_client.py +0 -0
  51. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/uni_websocket_manager.py +0 -0
  52. {unicex-0.15.2 → unicex-0.15.4}/unicex/bitget/user_websocket.py +0 -0
  53. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/__init__.py +0 -0
  54. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/client.py +0 -0
  55. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/exchange_info.py +0 -0
  56. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/uni_client.py +0 -0
  57. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/uni_websocket_manager.py +0 -0
  58. {unicex-0.15.2 → unicex-0.15.4}/unicex/bybit/user_websocket.py +0 -0
  59. {unicex-0.15.2 → unicex-0.15.4}/unicex/enums.py +0 -0
  60. {unicex-0.15.2 → unicex-0.15.4}/unicex/exceptions.py +0 -0
  61. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/__init__.py +0 -0
  62. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/client.py +0 -0
  63. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/exchange_info.py +0 -0
  64. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/uni_client.py +0 -0
  65. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/uni_websocket_manager.py +0 -0
  66. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/user_websocket.py +0 -0
  67. {unicex-0.15.2 → unicex-0.15.4}/unicex/gate/websocket_manager.py +0 -0
  68. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/__init__.py +0 -0
  69. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/client.py +0 -0
  70. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/exchange_info.py +0 -0
  71. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/uni_client.py +0 -0
  72. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
  73. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/user_websocket.py +0 -0
  74. {unicex-0.15.2 → unicex-0.15.4}/unicex/hyperliquid/websocket_manager.py +0 -0
  75. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/__init__.py +0 -0
  76. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/exchange_info.py +0 -0
  77. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/uni_websocket_manager.py +0 -0
  78. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/user_websocket.py +0 -0
  79. {unicex-0.15.2 → unicex-0.15.4}/unicex/kucoin/websocket_manager.py +0 -0
  80. {unicex-0.15.2 → unicex-0.15.4}/unicex/mapper.py +0 -0
  81. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/__init__.py +0 -0
  82. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +0 -0
  83. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +0 -0
  84. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +0 -0
  85. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +0 -0
  86. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +0 -0
  87. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +0 -0
  88. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +0 -0
  89. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +0 -0
  90. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +0 -0
  91. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +0 -0
  92. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +0 -0
  93. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +0 -0
  94. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +0 -0
  95. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +0 -0
  96. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +0 -0
  97. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +0 -0
  98. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +0 -0
  99. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/client.py +0 -0
  100. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/exchange_info.py +0 -0
  101. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/uni_client.py +0 -0
  102. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/uni_websocket_manager.py +0 -0
  103. {unicex-0.15.2 → unicex-0.15.4}/unicex/mexc/user_websocket.py +0 -0
  104. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/__init__.py +0 -0
  105. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/client.py +0 -0
  106. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/exchange_info.py +0 -0
  107. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/uni_client.py +0 -0
  108. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/uni_websocket_manager.py +0 -0
  109. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/user_websocket.py +0 -0
  110. {unicex-0.15.2 → unicex-0.15.4}/unicex/okx/websocket_manager.py +0 -0
  111. {unicex-0.15.2 → unicex-0.15.4}/unicex.egg-info/SOURCES.txt +0 -0
  112. {unicex-0.15.2 → unicex-0.15.4}/unicex.egg-info/dependency_links.txt +0 -0
  113. {unicex-0.15.2 → unicex-0.15.4}/unicex.egg-info/requires.txt +0 -0
  114. {unicex-0.15.2 → unicex-0.15.4}/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.15.4
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -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.15.4"
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]:
@@ -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
  }
@@ -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:
@@ -173,6 +173,7 @@ class Adapter:
173
173
  item["contract"]: OpenInterestItem(
174
174
  t=int(time.time() * 1000),
175
175
  v=float(item["total_size"]) * float(item["quanto_multiplier"]),
176
+ u="coins",
176
177
  )
177
178
  for item in raw_data
178
179
  }
@@ -256,6 +256,7 @@ class Adapter:
256
256
  universe[i]["name"]: OpenInterestItem(
257
257
  t=int(time.time() * 1000),
258
258
  v=float(item["openInterest"]),
259
+ u="coins",
259
260
  )
260
261
  for i, item in enumerate(metrics)
261
262
  }
@@ -126,27 +126,22 @@ class Adapter:
126
126
  item["symbol"]: OpenInterestItem(
127
127
  t=item["ts"],
128
128
  v=float(item["openInterest"]) * Adapter._get_contract_size(item["symbol"]),
129
+ u="coins",
129
130
  )
130
131
  for item in raw_data["data"]
131
132
  }
132
133
 
133
134
  @staticmethod
134
- def funding_rate(raw_data: dict) -> dict[str, float]:
135
+ def funding_rate(raw_data: dict) -> float:
135
136
  """Преобразует историю ставок финансирования в унифицированный формат.
136
137
 
137
138
  Параметры:
138
139
  raw_data (dict): Сырой ответ с биржи.
139
140
 
140
141
  Возвращает:
141
- dict[str, float]: Словарь, где ключ - тикер, а значение - актуальная ставка финансирования.
142
+ float: Актуальная ставка финансирования.
142
143
  """
143
- symbol = raw_data["data"]["symbol"]
144
- history = raw_data["data"]["list"]
145
- if not history:
146
- return {}
147
-
148
- last_point = max(history, key=lambda item: int(item["ts"]))
149
- return {symbol: float(last_point["fundingRate"]) * 100}
144
+ return round(raw_data["data"]["nextFundingRate"] * 100, 6)
150
145
 
151
146
  @staticmethod
152
147
  def _get_contract_size(symbol: str) -> float:
@@ -120,3 +120,16 @@ class Client(BaseClient):
120
120
  "/api/ua/v1/market/funding-rate-history",
121
121
  params=params,
122
122
  )
123
+
124
+ async def funding_rate(self, symbol: str) -> dict[str, Any]:
125
+ """Получение текущей ставки финансирования.
126
+
127
+ https://www.kucoin.com/docs-new/rest/ua/get-current-funding-rate
128
+ """
129
+ params = {"symbol": symbol}
130
+
131
+ return await self._make_request(
132
+ "GET",
133
+ "/api/ua/v1/market/funding-rate",
134
+ params=params,
135
+ )
@@ -1,7 +1,6 @@
1
1
  __all__ = ["UniClient"]
2
2
 
3
3
 
4
- import time
5
4
  from typing import overload
6
5
 
7
6
  from unicex._abc import IUniClient
@@ -181,19 +180,8 @@ class UniClient(IUniClient[Client]):
181
180
  """
182
181
  if not symbol:
183
182
  raise ValueError("Symbol is required to fetch Kucoin funding rate")
184
-
185
- end_time = int(time.time() * 1000)
186
- # Kucoin публикует ставку каждые 8 часов, берем окно в 24 часа для гарантии наличия записи.
187
- start_time = end_time - 24 * 60 * 60 * 1000
188
- raw_data = await self._client.funding_rate_history(
189
- symbol=symbol,
190
- start_at=start_time,
191
- end_at=end_time,
192
- )
193
- adapted_data = Adapter.funding_rate(raw_data)
194
- if symbol not in adapted_data:
195
- raise ValueError(f"Kucoin funding rate history is empty for {symbol}")
196
- return adapted_data[symbol]
183
+ raw_data = await self._client.funding_rate(symbol=symbol)
184
+ return Adapter.funding_rate(raw_data)
197
185
 
198
186
  @overload
199
187
  async def open_interest(self, symbol: str) -> OpenInterestItem: ...