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
@@ -8,6 +8,7 @@ import orjson
8
8
  from google.protobuf.json_format import MessageToDict
9
9
 
10
10
  from unicex._base import Websocket
11
+ from unicex.utils import validate_single_symbol_args
11
12
 
12
13
  from ._spot_ws_proto import PushDataV3ApiWrapper
13
14
  from .client import Client
@@ -27,7 +28,7 @@ class WebsocketManager:
27
28
  class _MexcProtobufDecoder:
28
29
  """Класс для декодирования сообщений в формате Protobuf со спотового рынка Mexc."""
29
30
 
30
- def decode(self, message: Any) -> dict:
31
+ def decode(self, message: Any) -> dict | Literal["ping"]:
31
32
  if isinstance(message, bytes):
32
33
  wrapper = PushDataV3ApiWrapper() # noqa
33
34
  wrapper.ParseFromString(message)
@@ -55,10 +56,7 @@ class WebsocketManager:
55
56
  **template_kwargs: Any,
56
57
  ) -> list[str]:
57
58
  """Сформировать сообщение для подписки на вебсокет."""
58
- if symbol and symbols:
59
- raise ValueError("Parameters symbol and symbols cannot be used together")
60
- if not (symbol or symbols):
61
- raise ValueError("Either symbol or symbols must be provided")
59
+ validate_single_symbol_args(symbol, symbols)
62
60
 
63
61
  if symbol:
64
62
  params = [channel_template.format(symbol=symbol, **template_kwargs)]
@@ -382,7 +380,30 @@ class WebsocketManager:
382
380
  `Websocket`: Объект для управления вебсокет соединением.
383
381
  """
384
382
  subscription_messages = self._generate_futures_subscription_message(
385
- topic="sub.deal", symbol=symbol, symbols=symbols, interval=interval
383
+ topic="sub.kline", symbol=symbol, symbols=symbols, interval=interval
384
+ )
385
+ return self._create_futures_websocket(callback, subscription_messages)
386
+
387
+ def futures_trade(
388
+ self,
389
+ callback: CallbackType,
390
+ symbol: str | None = None,
391
+ symbols: Sequence[str] | None = None,
392
+ ) -> Websocket:
393
+ """Создает вебсокет для получения сделок по фьючерсным контрактам.
394
+
395
+ https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels
396
+
397
+ Параметры:
398
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
399
+ symbol (`str | None`): Символ фьючерсного контракта.
400
+ symbols (`Sequence[str] | None`): Последовательность символов фьючерсных контрактов.
401
+
402
+ Возвращает:
403
+ `Websocket`: Объект для управления вебсокет соединением.
404
+ """
405
+ subscription_messages = self._generate_futures_subscription_message(
406
+ topic="sub.deal", symbol=symbol, symbols=symbols
386
407
  )
387
408
  return self._create_futures_websocket(callback, subscription_messages)
388
409
 
unicex/okx/adapter.py CHANGED
@@ -1,12 +1,15 @@
1
1
  __all__ = ["Adapter"]
2
2
 
3
3
 
4
+ from typing import Any
5
+
4
6
  from unicex.types import (
5
7
  KlineDict,
6
8
  OpenInterestDict,
7
9
  OpenInterestItem,
8
10
  TickerDailyDict,
9
11
  TickerDailyItem,
12
+ TradeDict,
10
13
  )
11
14
  from unicex.utils import catch_adapter_errors, decorate_all_methods
12
15
 
@@ -137,10 +140,58 @@ class Adapter:
137
140
  item["instId"]: OpenInterestItem(
138
141
  t=int(item["ts"]),
139
142
  v=float(item["oiCcy"]),
143
+ u="coins",
140
144
  )
141
145
  for item in raw_data["data"]
142
146
  }
143
147
 
148
+ @staticmethod
149
+ def klines_message(raw_msg: Any) -> list[KlineDict]:
150
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
151
+
152
+ Параметры:
153
+ raw_msg (Any): Сырое сообщение с вебсокета.
154
+
155
+ Возвращает:
156
+ list[KlineDict]: Список свечей в унифицированном формате.
157
+ """
158
+ return [
159
+ KlineDict(
160
+ s=raw_msg["arg"]["instId"],
161
+ t=int(kline[0]),
162
+ o=float(kline[1]),
163
+ h=float(kline[2]),
164
+ l=float(kline[3]),
165
+ c=float(kline[4]),
166
+ v=float(kline[6]),
167
+ q=float(kline[7]),
168
+ T=None,
169
+ x=bool(int(kline[8])),
170
+ )
171
+ for kline in sorted(raw_msg["data"], key=lambda item: int(item[0]))
172
+ ]
173
+
174
+ @staticmethod
175
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
176
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
177
+
178
+ Параметры:
179
+ raw_msg (Any): Сырое сообщение с вебсокета.
180
+
181
+ Возвращает:
182
+ list[TradeDict]: Список сделок в унифицированном формате.
183
+ """
184
+ return [
185
+ TradeDict(
186
+ t=int(trade["ts"]),
187
+ s=trade["instId"],
188
+ S=trade["side"].upper(),
189
+ p=float(trade["px"]),
190
+ v=float(trade["sz"]) * Adapter._get_contract_size(trade["instId"]),
191
+ )
192
+ for trade in sorted(raw_msg["data"], key=lambda item: int(item["ts"]))
193
+ ]
194
+
144
195
  @staticmethod
145
196
  def _get_contract_size(symbol: str) -> float:
146
197
  """Возвращает размер контракта для указанного символа тикера."""
unicex/okx/client.py CHANGED
@@ -6,7 +6,7 @@ from typing import Any, Literal
6
6
 
7
7
  from unicex._base import BaseClient
8
8
  from unicex.exceptions import NotAuthorized
9
- from unicex.types import RequestMethod
9
+ from unicex.types import NumberLike, RequestMethod
10
10
  from unicex.utils import filter_params, generate_hmac_sha256_signature
11
11
 
12
12
 
@@ -510,7 +510,7 @@ class Client(BaseClient):
510
510
  inst_id: str,
511
511
  td_mode: Literal["cross", "isolated", "cash", "spot_isolated"],
512
512
  ccy: str | None = None,
513
- px: str | None = None,
513
+ px: NumberLike | None = None,
514
514
  leverage: str | None = None,
515
515
  trade_quote_ccy: str | None = None,
516
516
  ) -> dict:
@@ -540,7 +540,7 @@ class Client(BaseClient):
540
540
  td_mode: Literal["cross", "isolated", "cash", "spot_isolated"],
541
541
  ccy: str | None = None,
542
542
  reduce_only: bool | None = None,
543
- px: str | None = None,
543
+ px: NumberLike | None = None,
544
544
  trade_quote_ccy: str | None = None,
545
545
  ) -> dict:
546
546
  """Получение максимального доступного баланса/эквити.
@@ -1313,14 +1313,14 @@ class Client(BaseClient):
1313
1313
  "mmp_and_post_only",
1314
1314
  "op_fok",
1315
1315
  ],
1316
- sz: str,
1316
+ sz: NumberLike,
1317
1317
  ccy: str | None = None,
1318
1318
  cl_ord_id: str | None = None,
1319
1319
  tag: str | None = None,
1320
1320
  pos_side: Literal["net", "long", "short"] | None = None,
1321
- px: str | None = None,
1322
- px_usd: str | None = None,
1323
- px_vol: str | None = None,
1321
+ px: NumberLike | None = None,
1322
+ px_usd: NumberLike | None = None,
1323
+ px_vol: NumberLike | None = None,
1324
1324
  reduce_only: bool | None = None,
1325
1325
  tgt_ccy: Literal["base_ccy", "quote_ccy"] | None = None,
1326
1326
  ban_amend: bool | None = None,
@@ -1430,10 +1430,10 @@ class Client(BaseClient):
1430
1430
  ord_id: str | None = None,
1431
1431
  cl_ord_id: str | None = None,
1432
1432
  *,
1433
- new_sz: str | None = None,
1434
- new_px: str | None = None,
1435
- new_px_usd: str | None = None,
1436
- new_px_vol: str | None = None,
1433
+ new_sz: NumberLike | None = None,
1434
+ new_px: NumberLike | None = None,
1435
+ new_px_usd: NumberLike | None = None,
1436
+ new_px_vol: NumberLike | None = None,
1437
1437
  cxl_on_fail: bool | None = None,
1438
1438
  req_id: str | None = None,
1439
1439
  px_amend_type: Literal["0", "1"] | None = None,
@@ -1987,9 +1987,9 @@ class Client(BaseClient):
1987
1987
  "ioc",
1988
1988
  "optimal_limit_ioc",
1989
1989
  ],
1990
- sz: str,
1990
+ sz: NumberLike,
1991
1991
  pos_side: Literal["net", "long", "short"] | None = None,
1992
- px: str | None = None,
1992
+ px: NumberLike | None = None,
1993
1993
  reduce_only: bool | None = None,
1994
1994
  tgt_ccy: Literal["base_ccy", "quote_ccy"] | None = None,
1995
1995
  attach_algo_orders: list[dict[str, Any]] | None = None,
@@ -2592,9 +2592,9 @@ class Client(BaseClient):
2592
2592
  async def convert_contract_coin(
2593
2593
  self,
2594
2594
  inst_id: str,
2595
- sz: str,
2595
+ sz: NumberLike,
2596
2596
  type_: Literal["1", "2"] | None = None,
2597
- px: str | None = None,
2597
+ px: NumberLike | None = None,
2598
2598
  unit: Literal["coin", "usds"] | None = None,
2599
2599
  op_type: Literal["open", "close"] | None = None,
2600
2600
  ) -> dict:
@@ -22,9 +22,9 @@ class ExchangeInfo(IExchangeInfo):
22
22
  for el in exchange_info["data"]:
23
23
  tickers_info[el["instId"]] = TickerInfoItem(
24
24
  tick_precision=None,
25
- tick_step=float(el["tickSz"]),
25
+ tick_step=float(el["tickSz"] or "0"),
26
26
  size_precision=None,
27
- size_step=float(el["lotSz"]),
27
+ size_step=float(el["lotSz"] or "0"),
28
28
  contract_size=1,
29
29
  )
30
30
 
@@ -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, Timeframe
9
9
  from unicex.types import LoggerLike
10
10
 
11
11
  from .adapter import Adapter
@@ -20,18 +20,39 @@ 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`): Клиент Okx или унифицированный клиент. Нужен для подключения к приватным топикам.
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_symbol(
40
+ self,
41
+ symbol: str | None,
42
+ symbols: Sequence[str] | None,
43
+ ) -> 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
+ normalized = list(symbols)
51
+ if len(normalized) != 1:
52
+ raise ValueError("OKX websocket поддерживает только один тикер на соединение")
53
+ return normalized[0]
54
+ raise ValueError("Either symbol or symbols must be provided")
55
+
35
56
  @overload
36
57
  def klines(
37
58
  self,
@@ -72,7 +93,13 @@ class UniWebsocketManager(IUniWebsocketManager):
72
93
  Возвращает:
73
94
  `Websocket`: Экземпляр вебсокета для управления соединением.
74
95
  """
75
- raise NotImplementedError()
96
+ inst_id = self._normalize_symbol(symbol, symbols)
97
+ wrapper = self._make_wrapper(self._adapter.klines_message, callback)
98
+ return self._websocket_manager.candlesticks(
99
+ callback=wrapper,
100
+ interval=timeframe.to_exchange_format(Exchange.OKX), # type: ignore
101
+ inst_id=inst_id,
102
+ )
76
103
 
77
104
  @overload
78
105
  def futures_klines(
@@ -114,7 +141,13 @@ class UniWebsocketManager(IUniWebsocketManager):
114
141
  Возвращает:
115
142
  `Websocket`: Экземпляр вебсокета.
116
143
  """
117
- raise NotImplementedError()
144
+ inst_id = self._normalize_symbol(symbol, symbols)
145
+ wrapper = self._make_wrapper(self._adapter.klines_message, callback)
146
+ return self._websocket_manager.candlesticks(
147
+ callback=wrapper,
148
+ interval=timeframe.to_exchange_format(Exchange.OKX), # type: ignore
149
+ inst_id=inst_id,
150
+ )
118
151
 
119
152
  @overload
120
153
  def trades(
@@ -152,7 +185,9 @@ class UniWebsocketManager(IUniWebsocketManager):
152
185
  Возвращает:
153
186
  `Websocket`: Экземпляр вебсокета.
154
187
  """
155
- raise NotImplementedError()
188
+ inst_id = self._normalize_symbol(symbol, symbols)
189
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
190
+ return self._websocket_manager.all_trades(callback=wrapper, inst_id=inst_id)
156
191
 
157
192
  @overload
158
193
  def aggtrades(
@@ -190,7 +225,9 @@ class UniWebsocketManager(IUniWebsocketManager):
190
225
  Возвращает:
191
226
  `Websocket`: Экземпляр вебсокета.
192
227
  """
193
- raise NotImplementedError()
228
+ inst_id = self._normalize_symbol(symbol, symbols)
229
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
230
+ return self._websocket_manager.trades(callback=wrapper, inst_id=inst_id)
194
231
 
195
232
  @overload
196
233
  def futures_trades(
@@ -228,7 +265,9 @@ class UniWebsocketManager(IUniWebsocketManager):
228
265
  Возвращает:
229
266
  `Websocket`: Экземпляр вебсокета.
230
267
  """
231
- raise NotImplementedError()
268
+ inst_id = self._normalize_symbol(symbol, symbols)
269
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
270
+ return self._websocket_manager.all_trades(callback=wrapper, inst_id=inst_id)
232
271
 
233
272
  @overload
234
273
  def futures_aggtrades(
@@ -266,4 +305,6 @@ class UniWebsocketManager(IUniWebsocketManager):
266
305
  Возвращает:
267
306
  `Websocket`: Экземпляр вебсокета.
268
307
  """
269
- raise NotImplementedError()
308
+ inst_id = self._normalize_symbol(symbol, symbols)
309
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
310
+ return self._websocket_manager.trades(callback=wrapper, inst_id=inst_id)