unicex 0.13.17__py3-none-any.whl → 0.16.5__py3-none-any.whl

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 (63) hide show
  1. unicex/__init__.py +36 -2
  2. unicex/_abc/exchange_info.py +1 -1
  3. unicex/_abc/uni_client.py +31 -1
  4. unicex/_abc/uni_websocket_manager.py +1 -1
  5. unicex/_base/client.py +7 -0
  6. unicex/_base/websocket.py +31 -9
  7. unicex/binance/adapter.py +7 -16
  8. unicex/binance/client.py +25 -25
  9. unicex/binance/uni_websocket_manager.py +6 -2
  10. unicex/bingx/__init__.py +27 -0
  11. unicex/bingx/adapter.py +284 -0
  12. unicex/bingx/client.py +521 -0
  13. unicex/bingx/exchange_info.py +22 -0
  14. unicex/bingx/uni_client.py +191 -0
  15. unicex/bingx/uni_websocket_manager.py +283 -0
  16. unicex/bingx/user_websocket.py +7 -0
  17. unicex/bingx/websocket_manager.py +118 -0
  18. unicex/bitget/adapter.py +2 -2
  19. unicex/bitget/client.py +64 -64
  20. unicex/bitget/uni_websocket_manager.py +27 -6
  21. unicex/bitget/websocket_manager.py +2 -4
  22. unicex/bybit/adapter.py +1 -0
  23. unicex/bybit/client.py +4 -4
  24. unicex/bybit/exchange_info.py +1 -1
  25. unicex/bybit/uni_websocket_manager.py +33 -4
  26. unicex/bybit/websocket_manager.py +8 -24
  27. unicex/enums.py +19 -3
  28. unicex/extra.py +37 -35
  29. unicex/gate/adapter.py +113 -0
  30. unicex/gate/client.py +9 -9
  31. unicex/gate/uni_client.py +1 -3
  32. unicex/gate/uni_websocket_manager.py +47 -9
  33. unicex/hyperliquid/adapter.py +1 -0
  34. unicex/hyperliquid/client.py +12 -12
  35. unicex/hyperliquid/uni_client.py +4 -7
  36. unicex/hyperliquid/uni_websocket_manager.py +6 -2
  37. unicex/kucoin/__init__.py +27 -0
  38. unicex/kucoin/adapter.py +181 -0
  39. unicex/kucoin/client.py +135 -0
  40. unicex/kucoin/exchange_info.py +50 -0
  41. unicex/kucoin/uni_client.py +208 -0
  42. unicex/kucoin/uni_websocket_manager.py +273 -0
  43. unicex/kucoin/user_websocket.py +7 -0
  44. unicex/kucoin/websocket_manager.py +11 -0
  45. unicex/mapper.py +62 -21
  46. unicex/mexc/adapter.py +104 -0
  47. unicex/mexc/client.py +7 -7
  48. unicex/mexc/uni_client.py +1 -3
  49. unicex/mexc/uni_websocket_manager.py +31 -9
  50. unicex/mexc/websocket_manager.py +27 -6
  51. unicex/okx/adapter.py +51 -0
  52. unicex/okx/client.py +15 -15
  53. unicex/okx/exchange_info.py +2 -2
  54. unicex/okx/uni_websocket_manager.py +50 -9
  55. unicex/okx/websocket_manager.py +119 -166
  56. unicex/types.py +14 -10
  57. unicex/utils.py +44 -1
  58. {unicex-0.13.17.dist-info → unicex-0.16.5.dist-info}/METADATA +7 -5
  59. unicex-0.16.5.dist-info/RECORD +109 -0
  60. unicex-0.13.17.dist-info/RECORD +0 -93
  61. {unicex-0.13.17.dist-info → unicex-0.16.5.dist-info}/WHEEL +0 -0
  62. {unicex-0.13.17.dist-info → unicex-0.16.5.dist-info}/licenses/LICENSE +0 -0
  63. {unicex-0.13.17.dist-info → unicex-0.16.5.dist-info}/top_level.txt +0 -0
unicex/extra.py CHANGED
@@ -125,6 +125,10 @@ def normalize_ticker(raw_ticker: str) -> str:
125
125
  .removesuffix("SWAP")
126
126
  )
127
127
 
128
+ # Меняем постфикс USDTM на USDT
129
+ if ticker.endswith("USDTM"):
130
+ ticker = ticker.removesuffix("USDTM") + "USDT"
131
+
128
132
  # Удаляем разделители
129
133
  ticker = ticker.translate(str.maketrans("", "", "-_."))
130
134
 
@@ -149,6 +153,7 @@ def normalize_symbol(raw_ticker: str, quote: Literal["USDT", "USDC"] = "USDT") -
149
153
  normalize_symbol("BTC") # "BTCUSDT"
150
154
  normalize_symbol("btc_usdt_swap") # "BTCUSDT"
151
155
  normalize_symbol("ETH", "USDC") # "ETHUSDC"
156
+ normalize_symbol("BTCUSDTM") # "BTCUSDT"
152
157
  ```
153
158
 
154
159
  Параметры:
@@ -174,23 +179,22 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
174
179
  Возвращает:
175
180
  `str`: Ссылка на биржу.
176
181
  """
177
- symbol = normalize_symbol(symbol)
178
182
  ticker = normalize_ticker(symbol)
179
183
  if exchange == Exchange.BINANCE:
180
184
  if market_type == MarketType.FUTURES:
181
- return f"https://www.binance.com/en/futures/{symbol}"
185
+ return f"https://www.binance.com/en/futures/{ticker}USDT"
182
186
  else:
183
187
  return f"https://www.binance.com/en/trade/{ticker}_USDT?type=spot"
184
188
  elif exchange == Exchange.BYBIT:
185
189
  if market_type == MarketType.FUTURES:
186
- return f"https://www.bybit.com/trade/usdt/{symbol}"
190
+ return f"https://www.bybit.com/trade/usdt/{ticker}USDT"
187
191
  else:
188
192
  return f"https://www.bybit.com/en/trade/spot/{ticker}/USDT"
189
193
  elif exchange == Exchange.BITGET:
190
194
  if market_type == MarketType.FUTURES:
191
- return f"https://www.bitget.com/ru/futures/usdt/{symbol}"
195
+ return f"https://www.bitget.com/ru/futures/usdt/{ticker}USDT"
192
196
  else:
193
- return f"https://www.bitget.com/ru/spot/{symbol}"
197
+ return f"https://www.bitget.com/ru/spot/{ticker}USDT"
194
198
  elif exchange == Exchange.OKX:
195
199
  if market_type == MarketType.FUTURES:
196
200
  return f"https://www.okx.com/ru/trade-swap/{ticker.lower()}-usdt-swap"
@@ -206,26 +210,21 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
206
210
  return f"https://www.gate.com/ru/futures/USDT/{ticker}_USDT"
207
211
  else:
208
212
  return f"https://www.gate.com/ru/trade/{ticker}_USDT"
209
- elif exchange == Exchange.XT:
210
- if market_type == MarketType.FUTURES:
211
- return f"https://www.xt.com/ru/futures/trade/{ticker.lower()}_usdt"
212
- else:
213
- return f"https://www.xt.com/ru/trade/{ticker.lower()}_usdt"
214
- elif exchange == Exchange.BITUNIX:
213
+ elif exchange == Exchange.HYPERLIQUID:
215
214
  if market_type == MarketType.FUTURES:
216
- return f"https://www.bitunix.com/ru-ru/contract-trade/{ticker.upper()}USDT"
215
+ return f"https://app.hyperliquid.xyz/trade/{ticker}"
217
216
  else:
218
- return f"https://www.bitunix.com/ru-ru/spot-trade/{ticker.upper()}USDT"
219
- elif exchange == Exchange.KCEX:
217
+ return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
218
+ elif exchange == Exchange.KUCOIN:
220
219
  if market_type == MarketType.FUTURES:
221
- return f"https://www.kcex.com/ru-RU/futures/exchange/{ticker.upper()}_USDT"
220
+ return f"https://www.kucoin.com/trade/futures/{ticker}USDTM"
222
221
  else:
223
- return f"https://www.kcex.com/ru-RU/exchange/{ticker.upper()}_USDT"
224
- elif exchange == Exchange.HYPERLIQUID:
222
+ return f"https://www.kucoin.com/trade/{ticker}-USDT"
223
+ elif exchange == Exchange.BINGX:
225
224
  if market_type == MarketType.FUTURES:
226
- return f"https://app.hyperliquid.xyz/trade/{ticker}"
225
+ return f"https://bingx.com/en/perpetual/{ticker}-USDT"
227
226
  else:
228
- return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
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,32 +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')}"
277
- case Exchange.BITUNIX:
278
- return f"{base_url}/Bitunix_{symbol}"
274
+ return f"{base_url}/Gate_{ticker}_USDT"
279
275
  case Exchange.HYPERLIQUID:
280
- return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
276
+ return f"{base_url}/Hyperliquid_{ticker}-USD"
277
+ case Exchange.KUCOIN:
278
+ return f"https://www.coinglass.com/tv/ru/KuCoin_{ticker}USDTM"
279
+ case Exchange.BINGX:
280
+ return f"https://www.coinglass.com/tv/ru/BingX_{ticker}-USDT"
281
281
  case _:
282
- return f"{base_url}/{exchange.capitalize()}_{symbol}"
282
+ return f"{base_url}/{exchange.capitalize()}_{ticker}USDT"
283
283
  else:
284
- # Для спота корректная ссылка есть только у OKX
285
- if exchange == Exchange.OKX:
286
- return f"{base_url}/SPOT_{exchange.upper()}_{symbol.replace('USDT', '-USDT')}"
287
- # Для остальных бирж ссылки нет → возвращаем заглушку
288
- 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"
289
291
 
290
292
 
291
293
  def make_humanreadable(value: float, locale: Literal["ru", "en"] = "ru") -> str:
unicex/gate/adapter.py CHANGED
@@ -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,9 +11,12 @@ 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
 
18
+ from .exchange_info import ExchangeInfo
19
+
16
20
 
17
21
  @decorate_all_methods(catch_adapter_errors)
18
22
  class Adapter:
@@ -173,6 +177,115 @@ class Adapter:
173
177
  item["contract"]: OpenInterestItem(
174
178
  t=int(time.time() * 1000),
175
179
  v=float(item["total_size"]) * float(item["quanto_multiplier"]),
180
+ u="coins",
176
181
  )
177
182
  for item in raw_data
178
183
  }
184
+
185
+ @staticmethod
186
+ def klines_message(raw_msg: Any) -> list[KlineDict]:
187
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
188
+
189
+ Параметры:
190
+ raw_msg (Any): Сырое сообщение с вебсокета.
191
+
192
+ Возвращает:
193
+ list[KlineDict]: Список свечей в унифицированном формате.
194
+ """
195
+ data = raw_msg["result"]
196
+ return [
197
+ KlineDict(
198
+ s=data["n"].split("_", 1)[1], # XRP_USDT
199
+ t=int(data["t"]) * 1000,
200
+ o=float(data["o"]),
201
+ h=float(data["h"]),
202
+ l=float(data["l"]),
203
+ c=float(data["c"]),
204
+ v=float(data["a"]),
205
+ q=float(data["v"]),
206
+ T=None,
207
+ x=not data["w"], # w=False → свеча закрыта
208
+ )
209
+ ]
210
+
211
+ @staticmethod
212
+ def futures_klines_message(raw_msg: Any) -> list[KlineDict]:
213
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
214
+
215
+ Параметры:
216
+ raw_msg (Any): Сырое сообщение с вебсокета.
217
+
218
+ Возвращает:
219
+ list[KlineDict]: Список свечей в унифицированном формате.
220
+ """
221
+ return [
222
+ KlineDict(
223
+ s=item["n"].split("_", 1)[1], # XRP_USDT
224
+ t=int(item["t"]) * 1000,
225
+ o=float(item["o"]),
226
+ h=float(item["h"]),
227
+ l=float(item["l"]),
228
+ c=float(item["c"]),
229
+ v=float(item["a"]),
230
+ q=float(item["v"]),
231
+ T=None,
232
+ x=not item["w"], # w=False → свеча закрыта
233
+ )
234
+ for item in sorted(
235
+ raw_msg["result"],
236
+ key=lambda x: int(x["t"]),
237
+ )
238
+ ]
239
+
240
+ @staticmethod
241
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
242
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
243
+
244
+ Параметры:
245
+ raw_msg (Any): Сырое сообщение с вебсокета.
246
+
247
+ Возвращает:
248
+ list[TradeDict]: Список сделок в унифицированном формате.
249
+ """
250
+ trade = raw_msg["result"]
251
+ return [
252
+ TradeDict(
253
+ t=trade["create_time_ms"],
254
+ s=trade["currency_pair"],
255
+ S=trade["side"].upper(),
256
+ p=float(trade["price"]),
257
+ v=float(trade["amount"]),
258
+ )
259
+ ]
260
+
261
+ @staticmethod
262
+ def futures_trades_message(raw_msg: Any) -> list[TradeDict]:
263
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
264
+
265
+ Параметры:
266
+ raw_msg (Any): Сырое сообщение с вебсокета.
267
+
268
+ Возвращает:
269
+ list[TradeDict]: Список сделок в унифицированном формате.
270
+ """
271
+ return [
272
+ TradeDict(
273
+ t=item["create_time_ms"],
274
+ s=item["contract"],
275
+ S="BUY" if float(item["size"]) > 0 else "SELL",
276
+ p=float(item["price"]),
277
+ v=abs(float(item["size"])) * Adapter._get_contract_size(item["contract"]),
278
+ )
279
+ for item in sorted(
280
+ raw_msg["result"],
281
+ key=lambda x: x["create_time_ms"],
282
+ )
283
+ ]
284
+
285
+ @staticmethod
286
+ def _get_contract_size(symbol: str) -> float:
287
+ """Возвращает размер контракта для указанного символа тикера."""
288
+ try:
289
+ return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
290
+ except: # noqa
291
+ return 1
unicex/gate/client.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any, Literal
8
8
 
9
9
  from unicex._base import BaseClient
10
10
  from unicex.exceptions import NotAuthorized
11
- from unicex.types import RequestMethod
11
+ from unicex.types import NumberLike, RequestMethod
12
12
  from unicex.utils import dict_to_query_string, filter_params
13
13
 
14
14
 
@@ -328,8 +328,8 @@ class Client(BaseClient):
328
328
  async def cross_liquidate_orders(
329
329
  self,
330
330
  currency_pair: str,
331
- amount: str,
332
- price: str,
331
+ amount: NumberLike,
332
+ price: NumberLike,
333
333
  text: str | None = None,
334
334
  action_mode: str | None = None,
335
335
  ) -> dict:
@@ -353,11 +353,11 @@ class Client(BaseClient):
353
353
  self,
354
354
  currency_pair: str,
355
355
  side: str,
356
- amount: str,
356
+ amount: NumberLike,
357
357
  text: str | None = None,
358
358
  type: str | None = None,
359
359
  account: str | None = None,
360
- price: str | None = None,
360
+ price: NumberLike | None = None,
361
361
  time_in_force: str | None = None,
362
362
  iceberg: str | None = None,
363
363
  auto_borrow: bool | None = None,
@@ -479,8 +479,8 @@ class Client(BaseClient):
479
479
  order_id: str,
480
480
  currency_pair: str | None = None,
481
481
  account: str | None = None,
482
- amount: str | None = None,
483
- price: str | None = None,
482
+ amount: NumberLike | None = None,
483
+ price: NumberLike | None = None,
484
484
  amend_text: str | None = None,
485
485
  action_mode: str | None = None,
486
486
  ) -> dict:
@@ -1344,8 +1344,8 @@ class Client(BaseClient):
1344
1344
  self,
1345
1345
  settle: str,
1346
1346
  order_id: str,
1347
- size: int | None = None,
1348
- price: str | None = None,
1347
+ size: NumberLike | None = None,
1348
+ price: NumberLike | None = None,
1349
1349
  amend_text: str | None = None,
1350
1350
  text: str | None = None,
1351
1351
  ) -> dict:
unicex/gate/uni_client.py CHANGED
@@ -173,9 +173,7 @@ class UniClient(IUniClient[Client]):
173
173
  raw_data = await self._client.futures_tickers(settle="usdt", contract=symbol)
174
174
  items = raw_data if isinstance(raw_data, list) else [raw_data]
175
175
  adapted_data = Adapter.funding_rate(raw_data=items) # type: ignore[reportArgumentType]
176
- if symbol:
177
- return adapted_data[symbol]
178
- return adapted_data
176
+ return adapted_data[symbol] if symbol else adapted_data
179
177
 
180
178
  @overload
181
179
  async def open_interest(self, symbol: str) -> OpenInterestItem: ...
@@ -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
@@ -20,18 +20,36 @@ class UniWebsocketManager(IUniWebsocketManager):
20
20
  """Реализация менеджера асинхронных унифицированных вебсокетов."""
21
21
 
22
22
  def __init__(
23
- self, client: Client | UniClient | None = None, logger: LoggerLike | None = None
23
+ self,
24
+ client: Client | UniClient | None = None,
25
+ logger: LoggerLike | None = None,
26
+ **ws_kwargs: Any,
24
27
  ) -> None:
25
28
  """Инициализирует унифицированный менеджер вебсокетов.
26
29
 
27
30
  Параметры:
28
31
  client (`Client | UniClient | None`): Клиент Gateio или унифицированный клиент. Нужен для подключения к приватным топикам.
29
32
  logger (`LoggerLike | None`): Логгер для записи логов.
33
+ ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
30
34
  """
31
35
  super().__init__(client=client, logger=logger)
32
- self._websocket_manager = WebsocketManager(self._client) # type: ignore
36
+ self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
33
37
  self._adapter = Adapter()
34
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
+
35
53
  @overload
36
54
  def klines(
37
55
  self,
@@ -72,7 +90,14 @@ class UniWebsocketManager(IUniWebsocketManager):
72
90
  Возвращает:
73
91
  `Websocket`: Экземпляр вебсокета для управления соединением.
74
92
  """
75
- 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
+ )
76
101
 
77
102
  @overload
78
103
  def futures_klines(
@@ -114,7 +139,14 @@ class UniWebsocketManager(IUniWebsocketManager):
114
139
  Возвращает:
115
140
  `Websocket`: Экземпляр вебсокета.
116
141
  """
117
- 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
+ )
118
150
 
119
151
  @overload
120
152
  def trades(
@@ -152,7 +184,10 @@ class UniWebsocketManager(IUniWebsocketManager):
152
184
  Возвращает:
153
185
  `Websocket`: Экземпляр вебсокета.
154
186
  """
155
- 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)
156
191
 
157
192
  @overload
158
193
  def aggtrades(
@@ -190,7 +225,7 @@ class UniWebsocketManager(IUniWebsocketManager):
190
225
  Возвращает:
191
226
  `Websocket`: Экземпляр вебсокета.
192
227
  """
193
- raise NotImplementedError()
228
+ return self.trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
194
229
 
195
230
  @overload
196
231
  def futures_trades(
@@ -228,7 +263,10 @@ class UniWebsocketManager(IUniWebsocketManager):
228
263
  Возвращает:
229
264
  `Websocket`: Экземпляр вебсокета.
230
265
  """
231
- 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)
232
270
 
233
271
  @overload
234
272
  def futures_aggtrades(
@@ -266,4 +304,4 @@ class UniWebsocketManager(IUniWebsocketManager):
266
304
  Возвращает:
267
305
  `Websocket`: Экземпляр вебсокета.
268
306
  """
269
- raise NotImplementedError()
307
+ return self.futures_trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
@@ -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
  }
@@ -13,7 +13,7 @@ from eth_utils.crypto import keccak
13
13
 
14
14
  from unicex._base import BaseClient
15
15
  from unicex.exceptions import NotAuthorized
16
- from unicex.types import LoggerLike
16
+ from unicex.types import LoggerLike, NumberLike
17
17
  from unicex.utils import filter_params
18
18
 
19
19
  # Authentication
@@ -1184,11 +1184,11 @@ class Client(BaseClient):
1184
1184
  self,
1185
1185
  asset: int,
1186
1186
  is_buy: bool,
1187
- size: str,
1187
+ size: NumberLike,
1188
1188
  reduce_only: bool,
1189
1189
  order_type: Literal["limit", "trigger"],
1190
1190
  order_body: dict,
1191
- price: str | None = None,
1191
+ price: NumberLike | None = None,
1192
1192
  client_order_id: str | None = None,
1193
1193
  grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
1194
1194
  builder_address: str | None = None,
@@ -1478,8 +1478,8 @@ class Client(BaseClient):
1478
1478
  order_id: int | str,
1479
1479
  asset: int,
1480
1480
  is_buy: bool,
1481
- price: str | float,
1482
- size: str | float,
1481
+ price: NumberLike,
1482
+ size: NumberLike,
1483
1483
  reduce_only: bool,
1484
1484
  order_type: Literal["limit", "trigger"],
1485
1485
  order_body: dict[str, Any],
@@ -1668,7 +1668,7 @@ class Client(BaseClient):
1668
1668
  hyperliquid_chain: Literal["Mainnet", "Testnet"],
1669
1669
  signature_chain_id: str,
1670
1670
  destination: str,
1671
- amount: str,
1671
+ amount: NumberLike,
1672
1672
  time_ms: int,
1673
1673
  nonce: int | None = None,
1674
1674
  ) -> dict:
@@ -1711,7 +1711,7 @@ class Client(BaseClient):
1711
1711
  signature_chain_id: str,
1712
1712
  destination: str,
1713
1713
  token: str,
1714
- amount: str,
1714
+ amount: NumberLike,
1715
1715
  time_ms: int,
1716
1716
  nonce: int | None = None,
1717
1717
  ) -> dict:
@@ -1753,7 +1753,7 @@ class Client(BaseClient):
1753
1753
  self,
1754
1754
  hyperliquid_chain: Literal["Mainnet", "Testnet"],
1755
1755
  signature_chain_id: str,
1756
- amount: str,
1756
+ amount: NumberLike,
1757
1757
  time_ms: int,
1758
1758
  destination: str,
1759
1759
  nonce: int | None = None,
@@ -1795,7 +1795,7 @@ class Client(BaseClient):
1795
1795
  self,
1796
1796
  hyperliquid_chain: Literal["Mainnet", "Testnet"],
1797
1797
  signature_chain_id: str,
1798
- amount: str,
1798
+ amount: NumberLike,
1799
1799
  to_perp: bool,
1800
1800
  subaccount: str | None = None,
1801
1801
  ) -> dict:
@@ -1845,7 +1845,7 @@ class Client(BaseClient):
1845
1845
  source_dex: str,
1846
1846
  destination_dex: str,
1847
1847
  token: str,
1848
- amount: str,
1848
+ amount: NumberLike,
1849
1849
  from_subaccount: str,
1850
1850
  nonce_value: int,
1851
1851
  nonce: int | None = None,
@@ -2015,7 +2015,7 @@ class Client(BaseClient):
2015
2015
  self,
2016
2016
  vault_address: str,
2017
2017
  is_deposit: bool,
2018
- usd: int,
2018
+ usd: NumberLike,
2019
2019
  nonce: int | None = None,
2020
2020
  expires_after: int | None = None,
2021
2021
  signing_vault_address: str | None = None,
@@ -2147,7 +2147,7 @@ class Client(BaseClient):
2147
2147
  self,
2148
2148
  asset: int,
2149
2149
  is_buy: bool,
2150
- size: str | float,
2150
+ size: NumberLike,
2151
2151
  reduce_only: bool,
2152
2152
  minutes: int,
2153
2153
  randomize: bool,
@@ -1,6 +1,5 @@
1
1
  __all__ = ["UniClient"]
2
2
 
3
- import time
4
3
  from typing import Self, overload
5
4
 
6
5
  import aiohttp
@@ -217,15 +216,14 @@ class UniClient(IUniClient[Client]):
217
216
  list[KlineDict]: Список свечей для тикера.
218
217
  """
219
218
  if not limit and not all([start_time, end_time]):
220
- raise ValueError("limit and (start_time and end_time) must be provided")
219
+ raise ValueError("limit or (start_time and end_time) must be provided")
221
220
 
222
221
  if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
223
222
  if not isinstance(interval, Timeframe):
224
223
  raise ValueError("interval must be a Timeframe if limit param provided")
225
- end_time = int(time.time() * 1000)
226
- start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
224
+ start_time, end_time = self.limit_to_start_and_end_time(interval, limit)
227
225
  interval = (
228
- interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.SPOT)
226
+ interval.to_exchange_format(Exchange.HYPERLIQUID)
229
227
  if isinstance(interval, Timeframe)
230
228
  else interval
231
229
  )
@@ -263,8 +261,7 @@ class UniClient(IUniClient[Client]):
263
261
  if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
264
262
  if not isinstance(interval, Timeframe):
265
263
  raise ValueError("interval must be a Timeframe if limit param provided")
266
- end_time = int(time.time() * 1000)
267
- start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
264
+ start_time, end_time = self.limit_to_start_and_end_time(interval, limit)
268
265
  interval = (
269
266
  interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.FUTURES)
270
267
  if isinstance(interval, Timeframe)
@@ -20,16 +20,20 @@ class UniWebsocketManager(IUniWebsocketManager):
20
20
  """Реализация менеджера асинхронных унифицированных вебсокетов."""
21
21
 
22
22
  def __init__(
23
- self, client: Client | UniClient | None = None, logger: LoggerLike | None = None
23
+ self,
24
+ client: Client | UniClient | None = None,
25
+ logger: LoggerLike | None = None,
26
+ **ws_kwargs: Any,
24
27
  ) -> None:
25
28
  """Инициализирует унифицированный менеджер вебсокетов.
26
29
 
27
30
  Параметры:
28
31
  client (`Client | UniClient | None`): Клиент Hyperliquid или унифицированный клиент. Нужен для подключения к приватным топикам.
29
32
  logger (`LoggerLike | None`): Логгер для записи логов.
33
+ ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
30
34
  """
31
35
  super().__init__(client=client, logger=logger)
32
- self._websocket_manager = WebsocketManager(self._client) # type: ignore
36
+ self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
33
37
  self._adapter = Adapter()
34
38
 
35
39
  @overload
@@ -0,0 +1,27 @@
1
+ """Пакет, содержащий реализации клиентов и менеджеров для работы с биржей Kucoin."""
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
+ """Загружает информацию о бирже Kucoin."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Kucoin."""
27
+ await ExchangeInfo.start(parse_interval_seconds)