unicex 0.5.0__py3-none-any.whl → 0.7.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.
Files changed (97) hide show
  1. unicex/__init__.py +56 -124
  2. unicex/_abc/__init__.py +2 -0
  3. unicex/_abc/exchange_info.py +176 -0
  4. unicex/_abc/uni_client.py +2 -2
  5. unicex/_base/client.py +2 -2
  6. unicex/binance/__init__.py +12 -0
  7. unicex/binance/adapter.py +1 -1
  8. unicex/binance/exchange_info.py +12 -0
  9. unicex/bitget/__init__.py +14 -4
  10. unicex/bitget/adapter.py +1 -1
  11. unicex/bitget/exchange_info.py +12 -0
  12. unicex/bitget/uni_websocket_manager.py +1 -1
  13. unicex/bybit/__init__.py +12 -0
  14. unicex/bybit/adapter.py +1 -1
  15. unicex/bybit/exchange_info.py +12 -0
  16. unicex/enums.py +16 -5
  17. unicex/gateio/__init__.py +12 -0
  18. unicex/gateio/adapter.py +2 -2
  19. unicex/gateio/exchange_info.py +12 -0
  20. unicex/hyperliquid/__init__.py +12 -0
  21. unicex/hyperliquid/adapter.py +140 -30
  22. unicex/hyperliquid/client.py +2208 -125
  23. unicex/hyperliquid/exchange_info.py +100 -0
  24. unicex/hyperliquid/uni_client.py +176 -22
  25. unicex/mapper.py +35 -33
  26. unicex/mexc/__init__.py +12 -0
  27. unicex/mexc/adapter.py +28 -11
  28. unicex/mexc/exchange_info.py +32 -0
  29. unicex/mexc/uni_client.py +6 -0
  30. unicex/okx/__init__.py +12 -0
  31. unicex/okx/adapter.py +23 -7
  32. unicex/okx/exchange_info.py +50 -0
  33. unicex/okx/uni_client.py +2 -2
  34. unicex/types.py +31 -0
  35. unicex-0.7.0.dist-info/METADATA +192 -0
  36. unicex-0.7.0.dist-info/RECORD +75 -0
  37. unicex/bitrue/__init__.py +0 -15
  38. unicex/bitrue/adapter.py +0 -8
  39. unicex/bitrue/client.py +0 -128
  40. unicex/bitrue/uni_client.py +0 -151
  41. unicex/bitrue/uni_websocket_manager.py +0 -269
  42. unicex/bitrue/user_websocket.py +0 -7
  43. unicex/bitrue/websocket_manager.py +0 -11
  44. unicex/bitunix/__init__.py +0 -15
  45. unicex/bitunix/adapter.py +0 -8
  46. unicex/bitunix/client.py +0 -8
  47. unicex/bitunix/uni_client.py +0 -151
  48. unicex/bitunix/uni_websocket_manager.py +0 -269
  49. unicex/bitunix/user_websocket.py +0 -7
  50. unicex/bitunix/websocket_manager.py +0 -11
  51. unicex/btse/__init__.py +0 -15
  52. unicex/btse/adapter.py +0 -8
  53. unicex/btse/client.py +0 -123
  54. unicex/btse/uni_client.py +0 -151
  55. unicex/btse/uni_websocket_manager.py +0 -269
  56. unicex/btse/user_websocket.py +0 -7
  57. unicex/btse/websocket_manager.py +0 -11
  58. unicex/kcex/__init__.py +0 -15
  59. unicex/kcex/adapter.py +0 -8
  60. unicex/kcex/client.py +0 -8
  61. unicex/kcex/uni_client.py +0 -151
  62. unicex/kcex/uni_websocket_manager.py +0 -269
  63. unicex/kcex/user_websocket.py +0 -7
  64. unicex/kcex/websocket_manager.py +0 -11
  65. unicex/kraken/__init__.py +0 -15
  66. unicex/kraken/adapter.py +0 -8
  67. unicex/kraken/client.py +0 -165
  68. unicex/kraken/uni_client.py +0 -151
  69. unicex/kraken/uni_websocket_manager.py +0 -269
  70. unicex/kraken/user_websocket.py +0 -7
  71. unicex/kraken/websocket_manager.py +0 -11
  72. unicex/kucoin/__init__.py +0 -15
  73. unicex/kucoin/adapter.py +0 -8
  74. unicex/kucoin/client.py +0 -120
  75. unicex/kucoin/uni_client.py +0 -151
  76. unicex/kucoin/uni_websocket_manager.py +0 -269
  77. unicex/kucoin/user_websocket.py +0 -7
  78. unicex/kucoin/websocket_manager.py +0 -11
  79. unicex/weex/__init__.py +0 -15
  80. unicex/weex/adapter.py +0 -8
  81. unicex/weex/client.py +0 -8
  82. unicex/weex/uni_client.py +0 -151
  83. unicex/weex/uni_websocket_manager.py +0 -269
  84. unicex/weex/user_websocket.py +0 -7
  85. unicex/weex/websocket_manager.py +0 -11
  86. unicex/xt/__init__.py +0 -15
  87. unicex/xt/adapter.py +0 -8
  88. unicex/xt/client.py +0 -8
  89. unicex/xt/uni_client.py +0 -151
  90. unicex/xt/uni_websocket_manager.py +0 -269
  91. unicex/xt/user_websocket.py +0 -7
  92. unicex/xt/websocket_manager.py +0 -11
  93. unicex-0.5.0.dist-info/METADATA +0 -170
  94. unicex-0.5.0.dist-info/RECORD +0 -123
  95. {unicex-0.5.0.dist-info → unicex-0.7.0.dist-info}/WHEEL +0 -0
  96. {unicex-0.5.0.dist-info → unicex-0.7.0.dist-info}/licenses/LICENSE +0 -0
  97. {unicex-0.5.0.dist-info → unicex-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,100 @@
1
+ __all__ = ["ExchangeInfo"]
2
+
3
+ from unicex._abc import IExchangeInfo
4
+
5
+ from .client import Client
6
+
7
+
8
+ class ExchangeInfo(IExchangeInfo):
9
+ """Предзагружает информацию о тикерах для биржи Hyperliquid."""
10
+
11
+ _spot_meta: dict = {}
12
+ """Словарь с метаинформацией о спотовом рынке."""
13
+
14
+ _spot_ident_to_idx: dict = {}
15
+ """Словарь, в котором ключ - индетефикатор тикера на бирже, например '@123', а значение - его индекс в _spot_meta."""
16
+
17
+ _spot_idx_to_name: dict = {}
18
+ """Словарь, в котором ключ - индекс в _spot_meta, например "123", а значение - название тикера, например 'BTC'."""
19
+
20
+ _futures_meta: dict = {}
21
+ """Словарь с метаинформацией о фьючерсном рынке."""
22
+
23
+ @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")
34
+
35
+ @classmethod
36
+ def _build_spot_mappings(cls, spot_meta: dict) -> None:
37
+ """Строит словари соответствия '@индекс' ↔ индекс ↔ 'BTC'."""
38
+ universe = spot_meta["universe"]
39
+ tokens = spot_meta["tokens"]
40
+
41
+ number_to_idx = {}
42
+ for u in universe:
43
+ number_to_idx[u["name"]] = u["tokens"][0]
44
+ cls._spot_ident_to_idx = number_to_idx
45
+
46
+ idx_to_name = {}
47
+ for t in tokens:
48
+ idx_to_name[t["index"]] = t["name"]
49
+ cls._spot_idx_to_name = idx_to_name
50
+
51
+ @classmethod
52
+ def get_spot_meta(cls) -> dict:
53
+ """Возвращает метаинформацию о спотовом рынке."""
54
+ cls._check_loaded()
55
+ return cls._spot_meta
56
+
57
+ @classmethod
58
+ def get_futures_meta(cls) -> dict:
59
+ """Возвращает метаинформацию о фьючерсном рынке."""
60
+ cls._check_loaded()
61
+ return cls._futures_meta
62
+
63
+ @classmethod
64
+ def resolve_spot_symbol(cls, ident: str) -> str:
65
+ """Преобразует внутренний идентификатор вида '@142' в тикер, например 'BTC'.
66
+ Не рейзит KeyError, если тикер не найден.
67
+
68
+ Параметры:
69
+ token_name (str): Имя токена на бирже, например '@142' или 'BTC'.
70
+
71
+ Возвращает:
72
+ str | None: Название тикера (например 'BTC'), либо None, если не найден.
73
+ """
74
+ cls._check_loaded()
75
+
76
+ try:
77
+ return cls._spot_idx_to_name[cls._spot_ident_to_idx[ident]]
78
+ except KeyError:
79
+ return ident
80
+
81
+ @classmethod
82
+ def resolve_spot_ident(cls, symbol: str) -> str:
83
+ """Преобразует тикер (например, 'BTC') в внутренний идентификатор (например, '@142').
84
+
85
+ Параметры:
86
+ symbol (str): Название тикера, например 'BTC'.
87
+
88
+ Возвращает:
89
+ str: Внутренний идентификатор (например '@142').
90
+
91
+ Исключения:
92
+ KeyError: Если тикер не найден в локальном кэше биржи.
93
+ """
94
+ cls._check_loaded()
95
+
96
+ # Находим индекс по тикеру
97
+ idx = next(k for k, v in cls._spot_idx_to_name.items() if v == symbol)
98
+
99
+ # Возвращаем внутренний идентификатор вида "@142"
100
+ return next(k for k, v in cls._spot_ident_to_idx.items() if v == idx)
@@ -1,11 +1,14 @@
1
1
  __all__ = ["UniClient"]
2
2
 
3
+ import time
4
+ from typing import Self, overload
3
5
 
4
- from typing import overload
6
+ import aiohttp
5
7
 
6
8
  from unicex._abc import IUniClient
7
- from unicex.enums import Timeframe
8
- from unicex.types import KlineDict, OpenInterestDict, OpenInterestItem, TickerDailyDict
9
+ from unicex.enums import Exchange, MarketType, Timeframe
10
+ from unicex.types import KlineDict, LoggerLike, OpenInterestDict, OpenInterestItem, TickerDailyDict
11
+ from unicex.utils import batched_list
9
12
 
10
13
  from .adapter import Adapter
11
14
  from .client import Client
@@ -14,6 +17,84 @@ from .client import Client
14
17
  class UniClient(IUniClient[Client]):
15
18
  """Унифицированный клиент для работы с Hyperliquid API."""
16
19
 
20
+ def __init__(
21
+ self,
22
+ session: aiohttp.ClientSession,
23
+ private_key: str | bytes | None = None,
24
+ wallet_address: str | None = None,
25
+ vault_address: str | None = None,
26
+ logger: LoggerLike | None = None,
27
+ max_retries: int = 3,
28
+ retry_delay: int | float = 0.1,
29
+ proxies: list[str] | None = None,
30
+ timeout: int = 10,
31
+ ) -> None:
32
+ """Инициализация клиента.
33
+
34
+ Параметры:
35
+ session (`aiohttp.ClientSession`): Сессия для выполнения HTTP‑запросов.
36
+ private_key (`str | bytes | None`): Приватный ключ API для аутентификации (Hyperliquid).
37
+ wallet_address (`str | None`): Адрес кошелька для аутентификации (Hyperliquid).
38
+ vault_address (`str | None`): Адрес хранилища для аутентификации (Hyperliquid).
39
+ logger (`LoggerLike | None`): Логгер для вывода информации.
40
+ max_retries (`int`): Максимальное количество повторных попыток запроса.
41
+ retry_delay (`int | float`): Задержка между повторными попытками, сек.
42
+ proxies (`list[str] | None`): Список HTTP(S)‑прокси для циклического использования.
43
+ timeout (`int`): Максимальное время ожидания ответа от сервера, сек.
44
+ """
45
+ self._client: Client = self._client_cls(
46
+ private_key=private_key,
47
+ wallet_address=wallet_address,
48
+ vault_address=vault_address,
49
+ session=session,
50
+ logger=logger,
51
+ max_retries=max_retries,
52
+ retry_delay=retry_delay,
53
+ proxies=proxies,
54
+ timeout=timeout,
55
+ )
56
+
57
+ @classmethod
58
+ async def create(
59
+ cls,
60
+ private_key: str | bytes | None = None,
61
+ wallet_address: str | None = None,
62
+ vault_address: str | None = None,
63
+ logger: LoggerLike | None = None,
64
+ max_retries: int = 3,
65
+ retry_delay: int | float = 0.1,
66
+ proxies: list[str] | None = None,
67
+ timeout: int = 10,
68
+ ) -> Self:
69
+ """Создает инстанцию клиента.
70
+ Создать клиент можно и через __init__, но в таком случае session: `aiohttp.ClientSession` - обязательный параметр.
71
+
72
+ Параметры:
73
+ session (`aiohttp.ClientSession`): Сессия для выполнения HTTP‑запросов.
74
+ private_key (`str | bytes | None`): Приватный ключ API для аутентификации (Hyperliquid).
75
+ wallet_address (`str | None`): Адрес кошелька для аутентификации (Hyperliquid).
76
+ vault_address (`str | None`): Адрес хранилища для аутентификации (Hyperliquid).
77
+ logger (`LoggerLike | None`): Логгер для вывода информации.
78
+ max_retries (`int`): Максимальное количество повторных попыток запроса.
79
+ retry_delay (`int | float`): Задержка между повторными попытками, сек.
80
+ proxies (`list[str] | None`): Список HTTP(S)‑прокси для циклического использования.
81
+ timeout (`int`): Максимальное время ожидания ответа от сервера, сек.
82
+
83
+ Возвращает:
84
+ `IUniClient`: Созданный экземпляр клиента.
85
+ """
86
+ return cls(
87
+ session=aiohttp.ClientSession(),
88
+ private_key=private_key,
89
+ wallet_address=wallet_address,
90
+ vault_address=vault_address,
91
+ logger=logger,
92
+ max_retries=max_retries,
93
+ retry_delay=retry_delay,
94
+ proxies=proxies,
95
+ timeout=timeout,
96
+ )
97
+
17
98
  @property
18
99
  def _client_cls(self) -> type[Client]:
19
100
  """Возвращает класс клиента для Hyperliquid.
@@ -23,36 +104,65 @@ class UniClient(IUniClient[Client]):
23
104
  """
24
105
  return Client
25
106
 
26
- async def tickers(self, only_usdt: bool = True) -> list[str]:
107
+ async def tickers(self, resolve_symbols: bool = False) -> list[str]:
27
108
  """Возвращает список тикеров.
28
109
 
29
110
  Параметры:
30
- only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
111
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
31
112
 
32
113
  Возвращает:
33
114
  list[str]: Список тикеров.
34
115
  """
35
- raise NotImplementedError()
116
+ raw_data = await self._client.spot_metadata()
117
+ return Adapter.tickers(raw_data, resolve_symbols)
36
118
 
37
- async def futures_tickers(self, only_usdt: bool = True) -> list[str]:
38
- """Возвращает список тикеров.
119
+ async def tickers_batched(
120
+ self, resolve_symbols: bool = False, batch_size: int = 20
121
+ ) -> list[list[str]]:
122
+ """Возвращает список тикеров в чанках.
39
123
 
40
124
  Параметры:
41
- only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
125
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
126
+ batch_size (`int`): Размер чанка.
127
+
128
+ Возвращает:
129
+ `list[list[str]]`: Список тикеров в чанках.
130
+ """
131
+ tickers = await self.tickers(resolve_symbols)
132
+ return batched_list(tickers, batch_size)
133
+
134
+ async def futures_tickers(self) -> list[str]:
135
+ """Возвращает список тикеров.
42
136
 
43
137
  Возвращает:
44
138
  list[str]: Список тикеров.
45
139
  """
46
- raw_data = await self._client.perp_asset_contexts()
140
+ raw_data = await self._client.perp_metadata()
47
141
  return Adapter.futures_tickers(raw_data)
48
142
 
49
- async def last_price(self) -> dict[str, float]:
143
+ async def futures_tickers_batched(self, batch_size: int = 20) -> list[list[str]]:
144
+ """Возвращает список тикеров в чанках.
145
+
146
+ Параметры:
147
+ batch_size (`int`): Размер чанка.
148
+
149
+ Возвращает:
150
+ `list[list[str]]`: Список тикеров в чанках.
151
+ """
152
+ tickers = await self.futures_tickers()
153
+ return batched_list(tickers, batch_size)
154
+
155
+ async def last_price(self, resolve_symbols: bool = False) -> dict[str, float]:
50
156
  """Возвращает последнюю цену для каждого тикера.
51
157
 
158
+ Параметры:
159
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
160
+
52
161
  Возвращает:
53
162
  dict[str, float]: Словарь с последними ценами для каждого тикера.
54
163
  """
55
- raise NotImplementedError()
164
+ raw_data = await self._client.all_mids()
165
+ return Adapter.last_price(raw_data, resolve_symbols)
56
166
 
57
167
  async def futures_last_price(self) -> dict[str, float]:
58
168
  """Возвращает последнюю цену для каждого тикера.
@@ -60,16 +170,20 @@ class UniClient(IUniClient[Client]):
60
170
  Возвращает:
61
171
  dict[str, float]: Словарь с последними ценами для каждого тикера.
62
172
  """
63
- raw_data = await self._client.perp_asset_contexts()
173
+ raw_data = await self._client.all_mids()
64
174
  return Adapter.futures_last_price(raw_data)
65
175
 
66
- async def ticker_24hr(self) -> TickerDailyDict:
176
+ async def ticker_24hr(self, resolve_symbols: bool = False) -> TickerDailyDict:
67
177
  """Возвращает статистику за последние 24 часа для каждого тикера.
68
178
 
179
+ Параметры:
180
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
181
+
69
182
  Возвращает:
70
183
  TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
71
184
  """
72
- raise NotImplementedError()
185
+ raw_data = await self._client.spot_meta_and_asset_contexts()
186
+ return Adapter.ticker_24hr(raw_data, resolve_symbols)
73
187
 
74
188
  async def futures_ticker_24hr(self) -> TickerDailyDict:
75
189
  """Возвращает статистику за последние 24 часа для каждого тикера.
@@ -77,7 +191,7 @@ class UniClient(IUniClient[Client]):
77
191
  Возвращает:
78
192
  TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
79
193
  """
80
- raw_data = await self._client.perp_asset_contexts()
194
+ raw_data = await self._client.perp_meta_and_asset_contexts()
81
195
  return Adapter.futures_ticker_24hr(raw_data)
82
196
 
83
197
  async def klines(
@@ -87,20 +201,41 @@ class UniClient(IUniClient[Client]):
87
201
  limit: int | None = None,
88
202
  start_time: int | None = None,
89
203
  end_time: int | None = None,
204
+ resolve_symbols: bool = False,
90
205
  ) -> list[KlineDict]:
91
206
  """Возвращает список свечей для тикера.
92
207
 
93
208
  Параметры:
94
- symbol (str): Название тикера.
209
+ symbol (str): Название тикера. Например "@1".
95
210
  limit (int | None): Количество свечей.
96
211
  interval (Timeframe | str): Таймфрейм свечей.
97
212
  start_time (int | None): Время начала периода в миллисекундах.
98
213
  end_time (int | None): Время окончания периода в миллисекундах.
214
+ resolve_symbols (bool): Если True, тикер маппится из вида "@123" в "BTC".
99
215
 
100
216
  Возвращает:
101
217
  list[KlineDict]: Список свечей для тикера.
102
218
  """
103
- raise NotImplementedError("Hyperliquid does not support klines")
219
+ if not limit and not all([start_time, end_time]):
220
+ raise ValueError("limit and (start_time and end_time) must be provided")
221
+
222
+ if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
223
+ if not isinstance(interval, Timeframe):
224
+ 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]
227
+ interval = (
228
+ interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.SPOT)
229
+ if isinstance(interval, Timeframe)
230
+ else interval
231
+ )
232
+ raw_data = await self._client.candle_snapshot(
233
+ coin=symbol,
234
+ interval=interval,
235
+ start_time=start_time, # type: ignore[reportArgumentType]
236
+ end_time=end_time, # type: ignore[reportArgumentType]
237
+ )
238
+ return Adapter.klines(raw_data=raw_data, resolve_symbols=resolve_symbols)
104
239
 
105
240
  async def futures_klines(
106
241
  self,
@@ -113,7 +248,7 @@ class UniClient(IUniClient[Client]):
113
248
  """Возвращает список свечей для тикера.
114
249
 
115
250
  Параметры:
116
- symbol (str): Название тикера.
251
+ symbol (str): Название тикера. Например "BTC".
117
252
  limit (int | None): Количество свечей.
118
253
  interval (Timeframe | str): Таймфрейм свечей.
119
254
  start_time (int | None): Время начала периода в миллисекундах.
@@ -122,7 +257,26 @@ class UniClient(IUniClient[Client]):
122
257
  Возвращает:
123
258
  list[KlineDict]: Список свечей для тикера.
124
259
  """
125
- raise NotImplementedError("Hyperliquid does not support klines")
260
+ if not limit and not all([start_time, end_time]):
261
+ raise ValueError("limit and (start_time and end_time) must be provided")
262
+
263
+ if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
264
+ if not isinstance(interval, Timeframe):
265
+ 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]
268
+ interval = (
269
+ interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.FUTURES)
270
+ if isinstance(interval, Timeframe)
271
+ else interval
272
+ )
273
+ raw_data = await self._client.candle_snapshot(
274
+ coin=symbol,
275
+ interval=interval,
276
+ start_time=start_time, # type: ignore[reportArgumentType]
277
+ end_time=end_time, # type: ignore[reportArgumentType]
278
+ )
279
+ return Adapter.futures_klines(raw_data)
126
280
 
127
281
  @overload
128
282
  async def funding_rate(self, symbol: str) -> float: ...
@@ -142,7 +296,7 @@ class UniClient(IUniClient[Client]):
142
296
  Возвращает:
143
297
  `dict[str, float] | float`: Ставка финансирования для тикера или словарь со ставками для всех тикеров.
144
298
  """
145
- raw_data = await self._client.perp_asset_contexts()
299
+ raw_data = await self._client.perp_meta_and_asset_contexts()
146
300
  adapted_data = Adapter.funding_rate(raw_data)
147
301
  return adapted_data[symbol] if symbol else adapted_data
148
302
 
@@ -166,6 +320,6 @@ class UniClient(IUniClient[Client]):
166
320
  открытого интереса в монетах. Если нет передан - то словарь, в котором ключ - тикер,
167
321
  а значение - словарь с временем и объемом открытого интереса в монетах.
168
322
  """
169
- raw_data = await self._client.perp_asset_contexts()
323
+ raw_data = await self._client.perp_meta_and_asset_contexts()
170
324
  adapted_data = Adapter.open_interest(raw_data)
171
325
  return adapted_data[symbol] if symbol else adapted_data
unicex/mapper.py CHANGED
@@ -3,81 +3,68 @@
3
3
  __all__ = [
4
4
  "get_uni_client",
5
5
  "get_uni_websocket_manager",
6
+ "get_exchange_info",
6
7
  ]
7
8
 
8
9
 
9
- from ._abc import IUniClient, IUniWebsocketManager
10
+ from ._abc import IExchangeInfo, IUniClient, IUniWebsocketManager
11
+ from .binance import ExchangeInfo as BinanceExchangeInfo
10
12
  from .binance import UniClient as BinanceUniClient
11
13
  from .binance import UniWebsocketManager as BinanceUniWebsocketManager
14
+ from .bitget import ExchangeInfo as BitgetExchangeInfo
12
15
  from .bitget import UniClient as BitgetUniClient
13
16
  from .bitget import UniWebsocketManager as BitgetUniWebsocketManager
14
- from .bitrue import UniClient as BitrueUniClient
15
- from .bitrue import UniWebsocketManager as BitrueUniWebsocketManager
16
- from .bitunix import UniClient as BitunixUniClient
17
- from .bitunix import UniWebsocketManager as BitunixUniWebsocketManager
18
- from .btse import UniClient as BtseUniClient
19
- from .btse import UniWebsocketManager as BtseUniWebsocketManager
17
+ from .bybit import ExchangeInfo as BybitExchangeInfo
20
18
  from .bybit import UniClient as BybitUniClient
21
19
  from .bybit import UniWebsocketManager as BybitUniWebsocketManager
22
20
  from .enums import Exchange
23
21
  from .exceptions import NotSupported
22
+ from .gateio import ExchangeInfo as GateioExchangeInfo
24
23
  from .gateio import UniClient as GateioUniClient
25
24
  from .gateio import UniWebsocketManager as GateioUniWebsocketManager
25
+ from .hyperliquid import ExchangeInfo as HyperliquidExchangeInfo
26
26
  from .hyperliquid import UniClient as HyperliquidUniClient
27
27
  from .hyperliquid import UniWebsocketManager as HyperliquidUniWebsocketManager
28
- from .kcex import UniClient as KcexUniClient
29
- from .kcex import UniWebsocketManager as KcexUniWebsocketManager
30
- from .kraken import UniClient as KrakenUniClient
31
- from .kraken import UniWebsocketManager as KrakenUniWebsocketManager
32
- from .kucoin import UniClient as KucoinUniClient
33
- from .kucoin import UniWebsocketManager as KucoinUniWebsocketManager
28
+ from .mexc import ExchangeInfo as MexcExchangeInfo
34
29
  from .mexc import UniClient as MexcUniClient
35
30
  from .mexc import UniWebsocketManager as MexcUniWebsocketManager
31
+ from .okx import ExchangeInfo as OkxExchangeInfo
36
32
  from .okx import UniClient as OkxUniClient
37
33
  from .okx import UniWebsocketManager as OkxUniWebsocketManager
38
- from .weex import UniClient as WeexUniClient
39
- from .weex import UniWebsocketManager as WeexUniWebsocketManager
40
- from .xt import UniClient as XtUniClient
41
- from .xt import UniWebsocketManager as XtUniWebsocketManager
42
34
 
43
35
  _UNI_CLIENT_MAPPER: dict[Exchange, type[IUniClient]] = {
44
36
  Exchange.BINANCE: BinanceUniClient,
45
37
  Exchange.BITGET: BitgetUniClient,
46
- Exchange.BITRUE: BitrueUniClient,
47
- Exchange.BITUNIX: BitunixUniClient,
48
- Exchange.BTSE: BtseUniClient,
49
38
  Exchange.BYBIT: BybitUniClient,
50
39
  Exchange.GATEIO: GateioUniClient,
51
40
  Exchange.HYPERLIQUID: HyperliquidUniClient,
52
- Exchange.KCEX: KcexUniClient,
53
- Exchange.KRAKEN: KrakenUniClient,
54
- Exchange.KUCOIN: KucoinUniClient,
55
41
  Exchange.MEXC: MexcUniClient,
56
42
  Exchange.OKX: OkxUniClient,
57
- Exchange.WEEX: WeexUniClient,
58
- Exchange.XT: XtUniClient,
59
43
  }
60
44
  """Маппер, который связывает биржу и реализацию унифицированного клиента."""
61
45
 
62
46
  _UNI_WS_MANAGER_MAPPER: dict[Exchange, type[IUniWebsocketManager]] = {
63
47
  Exchange.BINANCE: BinanceUniWebsocketManager,
64
48
  Exchange.BITGET: BitgetUniWebsocketManager,
65
- Exchange.BITRUE: BitrueUniWebsocketManager,
66
- Exchange.BITUNIX: BitunixUniWebsocketManager,
67
- Exchange.BTSE: BtseUniWebsocketManager,
68
49
  Exchange.BYBIT: BybitUniWebsocketManager,
69
50
  Exchange.GATEIO: GateioUniWebsocketManager,
70
51
  Exchange.HYPERLIQUID: HyperliquidUniWebsocketManager,
71
- Exchange.KCEX: KcexUniWebsocketManager,
72
- Exchange.KRAKEN: KrakenUniWebsocketManager,
73
- Exchange.KUCOIN: KucoinUniWebsocketManager,
74
52
  Exchange.MEXC: MexcUniWebsocketManager,
75
53
  Exchange.OKX: OkxUniWebsocketManager,
76
- Exchange.WEEX: WeexUniWebsocketManager,
77
- Exchange.XT: XtUniWebsocketManager,
78
54
  }
79
55
  """Маппер, который связывает биржу и реализацию унифицированного вебсокет-менеджера."""
80
56
 
57
+ _EXCHANGE_INFO_MAPPER: dict[Exchange, type[IExchangeInfo]] = {
58
+ Exchange.BINANCE: BinanceExchangeInfo,
59
+ Exchange.BITGET: BitgetExchangeInfo,
60
+ Exchange.BYBIT: BybitExchangeInfo,
61
+ Exchange.GATEIO: GateioExchangeInfo,
62
+ Exchange.HYPERLIQUID: HyperliquidExchangeInfo,
63
+ Exchange.MEXC: MexcExchangeInfo,
64
+ Exchange.OKX: OkxExchangeInfo,
65
+ }
66
+ """Маппер, который связывает биржу и реализацию сборщика информации о тикерах на бирже."""
67
+
81
68
 
82
69
  def get_uni_client(exchange: Exchange) -> type[IUniClient]:
83
70
  """Возвращает унифицированный клиент для указанной биржи.
@@ -107,3 +94,18 @@ def get_uni_websocket_manager(exchange: Exchange) -> type[IUniWebsocketManager]:
107
94
  return _UNI_WS_MANAGER_MAPPER[exchange]
108
95
  except KeyError as e:
109
96
  raise NotSupported(f"Unsupported exchange: {exchange}") from e
97
+
98
+
99
+ def get_exchange_info(exchange: Exchange) -> type[IExchangeInfo]:
100
+ """Возвращает унифицированный интерфейс для получения информации о бирже.
101
+
102
+ Параметры:
103
+ exchange (`Exchange`): Биржа.
104
+
105
+ Возвращает:
106
+ `type[IExchangeInfo]`: Унифицированный интерфейс для получения информации о бирже.
107
+ """
108
+ try:
109
+ return _EXCHANGE_INFO_MAPPER[exchange]
110
+ except KeyError as e:
111
+ raise NotSupported(f"Unsupported exchange: {exchange}") from e
unicex/mexc/__init__.py CHANGED
@@ -6,10 +6,22 @@ __all__ = [
6
6
  "UserWebsocket",
7
7
  "WebsocketManager",
8
8
  "UniWebsocketManager",
9
+ "ExchangeInfo",
9
10
  ]
10
11
 
11
12
  from .client import Client
13
+ from .exchange_info import ExchangeInfo
12
14
  from .uni_client import UniClient
13
15
  from .uni_websocket_manager import UniWebsocketManager
14
16
  from .user_websocket import UserWebsocket
15
17
  from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже Mexc."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Mexc."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
unicex/mexc/adapter.py CHANGED
@@ -9,6 +9,8 @@ from unicex.types import (
9
9
  )
10
10
  from unicex.utils import catch_adapter_errors, decorate_all_methods
11
11
 
12
+ from .exchange_info import ExchangeInfo
13
+
12
14
 
13
15
  @decorate_all_methods(catch_adapter_errors)
14
16
  class Adapter:
@@ -25,7 +27,9 @@ class Adapter:
25
27
  Возвращает:
26
28
  list[str]: Список тикеров.
27
29
  """
28
- return [item["symbol"] for item in raw_data if only_usdt or item["symbol"].endswith("USDT")]
30
+ return [
31
+ item["symbol"] for item in raw_data if item["symbol"].endswith("USDT") or not only_usdt
32
+ ]
29
33
 
30
34
  @staticmethod
31
35
  def futures_tickers(raw_data: dict, only_usdt: bool = True) -> list[str]:
@@ -41,7 +45,7 @@ class Adapter:
41
45
  return [
42
46
  item["symbol"]
43
47
  for item in raw_data["data"]
44
- if only_usdt or item["symbol"].endswith("USDT")
48
+ if item["symbol"].endswith("USDT") or not only_usdt
45
49
  ]
46
50
 
47
51
  @staticmethod
@@ -97,14 +101,15 @@ class Adapter:
97
101
  Возвращает:
98
102
  TickerDailyDict: Словарь, где ключ - тикер, а значение - статистика за последние 24 часа.
99
103
  """
100
- return {
101
- item["symbol"]: TickerDailyItem(
104
+ result = {}
105
+ for item in raw_data["data"]:
106
+ symbol = item["symbol"]
107
+ result[symbol] = TickerDailyItem(
102
108
  p=float(item["riseFallRate"]) * 100,
103
- v=float(item["volume24"]),
109
+ v=float(item["volume24"]) * Adapter._get_contract_size(symbol),
104
110
  q=float(item["amount24"]),
105
111
  )
106
- for item in raw_data["data"]
107
- }
112
+ return result
108
113
 
109
114
  @staticmethod
110
115
  def open_interest(raw_data: dict) -> OpenInterestDict:
@@ -116,10 +121,14 @@ class Adapter:
116
121
  Возвращает:
117
122
  OpenInterestDict: Словарь, где ключ - тикер, а значение - агрегированные данные открытого интереса.
118
123
  """
119
- return {
120
- item["symbol"]: OpenInterestItem(t=item["timestamp"], v=float(item["holdVol"]))
121
- for item in raw_data["data"]
122
- }
124
+ result = {}
125
+ for item in raw_data["data"]:
126
+ symbol = item["symbol"]
127
+ result[symbol] = OpenInterestItem(
128
+ t=item["timestamp"],
129
+ v=float(item["holdVol"]) * Adapter._get_contract_size(symbol),
130
+ )
131
+ return result
123
132
 
124
133
  @staticmethod
125
134
  def funding_rate(raw_data: dict) -> dict[str, float]:
@@ -220,3 +229,11 @@ class Adapter:
220
229
  )
221
230
 
222
231
  return sorted(klines, key=lambda kline_item: kline_item["t"])
232
+
233
+ @staticmethod
234
+ def _get_contract_size(symbol: str) -> float:
235
+ """Возвращает размер контракта для указанного символа тикера."""
236
+ try:
237
+ return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
238
+ except: # noqa
239
+ return 1
@@ -0,0 +1,32 @@
1
+ __all__ = ["ExchangeInfo"]
2
+
3
+ import aiohttp
4
+
5
+ from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+
9
+ class ExchangeInfo(IExchangeInfo):
10
+ """Предзагружает информацию о тикерах для биржи Mexc."""
11
+
12
+ @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")