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.
- unicex/__init__.py +200 -0
- unicex/_abc/__init__.py +11 -0
- unicex/_abc/exchange_info.py +216 -0
- unicex/_abc/uni_client.py +329 -0
- unicex/_abc/uni_websocket_manager.py +294 -0
- unicex/_base/__init__.py +9 -0
- unicex/_base/client.py +214 -0
- unicex/_base/websocket.py +261 -0
- unicex/binance/__init__.py +27 -0
- unicex/binance/adapter.py +202 -0
- unicex/binance/client.py +1577 -0
- unicex/binance/exchange_info.py +62 -0
- unicex/binance/uni_client.py +188 -0
- unicex/binance/uni_websocket_manager.py +166 -0
- unicex/binance/user_websocket.py +186 -0
- unicex/binance/websocket_manager.py +912 -0
- unicex/bitget/__init__.py +27 -0
- unicex/bitget/adapter.py +188 -0
- unicex/bitget/client.py +2514 -0
- unicex/bitget/exchange_info.py +48 -0
- unicex/bitget/uni_client.py +198 -0
- unicex/bitget/uni_websocket_manager.py +275 -0
- unicex/bitget/user_websocket.py +7 -0
- unicex/bitget/websocket_manager.py +232 -0
- unicex/bybit/__init__.py +27 -0
- unicex/bybit/adapter.py +208 -0
- unicex/bybit/client.py +1876 -0
- unicex/bybit/exchange_info.py +53 -0
- unicex/bybit/uni_client.py +200 -0
- unicex/bybit/uni_websocket_manager.py +291 -0
- unicex/bybit/user_websocket.py +7 -0
- unicex/bybit/websocket_manager.py +339 -0
- unicex/enums.py +273 -0
- unicex/exceptions.py +64 -0
- unicex/extra.py +335 -0
- unicex/gate/__init__.py +27 -0
- unicex/gate/adapter.py +178 -0
- unicex/gate/client.py +1667 -0
- unicex/gate/exchange_info.py +55 -0
- unicex/gate/uni_client.py +214 -0
- unicex/gate/uni_websocket_manager.py +269 -0
- unicex/gate/user_websocket.py +7 -0
- unicex/gate/websocket_manager.py +513 -0
- unicex/hyperliquid/__init__.py +27 -0
- unicex/hyperliquid/adapter.py +261 -0
- unicex/hyperliquid/client.py +2315 -0
- unicex/hyperliquid/exchange_info.py +119 -0
- unicex/hyperliquid/uni_client.py +325 -0
- unicex/hyperliquid/uni_websocket_manager.py +269 -0
- unicex/hyperliquid/user_websocket.py +7 -0
- unicex/hyperliquid/websocket_manager.py +393 -0
- unicex/mapper.py +111 -0
- unicex/mexc/__init__.py +27 -0
- unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/__init__.py +335 -0
- unicex/mexc/adapter.py +239 -0
- unicex/mexc/client.py +846 -0
- unicex/mexc/exchange_info.py +47 -0
- unicex/mexc/uni_client.py +211 -0
- unicex/mexc/uni_websocket_manager.py +269 -0
- unicex/mexc/user_websocket.py +7 -0
- unicex/mexc/websocket_manager.py +456 -0
- unicex/okx/__init__.py +27 -0
- unicex/okx/adapter.py +150 -0
- unicex/okx/client.py +2864 -0
- unicex/okx/exchange_info.py +47 -0
- unicex/okx/uni_client.py +202 -0
- unicex/okx/uni_websocket_manager.py +269 -0
- unicex/okx/user_websocket.py +7 -0
- unicex/okx/websocket_manager.py +743 -0
- unicex/types.py +164 -0
- unicex/utils.py +218 -0
- unicex-0.13.17.dist-info/METADATA +243 -0
- unicex-0.13.17.dist-info/RECORD +93 -0
- unicex-0.13.17.dist-info/WHEEL +5 -0
- unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
- 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}"
|