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
@@ -0,0 +1,339 @@
1
+ __all__ = ["WebsocketManager"]
2
+
3
+ import json
4
+ import warnings
5
+ from collections.abc import Awaitable, Callable, Sequence
6
+ from typing import Any, Literal
7
+
8
+ from unicex._base import Websocket
9
+
10
+ from .client import Client
11
+
12
+ type CallbackType = Callable[[Any], Awaitable[None]]
13
+
14
+
15
+ class WebsocketManager:
16
+ """Менеджер асинхронных вебсокетов для Bybit."""
17
+
18
+ _SPOT_URL: str = "wss://stream.bybit.com/v5/public/spot"
19
+ """Базовый URL для вебсокета на спот."""
20
+
21
+ _LINEAR_URL: str = "wss://stream.bybit.com/v5/public/linear"
22
+ """Базовый URL для вебсокета на USDT/USDC перпетуалы и фьючерсы."""
23
+
24
+ _INVERSE_URL: str = "wss://stream.bybit.com/v5/public/inverse"
25
+ """Базовый URL для вебсокета на инверсные контракты."""
26
+
27
+ _OPTION_URL: str = "wss://stream.bybit.com/v5/public/option"
28
+ """Базовый URL для вебсокета на опционы."""
29
+
30
+ _PRIVATE_URL: str = "wss://stream.bybit.com/v5/private"
31
+ """Базовый URL для приватных вебсокетов."""
32
+
33
+ def __init__(self, client: Client | None = None, **ws_kwargs: Any) -> None:
34
+ """Инициализирует менеджер вебсокетов для Bybit.
35
+
36
+ Параметры:
37
+ client (`Client | None`): Клиент для выполнения запросов. Нужен, чтобы открыть приватные вебсокеты.
38
+ ws_kwargs (`dict[str, Any]`): Дополнительные аргументы, которые прокидываются в `Websocket`.
39
+ """
40
+ self.client = client
41
+ self._ws_kwargs = ws_kwargs
42
+
43
+ def _generate_subscription_message(
44
+ self,
45
+ topics: Sequence[str],
46
+ req_id: str | None = None,
47
+ ) -> list[str]:
48
+ """Сформировать сообщение для подписки на вебсокет.
49
+
50
+ Параметры:
51
+ topics (`Sequence[str]`): Список топиков для подписки.
52
+ req_id (`str | None`): Опциональный идентификатор запроса.
53
+
54
+ Возвращает:
55
+ `list[str]`: Список JSON строк для отправки.
56
+ """
57
+ message = {"op": "subscribe", "args": list(topics)}
58
+ if req_id:
59
+ message["req_id"] = req_id
60
+
61
+ return [json.dumps(message)]
62
+
63
+ def _get_url_for_category(
64
+ self, category: Literal["spot", "linear", "inverse", "option", "private"]
65
+ ) -> str:
66
+ """Получить URL для категории.
67
+
68
+ Параметры:
69
+ category (`Literal["spot", "linear", "inverse", "option", "private"]`): Категория рынка.
70
+
71
+ Возвращает:
72
+ `str`: URL для вебсокета.
73
+ """
74
+ if category == "spot":
75
+ return self._SPOT_URL
76
+ elif category == "linear":
77
+ return self._LINEAR_URL
78
+ elif category == "inverse":
79
+ return self._INVERSE_URL
80
+ elif category == "option":
81
+ return self._OPTION_URL
82
+ elif category == "private":
83
+ return self._PRIVATE_URL
84
+ else:
85
+ raise ValueError(f"Unsupported category: {category}")
86
+
87
+ def orderbook(
88
+ self,
89
+ callback: CallbackType,
90
+ category: Literal["spot", "linear", "inverse", "option"],
91
+ depth: Literal[1, 25, 50, 100, 200, 500, 1000] = 1,
92
+ symbol: str | None = None,
93
+ symbols: Sequence[str] | None = None,
94
+ req_id: str | None = None,
95
+ ) -> Websocket:
96
+ """Создает вебсокет для получения данных order book.
97
+
98
+ https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook
99
+
100
+ Параметры:
101
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
102
+ category (`Literal["spot", "linear", "inverse", "option"]`): Категория рынка.
103
+ depth (`Literal[1, 25, 50, 100, 200, 500, 1000]`): Глубина order book.
104
+ symbol (`str | None`): Один символ для подписки.
105
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
106
+ req_id (`str | None`): Опциональный идентификатор запроса.
107
+
108
+ Возвращает:
109
+ `Websocket`: Объект для управления вебсокет соединением.
110
+ """
111
+ if symbol and symbols:
112
+ raise ValueError("Parameters symbol and symbols cannot be used together")
113
+ if not (symbol or symbols):
114
+ raise ValueError("Either symbol or symbols must be provided")
115
+
116
+ tickers = [symbol] if symbol else symbols
117
+ topics = [f"orderbook.{depth}.{ticker.upper()}" for ticker in tickers] # type: ignore
118
+
119
+ subscription_messages = self._generate_subscription_message(topics, req_id)
120
+ url = self._get_url_for_category(category)
121
+
122
+ return Websocket(
123
+ callback=callback,
124
+ url=url,
125
+ subscription_messages=subscription_messages,
126
+ **self._ws_kwargs,
127
+ )
128
+
129
+ def kline(
130
+ self,
131
+ callback: CallbackType,
132
+ category: Literal["spot", "linear", "inverse", "option"],
133
+ interval: Literal[
134
+ "1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W", "M"
135
+ ],
136
+ symbol: str | None = None,
137
+ symbols: Sequence[str] | None = None,
138
+ req_id: str | None = None,
139
+ ) -> Websocket:
140
+ """Создает вебсокет для получения данных klines (свечей).
141
+
142
+ https://bybit-exchange.github.io/docs/v5/websocket/public/kline
143
+
144
+ Параметры:
145
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
146
+ category (`Literal["spot", "linear", "inverse", "option"]`): Категория рынка.
147
+ interval (`Literal["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W", "M"]`): Интервал свечи.
148
+ symbol (`str | None`): Один символ для подписки.
149
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
150
+ req_id (`str | None`): Опциональный идентификатор запроса.
151
+
152
+ Возвращает:
153
+ `Websocket`: Объект для управления вебсокет соединением.
154
+ """
155
+ if symbol and symbols:
156
+ raise ValueError("Parameters symbol and symbols cannot be used together")
157
+ if not (symbol or symbols):
158
+ raise ValueError("Either symbol or symbols must be provided")
159
+
160
+ tickers = [symbol] if symbol else symbols
161
+ topics = [f"kline.{interval}.{ticker.upper()}" for ticker in tickers] # type: ignore
162
+
163
+ subscription_messages = self._generate_subscription_message(topics, req_id)
164
+ url = self._get_url_for_category(category)
165
+
166
+ return Websocket(
167
+ callback=callback,
168
+ url=url,
169
+ subscription_messages=subscription_messages,
170
+ **self._ws_kwargs,
171
+ )
172
+
173
+ def trade(
174
+ self,
175
+ callback: CallbackType,
176
+ category: Literal["spot", "linear", "inverse", "option"],
177
+ symbol: str | None = None,
178
+ symbols: Sequence[str] | None = None,
179
+ req_id: str | None = None,
180
+ ) -> Websocket:
181
+ """Создает вебсокет для получения публичных сделок.
182
+
183
+ https://bybit-exchange.github.io/docs/v5/websocket/public/trade
184
+
185
+ Параметры:
186
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
187
+ category (`Literal["spot", "linear", "inverse", "option"]`): Категория рынка.
188
+ symbol (`str | None`): Один символ для подписки.
189
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
190
+ req_id (`str | None`): Опциональный идентификатор запроса.
191
+
192
+ Возвращает:
193
+ `Websocket`: Объект для управления вебсокет соединением.
194
+ """
195
+ if symbol and symbols:
196
+ raise ValueError("Parameters symbol and symbols cannot be used together")
197
+ if not (symbol or symbols):
198
+ raise ValueError("Either symbol or symbols must be provided")
199
+
200
+ tickers = [symbol] if symbol else symbols
201
+ topics = [f"publicTrade.{ticker.upper()}" for ticker in tickers] # type: ignore
202
+
203
+ subscription_messages = self._generate_subscription_message(topics, req_id)
204
+ url = self._get_url_for_category(category)
205
+
206
+ return Websocket(
207
+ callback=callback,
208
+ url=url,
209
+ subscription_messages=subscription_messages,
210
+ **self._ws_kwargs,
211
+ )
212
+
213
+ def ticker(
214
+ self,
215
+ callback: CallbackType,
216
+ category: Literal["spot", "linear", "inverse", "option"],
217
+ symbol: str | None = None,
218
+ symbols: Sequence[str] | None = None,
219
+ req_id: str | None = None,
220
+ ) -> Websocket:
221
+ """Создает вебсокет для получения тикеров.
222
+
223
+ https://bybit-exchange.github.io/docs/v5/websocket/public/ticker
224
+
225
+ Параметры:
226
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
227
+ category (`Literal["spot", "linear", "inverse", "option"]`): Категория рынка.
228
+ symbol (`str | None`): Один символ для подписки.
229
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
230
+ req_id (`str | None`): Опциональный идентификатор запроса.
231
+
232
+ Возвращает:
233
+ `Websocket`: Объект для управления вебсокет соединением.
234
+ """
235
+ if symbol and symbols:
236
+ raise ValueError("Parameters symbol and symbols cannot be used together")
237
+ if not (symbol or symbols):
238
+ raise ValueError("Either symbol or symbols must be provided")
239
+
240
+ tickers = [symbol] if symbol else symbols
241
+ topics = [f"tickers.{ticker.upper()}" for ticker in tickers] # type: ignore
242
+
243
+ subscription_messages = self._generate_subscription_message(topics, req_id)
244
+ url = self._get_url_for_category(category)
245
+
246
+ return Websocket(
247
+ callback=callback,
248
+ url=url,
249
+ subscription_messages=subscription_messages,
250
+ **self._ws_kwargs,
251
+ )
252
+
253
+ def liquidation(
254
+ self,
255
+ callback: CallbackType,
256
+ category: Literal["linear", "inverse"],
257
+ symbol: str | None = None,
258
+ symbols: Sequence[str] | None = None,
259
+ req_id: str | None = None,
260
+ ) -> Websocket:
261
+ """Создает вебсокет для получения данных ликвидаций.
262
+
263
+ https://bybit-exchange.github.io/docs/v5/websocket/public/liquidation
264
+
265
+ Параметры:
266
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
267
+ category (`Literal["linear", "inverse"]`): Категория рынка (только для деривативов).
268
+ symbol (`str | None`): Один символ для подписки.
269
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
270
+ req_id (`str | None`): Опциональный идентификатор запроса.
271
+
272
+ Возвращает:
273
+ `Websocket`: Объект для управления вебсокет соединением.
274
+ """
275
+ warnings.warn(
276
+ "TDepreicated liquidation stream, please move to All Liquidation Subscribe"
277
+ "to the liquidation stream. Pushes at most one order per second per symbol."
278
+ "As such, this feed does not push all liquidations that occur on Bybit.",
279
+ DeprecationWarning,
280
+ stacklevel=2,
281
+ )
282
+
283
+ if symbol and symbols:
284
+ raise ValueError("Parameters symbol and symbols cannot be used together")
285
+ if not (symbol or symbols):
286
+ raise ValueError("Either symbol or symbols must be provided")
287
+
288
+ tickers = [symbol] if symbol else symbols
289
+ topics = [f"liquidation.{ticker.upper()}" for ticker in tickers] # type: ignore
290
+
291
+ subscription_messages = self._generate_subscription_message(topics, req_id)
292
+ url = self._get_url_for_category(category)
293
+
294
+ return Websocket(
295
+ callback=callback,
296
+ url=url,
297
+ subscription_messages=subscription_messages,
298
+ **self._ws_kwargs,
299
+ )
300
+
301
+ def all_liquidation(
302
+ self,
303
+ callback: CallbackType,
304
+ category: Literal["linear", "inverse"],
305
+ symbol: str | None = None,
306
+ symbols: Sequence[str] | None = None,
307
+ req_id: str | None = None,
308
+ ) -> Websocket:
309
+ """Создает вебсокет для получения данных ликвидаций.
310
+
311
+ https://bybit-exchange.github.io/docs/v5/websocket/public/all-liquidation
312
+
313
+ Параметры:
314
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
315
+ category (`Literal["linear", "inverse"]`): Категория рынка (только для деривативов).
316
+ symbol (`str | None`): Один символ для подписки.
317
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
318
+ req_id (`str | None`): Опциональный идентификатор запроса.
319
+
320
+ Возвращает:
321
+ `Websocket`: Объект для управления вебсокет соединением.
322
+ """
323
+ if symbol and symbols:
324
+ raise ValueError("Parameters symbol and symbols cannot be used together")
325
+ if not (symbol or symbols):
326
+ raise ValueError("Either symbol or symbols must be provided")
327
+
328
+ tickers = [symbol] if symbol else symbols
329
+ topics = [f"allLiquidation.{ticker.upper()}" for ticker in tickers] # type: ignore
330
+
331
+ subscription_messages = self._generate_subscription_message(topics, req_id)
332
+ url = self._get_url_for_category(category)
333
+
334
+ return Websocket(
335
+ callback=callback,
336
+ url=url,
337
+ subscription_messages=subscription_messages,
338
+ **self._ws_kwargs,
339
+ )
unicex/enums.py ADDED
@@ -0,0 +1,273 @@
1
+ """Модуль, который описывает перечисления."""
2
+
3
+ __all__ = [
4
+ "MarketType",
5
+ "Exchange",
6
+ "Timeframe",
7
+ "Side",
8
+ ]
9
+
10
+ from enum import StrEnum
11
+
12
+
13
+ class MarketType(StrEnum):
14
+ """Перечисление типов криптовалютных рынков."""
15
+
16
+ FUTURES = "FUTURES"
17
+ SPOT = "SPOT"
18
+
19
+ def __add__(self, exchange: "Exchange") -> tuple["Exchange", "MarketType"]:
20
+ """Возвращает кортеж из биржи и типа рынка."""
21
+ return exchange, self
22
+
23
+
24
+ class Exchange(StrEnum):
25
+ """Перечисление бирж."""
26
+
27
+ BINANCE = "BINANCE"
28
+ BITGET = "BITGET"
29
+ BITUNIX = "BITUNIX"
30
+ BYBIT = "BYBIT"
31
+ GATE = "GATE"
32
+ HYPERLIQUID = "HYPERLIQUID"
33
+ KCEX = "KCEX"
34
+ MEXC = "MEXC"
35
+ OKX = "OKX"
36
+ XT = "XT"
37
+
38
+ def __add__(self, market_type: "MarketType") -> tuple["Exchange", "MarketType"]:
39
+ """Возвращает кортеж из биржи и типа рынка."""
40
+ return self, market_type
41
+
42
+
43
+ class Side(StrEnum):
44
+ """Перечисление сторон сделки."""
45
+
46
+ BUY = "BUY"
47
+ SELL = "SELL"
48
+
49
+
50
+ class Timeframe(StrEnum):
51
+ """Перечисление таймфреймов."""
52
+
53
+ SECOND_1 = "1s"
54
+
55
+ MIN_1 = "1m"
56
+ MIN_3 = "3m"
57
+ MIN_5 = "5m"
58
+ MIN_15 = "15m"
59
+ MIN_30 = "30m"
60
+
61
+ HOUR_1 = "1h"
62
+ HOUR_2 = "2h"
63
+ HOUR_4 = "4h"
64
+ HOUR_6 = "6h"
65
+ HOUR_8 = "8h"
66
+ HOUR_12 = "12h"
67
+
68
+ DAY_1 = "1d"
69
+ DAY_3 = "3d"
70
+
71
+ WEEK_1 = "1w"
72
+
73
+ MONTH_1 = "1M"
74
+
75
+ @property
76
+ def mapping(self) -> dict[Exchange | tuple[Exchange, MarketType], dict["Timeframe", str]]:
77
+ """Возвращает словарь с маппингом таймфреймов для каждой биржи."""
78
+ return {
79
+ (Exchange.BINANCE, MarketType.SPOT): {
80
+ Timeframe.SECOND_1: "1s",
81
+ Timeframe.MIN_1: "1m",
82
+ Timeframe.MIN_3: "3m",
83
+ Timeframe.MIN_5: "5m",
84
+ Timeframe.MIN_15: "15m",
85
+ Timeframe.MIN_30: "30m",
86
+ Timeframe.HOUR_1: "1h",
87
+ Timeframe.HOUR_2: "2h",
88
+ Timeframe.HOUR_4: "4h",
89
+ Timeframe.HOUR_6: "6h",
90
+ Timeframe.HOUR_8: "8h",
91
+ Timeframe.HOUR_12: "12h",
92
+ Timeframe.DAY_1: "1d",
93
+ Timeframe.DAY_3: "3d",
94
+ Timeframe.WEEK_1: "1w",
95
+ Timeframe.MONTH_1: "1M",
96
+ },
97
+ (Exchange.BINANCE, MarketType.FUTURES): {
98
+ Timeframe.MIN_1: "1m",
99
+ Timeframe.MIN_3: "3m",
100
+ Timeframe.MIN_5: "5m",
101
+ Timeframe.MIN_15: "15m",
102
+ Timeframe.MIN_30: "30m",
103
+ Timeframe.HOUR_1: "1h",
104
+ Timeframe.HOUR_2: "2h",
105
+ Timeframe.HOUR_4: "4h",
106
+ Timeframe.HOUR_6: "6h",
107
+ Timeframe.HOUR_8: "8h",
108
+ Timeframe.HOUR_12: "12h",
109
+ Timeframe.DAY_1: "1d",
110
+ Timeframe.DAY_3: "3d",
111
+ Timeframe.WEEK_1: "1w",
112
+ Timeframe.MONTH_1: "1M",
113
+ },
114
+ Exchange.BYBIT: {
115
+ Timeframe.MIN_1: "1",
116
+ Timeframe.MIN_3: "3",
117
+ Timeframe.MIN_5: "5",
118
+ Timeframe.MIN_15: "15",
119
+ Timeframe.MIN_30: "30",
120
+ Timeframe.HOUR_1: "60",
121
+ Timeframe.HOUR_2: "120",
122
+ Timeframe.HOUR_4: "240",
123
+ Timeframe.HOUR_6: "360",
124
+ Timeframe.HOUR_12: "720",
125
+ Timeframe.DAY_1: "D",
126
+ Timeframe.WEEK_1: "W",
127
+ Timeframe.MONTH_1: "M",
128
+ },
129
+ (Exchange.BITGET, MarketType.SPOT): {
130
+ Timeframe.MIN_1: "1min",
131
+ Timeframe.MIN_5: "5min",
132
+ Timeframe.MIN_15: "15min",
133
+ Timeframe.MIN_30: "30min",
134
+ Timeframe.HOUR_1: "1h",
135
+ Timeframe.HOUR_4: "4h",
136
+ Timeframe.HOUR_6: "6h",
137
+ Timeframe.HOUR_12: "12h",
138
+ Timeframe.DAY_1: "1day",
139
+ Timeframe.DAY_3: "3day",
140
+ Timeframe.WEEK_1: "1week",
141
+ Timeframe.MONTH_1: "1M",
142
+ },
143
+ (Exchange.BITGET, MarketType.FUTURES): {
144
+ Timeframe.MIN_1: "1m",
145
+ Timeframe.MIN_5: "5m",
146
+ Timeframe.MIN_15: "15m",
147
+ Timeframe.MIN_30: "30m",
148
+ Timeframe.HOUR_1: "1H",
149
+ Timeframe.HOUR_4: "4H",
150
+ Timeframe.HOUR_6: "6H",
151
+ Timeframe.HOUR_12: "12H",
152
+ Timeframe.DAY_1: "1D",
153
+ Timeframe.DAY_3: "3D",
154
+ Timeframe.WEEK_1: "1W",
155
+ Timeframe.MONTH_1: "1M",
156
+ },
157
+ (Exchange.MEXC, MarketType.SPOT): {
158
+ Timeframe.MIN_1: "1m",
159
+ Timeframe.MIN_5: "5m",
160
+ Timeframe.MIN_15: "15m",
161
+ Timeframe.MIN_30: "30m",
162
+ Timeframe.HOUR_1: "60m",
163
+ Timeframe.HOUR_4: "4h",
164
+ Timeframe.HOUR_8: "8h",
165
+ Timeframe.DAY_1: "1d",
166
+ Timeframe.WEEK_1: "1W",
167
+ Timeframe.MONTH_1: "1M",
168
+ },
169
+ (Exchange.MEXC, MarketType.FUTURES): {
170
+ Timeframe.MIN_1: "Min1",
171
+ Timeframe.MIN_5: "Min5",
172
+ Timeframe.MIN_15: "Min15",
173
+ Timeframe.MIN_30: "Min30",
174
+ Timeframe.HOUR_1: "Min60",
175
+ Timeframe.HOUR_4: "Hour4",
176
+ Timeframe.HOUR_8: "Hour8",
177
+ Timeframe.DAY_1: "Day1",
178
+ Timeframe.WEEK_1: "Week1",
179
+ Timeframe.MONTH_1: "Month1",
180
+ },
181
+ Exchange.OKX: {
182
+ Timeframe.MIN_1: "1m",
183
+ Timeframe.MIN_3: "3m",
184
+ Timeframe.MIN_5: "5m",
185
+ Timeframe.MIN_15: "15m",
186
+ Timeframe.MIN_30: "30m",
187
+ Timeframe.HOUR_1: "1H",
188
+ Timeframe.HOUR_2: "2H",
189
+ Timeframe.HOUR_4: "4H",
190
+ Timeframe.HOUR_6: "6H",
191
+ Timeframe.HOUR_12: "12H",
192
+ Timeframe.DAY_1: "1D",
193
+ Timeframe.DAY_3: "3D",
194
+ Timeframe.WEEK_1: "1W",
195
+ Timeframe.MONTH_1: "1M",
196
+ },
197
+ (Exchange.GATE, MarketType.FUTURES): {
198
+ Timeframe.SECOND_1: "1s",
199
+ Timeframe.MIN_1: "1m",
200
+ Timeframe.MIN_5: "5m",
201
+ Timeframe.MIN_15: "15m",
202
+ Timeframe.MIN_30: "30m",
203
+ Timeframe.HOUR_1: "1h",
204
+ Timeframe.HOUR_2: "2h",
205
+ Timeframe.HOUR_4: "4h",
206
+ Timeframe.HOUR_6: "6h",
207
+ Timeframe.HOUR_8: "8h",
208
+ Timeframe.HOUR_12: "12h",
209
+ Timeframe.DAY_1: "1d",
210
+ Timeframe.WEEK_1: "1w",
211
+ Timeframe.MONTH_1: "30d",
212
+ },
213
+ (Exchange.GATE, MarketType.SPOT): {
214
+ Timeframe.MIN_1: "1m",
215
+ Timeframe.MIN_5: "5m",
216
+ Timeframe.MIN_15: "15m",
217
+ Timeframe.MIN_30: "30m",
218
+ Timeframe.HOUR_1: "1h",
219
+ Timeframe.HOUR_4: "4h",
220
+ Timeframe.HOUR_8: "8h",
221
+ Timeframe.DAY_1: "1d",
222
+ Timeframe.WEEK_1: "7d",
223
+ Timeframe.MONTH_1: "30d",
224
+ },
225
+ Exchange.HYPERLIQUID: {
226
+ Timeframe.MIN_1: "1m",
227
+ Timeframe.MIN_3: "3m",
228
+ Timeframe.MIN_5: "5m",
229
+ Timeframe.MIN_15: "15m",
230
+ Timeframe.MIN_30: "30m",
231
+ Timeframe.HOUR_1: "1h",
232
+ Timeframe.HOUR_2: "2h",
233
+ Timeframe.HOUR_4: "4h",
234
+ Timeframe.HOUR_8: "8h",
235
+ Timeframe.HOUR_12: "12h",
236
+ Timeframe.DAY_1: "1d",
237
+ Timeframe.DAY_3: "3d",
238
+ Timeframe.WEEK_1: "1w",
239
+ Timeframe.MONTH_1: "1M",
240
+ },
241
+ }
242
+
243
+ def to_exchange_format(self, exchange: Exchange, market_type: MarketType | None = None) -> str:
244
+ """Конвертирует таймфрейм в формат, подходящий для указанной биржи."""
245
+ # Обрабатываем ситуацию, при которой биржа имеет одинаковый маппинг как на споте, так и на фьючерсах:
246
+ if exchange in self.mapping:
247
+ key = exchange
248
+ else:
249
+ if not market_type:
250
+ raise ValueError(
251
+ f"Market type is required for exchange {exchange.value} to map to timeframe {self.value}"
252
+ )
253
+ key = exchange + market_type
254
+ try:
255
+ return self.mapping[key][self] # type: ignore
256
+ except KeyError as e:
257
+ details = f" and market type {market_type.value}" if market_type else ""
258
+ raise ValueError(
259
+ f"Timeframe {self.value} is not supported for exchange {exchange.value}{details}"
260
+ ) from e
261
+
262
+ @property
263
+ def to_seconds(self) -> int:
264
+ """Возвращает количество секунд для таймфрейма."""
265
+ unit_map = {
266
+ "m": 60,
267
+ "h": 3600,
268
+ "d": 86400,
269
+ "w": 604800,
270
+ "M": 2592000,
271
+ } # Условно 30 дней в месяце
272
+ value, unit = int(self.value[:-1]), self.value[-1]
273
+ return value * unit_map[unit]
unicex/exceptions.py ADDED
@@ -0,0 +1,64 @@
1
+ """Модуль,который описывает исключения и ошибки, которые могут возникнуть при работе с библиотекой."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass
7
+ class UniCexException(Exception):
8
+ """Базовое исключение библиотеки."""
9
+
10
+ message: str
11
+ """Сообщение об ошибке."""
12
+
13
+
14
+ @dataclass
15
+ class NotAuthorized(UniCexException):
16
+ """Исключение, возникающее при отсутствии авторизации."""
17
+
18
+ pass
19
+
20
+
21
+ @dataclass
22
+ class NotSupported(UniCexException):
23
+ """Исключение, возникающее при попытке использования не поддерживаемой функции."""
24
+
25
+ pass
26
+
27
+
28
+ @dataclass
29
+ class AdapterError(UniCexException):
30
+ """Исключение, возникающее при ошибке адаптации данных."""
31
+
32
+ pass
33
+
34
+
35
+ @dataclass
36
+ class QueueOverflowError(UniCexException):
37
+ """Исключение, возникающее при переполнении очереди сообщений."""
38
+
39
+ pass
40
+
41
+
42
+ @dataclass
43
+ class ResponseError(UniCexException):
44
+ """Исключение, возникающее при ошибке ответа."""
45
+
46
+ status_code: int
47
+ code: str = "" # "" - means undefined
48
+ response_json: dict = field(default_factory=dict)
49
+ response_text: str = ""
50
+
51
+ def __str__(self) -> str:
52
+ """Возвращает строковое представление исключения."""
53
+ if self.response_json:
54
+ preview = str(self.response_json)
55
+ if len(preview) > 500:
56
+ preview = preview[:500] + "..."
57
+ return f"ResponseError: status_code={self.status_code}, code={self.code}, response_json: {preview}"
58
+ elif self.response_text:
59
+ preview = str(self.response_text)
60
+ if len(preview) > 500:
61
+ preview = preview[:500] + "..."
62
+ return f"ResponseError: status_code={self.status_code}, code={self.code}, response_text: {preview}"
63
+ else:
64
+ return f"ResponseError: status_code={self.status_code}, code={self.code}"