unicex 0.10.5__py3-none-any.whl → 0.13.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/__init__.py CHANGED
@@ -26,6 +26,7 @@ __all__ = [
26
26
  "OpenInterestItem",
27
27
  "TickerInfoItem",
28
28
  "TickersInfoDict",
29
+ "LiquidationDict",
29
30
  # Interfaces
30
31
  "IUniClient",
31
32
  "IUniWebsocketManager",
@@ -93,7 +94,21 @@ from ._base import BaseClient, Websocket
93
94
  # enums, mappers, types
94
95
  from .enums import Exchange, MarketType, Side, Timeframe
95
96
  from .mapper import get_uni_client, get_uni_websocket_manager, get_exchange_info
96
- from .types import *
97
+ from .types import (
98
+ TickerDailyDict,
99
+ TickerDailyItem,
100
+ KlineDict,
101
+ TradeDict,
102
+ AggTradeDict,
103
+ RequestMethod,
104
+ LoggerLike,
105
+ AccountType,
106
+ OpenInterestDict,
107
+ OpenInterestItem,
108
+ TickerInfoItem,
109
+ TickersInfoDict,
110
+ LiquidationDict,
111
+ )
97
112
 
98
113
  # exchanges
99
114
 
@@ -1,10 +1,12 @@
1
1
  __all__ = ["IExchangeInfo"]
2
2
 
3
3
  import asyncio
4
+ import math
4
5
  from abc import ABC, abstractmethod
5
6
  from decimal import Decimal
6
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, ClassVar
7
8
 
9
+ import aiohttp
8
10
  from loguru import logger
9
11
 
10
12
  from unicex.enums import MarketType
@@ -32,6 +34,9 @@ class IExchangeInfo(ABC):
32
34
  _logger: "loguru.Logger"
33
35
  """Логгер для записи сообщений о работе с биржей."""
34
36
 
37
+ exchange_name: ClassVar[str] = "not_defined_exchange"
38
+ """Название биржи, на которой работает класс."""
39
+
35
40
  def __init_subclass__(cls, **kwargs):
36
41
  """Инициализация подкласса. Функция нужна, чтобы у каждого наследника была своя копия атрибутов."""
37
42
  super().__init_subclass__(**kwargs)
@@ -63,7 +68,7 @@ class IExchangeInfo(ABC):
63
68
  try:
64
69
  await cls.load_exchange_info()
65
70
  except Exception as e:
66
- cls._logger.error(f"Error loading exchange data: {e}")
71
+ cls._logger.error(f"Error loading exchange data for {cls.exchange_name}: {e}")
67
72
  for _ in range(update_interval_seconds):
68
73
  if not cls._running:
69
74
  break
@@ -72,9 +77,33 @@ class IExchangeInfo(ABC):
72
77
  @classmethod
73
78
  async def load_exchange_info(cls) -> None:
74
79
  """Принудительно вызывает загрузку информации о бирже."""
75
- await cls._load_exchange_info()
80
+ async with aiohttp.ClientSession() as session:
81
+ try:
82
+ await cls._load_spot_exchange_info(session)
83
+ cls._logger.debug(f"Loaded spot exchange data for {cls.exchange_name} ")
84
+ except Exception as e:
85
+ cls._logger.error(f"Error loading spot exchange data for {cls.exchange_name}: {e}")
86
+ try:
87
+ await cls._load_futures_exchange_info(session)
88
+ cls._logger.debug(f"Loaded futures exchange data for {cls.exchange_name} ")
89
+ except Exception as e:
90
+ cls._logger.error(
91
+ f"Error loading futures exchange data for {cls.exchange_name}: {e}"
92
+ )
76
93
  cls._loaded = True
77
94
 
95
+ @classmethod
96
+ @abstractmethod
97
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
98
+ """Загружает информацию о бирже для спотового рынка."""
99
+ ...
100
+
101
+ @classmethod
102
+ @abstractmethod
103
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
104
+ """Загружает информацию о бирже для фьючерсного рынка."""
105
+ ...
106
+
78
107
  @classmethod
79
108
  def get_ticker_info(
80
109
  cls, symbol: str, market_type: MarketType = MarketType.SPOT
@@ -92,39 +121,41 @@ class IExchangeInfo(ABC):
92
121
  """Возвращает информацию о тикере фьючерсов по его символу."""
93
122
  return cls.get_ticker_info(symbol, MarketType.FUTURES)
94
123
 
95
- @classmethod
96
- @abstractmethod
97
- async def _load_exchange_info(cls) -> None:
98
- """Загружает информацию о бирже."""
99
- ...
100
-
101
124
  @classmethod
102
125
  def round_price(
103
126
  cls, symbol: str, price: float, market_type: MarketType = MarketType.SPOT
104
- ) -> float:
127
+ ) -> float: # type: ignore
105
128
  """Округляет цену до ближайшего возможного значения."""
106
129
  try:
107
130
  if market_type == MarketType.SPOT:
108
131
  precision = cls._tickers_info[symbol]["tick_precision"]
132
+ step = cls._tickers_info[symbol]["tick_step"]
109
133
  else:
110
134
  precision = cls._futures_tickers_info[symbol]["tick_precision"]
135
+ step = cls._futures_tickers_info[symbol]["tick_step"]
136
+ if precision:
137
+ return cls._floor_round(price, precision)
138
+ return cls._floor_to_step(price, step) # type: ignore
111
139
  except KeyError as e:
112
140
  cls._handle_key_error(e, symbol)
113
- return round(price, precision)
114
141
 
115
142
  @classmethod
116
143
  def round_quantity(
117
144
  cls, symbol: str, quantity: float, market_type: MarketType = MarketType.SPOT
118
- ) -> float:
145
+ ) -> float: # type: ignore
119
146
  """Округляет объем до ближайшего возможного значения."""
120
147
  try:
121
148
  if market_type == MarketType.SPOT:
122
149
  precision = cls._tickers_info[symbol]["size_precision"]
150
+ step = cls._tickers_info[symbol]["size_step"]
123
151
  else:
124
152
  precision = cls._futures_tickers_info[symbol]["size_precision"]
153
+ step = cls._futures_tickers_info[symbol]["size_step"]
154
+ if precision:
155
+ return cls._floor_round(quantity, precision)
156
+ return cls._floor_to_step(quantity, step) # type: ignore
125
157
  except KeyError as e:
126
158
  cls._handle_key_error(e, symbol)
127
- return round(quantity, precision)
128
159
 
129
160
  @classmethod
130
161
  def round_futures_price(cls, symbol: str, price: float) -> float:
@@ -137,31 +168,40 @@ class IExchangeInfo(ABC):
137
168
  return cls.round_quantity(symbol, quantity, MarketType.FUTURES)
138
169
 
139
170
  @staticmethod
140
- def _step_size_to_precision(tick_size: str | int | float) -> int:
141
- """Возвращает precision для round(x, precision) по шагу цены/объёма.
171
+ def _floor_to_step(value: float, step: float) -> float:
172
+ """Округляет число вниз до ближайшего кратного шага.
173
+
174
+ Принимает:
175
+ value (float): Исходное число.
176
+ step: (float): Шаг округления (> 0).
177
+
178
+ Возвращает:
179
+ Число, округлённое вниз до кратного step.
142
180
 
143
- Работает только для шагов — степеней 10.
144
181
  Примеры:
145
- "0.0001" -> 4
146
- "0.01" -> 2
147
- "0.1" -> 1
148
- "1" -> 0
149
- "10" -> -1
150
- "100" -> -2
182
+ >>> floor_to_step(0.16, 0.05)
183
+ 0.15
184
+ >>> floor_to_step(16, 5)
185
+ 15
186
+ >>> floor_to_step(1.2345, 0.01)
187
+ 1.23
188
+ >>> floor_to_step(-1.23, 0.1)
189
+ -1.3
190
+ >>> floor_to_step(100, 25)
191
+ 100
192
+
151
193
  """
152
- d = Decimal(str(tick_size)).normalize()
153
- if d <= 0:
154
- raise ValueError("tick_size must be > 0")
155
-
156
- t = d.as_tuple()
157
- # Степень десяти даёт один значащий разряд = 1 (1eN)
158
- if t.digits == (1,):
159
- return -t.exponent # type: ignore
160
-
161
- # Иначе это не степень 10 (например, 0.5, 5 и т.п.)
162
- raise ValueError(
163
- f"tick_size={tick_size} is not a power of 10; cannot map to round() precision."
164
- )
194
+ if step <= 0:
195
+ raise ValueError("step must be > 0")
196
+ result = math.floor(value / step) * step
197
+ digits = abs(Decimal(str(step)).as_tuple().exponent) # type: ignore
198
+ return round(result, digits)
199
+
200
+ @staticmethod
201
+ def _floor_round(value: float, digits: int) -> float:
202
+ """Округляет число вниз до указанного количества знаков после запятой."""
203
+ factor = 10**digits
204
+ return math.floor(value * factor) / factor
165
205
 
166
206
  @classmethod
167
207
  def _handle_key_error(cls, exception: KeyError, symbol: str) -> None:
@@ -1,12 +1,62 @@
1
1
  __all__ = ["ExchangeInfo"]
2
2
 
3
+
4
+ import aiohttp
5
+
3
6
  from unicex._abc import IExchangeInfo
7
+ from unicex.types import TickerInfoItem
8
+
9
+ from .client import Client
4
10
 
5
11
 
6
12
  class ExchangeInfo(IExchangeInfo):
7
13
  """Предзагружает информацию о тикерах для биржи Binance."""
8
14
 
15
+ exchange_name = "Binance"
16
+ """Название биржи, на которой работает класс."""
17
+
9
18
  @classmethod
10
- async def _load_exchange_info(cls) -> None:
11
- """Загружает информацию о бирже."""
12
- ...
19
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
20
+ """Загружает информацию о бирже для спотового рынка."""
21
+ exchange_info = await Client(session).exchange_info()
22
+ tickers_info: dict[str, TickerInfoItem] = {}
23
+ for symbol_info in exchange_info["symbols"]:
24
+ filters = {
25
+ flt["filterType"]: flt
26
+ for flt in symbol_info.get("filters", [])
27
+ if "filterType" in flt
28
+ }
29
+ price_filter = filters["PRICE_FILTER"]
30
+ lot_size_filter = filters["LOT_SIZE"]
31
+ tickers_info[symbol_info["symbol"]] = TickerInfoItem(
32
+ tick_step=float(price_filter["tickSize"]),
33
+ tick_precision=None,
34
+ size_step=float(lot_size_filter["stepSize"]),
35
+ size_precision=None,
36
+ contract_size=1,
37
+ )
38
+
39
+ cls._tickers_info = tickers_info
40
+
41
+ @classmethod
42
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
43
+ """Загружает информацию о бирже для фьючерсного рынка."""
44
+ exchange_info = await Client(session).futures_exchange_info()
45
+ tickers_info: dict[str, TickerInfoItem] = {}
46
+ for symbol_info in exchange_info["symbols"]:
47
+ filters = {
48
+ flt["filterType"]: flt
49
+ for flt in symbol_info.get("filters", [])
50
+ if "filterType" in flt
51
+ }
52
+ price_filter = filters["PRICE_FILTER"]
53
+ lot_size_filter = filters["LOT_SIZE"]
54
+ tickers_info[symbol_info["symbol"]] = TickerInfoItem(
55
+ tick_step=float(price_filter["tickSize"]),
56
+ tick_precision=None,
57
+ size_step=float(lot_size_filter["stepSize"]),
58
+ size_precision=None,
59
+ contract_size=1,
60
+ )
61
+
62
+ cls._futures_tickers_info = tickers_info
@@ -1,12 +1,48 @@
1
1
  __all__ = ["ExchangeInfo"]
2
2
 
3
+ import aiohttp
4
+
3
5
  from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+ from .client import Client
4
9
 
5
10
 
6
11
  class ExchangeInfo(IExchangeInfo):
7
12
  """Предзагружает информацию о тикерах для биржи Bitget."""
8
13
 
14
+ exchange_name = "Bitget"
15
+ """Название биржи, на которой работает класс."""
16
+
9
17
  @classmethod
10
- async def _load_exchange_info(cls) -> None:
11
- """Загружает информацию о бирже."""
12
- ...
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
20
+ exchange_info = await Client(session).get_symbol_info()
21
+ tickers_info: dict[str, TickerInfoItem] = {}
22
+ for symbol_info in exchange_info["data"]:
23
+ tickers_info[symbol_info["symbol"]] = TickerInfoItem(
24
+ tick_precision=int(symbol_info["pricePrecision"]),
25
+ tick_step=None,
26
+ size_precision=int(symbol_info["quantityPrecision"]),
27
+ size_step=None,
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
32
+
33
+ @classmethod
34
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
35
+ """Загружает информацию о бирже для фьючерсного рынка."""
36
+ tickers_info: dict[str, TickerInfoItem] = {}
37
+ exchange_info = await Client(session).futures_get_contracts("USDT-FUTURES")
38
+ for symbol_info in exchange_info["data"]:
39
+ symbol = symbol_info["symbol"]
40
+ tickers_info[symbol] = TickerInfoItem(
41
+ tick_precision=int(symbol_info["pricePlace"]),
42
+ tick_step=None,
43
+ size_precision=int(symbol_info["volumePlace"]),
44
+ size_step=None,
45
+ contract_size=float(symbol_info["sizeMultiplier"]),
46
+ )
47
+
48
+ cls._futures_tickers_info = tickers_info
unicex/bybit/adapter.py CHANGED
@@ -4,6 +4,7 @@ from typing import Any
4
4
 
5
5
  from unicex.types import (
6
6
  KlineDict,
7
+ LiquidationDict,
7
8
  OpenInterestDict,
8
9
  OpenInterestItem,
9
10
  TickerDailyDict,
@@ -130,6 +131,14 @@ class Adapter:
130
131
 
131
132
  @staticmethod
132
133
  def Klines_message(raw_msg: Any) -> list[KlineDict]:
134
+ """Преобразует вебсокет-сообщение с данными о свечах в унифицированный формат.
135
+
136
+ Параметры:
137
+ raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
138
+
139
+ Возвращает:
140
+ `list[KlineDict]`: Список свечей в унифицированном формате.
141
+ """
133
142
  symbol = raw_msg["topic"].split(".")[-1]
134
143
  return [
135
144
  KlineDict(
@@ -144,11 +153,56 @@ class Adapter:
144
153
  T=kline["end"],
145
154
  x=kline["confirm"],
146
155
  )
147
- for kline in raw_msg["data"]
156
+ for kline in sorted(
157
+ raw_msg["data"],
158
+ key=lambda x: int(x["start"]),
159
+ )
148
160
  ]
149
161
 
150
162
  @staticmethod
151
- def aggtrades_message(raw_msg: Any) -> list[TradeDict]: ...
163
+ def trades_message(raw_msg: Any) -> list[TradeDict]:
164
+ """Преобразует вебсокет-сообщение с данными о сделках в унифицированный формат.
165
+
166
+ Параметры:
167
+ raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
168
+
169
+ Возвращает:
170
+ `list[TradeDict]`: Список сделок в унифицированном формате.
171
+ """
172
+ return [
173
+ TradeDict(
174
+ t=trade["T"],
175
+ s=trade["s"],
176
+ S=trade["S"].upper(),
177
+ p=float(trade["p"]),
178
+ v=float(trade["v"]),
179
+ )
180
+ for trade in sorted(
181
+ raw_msg["data"],
182
+ key=lambda x: int(x["T"]),
183
+ )
184
+ ]
152
185
 
153
186
  @staticmethod
154
- def trades_message(raw_msg: Any) -> list[TradeDict]: ...
187
+ def liquidations_message(raw_msg: Any) -> list[LiquidationDict]:
188
+ """Преобразует вебсокет-сообщение с данными о ликвидациях в унифицированный формат.
189
+
190
+ Параметры:
191
+ raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
192
+
193
+ Возвращает:
194
+ `list[LiquidationDict]`: Список ликвидаций в унифицированном формате.
195
+ """
196
+ return [
197
+ LiquidationDict(
198
+ t=liquidation["T"],
199
+ s=liquidation["s"],
200
+ S=liquidation["S"].upper(),
201
+ v=float(liquidation["v"]),
202
+ p=float(liquidation["p"]),
203
+ )
204
+ for liquidation in sorted(
205
+ raw_msg["data"],
206
+ key=lambda x: int(x["T"]),
207
+ )
208
+ ]
@@ -1,12 +1,51 @@
1
1
  __all__ = ["ExchangeInfo"]
2
2
 
3
+ import aiohttp
4
+
3
5
  from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+ from .client import Client
4
9
 
5
10
 
6
11
  class ExchangeInfo(IExchangeInfo):
7
12
  """Предзагружает информацию о тикерах для биржи Bybit."""
8
13
 
14
+ exchange_name = "Bybit"
15
+ """Название биржи, на которой работает класс."""
16
+
17
+ @classmethod
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
20
+ exchange_info = await Client(session).instruments_info("spot")
21
+ tickers_info: dict[str, TickerInfoItem] = {}
22
+ for symbol_info in exchange_info["result"]["list"]:
23
+ tickers_info[symbol_info["symbol"]] = TickerInfoItem(
24
+ tick_step=float(symbol_info["priceFilter"]["tickSize"]),
25
+ tick_precision=None,
26
+ size_step=float(symbol_info["lotSizeFilter"]["basePrecision"]),
27
+ size_precision=None,
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
32
+
9
33
  @classmethod
10
- async def _load_exchange_info(cls) -> None:
11
- """Загружает информацию о бирже."""
12
- ...
34
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
35
+ """Загружает информацию о бирже для фьючерсного рынка."""
36
+ exchange_info = await Client(session).instruments_info("linear", limit=1000)
37
+ tickers_info: dict[str, TickerInfoItem] = {}
38
+ for symbol_info in exchange_info["result"]["list"]:
39
+ try:
40
+ tickers_info[symbol_info["symbol"]] = TickerInfoItem(
41
+ tick_step=float(symbol_info["priceFilter"]["tickSize"]),
42
+ tick_precision=None,
43
+ size_step=float(symbol_info["lotSizeFilter"]["qtyStep"]),
44
+ size_precision=None,
45
+ contract_size=1,
46
+ )
47
+ except ValueError as e:
48
+ cls._logger.trace(
49
+ f"ValueError on {cls.exchange_name} by {symbol_info['symbol']}: {e}"
50
+ )
51
+ cls._futures_tickers_info = tickers_info
@@ -72,7 +72,7 @@ class UniWebsocketManager(IUniWebsocketManager):
72
72
  Возвращает:
73
73
  `Websocket`: Экземпляр вебсокета для управления соединением.
74
74
  """
75
- return self._websocket_manager.klines(
75
+ return self._websocket_manager.kline(
76
76
  callback=self._make_wrapper(self._adapter.Klines_message, callback),
77
77
  category="spot",
78
78
  interval=timeframe.to_exchange_format(Exchange.BYBIT), # type: ignore
@@ -120,7 +120,7 @@ class UniWebsocketManager(IUniWebsocketManager):
120
120
  Возвращает:
121
121
  `Websocket`: Экземпляр вебсокета.
122
122
  """
123
- return self._websocket_manager.klines(
123
+ return self._websocket_manager.kline(
124
124
  callback=self._make_wrapper(self._adapter.Klines_message, callback),
125
125
  category="linear",
126
126
  interval=timeframe.to_exchange_format(Exchange.BYBIT), # type: ignore
@@ -164,7 +164,12 @@ class UniWebsocketManager(IUniWebsocketManager):
164
164
  Возвращает:
165
165
  `Websocket`: Экземпляр вебсокета.
166
166
  """
167
- raise NotImplementedError()
167
+ return self._websocket_manager.trade(
168
+ callback=self._make_wrapper(self._adapter.trades_message, callback),
169
+ category="spot",
170
+ symbol=symbol,
171
+ symbols=symbols,
172
+ )
168
173
 
169
174
  @overload
170
175
  def aggtrades(
@@ -240,7 +245,12 @@ class UniWebsocketManager(IUniWebsocketManager):
240
245
  Возвращает:
241
246
  `Websocket`: Экземпляр вебсокета.
242
247
  """
243
- raise NotImplementedError()
248
+ return self._websocket_manager.trade(
249
+ callback=self._make_wrapper(self._adapter.trades_message, callback),
250
+ category="linear",
251
+ symbol=symbol,
252
+ symbols=symbols,
253
+ )
244
254
 
245
255
  @overload
246
256
  def futures_aggtrades(
@@ -126,7 +126,7 @@ class WebsocketManager:
126
126
  **self._ws_kwargs,
127
127
  )
128
128
 
129
- def klines(
129
+ def kline(
130
130
  self,
131
131
  callback: CallbackType,
132
132
  category: Literal["spot", "linear", "inverse", "option"],
@@ -170,7 +170,7 @@ class WebsocketManager:
170
170
  **self._ws_kwargs,
171
171
  )
172
172
 
173
- def public_trade(
173
+ def trade(
174
174
  self,
175
175
  callback: CallbackType,
176
176
  category: Literal["spot", "linear", "inverse", "option"],
unicex/gateio/client.py CHANGED
@@ -4,7 +4,7 @@ import hashlib
4
4
  import hmac
5
5
  import json
6
6
  import time
7
- from typing import Any
7
+ from typing import Any, Literal
8
8
 
9
9
  from unicex._base import BaseClient
10
10
  from unicex.exceptions import NotAuthorized
@@ -691,7 +691,7 @@ class Client(BaseClient):
691
691
 
692
692
  async def futures_contracts(
693
693
  self,
694
- settle: str,
694
+ settle: Literal["usdt", "btc"],
695
695
  limit: int | None = None,
696
696
  offset: int | None = None,
697
697
  ) -> dict:
@@ -1,12 +1,55 @@
1
1
  __all__ = ["ExchangeInfo"]
2
2
 
3
+ import aiohttp
4
+
3
5
  from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+ from .client import Client
4
9
 
5
10
 
6
11
  class ExchangeInfo(IExchangeInfo):
7
12
  """Предзагружает информацию о тикерах для биржи Gateio."""
8
13
 
14
+ exchange_name = "Gateio"
15
+ """Название биржи, на которой работает класс."""
16
+
9
17
  @classmethod
10
- async def _load_exchange_info(cls) -> None:
11
- """Загружает информацию о бирже."""
12
- ...
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
20
+ currency_pairs = await Client(session).currency_pairs()
21
+ tickers_info: dict[str, TickerInfoItem] = {}
22
+ for symbol_info in currency_pairs:
23
+ try:
24
+ tickers_info[symbol_info.get("id")] = TickerInfoItem(
25
+ tick_precision=int(symbol_info["precision"]),
26
+ tick_step=None,
27
+ size_precision=int(symbol_info["amount_precision"]),
28
+ size_step=None,
29
+ contract_size=1,
30
+ )
31
+ except ValueError as e:
32
+ cls._logger.trace(
33
+ f"ValueError on {cls.exchange_name} by {symbol_info['symbol']}: {e}"
34
+ )
35
+
36
+ cls._tickers_info = tickers_info
37
+
38
+ @classmethod
39
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
40
+ """Загружает информацию о бирже для фьючерсного рынка."""
41
+ contracts = await Client(session).futures_contracts("usdt")
42
+ tickers_info: dict[str, TickerInfoItem] = {}
43
+ for contract in contracts:
44
+ try:
45
+ tickers_info[contract.get("name")] = TickerInfoItem(
46
+ tick_precision=None,
47
+ tick_step=float(contract["order_price_round"]),
48
+ size_precision=None,
49
+ size_step=float(contract["quanto_multiplier"]),
50
+ contract_size=float(contract["quanto_multiplier"]),
51
+ )
52
+ except ValueError as e:
53
+ cls._logger.trace(f"ValueError on {cls.exchange_name} by {contract['name']}: {e}")
54
+
55
+ cls._futures_tickers_info = tickers_info
@@ -1,6 +1,9 @@
1
1
  __all__ = ["ExchangeInfo"]
2
2
 
3
+ import aiohttp
4
+
3
5
  from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
4
7
 
5
8
  from .client import Client
6
9
 
@@ -8,6 +11,9 @@ from .client import Client
8
11
  class ExchangeInfo(IExchangeInfo):
9
12
  """Предзагружает информацию о тикерах для биржи Hyperliquid."""
10
13
 
14
+ exchange_name = "Hyperliquid"
15
+ """Название биржи, на которой работает класс."""
16
+
11
17
  _spot_meta: dict = {}
12
18
  """Словарь с метаинформацией о спотовом рынке."""
13
19
 
@@ -20,17 +26,30 @@ class ExchangeInfo(IExchangeInfo):
20
26
  _futures_meta: dict = {}
21
27
  """Словарь с метаинформацией о фьючерсном рынке."""
22
28
 
29
+ # DOCS: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
30
+
31
+ @classmethod
32
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
33
+ """Загружает информацию о бирже для спотового рынка."""
34
+ cls._spot_meta = await Client(session).spot_metadata()
35
+ cls._build_spot_mappings(cls._spot_meta)
36
+
37
+ tickers_info: dict[str, TickerInfoItem] = {}
38
+ for symbol_info in cls._spot_meta["tokens"]:
39
+ tickers_info[symbol_info["name"]] = TickerInfoItem(
40
+ tick_step=None,
41
+ tick_precision=int(symbol_info["weiDecimals"]),
42
+ size_step=None,
43
+ size_precision=int(symbol_info["szDecimals"]),
44
+ contract_size=1,
45
+ )
46
+
47
+ cls._tickers_info = tickers_info
48
+
23
49
  @classmethod
24
- async def _load_exchange_info(cls) -> None:
25
- """Загружает информацию о бирже."""
26
- client = await Client.create()
27
- async with client as conn:
28
- cls._spot_meta = await conn.spot_metadata()
29
- cls._build_spot_mappings(cls._spot_meta)
30
- cls._logger.debug("Hyperliquid spot exchange info loaded")
31
-
32
- cls._futures_meta = await conn.perp_metadata()
33
- cls._logger.debug("Hyperliquid futures exchange info loaded")
50
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
51
+ """Загружает информацию о бирже для фьючерсного рынка."""
52
+ cls._futures_meta = await Client(session).perp_metadata()
34
53
 
35
54
  @classmethod
36
55
  def _build_spot_mappings(cls, spot_meta: dict) -> None:
@@ -5,28 +5,43 @@ import aiohttp
5
5
  from unicex._abc import IExchangeInfo
6
6
  from unicex.types import TickerInfoItem
7
7
 
8
+ from .client import Client
9
+
8
10
 
9
11
  class ExchangeInfo(IExchangeInfo):
10
12
  """Предзагружает информацию о тикерах для биржи Mexc."""
11
13
 
14
+ exchange_name = "Mexc"
15
+ """Название биржи, на которой работает класс."""
16
+
17
+ @classmethod
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
20
+ exchange_info = await Client(session).exchange_info()
21
+ tickers_info = {}
22
+ for el in exchange_info["symbols"]:
23
+ tickers_info[el["symbol"]] = TickerInfoItem(
24
+ tick_precision=int(el["quotePrecision"]),
25
+ tick_step=None,
26
+ size_precision=int(el["baseAssetPrecision"]),
27
+ size_step=None,
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
32
+
12
33
  @classmethod
13
- async def _load_exchange_info(cls) -> None:
14
- """Загружает информацию о бирже."""
15
- futures_tickers_info = {}
16
- async with aiohttp.ClientSession() as session:
17
- url = "https://contract.mexc.com/api/v1/contract/detail"
18
- async with session.get(url) as response:
19
- data = await response.json()
20
- for el in data["data"]:
21
- futures_tickers_info[el["symbol"]] = TickerInfoItem(
22
- tick_precision=cls._step_size_to_precision(el["priceUnit"]),
23
- size_precision=el["amountScale"],
24
- contract_size=el["contractSize"],
25
- min_market_size=el["minVol"],
26
- max_market_size=el["maxVol"],
27
- min_limit_size=el["minVol"],
28
- max_limit_size=el["maxVol"],
29
- )
30
-
31
- cls._futures_tickers_info = futures_tickers_info
32
- cls._logger.debug("Mexc futures exchange info loaded")
34
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
35
+ """Загружает информацию о бирже для фьючерсного рынка."""
36
+ exchange_info = await Client(session).futures_contract_detail()
37
+ tickers_info = {}
38
+ for el in exchange_info["data"]:
39
+ tickers_info[el["symbol"]] = TickerInfoItem(
40
+ tick_precision=None,
41
+ tick_step=el["priceUnit"],
42
+ size_precision=None,
43
+ size_step=el["contractSize"],
44
+ contract_size=el["contractSize"],
45
+ )
46
+
47
+ cls._futures_tickers_info = tickers_info
@@ -5,46 +5,43 @@ import aiohttp
5
5
  from unicex._abc import IExchangeInfo
6
6
  from unicex.types import TickerInfoItem
7
7
 
8
+ from .client import Client
9
+
8
10
 
9
11
  class ExchangeInfo(IExchangeInfo):
10
12
  """Предзагружает информацию о тикерах для биржи Okx."""
11
13
 
14
+ exchange_name = "Okx"
15
+ """Название биржи, на которой работает класс."""
16
+
17
+ @classmethod
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
20
+ tickers_info = {}
21
+ exchange_info = await Client(session).get_instruments("SPOT")
22
+ for el in exchange_info["data"]:
23
+ tickers_info[el["instId"]] = TickerInfoItem(
24
+ tick_precision=None,
25
+ tick_step=float(el["tickSz"]),
26
+ size_precision=None,
27
+ size_step=float(el["lotSz"]),
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
32
+
12
33
  @classmethod
13
- async def _load_exchange_info(cls) -> None:
14
- """Загружает информацию о бирже."""
15
- async with aiohttp.ClientSession() as session:
16
- tickers_info = {}
17
- url = "https://www.okx.com/api/v5/public/instruments?instType=SPOT"
18
- async with session.get(url) as response:
19
- data = await response.json()
20
- for el in data["data"]:
21
- tickers_info[el["instId"]] = TickerInfoItem(
22
- tick_precision=cls._step_size_to_precision(el["tickSz"]),
23
- size_precision=cls._step_size_to_precision(el["lotSz"]),
24
- contract_size=1,
25
- min_market_size=float(el["minSz"]),
26
- max_market_size=float(el["maxMktSz"]),
27
- min_limit_size=float(el["minSz"]),
28
- max_limit_size=float(el["maxLmtSz"]),
29
- )
30
-
31
- cls._tickers_info = tickers_info
32
- cls._logger.debug("Okx spot exchange info loaded")
33
-
34
- futures_tickers_info = {}
35
- url = "https://www.okx.com/api/v5/public/instruments?instType=SWAP"
36
- async with session.get(url) as response:
37
- data = await response.json()
38
- for el in data["data"]:
39
- futures_tickers_info[el["instId"]] = TickerInfoItem(
40
- tick_precision=cls._step_size_to_precision(el["tickSz"]),
41
- size_precision=cls._step_size_to_precision(el["lotSz"]),
42
- contract_size=float(el["ctVal"]),
43
- min_market_size=el["minSz"],
44
- max_market_size=el["maxMktSz"],
45
- min_limit_size=el["minSz"],
46
- max_limit_size=el["maxLmtSz"],
47
- )
48
-
49
- cls._futures_tickers_info = futures_tickers_info
50
- cls._logger.debug("Okx futures exchange info loaded")
34
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
35
+ """Загружает информацию о бирже для фьючерсного рынка."""
36
+ tickers_info = {}
37
+ exchange_info = await Client(session).get_instruments("SWAP")
38
+ for el in exchange_info["data"]:
39
+ tickers_info[el["instId"]] = TickerInfoItem(
40
+ tick_precision=None,
41
+ tick_step=float(el["tickSz"]),
42
+ size_precision=None,
43
+ size_step=float(el["lotSz"]) * float(el["ctVal"]),
44
+ contract_size=float(el["ctVal"]),
45
+ )
46
+
47
+ cls._futures_tickers_info = tickers_info
unicex/types.py CHANGED
@@ -13,6 +13,7 @@ __all__ = [
13
13
  "OpenInterestItem",
14
14
  "TickerInfoItem",
15
15
  "TickersInfoDict",
16
+ "LiquidationDict",
16
17
  ]
17
18
 
18
19
  from logging import Logger as LoggingLogger
@@ -117,34 +118,52 @@ type OpenInterestDict = dict[str, OpenInterestItem]
117
118
  """Модель открытого интереса."""
118
119
 
119
120
 
121
+ class LiquidationDict(TypedDict):
122
+ """Модель ликвидации."""
123
+
124
+ t: int
125
+ """Время. В миллисекундах."""
126
+
127
+ s: str
128
+ """Символ."""
129
+
130
+ S: Literal["BUY", "SELL"]
131
+ """Сторона."""
132
+
133
+ v: float
134
+ """Объем ликвидации. В монетах."""
135
+
136
+ p: float
137
+ """Цена ликвидации."""
138
+
139
+
120
140
  type AccountType = Literal["SPOT", "FUTURES"]
121
141
  """Тип аккаунта."""
122
142
 
123
143
 
124
144
  class TickerInfoItem(TypedDict):
125
- """Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера."""
145
+ """Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
126
146
 
127
- tick_precision: int
147
+ На некоторых биржах удобнее делать округление через precisions, на некоторых через step,
148
+ потому что иногда встречаются шаги, которые не являются степенью 10. Поэтому обязательно
149
+ должны быть определены tick_precision ИЛИ tick_step, а так же size_precision ИЛИ size_step.
150
+ """
151
+
152
+ tick_precision: int | None
128
153
  """Количество знаков после запятой для цены."""
129
154
 
130
- size_precision: int
155
+ tick_step: float | None
156
+ """Шаг одного деления для цены."""
157
+
158
+ size_precision: int | None
131
159
  """Количество знаков после запятой для объема."""
132
160
 
161
+ size_step: float | None
162
+ """Шаг одного деления для объема."""
163
+
133
164
  contract_size: float | None
134
165
  """Множитель контракта (если есть)."""
135
166
 
136
- min_market_size: float | None
137
- """Минимальный размер рыночного ордера в монетах (если есть)."""
138
-
139
- max_market_size: float | None
140
- """Максимальный размер рыночного ордера в монетах (если есть)."""
141
-
142
- min_limit_size: float | None
143
- """Минимальный размер лимитного ордера в монетах (если есть)."""
144
-
145
- max_limit_size: float | None
146
- """Максимальный размер лимитного ордера в монетах (если есть)."""
147
-
148
167
 
149
168
  type TickersInfoDict = dict[str, TickerInfoItem]
150
169
  """Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для всех тикеров."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unicex
3
- Version: 0.10.5
3
+ Version: 0.13.0
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -49,18 +49,18 @@ Dynamic: license-file
49
49
 
50
50
  # Unified Crypto Exchange API
51
51
 
52
- `unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж.
52
+ `unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж. Поддерживает спотовый и USDT-фьючерсный рынки.
53
53
 
54
54
  ## ✅ Статус реализации
55
55
 
56
56
  | Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
57
57
  |-----------------|--------|------|------------|---------|------------|----------------|--------------|
58
- | **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
59
- | **Bitget** | ✓ | ✓ | ✓ | | ✓ | | |
60
- | **Bybit** | ✓ | ✓ | ✓ | | ✓ | | |
61
- | **Gateio** | ✓ | ✓ | ✓ | | ✓ | | |
58
+ | **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ ||
59
+ | **Bitget** | ✓ | ✓ | ✓ | | ✓ | ||
60
+ | **Bybit** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
61
+ | **Gateio** | ✓ | ✓ | ✓ | | ✓ | ||
62
62
  | **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
63
- | **Mexc** | ✓ | ✓ | ✓ | | ✓ | | |
63
+ | **Mexc** | ✓ | ✓ | ✓ | | ✓ | ||
64
64
  | **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
65
65
  ---
66
66
 
@@ -191,3 +191,53 @@ async def callback(trade: TradeDict) -> None:
191
191
  if __name__ == "__main__":
192
192
  asyncio.run(main())
193
193
  ```
194
+
195
+
196
+ ### Пример: Округление цен используя фоновый класс ExchangeInfo
197
+
198
+
199
+ ```python
200
+ import asyncio
201
+ from unicex import start_exchanges_info, get_exchange_info, Exchange
202
+
203
+
204
+ async def main() -> None:
205
+ # ⏳ Запускаем фоновые процессы, которые собирают рыночные параметры всех бирж:
206
+ # - количество знаков после точки для цены и объема
207
+ # - множители контрактов для фьючерсов
208
+ await start_exchanges_info()
209
+
210
+ # Небольшая пауза, чтобы данные успели подгрузиться
211
+ await asyncio.sleep(1)
212
+
213
+ # 1️⃣ Пример 1: Округление цены для фьючерсов OKX
214
+ okx_exchange_info = get_exchange_info(Exchange.OKX)
215
+ okx_rounded_price = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", 123456.1234567890)
216
+ print(okx_rounded_price) # >> 123456.1
217
+
218
+ # 2️⃣ Пример 2: Округление объема для спота Binance
219
+ binance_exchange_info = get_exchange_info(Exchange.BINANCE)
220
+ binance_rounded_quantity = binance_exchange_info.round_quantity("BTCUSDT", 1.123456789)
221
+ print(binance_rounded_quantity) # >> 1.12345
222
+
223
+ # 3️⃣ Пример 3: Получение множителя контракта (например, Mexc Futures)
224
+ mexc_exchange_info = get_exchange_info(Exchange.MEXC)
225
+ mexc_contract_multiplier = mexc_exchange_info.get_futures_ticker_info("BTC_USDT")["contract_size"]
226
+ print(mexc_contract_multiplier) # >> 0.0001
227
+
228
+ # 4️⃣ Пример 4: Реальное применение — вычисляем тейк-профит вручную
229
+ # Допустим, позиция открыта по 123123.1 USDT, хотим +3.5% тейк-профит:
230
+ take_profit_raw = 123123.1 * 1.035
231
+ print("До округления:", take_profit_raw) # >> 127432.40849999999
232
+
233
+ # Биржа требует цену в допустимом формате — округляем:
234
+ take_profit = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", take_profit_raw)
235
+ print("После округления:", take_profit) # >> 127432.4
236
+
237
+ # Теперь это число можно безопасно передать в API без ошибок:
238
+ # await client.create_order(symbol="BTC-USDT-SWAP", price=take_profit, ...)
239
+
240
+
241
+ if __name__ == "__main__":
242
+ asyncio.run(main())
243
+ ```
@@ -1,12 +1,12 @@
1
- unicex/__init__.py,sha256=J9UDSWyAS8ozDxSWFXgN4002fqHVUb1Ol-YtafNtJgs,5533
1
+ unicex/__init__.py,sha256=v3yO0P9GqC88cV4L-YSJ6h2YzlyOTnt1NPNLSJwhIRE,5806
2
2
  unicex/enums.py,sha256=8E_Nb57kriOif57XSLnW8joFufbthZTJ7tcExKWf1Wg,9633
3
3
  unicex/exceptions.py,sha256=r-xZzX78VuxVnI5pe99AM8FIiGcdIUDcF5CaTkQ4NE0,2213
4
4
  unicex/extra.py,sha256=MZRSsDRok05KZCqKur-hjOexZuoZ-tC9J6e-EIZr_lw,13824
5
5
  unicex/mapper.py,sha256=zOuInRQGJnSnwRI5yJ_axx-0svGn-nOqgLr7XXSlq14,4915
6
- unicex/types.py,sha256=NMitdbyD-_pRvR_gm2aapHU7wwL8CZCaqWFIuCAYMgA,4291
6
+ unicex/types.py,sha256=quGNpQm-_lSkijRKz04A5lPVxQ2PYrFzggYSPiJGzXI,4728
7
7
  unicex/utils.py,sha256=dwU1VYuP2xcMpzaETtNQerL1V8Y_JH8H8EsLJ__-M4s,8050
8
8
  unicex/_abc/__init__.py,sha256=fxZjNFJFeFwWTXz8iSDe7eCWwE6xfFwFwAuG6l-TI8A,289
9
- unicex/_abc/exchange_info.py,sha256=j0nbTeF9-_LwHGDZLBHYF2Dd1DYBCQ-DlUO5N5tZgEY,7541
9
+ unicex/_abc/exchange_info.py,sha256=NHBQAjknIRebyjl3musrvneN5u9vKYXjAca5PmTvJTw,9545
10
10
  unicex/_abc/uni_client.py,sha256=ZjxK8aqCGLUUYy1UQTM9EvWn1IXwMkH2Db8sZrs1e_I,13728
11
11
  unicex/_abc/uni_websocket_manager.py,sha256=yYKypPkIe3rKfWBuTsS8rkwIPljpd1588CYDkeTOYqE,9905
12
12
  unicex/_base/__init__.py,sha256=0TmevATGnRB3qow6tkCR8dQKNZCWKeib6YQjNJ4a1b0,236
@@ -15,7 +15,7 @@ unicex/_base/websocket.py,sha256=7IUIO2-KyjTswK3nTkRI7uQkNq6NYS_EhaG7tJQCPeg,114
15
15
  unicex/binance/__init__.py,sha256=sDk4ZjakRdpFMaMSpOCfqjf6ZPfAS9tlrt4WlDHtDkw,932
16
16
  unicex/binance/adapter.py,sha256=JbUFyjnDAFtyuYYrh90YeOvQOZQ6faim0nWS6U0NxXw,8799
17
17
  unicex/binance/client.py,sha256=1qPx0uRT4prC6saLBQ55pXDWcWTCKhYEwVIysiihPgU,60984
18
- unicex/binance/exchange_info.py,sha256=_1AEa9B8Id4aj4-0VPDBL3rWE0yqwj0KFwT9N97OSeA,353
18
+ unicex/binance/exchange_info.py,sha256=LNDkgBC5HB3JxtIBi39puqDg6LIVWqIWjT-6akDxtMs,2437
19
19
  unicex/binance/uni_client.py,sha256=W4yxiU0kkJKPJjimhv4KAWreuEBwt7GgrWXefcw5BEA,8365
20
20
  unicex/binance/uni_websocket_manager.py,sha256=FywEuUt3CiDcAQPo3ItdW2pPgbLDvVpVYFM2D_QscPU,8679
21
21
  unicex/binance/user_websocket.py,sha256=HJ_3VZV0Bil0vfsdLrZElXm-gUqd8mGzXzfQ0_sHjFc,7861
@@ -23,23 +23,23 @@ unicex/binance/websocket_manager.py,sha256=Idrq0Qzi14QgpIG5d77S-r1h_BvRXphcxY2BT
23
23
  unicex/bitget/__init__.py,sha256=8govSOEyWjA62js-ZTQIiSYWSmcEUFSC9hVTpS8eosk,929
24
24
  unicex/bitget/adapter.py,sha256=frQBOKFsIB8mXc_Ime2-Iby_nRQlSpzXjshC5AxoDTA,7741
25
25
  unicex/bitget/client.py,sha256=RbOrW8Q21OwYFac2xrKQURdYSAkVQ0jj18cDoLj5Edk,90155
26
- unicex/bitget/exchange_info.py,sha256=oR0L60Ew4QDkNle5-e7CBmWBwTj-5IbW57CkEeTGDMw,352
26
+ unicex/bitget/exchange_info.py,sha256=_UMvAqP0zcpmv9dkovkFxrXLlol6q8_v7-0sy6FSfrE,1959
27
27
  unicex/bitget/uni_client.py,sha256=MrXAmthTDTEQZ1ZY3LuqkCKL1bw_mKHMdiV4XiRFO-M,8641
28
28
  unicex/bitget/uni_websocket_manager.py,sha256=y-HXrREy_ruNeUzdRv5nHwBZQgbupxzp1UV-IFOpN_8,9793
29
29
  unicex/bitget/user_websocket.py,sha256=tlkv7Rmsw_FSfCJnEMOK_9jRsXRk2Ah_slqG8C-uhuo,129
30
30
  unicex/bitget/websocket_manager.py,sha256=2OhH2gKy2gN03oYeCFvf3_l6RA29dDOvpajGhbXI9Zw,9886
31
31
  unicex/bybit/__init__.py,sha256=SrMBh6K5zUt4JheWUpNUYNb1NCDr2ujTFv4IDguaGZI,926
32
- unicex/bybit/adapter.py,sha256=DkQwm28Ujyw04Ppgl-Itck-J73J0AIx7NOGsYH4M_TI,6008
32
+ unicex/bybit/adapter.py,sha256=U6QP2eGefZqfXq7H4P1G9M23HieAjmylb5FnrHISn5s,8147
33
33
  unicex/bybit/client.py,sha256=U1mCcF-Mg2spiKlp0ucIA-kDOisYLO0edRS6XC16iNo,61014
34
- unicex/bybit/exchange_info.py,sha256=36WiJ49TxpSdZpeMwZdjQIkRdf2wJXahdkNxVaNjNRE,351
34
+ unicex/bybit/exchange_info.py,sha256=6DySi-61kyU1Wpfp7G-5Zu7G1bFfcR4qo2oO5d0YutI,2200
35
35
  unicex/bybit/uni_client.py,sha256=0wmIRRgofuJXWvZMB1gHwuIEfoWuPhLJXthmK1f9w40,8382
36
- unicex/bybit/uni_websocket_manager.py,sha256=1Y0m0ulH7W-18pSl0smlX2lXIeegy9FFqzrActCgZR0,9880
36
+ unicex/bybit/uni_websocket_manager.py,sha256=OpnvWD0xZ8T_By0HZCSg3jWoZqScRATAsku4IIBqhlw,10252
37
37
  unicex/bybit/user_websocket.py,sha256=IGGEnwyWs5jOppgK_R7SisBDvsiF1_piTswBrdQOgDg,128
38
- unicex/bybit/websocket_manager.py,sha256=ZBSwa_nXFvXoq2MoxE7llCYeKGyPdrXDOZXoyxbROt4,15412
38
+ unicex/bybit/websocket_manager.py,sha256=ePHvngoqoVPr6p-BayoEGhCuRK-cJ69CAPzF94qTalQ,15404
39
39
  unicex/gateio/__init__.py,sha256=dsKvhQhDcw4_w0S4c9IjKkCoOg4DCUtSecEUOlfstug,929
40
40
  unicex/gateio/adapter.py,sha256=PE5lXQORAKLhzFZBtxcarNIt2xEcDrPrphxWS0k0wEk,6842
41
- unicex/gateio/client.py,sha256=LS-g44vSY8FHCnDjFKzM8gIoPwWpKv15JxQIgwSietI,53732
42
- unicex/gateio/exchange_info.py,sha256=x_jZT14vMt2Cv4AtGhkFiAuCv43t4hwqQ4tiEjJ-HPY,352
41
+ unicex/gateio/client.py,sha256=bhWkYBn5B4JKpLRfQERTVzisiCSg7mMi6mM0kh4Hes4,53760
42
+ unicex/gateio/exchange_info.py,sha256=ANzfe4mqxtLnj2TBJJxoc31KUosvxdApp1_xYrRNQDs,2300
43
43
  unicex/gateio/uni_client.py,sha256=btfI-dozBRsbjEMvSlUX8zp6SOvIS1xDxk_5hNqRfz4,9648
44
44
  unicex/gateio/uni_websocket_manager.py,sha256=3KAHhgJNISTOZRBTwbZPigc8iq_IHS19NwnuAsnfeXg,9329
45
45
  unicex/gateio/user_websocket.py,sha256=4qZX9N2RjlJ-e25Eszz12OeCM17j5DdXVimBVaLj53w,129
@@ -47,7 +47,7 @@ unicex/gateio/websocket_manager.py,sha256=phtHbvAGQD3mtewCUxBuuD1Nj0FXN6oZrd7tnm
47
47
  unicex/hyperliquid/__init__.py,sha256=qGTAkwfXLvknvHET_iA7Qml3jkxxxA0moU_98nGTcVU,944
48
48
  unicex/hyperliquid/adapter.py,sha256=0aULPGDhppjbRvShbR49GNt6qmw4o_CmrFi_xGVRLHg,10612
49
49
  unicex/hyperliquid/client.py,sha256=8YnPh_6Hj8vFYcPbMaPae6OfroFzn28VI6iL3FQfa84,84255
50
- unicex/hyperliquid/exchange_info.py,sha256=OBaBpAEij2Fv6q10rbQwGzBEU-5xGkCze_slEo9Q2N0,4128
50
+ unicex/hyperliquid/exchange_info.py,sha256=D1h1fvH9DycRq-xDTZ_-Y1NC2JCp165IyWMXs39VjFI,4936
51
51
  unicex/hyperliquid/uni_client.py,sha256=4jv2uC076PBeq-EzCKvwaEEMk_M3HpsWA6iwNepJg7E,15674
52
52
  unicex/hyperliquid/uni_websocket_manager.py,sha256=AzR_8Aq98NkCA1oc2IiH02ysMOYLaisEmcuuaafeaJw,9334
53
53
  unicex/hyperliquid/user_websocket.py,sha256=BKD9ap2bx5DwpkkwwecfOTVedrZBR9eMAITgCBgg02w,134
@@ -55,7 +55,7 @@ unicex/hyperliquid/websocket_manager.py,sha256=GNBQT4ihk1XsTAaas8lxAfo3ASBGs_8EV
55
55
  unicex/mexc/__init__.py,sha256=lltANqM_2P-fmF5j8o5-pjmORPuK6C5sVjcQhuUU_R0,923
56
56
  unicex/mexc/adapter.py,sha256=uaJ6157wTrMuf72thIggDJHiaeRVYyH0qMIn1x5Mrp8,9820
57
57
  unicex/mexc/client.py,sha256=oJSpz-8hvA1WMCN2B7oqI2lXEx1_VJLAn8jbzhW8mF0,30950
58
- unicex/mexc/exchange_info.py,sha256=Nqzn4b791H3DLXsI6YZiY34VOZgHTG7evfRNWQnd9Nk,1329
58
+ unicex/mexc/exchange_info.py,sha256=z2bQsVU0ciXV2_DFkueZHo1X35KIK2alD-7ZZjNg5Kc,1763
59
59
  unicex/mexc/uni_client.py,sha256=0unhbU3WfqrMsV_y15VnIFjwUzQLVtp1tx6spV6cHKI,9477
60
60
  unicex/mexc/uni_websocket_manager.py,sha256=MAKyGyit8Ve4doM7y8cTQbVVbpzlJx2HcHaSkLG4Jp4,9327
61
61
  unicex/mexc/user_websocket.py,sha256=l77-e6i0B2btd7a5IcCytbgswnV171NqOhunTcbaq48,127
@@ -81,13 +81,13 @@ unicex/mexc/_spot_ws_proto/__init__.py,sha256=L8Jft1713_M8CLR9drgSjLBdY_46sPT3O9
81
81
  unicex/okx/__init__.py,sha256=Ljbw3AP0YrPF5bIPJi_3JP3B_czR9xurYHI24rgWk9M,920
82
82
  unicex/okx/adapter.py,sha256=zXSLieRC80KY56ey1X-ss51uQ0QzouDxXppjcofLNRQ,5351
83
83
  unicex/okx/client.py,sha256=2XL4YWPdRZYRMfLF1vSpkmwSd8Dxn8uyw5vCwDJab48,90969
84
- unicex/okx/exchange_info.py,sha256=q2DSVDq-JYN72LA5d_RX0C73Ha_xAxjeOobX8IxjpkM,2257
84
+ unicex/okx/exchange_info.py,sha256=gkTwYnXgswa1FGLXdKo9qLYqZA0BS9VefpALhR4_t-Q,1772
85
85
  unicex/okx/uni_client.py,sha256=E_Wod0JSGt1K6k1mAIWnOv350pELbv-nic7g1KgOuos,8694
86
86
  unicex/okx/uni_websocket_manager.py,sha256=b4f_QjA64DJmENQdIGb5IOVc7kvit7KMCdWeCmRbxGY,9326
87
87
  unicex/okx/user_websocket.py,sha256=8c9kpm-xVa729pW93OKUGLHaE9MY0uzEpjIgNIFRF80,126
88
88
  unicex/okx/websocket_manager.py,sha256=wROXTUDqKzOE-wDnCtXso_MC4SzfPuPols5aPg_Z3y4,26027
89
- unicex-0.10.5.dist-info/licenses/LICENSE,sha256=lNNK4Vqak9cXm6qVJLhbqS7iR_BMj6k7fd7XQ6l1k54,1507
90
- unicex-0.10.5.dist-info/METADATA,sha256=szG-lX8SWtYyFHjxbXC4vtXFCXaoONAMp0_qubxxWkA,9146
91
- unicex-0.10.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
92
- unicex-0.10.5.dist-info/top_level.txt,sha256=_7rar-0OENIg4KRy6cgjWiebFYAJhjKEcMggAocGWG4,7
93
- unicex-0.10.5.dist-info/RECORD,,
89
+ unicex-0.13.0.dist-info/licenses/LICENSE,sha256=lNNK4Vqak9cXm6qVJLhbqS7iR_BMj6k7fd7XQ6l1k54,1507
90
+ unicex-0.13.0.dist-info/METADATA,sha256=mcRAXzA8_-E8J4FPnOX8HStWwYigLbvL_6E9IyCvt90,11752
91
+ unicex-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
92
+ unicex-0.13.0.dist-info/top_level.txt,sha256=_7rar-0OENIg4KRy6cgjWiebFYAJhjKEcMggAocGWG4,7
93
+ unicex-0.13.0.dist-info/RECORD,,