unicex 0.13.17__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 (93) hide show
  1. unicex/__init__.py +200 -0
  2. unicex/_abc/__init__.py +11 -0
  3. unicex/_abc/exchange_info.py +216 -0
  4. unicex/_abc/uni_client.py +329 -0
  5. unicex/_abc/uni_websocket_manager.py +294 -0
  6. unicex/_base/__init__.py +9 -0
  7. unicex/_base/client.py +214 -0
  8. unicex/_base/websocket.py +261 -0
  9. unicex/binance/__init__.py +27 -0
  10. unicex/binance/adapter.py +202 -0
  11. unicex/binance/client.py +1577 -0
  12. unicex/binance/exchange_info.py +62 -0
  13. unicex/binance/uni_client.py +188 -0
  14. unicex/binance/uni_websocket_manager.py +166 -0
  15. unicex/binance/user_websocket.py +186 -0
  16. unicex/binance/websocket_manager.py +912 -0
  17. unicex/bitget/__init__.py +27 -0
  18. unicex/bitget/adapter.py +188 -0
  19. unicex/bitget/client.py +2514 -0
  20. unicex/bitget/exchange_info.py +48 -0
  21. unicex/bitget/uni_client.py +198 -0
  22. unicex/bitget/uni_websocket_manager.py +275 -0
  23. unicex/bitget/user_websocket.py +7 -0
  24. unicex/bitget/websocket_manager.py +232 -0
  25. unicex/bybit/__init__.py +27 -0
  26. unicex/bybit/adapter.py +208 -0
  27. unicex/bybit/client.py +1876 -0
  28. unicex/bybit/exchange_info.py +53 -0
  29. unicex/bybit/uni_client.py +200 -0
  30. unicex/bybit/uni_websocket_manager.py +291 -0
  31. unicex/bybit/user_websocket.py +7 -0
  32. unicex/bybit/websocket_manager.py +339 -0
  33. unicex/enums.py +273 -0
  34. unicex/exceptions.py +64 -0
  35. unicex/extra.py +335 -0
  36. unicex/gate/__init__.py +27 -0
  37. unicex/gate/adapter.py +178 -0
  38. unicex/gate/client.py +1667 -0
  39. unicex/gate/exchange_info.py +55 -0
  40. unicex/gate/uni_client.py +214 -0
  41. unicex/gate/uni_websocket_manager.py +269 -0
  42. unicex/gate/user_websocket.py +7 -0
  43. unicex/gate/websocket_manager.py +513 -0
  44. unicex/hyperliquid/__init__.py +27 -0
  45. unicex/hyperliquid/adapter.py +261 -0
  46. unicex/hyperliquid/client.py +2315 -0
  47. unicex/hyperliquid/exchange_info.py +119 -0
  48. unicex/hyperliquid/uni_client.py +325 -0
  49. unicex/hyperliquid/uni_websocket_manager.py +269 -0
  50. unicex/hyperliquid/user_websocket.py +7 -0
  51. unicex/hyperliquid/websocket_manager.py +393 -0
  52. unicex/mapper.py +111 -0
  53. unicex/mexc/__init__.py +27 -0
  54. unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
  55. unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
  56. unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
  57. unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
  58. unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
  59. unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
  60. unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
  61. unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
  62. unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
  63. unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
  64. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
  65. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
  66. unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
  67. unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
  68. unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
  69. unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
  70. unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
  71. unicex/mexc/_spot_ws_proto/__init__.py +335 -0
  72. unicex/mexc/adapter.py +239 -0
  73. unicex/mexc/client.py +846 -0
  74. unicex/mexc/exchange_info.py +47 -0
  75. unicex/mexc/uni_client.py +211 -0
  76. unicex/mexc/uni_websocket_manager.py +269 -0
  77. unicex/mexc/user_websocket.py +7 -0
  78. unicex/mexc/websocket_manager.py +456 -0
  79. unicex/okx/__init__.py +27 -0
  80. unicex/okx/adapter.py +150 -0
  81. unicex/okx/client.py +2864 -0
  82. unicex/okx/exchange_info.py +47 -0
  83. unicex/okx/uni_client.py +202 -0
  84. unicex/okx/uni_websocket_manager.py +269 -0
  85. unicex/okx/user_websocket.py +7 -0
  86. unicex/okx/websocket_manager.py +743 -0
  87. unicex/types.py +164 -0
  88. unicex/utils.py +218 -0
  89. unicex-0.13.17.dist-info/METADATA +243 -0
  90. unicex-0.13.17.dist-info/RECORD +93 -0
  91. unicex-0.13.17.dist-info/WHEEL +5 -0
  92. unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
  93. unicex-0.13.17.dist-info/top_level.txt +1 -0
unicex/extra.py ADDED
@@ -0,0 +1,335 @@
1
+ """Модуль, который предоставляет дополнительные функции, которые могут пригодиться в работе."""
2
+
3
+ __all__ = [
4
+ "percent_greater",
5
+ "percent_less",
6
+ "TimeoutTracker",
7
+ "generate_ex_link",
8
+ "generate_tv_link",
9
+ "generate_cg_link",
10
+ "make_humanreadable",
11
+ "normalize_ticker",
12
+ "normalize_symbol",
13
+ ]
14
+
15
+ import time
16
+ from typing import Literal
17
+
18
+ from .enums import Exchange, MarketType
19
+ from .exceptions import NotSupported
20
+
21
+
22
+ def percent_greater(higher: float, lower: float) -> float:
23
+ """Возвращает на сколько процентов `higher` больше `lower`.
24
+
25
+ Можно воспринимать полученное значение как если вести линейку на tradingview.com от меньшего значения к большему.
26
+
27
+ Например:
28
+ ```python
29
+ percent_greater(120, 100)
30
+ >> 20.0
31
+ ```
32
+
33
+ Возвращает:
34
+ `float`: На сколько процентов `higher` больше `lower`.
35
+ """
36
+ if lower == 0:
37
+ return 0.0 # Не будем возвращать float('inf'), чтобы не ломать логику приложения
38
+ return (higher / lower - 1) * 100
39
+
40
+
41
+ def percent_less(higher: float, lower: float) -> float:
42
+ """Возвращает на сколько процентов `lower` меньше `higher`.
43
+
44
+ Можно воспринимать полученное значение как если вести линейку на tradingview.com от большего значения к меньшему.
45
+
46
+ Например:
47
+ ```python
48
+ percent_less(120, 100)
49
+ >> 16.67777777777777
50
+ ```
51
+
52
+ Возвращает:
53
+ `float`: На сколько процентов `lower` меньше `higher`.
54
+ """
55
+ if lower == 0:
56
+ return 0.0 # Не будем возвращать float('inf'), чтобы не ломать логику приложения
57
+ return (1 - lower / higher) * 100
58
+
59
+
60
+ class TimeoutTracker[T]:
61
+ """Универсальный менеджер для управления таймаутами любых объектов.
62
+ Позволяет временно блокировать объекты на заданный промежуток времени.
63
+ """
64
+
65
+ def __init__(self) -> None:
66
+ """Инициализирует пустой словарь для отслеживания заблокированных объектов."""
67
+ self._blocked_items: dict[T, float] = {}
68
+
69
+ def is_blocked(self, item: T) -> bool:
70
+ """Проверяет, находится ли объект в состоянии блокировки.
71
+ Если срок блокировки истёк, удаляет объект из списка.
72
+
73
+ Параметры:
74
+ item (`T`): Объект, который нужно проверить.
75
+
76
+ Возвращает:
77
+ `bool`: True, если объект заблокирован, иначе False.
78
+ """
79
+ if item in self._blocked_items:
80
+ if time.time() < self._blocked_items[item]:
81
+ return True
82
+ else:
83
+ del self._blocked_items[item]
84
+ return False
85
+
86
+ def block(self, item: T, duration: int) -> None:
87
+ """Блокирует объект на указанное количество секунд.
88
+
89
+ Параметры:
90
+ item (`T`): Объект, который нужно заблокировать.
91
+ duration (`int`): Длительность блокировки в секундах.
92
+ """
93
+ self._blocked_items[item] = time.time() + duration
94
+
95
+
96
+ def normalize_ticker(raw_ticker: str) -> str:
97
+ """Нормализует тикер и возвращает базовую валюту (например, `BTC`).
98
+
99
+ Эта функция принимает тикер в различных форматах (с разделителями, постфиксом SWAP,
100
+ в верхнем или нижнем регистре) и приводит его к стандартному виду — только базовый актив.
101
+
102
+ Примеры:
103
+ ```python
104
+ normalize_ticker("BTC-USDT") # "BTC"
105
+ normalize_ticker("BTC-USDT-SWAP") # "BTC"
106
+ normalize_ticker("btc_usdt") # "BTC"
107
+ normalize_ticker("BTCUSDT") # "BTC"
108
+ normalize_ticker("BTC") # "BTC"
109
+ ```
110
+
111
+ Параметры:
112
+ raw_ticker (`str`): Исходный тикер в любом из распространённых форматов.
113
+
114
+ Возвращает:
115
+ `str`: Базовый актив в верхнем регистре (например, `"BTC"`).
116
+ """
117
+ ticker = raw_ticker.upper()
118
+
119
+ # Удаляем постфиксы SWAP
120
+ if ticker.endswith(("SWAP", "-SWAP", "_SWAP", ".SWAP")):
121
+ ticker = (
122
+ ticker.removesuffix("-SWAP")
123
+ .removesuffix("_SWAP")
124
+ .removesuffix(".SWAP")
125
+ .removesuffix("SWAP")
126
+ )
127
+
128
+ # Удаляем разделители
129
+ ticker = ticker.translate(str.maketrans("", "", "-_."))
130
+
131
+ # Убираем суффикс валюты котировки
132
+ for quote in ("USDT", "USDC"):
133
+ if ticker.endswith(quote):
134
+ ticker = ticker.removesuffix(quote)
135
+ break
136
+
137
+ return ticker
138
+
139
+
140
+ def normalize_symbol(raw_ticker: str, quote: Literal["USDT", "USDC"] = "USDT") -> str:
141
+ """Нормализует тикер до унифицированного символа (например, `BTCUSDT`).
142
+
143
+ Функция принимает тикер в любом из популярных форматов и возвращает полный символ,
144
+ состоящий из базовой валюты и указанной валюты котировки (`USDT` или `USDC`).
145
+
146
+ Примеры:
147
+ ```python
148
+ normalize_symbol("BTC-USDT") # "BTCUSDT"
149
+ normalize_symbol("BTC") # "BTCUSDT"
150
+ normalize_symbol("btc_usdt_swap") # "BTCUSDT"
151
+ normalize_symbol("ETH", "USDC") # "ETHUSDC"
152
+ ```
153
+
154
+ Параметры:
155
+ raw_ticker (`str`): Исходный тикер в любом из распространённых форматов.
156
+ quote (`Literal["USDT", "USDC"]`, optional): Валюта котировки.
157
+ По умолчанию `"USDT"`.
158
+
159
+ Возвращает:
160
+ `str`: Символ в унифицированном формате, например `"BTCUSDT"`.
161
+ """
162
+ base = normalize_ticker(raw_ticker)
163
+ return f"{base}{quote}"
164
+
165
+
166
+ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
167
+ """Генерирует ссылку на биржу.
168
+
169
+ Параметры:
170
+ exchange (`Exchange`): Биржа.
171
+ market_type (`MarketType`): Тип рынка.
172
+ symbol (`str`): Символ.
173
+
174
+ Возвращает:
175
+ `str`: Ссылка на биржу.
176
+ """
177
+ symbol = normalize_symbol(symbol)
178
+ ticker = normalize_ticker(symbol)
179
+ if exchange == Exchange.BINANCE:
180
+ if market_type == MarketType.FUTURES:
181
+ return f"https://www.binance.com/en/futures/{symbol}"
182
+ else:
183
+ return f"https://www.binance.com/en/trade/{ticker}_USDT?type=spot"
184
+ elif exchange == Exchange.BYBIT:
185
+ if market_type == MarketType.FUTURES:
186
+ return f"https://www.bybit.com/trade/usdt/{symbol}"
187
+ else:
188
+ return f"https://www.bybit.com/en/trade/spot/{ticker}/USDT"
189
+ elif exchange == Exchange.BITGET:
190
+ if market_type == MarketType.FUTURES:
191
+ return f"https://www.bitget.com/ru/futures/usdt/{symbol}"
192
+ else:
193
+ return f"https://www.bitget.com/ru/spot/{symbol}"
194
+ elif exchange == Exchange.OKX:
195
+ if market_type == MarketType.FUTURES:
196
+ return f"https://www.okx.com/ru/trade-swap/{ticker.lower()}-usdt-swap"
197
+ else:
198
+ return f"https://www.okx.com/ru/trade-spot/{ticker.lower()}-usdt"
199
+ elif exchange == Exchange.MEXC:
200
+ if market_type == MarketType.FUTURES:
201
+ return f"https://www.mexc.com/ru-RU/futures/{ticker}_USDT?type=linear_swap"
202
+ else:
203
+ return f"https://www.mexc.com/ru-RU/exchange/{ticker}_USDT"
204
+ elif exchange == Exchange.GATE:
205
+ if market_type == MarketType.FUTURES:
206
+ return f"https://www.gate.com/ru/futures/USDT/{ticker}_USDT"
207
+ else:
208
+ return f"https://www.gate.com/ru/trade/{ticker}_USDT"
209
+ elif exchange == Exchange.XT:
210
+ if market_type == MarketType.FUTURES:
211
+ return f"https://www.xt.com/ru/futures/trade/{ticker.lower()}_usdt"
212
+ else:
213
+ return f"https://www.xt.com/ru/trade/{ticker.lower()}_usdt"
214
+ elif exchange == Exchange.BITUNIX:
215
+ if market_type == MarketType.FUTURES:
216
+ return f"https://www.bitunix.com/ru-ru/contract-trade/{ticker.upper()}USDT"
217
+ else:
218
+ return f"https://www.bitunix.com/ru-ru/spot-trade/{ticker.upper()}USDT"
219
+ elif exchange == Exchange.KCEX:
220
+ if market_type == MarketType.FUTURES:
221
+ return f"https://www.kcex.com/ru-RU/futures/exchange/{ticker.upper()}_USDT"
222
+ else:
223
+ return f"https://www.kcex.com/ru-RU/exchange/{ticker.upper()}_USDT"
224
+ elif exchange == Exchange.HYPERLIQUID:
225
+ if market_type == MarketType.FUTURES:
226
+ return f"https://app.hyperliquid.xyz/trade/{ticker}"
227
+ else:
228
+ return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
229
+ else:
230
+ raise NotSupported(f"Exchange {exchange} is not supported")
231
+
232
+
233
+ def generate_tv_link(exchange: Exchange, market_type: MarketType, symbol: str) -> str:
234
+ """Генерирует ссылку для TradingView.
235
+
236
+ Параметры:
237
+ exchange (`Exchange`): Биржа.
238
+ market_type (`MarketType`): Тип рынка.
239
+ symbol (`str`): Символ.
240
+
241
+ Возвращает:
242
+ `str`: Ссылка для TradingView.
243
+ """
244
+ symbol = normalize_symbol(symbol)
245
+ exchange_str = "GATEIO" if exchange == Exchange.GATE else str(exchange)
246
+ if market_type == MarketType.FUTURES:
247
+ return f"https://www.tradingview.com/chart/?symbol={exchange_str}:{symbol}.P"
248
+ else:
249
+ return f"https://www.tradingview.com/chart/?symbol={exchange_str}:{symbol}"
250
+
251
+
252
+ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -> str:
253
+ """Генерирует ссылку для CoinGlass.
254
+
255
+ Параметры:
256
+ exchange (`Exchange`): Биржа.
257
+ market_type (`MarketType`): Тип рынка.
258
+ symbol (`str`): Символ.
259
+
260
+ Возвращает:
261
+ `str`: Ссылка для CoinGlass.
262
+ """
263
+ base_url = "https://www.coinglass.com/tv/ru"
264
+
265
+ symbol = normalize_symbol(symbol)
266
+
267
+ if market_type == MarketType.FUTURES:
268
+ match exchange:
269
+ case Exchange.OKX:
270
+ return f"{base_url}/OKX_{symbol.replace('USDT', '-USDT')}-SWAP"
271
+ case Exchange.MEXC:
272
+ return f"{base_url}/MEXC_{symbol.replace('USDT', '_USDT')}"
273
+ case Exchange.BITGET:
274
+ return f"{base_url}/Bitget_{symbol}_UMCBL"
275
+ case Exchange.GATE:
276
+ return f"{base_url}/Gate_{symbol.replace('USDT', '_USDT')}"
277
+ case Exchange.BITUNIX:
278
+ return f"{base_url}/Bitunix_{symbol}"
279
+ case Exchange.HYPERLIQUID:
280
+ return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
281
+ case _:
282
+ return f"{base_url}/{exchange.capitalize()}_{symbol}"
283
+ else:
284
+ # Для спота корректная ссылка есть только у OKX
285
+ if exchange == Exchange.OKX:
286
+ return f"{base_url}/SPOT_{exchange.upper()}_{symbol.replace('USDT', '-USDT')}"
287
+ # Для остальных бирж ссылки нет → возвращаем заглушку
288
+ return generate_cg_link(exchange, MarketType.FUTURES, symbol)
289
+
290
+
291
+ def make_humanreadable(value: float, locale: Literal["ru", "en"] = "ru") -> str:
292
+ """Функция превращает большие числа в удобочитаемый вид.
293
+
294
+ Принимает:
295
+ value (float): число для преобразования
296
+ locale (Literal["ru", "en"]): язык для форматирования числа
297
+
298
+ Возвращает:
299
+ str: Человеческое представление числа
300
+ """
301
+ suffixes = {
302
+ "ru": {
303
+ 1_000: "тыс.",
304
+ 1_000_000: "млн.",
305
+ 1_000_000_000: "млрд.",
306
+ 1_000_000_000_000: "трлн.",
307
+ 1_000_000_000_000_000: "квдрлн.",
308
+ 1_000_000_000_000_000_000: "квнтлн.",
309
+ },
310
+ "en": {
311
+ 1_000: "K",
312
+ 1_000_000: "M",
313
+ 1_000_000_000: "B",
314
+ 1_000_000_000_000: "T",
315
+ 1_000_000_000_000_000: "Qa", # Quadrillion
316
+ 1_000_000_000_000_000_000: "Qi", # Quintillion
317
+ },
318
+ }
319
+
320
+ selected_suffixes = suffixes[locale]
321
+
322
+ for divisor in sorted(selected_suffixes.keys(), reverse=True):
323
+ if abs(value) >= divisor:
324
+ number = value / divisor
325
+ if locale == "ru":
326
+ return (
327
+ f"{number:,.2f}".replace(",", " ").replace(".", ",")
328
+ + f" {selected_suffixes[divisor]}"
329
+ )
330
+ return f"{number:,.2f} {selected_suffixes[divisor]}"
331
+
332
+ # Форматирование "малых" чисел
333
+ if locale == "ru":
334
+ return f"{value:,.2f}".replace(",", " ").replace(".", ",")
335
+ return f"{value:,.2f}"
@@ -0,0 +1,27 @@
1
+ """Пакет, содержащий реализации клиентов и менеджеров для работы с биржей Gateio."""
2
+
3
+ __all__ = [
4
+ "Client",
5
+ "UniClient",
6
+ "UserWebsocket",
7
+ "WebsocketManager",
8
+ "UniWebsocketManager",
9
+ "ExchangeInfo",
10
+ ]
11
+
12
+ from .client import Client
13
+ from .exchange_info import ExchangeInfo
14
+ from .uni_client import UniClient
15
+ from .uni_websocket_manager import UniWebsocketManager
16
+ from .user_websocket import UserWebsocket
17
+ from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже Gateio."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Gateio."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
unicex/gate/adapter.py ADDED
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+
5
+ __all__ = ["Adapter"]
6
+
7
+ from unicex.types import (
8
+ KlineDict,
9
+ OpenInterestDict,
10
+ OpenInterestItem,
11
+ TickerDailyDict,
12
+ TickerDailyItem,
13
+ )
14
+ from unicex.utils import catch_adapter_errors, decorate_all_methods
15
+
16
+
17
+ @decorate_all_methods(catch_adapter_errors)
18
+ class Adapter:
19
+ """Адаптер для унификации данных с Gateio API."""
20
+
21
+ @staticmethod
22
+ def tickers(raw_data: list[dict], only_usdt: bool) -> list[str]:
23
+ """Преобразует сырой ответ о тикерах в список символов.
24
+
25
+ Параметры:
26
+ raw_data (list[dict]): Сырой ответ с биржи.
27
+ only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре c USDT.
28
+
29
+ Возвращает:
30
+ list[str]: Список тикеров.
31
+ """
32
+ return [
33
+ item["currency_pair"]
34
+ for item in raw_data
35
+ if item["currency_pair"].endswith("USDT") or not only_usdt
36
+ ]
37
+
38
+ @staticmethod
39
+ def futures_tickers(raw_data: list[dict], only_usdt: bool) -> list[str]:
40
+ """Преобразует сырой ответ о фьючерсных тикерах в список символов.
41
+
42
+ Параметры:
43
+ raw_data (list[dict]): Сырой ответ с биржи.
44
+ only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре c USDT.
45
+
46
+ Возвращает:
47
+ list[str]: Список тикеров.
48
+ """
49
+ return [
50
+ item["contract"]
51
+ for item in raw_data
52
+ if item["contract"].endswith("USDT") or not only_usdt
53
+ ]
54
+
55
+ @staticmethod
56
+ def last_price(raw_data: list[dict]) -> dict[str, float]:
57
+ """Преобразует данные о последних ценах (spot) в унифицированный формат.
58
+
59
+ Параметры:
60
+ raw_data (list[dict]): Сырой ответ с биржи.
61
+
62
+ Возвращает:
63
+ dict[str, float]: Словарь, где ключ — тикер, а значение — последняя цена.
64
+ """
65
+ return {item["currency_pair"]: float(item["last"]) for item in raw_data}
66
+
67
+ @staticmethod
68
+ def futures_last_price(raw_data: list[dict]) -> dict[str, float]:
69
+ """Преобразует данные о последних ценах (futures) в унифицированный формат."""
70
+ return {item["contract"]: float(item["last"]) for item in raw_data}
71
+
72
+ @staticmethod
73
+ def ticker_24hr(raw_data: list[dict]) -> TickerDailyDict:
74
+ """Преобразует 24-часовую статистику (spot) в унифицированный формат.
75
+
76
+ Параметры:
77
+ raw_data (list[dict]): Сырой ответ с биржи.
78
+
79
+ Возвращает:
80
+ TickerDailyDict: Словарь, где ключ — тикер, а значение — агрегированная статистика.
81
+ """
82
+ return {
83
+ item["currency_pair"]: TickerDailyItem(
84
+ p=float(item["change_percentage"]),
85
+ v=float(item["base_volume"]),
86
+ q=float(item["quote_volume"]),
87
+ )
88
+ for item in raw_data
89
+ }
90
+
91
+ @staticmethod
92
+ def futures_ticker_24hr(raw_data: list[dict]) -> TickerDailyDict:
93
+ """Преобразует 24-часовую статистику (futures) в унифицированный формат."""
94
+ return {
95
+ item["contract"]: TickerDailyItem(
96
+ p=float(item["change_percentage"]),
97
+ v=float(item["volume_24h_base"]),
98
+ q=float(item["volume_24h_quote"]),
99
+ )
100
+ for item in raw_data
101
+ }
102
+
103
+ @staticmethod
104
+ def klines(raw_data: list[list], symbol: str) -> list[KlineDict]:
105
+ """Преобразует данные о свечах в унифицированный формат.
106
+
107
+ Параметры:
108
+ raw_data (list[list]): Сырой ответ с биржи.
109
+ symbol (str): Символ тикера.
110
+
111
+ Возвращает:
112
+ list[KlineDict]: Список свечей.
113
+ """
114
+ return [
115
+ KlineDict(
116
+ s=symbol,
117
+ t=int(kline[0]) * 1000, # переводим секунды → миллисекунды
118
+ o=float(kline[5]),
119
+ h=float(kline[3]),
120
+ l=float(kline[4]),
121
+ c=float(kline[2]),
122
+ v=float(kline[6]),
123
+ q=float(kline[1]),
124
+ T=None,
125
+ x=kline[7] == "true",
126
+ )
127
+ for kline in sorted(
128
+ raw_data,
129
+ key=lambda x: int(x[0]),
130
+ )
131
+ ]
132
+
133
+ @staticmethod
134
+ def futures_klines(raw_data: list[dict], symbol: str) -> list[KlineDict]:
135
+ """Преобразует данные о свечах в унифицированный формат.
136
+
137
+ Параметры:
138
+ raw_data (list[dict]): Сырой ответ с биржи.
139
+ symbol (str): Символ тикера.
140
+
141
+ Возвращает:
142
+ list[KlineDict]: Список свечей.
143
+ """
144
+ return [
145
+ KlineDict(
146
+ s=symbol,
147
+ t=int(kline["t"]) * 1000, # переводим секунды → миллисекунды
148
+ o=float(kline["o"]),
149
+ h=float(kline["h"]),
150
+ l=float(kline["l"]),
151
+ c=float(kline["c"]),
152
+ v=float(kline["v"]),
153
+ q=float(kline["sum"]), # "sum" = объем в $ (quote volume)
154
+ T=None,
155
+ x=None,
156
+ )
157
+ for kline in sorted(raw_data, key=lambda x: int(x["t"]))
158
+ ]
159
+
160
+ @staticmethod
161
+ def funding_rate(raw_data: list[dict]) -> dict[str, float]:
162
+ """Преобразует данные о ставках финансирования в унифицированный формат."""
163
+ return {
164
+ item["contract"]: float(item["funding_rate"]) * 100
165
+ for item in raw_data
166
+ if item.get("funding_rate") is not None
167
+ }
168
+
169
+ @staticmethod
170
+ def open_interest(raw_data: list[dict]) -> OpenInterestDict:
171
+ """Преобразует данные об открытом интересе в унифицированный формат."""
172
+ return {
173
+ item["contract"]: OpenInterestItem(
174
+ t=int(time.time() * 1000),
175
+ v=float(item["total_size"]) * float(item["quanto_multiplier"]),
176
+ )
177
+ for item in raw_data
178
+ }