unicex 0.15.2__py3-none-any.whl → 0.16.0__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.
unicex/mexc/adapter.py CHANGED
@@ -1,11 +1,14 @@
1
1
  __all__ = ["Adapter"]
2
2
 
3
+ from typing import Any
4
+
3
5
  from unicex.types import (
4
6
  KlineDict,
5
7
  OpenInterestDict,
6
8
  OpenInterestItem,
7
9
  TickerDailyDict,
8
10
  TickerDailyItem,
11
+ TradeDict,
9
12
  )
10
13
  from unicex.utils import catch_adapter_errors, decorate_all_methods
11
14
 
@@ -127,6 +130,7 @@ class Adapter:
127
130
  result[symbol] = OpenInterestItem(
128
131
  t=item["timestamp"],
129
132
  v=float(item["holdVol"]) * Adapter._get_contract_size(symbol),
133
+ u="coins",
130
134
  )
131
135
  return result
132
136
 
@@ -230,6 +234,106 @@ class Adapter:
230
234
 
231
235
  return sorted(klines, key=lambda kline_item: kline_item["t"])
232
236
 
237
+ @staticmethod
238
+ def klines_message(raw_msg: Any) -> list[KlineDict]:
239
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
240
+
241
+ Параметры:
242
+ raw_msg (Any): Сырое сообщение с вебсокета.
243
+
244
+ Возвращает:
245
+ list[KlineDict]: Список свечей в унифицированном формате.
246
+ """
247
+ kline = raw_msg["publicSpotKline"]
248
+ return [
249
+ KlineDict(
250
+ s=raw_msg["symbol"],
251
+ t=int(kline["windowStart"]) * 1000,
252
+ o=float(kline["openingPrice"]),
253
+ h=float(kline["highestPrice"]),
254
+ l=float(kline["lowestPrice"]),
255
+ c=float(kline["closingPrice"]),
256
+ v=float(kline["volume"]),
257
+ T=int(kline["windowEnd"]) * 1000,
258
+ x=None,
259
+ q=float(kline["amount"]),
260
+ )
261
+ ]
262
+
263
+ @staticmethod
264
+ def futures_klines_message(raw_msg: Any) -> list[KlineDict]:
265
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
266
+
267
+ Параметры:
268
+ raw_msg (Any): Сырое сообщение с вебсокета.
269
+
270
+ Возвращает:
271
+ list[KlineDict]: Список свечей в унифицированном формате.
272
+ """
273
+ data = raw_msg["data"]
274
+ return [
275
+ KlineDict(
276
+ s=data["symbol"],
277
+ t=data["t"] * 1000,
278
+ o=data["o"],
279
+ h=data["h"],
280
+ l=data["l"],
281
+ c=data["c"],
282
+ v=data["q"], # Контракты
283
+ q=data["a"],
284
+ T=None,
285
+ x=None,
286
+ )
287
+ ]
288
+
289
+ @staticmethod
290
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
291
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
292
+
293
+ Параметры:
294
+ raw_msg (Any): Сырое сообщение с вебсокета.
295
+
296
+ Возвращает:
297
+ list[TradeDict]: Список сделок в унифицированном формате.
298
+ """
299
+ return [
300
+ TradeDict(
301
+ t=trade["time"],
302
+ s=raw_msg["symbol"],
303
+ S="BUY" if trade["tradeType"] == 1 else "SELL",
304
+ p=float(trade["price"]),
305
+ v=float(trade["quantity"]),
306
+ )
307
+ for trade in sorted(
308
+ raw_msg["publicAggreDeals"]["deals"],
309
+ key=lambda item: item["time"],
310
+ )
311
+ ]
312
+
313
+ @staticmethod
314
+ def futures_trades_message(raw_msg: Any) -> list[TradeDict]:
315
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
316
+
317
+ Параметры:
318
+ raw_msg (Any): Сырое сообщение с вебсокета.
319
+
320
+ Возвращает:
321
+ list[TradeDict]: Список сделок в унифицированном формате.
322
+ """
323
+ return [
324
+ TradeDict(
325
+ t=item["t"],
326
+ s=raw_msg["symbol"],
327
+ S="BUY" if item["T"] == 1 else "SELL",
328
+ p=item["p"],
329
+ v=item["v"],
330
+ )
331
+ for item in sorted(
332
+ raw_msg["data"],
333
+ key=lambda item: item["t"],
334
+ )
335
+ ]
336
+
233
337
  @staticmethod
234
338
  def _get_contract_size(symbol: str) -> float:
235
339
  """Возвращает размер контракта для указанного символа тикера."""
@@ -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,15 @@ 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.klines(
81
+ callback=wrapper,
82
+ symbol=symbol,
83
+ symbols=symbols,
84
+ interval=timeframe.to_exchange_format(
85
+ Exchange.MEXC, MarketType.FUTURES
86
+ ), # Тут фьючерсный интервал, потому что для вебсокета MEXC решили что сделают так (идиоты)
87
+ )
80
88
 
81
89
  @overload
82
90
  def futures_klines(
@@ -118,7 +126,13 @@ class UniWebsocketManager(IUniWebsocketManager):
118
126
  Возвращает:
119
127
  `Websocket`: Экземпляр вебсокета.
120
128
  """
121
- raise NotImplementedError()
129
+ wrapper = self._make_wrapper(self._adapter.futures_klines_message, callback)
130
+ return self._websocket_manager.futures_kline(
131
+ callback=wrapper,
132
+ symbol=symbol,
133
+ symbols=symbols,
134
+ interval=timeframe.to_exchange_format(Exchange.MEXC, MarketType.FUTURES),
135
+ )
122
136
 
123
137
  @overload
124
138
  def trades(
@@ -156,7 +170,8 @@ class UniWebsocketManager(IUniWebsocketManager):
156
170
  Возвращает:
157
171
  `Websocket`: Экземпляр вебсокета.
158
172
  """
159
- raise NotImplementedError()
173
+ wrapper = self._make_wrapper(self._adapter.trades_message, callback)
174
+ return self._websocket_manager.trade(callback=wrapper, symbol=symbol, symbols=symbols)
160
175
 
161
176
  @overload
162
177
  def aggtrades(
@@ -194,7 +209,7 @@ class UniWebsocketManager(IUniWebsocketManager):
194
209
  Возвращает:
195
210
  `Websocket`: Экземпляр вебсокета.
196
211
  """
197
- raise NotImplementedError()
212
+ return self.trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
198
213
 
199
214
  @overload
200
215
  def futures_trades(
@@ -232,7 +247,10 @@ class UniWebsocketManager(IUniWebsocketManager):
232
247
  Возвращает:
233
248
  `Websocket`: Экземпляр вебсокета.
234
249
  """
235
- raise NotImplementedError()
250
+ wrapper = self._make_wrapper(self._adapter.futures_trades_message, callback)
251
+ return self._websocket_manager.futures_trade(
252
+ callback=wrapper, symbol=symbol, symbols=symbols
253
+ )
236
254
 
237
255
  @overload
238
256
  def futures_aggtrades(
@@ -270,4 +288,4 @@ class UniWebsocketManager(IUniWebsocketManager):
270
288
  Возвращает:
271
289
  `Websocket`: Экземпляр вебсокета.
272
290
  """
273
- raise NotImplementedError()
291
+ return self.futures_trades(callback=callback, symbol=symbol, symbols=symbols) # type: ignore[reportCallIssue]
@@ -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
@@ -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
 
@@ -135,6 +138,54 @@ class Adapter:
135
138
  item["instId"]: OpenInterestItem(
136
139
  t=int(item["ts"]),
137
140
  v=float(item["oiCcy"]),
141
+ u="coins",
138
142
  )
139
143
  for item in raw_data["data"]
140
144
  }
145
+
146
+ @staticmethod
147
+ def klines_message(raw_msg: Any) -> list[KlineDict]:
148
+ """Преобразует вебсокет-сообщение со свечами в унифицированный формат.
149
+
150
+ Параметры:
151
+ raw_msg (Any): Сырое сообщение с вебсокета.
152
+
153
+ Возвращает:
154
+ list[KlineDict]: Список свечей в унифицированном формате.
155
+ """
156
+ return [
157
+ KlineDict(
158
+ s=raw_msg["arg"]["instId"],
159
+ t=int(kline[0]),
160
+ o=float(kline[1]),
161
+ h=float(kline[2]),
162
+ l=float(kline[3]),
163
+ c=float(kline[4]),
164
+ v=float(kline[6]),
165
+ q=float(kline[7]),
166
+ T=None,
167
+ x=bool(int(kline[8])),
168
+ )
169
+ for kline in sorted(raw_msg["data"], key=lambda item: int(item[0]))
170
+ ]
171
+
172
+ @staticmethod
173
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
174
+ """Преобразует вебсокет-сообщение со сделками в унифицированный формат.
175
+
176
+ Параметры:
177
+ raw_msg (Any): Сырое сообщение с вебсокета.
178
+
179
+ Возвращает:
180
+ list[TradeDict]: Список сделок в унифицированном формате.
181
+ """
182
+ return [
183
+ TradeDict(
184
+ t=int(trade["ts"]),
185
+ s=trade["instId"],
186
+ S=trade["side"].upper(),
187
+ p=float(trade["px"]),
188
+ v=float(trade["sz"]),
189
+ )
190
+ for trade in sorted(raw_msg["data"], key=lambda item: int(item["ts"]))
191
+ ]
@@ -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
@@ -36,6 +36,23 @@ 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_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
+
39
56
  @overload
40
57
  def klines(
41
58
  self,
@@ -76,7 +93,13 @@ class UniWebsocketManager(IUniWebsocketManager):
76
93
  Возвращает:
77
94
  `Websocket`: Экземпляр вебсокета для управления соединением.
78
95
  """
79
- 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
+ )
80
103
 
81
104
  @overload
82
105
  def futures_klines(
@@ -118,7 +141,13 @@ class UniWebsocketManager(IUniWebsocketManager):
118
141
  Возвращает:
119
142
  `Websocket`: Экземпляр вебсокета.
120
143
  """
121
- 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
+ )
122
151
 
123
152
  @overload
124
153
  def trades(
@@ -156,7 +185,9 @@ class UniWebsocketManager(IUniWebsocketManager):
156
185
  Возвращает:
157
186
  `Websocket`: Экземпляр вебсокета.
158
187
  """
159
- 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)
160
191
 
161
192
  @overload
162
193
  def aggtrades(
@@ -194,7 +225,9 @@ class UniWebsocketManager(IUniWebsocketManager):
194
225
  Возвращает:
195
226
  `Websocket`: Экземпляр вебсокета.
196
227
  """
197
- 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)
198
231
 
199
232
  @overload
200
233
  def futures_trades(
@@ -232,7 +265,9 @@ class UniWebsocketManager(IUniWebsocketManager):
232
265
  Возвращает:
233
266
  `Websocket`: Экземпляр вебсокета.
234
267
  """
235
- 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)
236
271
 
237
272
  @overload
238
273
  def futures_aggtrades(
@@ -270,4 +305,6 @@ class UniWebsocketManager(IUniWebsocketManager):
270
305
  Возвращает:
271
306
  `Websocket`: Экземпляр вебсокета.
272
307
  """
273
- 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)
unicex/types.py CHANGED
@@ -79,10 +79,10 @@ class KlineDict(TypedDict):
79
79
  q: float
80
80
  """Объем свечи. В долларах."""
81
81
 
82
- T: int | None
82
+ T: int | None # `None` means untrackable
83
83
  """Время закрытия. В миллисекундах."""
84
84
 
85
- x: bool | None
85
+ x: bool | None # `None` means untrackable
86
86
  """Флаг закрыта ли свеча."""
87
87
 
88
88
 
@@ -118,7 +118,10 @@ class OpenInterestItem(TypedDict):
118
118
  """Время. В миллисекундах."""
119
119
 
120
120
  v: float
121
- """Открытый интерес. В монетах."""
121
+ """Открытый интерес."""
122
+
123
+ u: Literal["coins", "usd"]
124
+ """Единица измерения открытого интереса."""
122
125
 
123
126
 
124
127
  type OpenInterestDict = dict[str, OpenInterestItem]
unicex/utils.py CHANGED
@@ -10,6 +10,7 @@ __all__ = [
10
10
  "catch_adapter_errors",
11
11
  "decorate_all_methods",
12
12
  "symbol_to_exchange_format",
13
+ "validate_single_symbol_args",
13
14
  ]
14
15
 
15
16
  import base64
@@ -17,7 +18,7 @@ import hashlib
17
18
  import hmac
18
19
  import json
19
20
  import time
20
- from collections.abc import Callable, Iterable
21
+ from collections.abc import Callable, Iterable, Sequence
21
22
  from functools import wraps
22
23
  from typing import Any, Literal
23
24
  from urllib.parse import urlencode
@@ -232,9 +233,29 @@ def symbol_to_exchange_format(
232
233
  return symbol.removesuffix("USDT") # Вот тут мб и не так, там вроде что-то к USDC
233
234
  elif exchange == Exchange.KUCOIN:
234
235
  if market_type == MarketType.FUTURES:
236
+ if symbol_upper == "BTCUSDT":
237
+ return "XBTUSDTM"
235
238
  return symbol_upper + "M"
236
239
  else:
237
240
  return symbol_upper.replace("USDT", "-USDT")
238
241
  elif exchange == Exchange.BINGX:
239
242
  return symbol_upper.replace("USDT", "-USDT")
240
243
  return symbol_upper
244
+
245
+
246
+ def validate_single_symbol_args(
247
+ symbol: str | None = None, symbols: Sequence[str] | None = None
248
+ ) -> None:
249
+ """Проверяет, что передан ровно один из аргументов symbol/symbols.
250
+
251
+ Параметры:
252
+ symbol (`str | None`, опционально): Одиночный торговый символ.
253
+ symbols (`Sequence[str] | None`, опционально): Список торговых символов.
254
+
255
+ Возвращает:
256
+ `None`: Ничего не возвращает, выбрасывает ValueError при нарушении условий.
257
+ """
258
+ if symbol and symbols:
259
+ raise ValueError("Parameters symbol and symbols cannot be used together")
260
+ if not (symbol or symbols):
261
+ raise ValueError("Either symbol or symbols must be provided")
@@ -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
  ---