unicex 0.16.7__py3-none-any.whl → 0.17.2__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.
@@ -0,0 +1,191 @@
1
+ __all__ = ["UserWebsocket"]
2
+
3
+ import asyncio
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import Any
6
+
7
+ from loguru import logger as _logger
8
+
9
+ from unicex._base import Websocket
10
+ from unicex.types import LoggerLike
11
+
12
+ from .client import Client
13
+
14
+ type CallbackType = Callable[[Any], Awaitable[None]]
15
+
16
+
17
+ class UserWebsocket:
18
+ """Пользовательский вебсокет Aster с авто-продлением listenKey.
19
+
20
+ Работает только с фьючерсным пользовательским стримом.
21
+ """
22
+
23
+ _BASE_FUTURES_URL: str = "wss://fstream.asterdex.com"
24
+ """Базовый URL для пользовательского вебсокета Aster."""
25
+
26
+ _RENEW_INTERVAL: int = 30 * 60
27
+ """Интервал продления listenKey (сек.)."""
28
+
29
+ def __init__(
30
+ self,
31
+ callback: CallbackType,
32
+ client: Client,
33
+ logger: LoggerLike | None = None,
34
+ **kwargs: Any,
35
+ ) -> None:
36
+ """Инициализирует пользовательский вебсокет Aster.
37
+
38
+ - Параметры:
39
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
40
+ client (`Client`): Авторизованный клиент Aster.
41
+ logger (`LoggerLike | None`): Логгер для записи логов.
42
+ kwargs (`dict[str, Any]`): Дополнительные параметры, которые будут переданы в `Websocket`.
43
+
44
+ Возвращает:
45
+ `None`: Ничего не возвращает.
46
+ """
47
+ self._callback = callback
48
+ self._client = client
49
+ self._logger = logger or _logger
50
+ self._ws_kwargs = kwargs
51
+
52
+ self._listen_key: str | None = None
53
+ self._ws: Websocket | None = None
54
+ self._keepalive_task: asyncio.Task | None = None
55
+
56
+ self._running = False
57
+
58
+ async def start(self) -> None:
59
+ """Запускает пользовательский стрим с автопродлением listenKey."""
60
+ if self._running:
61
+ return
62
+
63
+ self._running = True
64
+
65
+ # Создаем listenKey, запускаем keepalive и вебсокет.
66
+ try:
67
+ self._listen_key = await self._create_listen_key()
68
+
69
+ # Запускаем фоновое продление ключа до подключения.
70
+ self._keepalive_task = asyncio.create_task(self._keepalive_loop())
71
+
72
+ await self._start_ws(self._listen_key)
73
+ except Exception:
74
+ # Если старт не удался - сбрасываем состояние и чистим ресурсы.
75
+ self._running = False
76
+ await self._stop_keepalive_task()
77
+ if self._listen_key:
78
+ try:
79
+ await self._close_listen_key()
80
+ except Exception as exc:
81
+ self._logger.error(f"Error closing listenKey: {exc}")
82
+ finally:
83
+ self._listen_key = None
84
+ raise
85
+
86
+ async def stop(self) -> None:
87
+ """Останавливает пользовательский стрим и закрывает listenKey."""
88
+ self._running = False
89
+
90
+ # Останавливаем вебсокет.
91
+ try:
92
+ if isinstance(self._ws, Websocket):
93
+ await self._ws.stop()
94
+ except Exception as exc:
95
+ self._logger.error(f"Error stopping websocket: {exc}")
96
+ finally:
97
+ self._ws = None
98
+
99
+ # Останавливаем фоновое продление ключа.
100
+ await self._stop_keepalive_task()
101
+
102
+ # Закрываем listenKey.
103
+ try:
104
+ if self._listen_key:
105
+ await self._close_listen_key()
106
+ except Exception as exc:
107
+ self._logger.error(f"Error closing listenKey: {exc}")
108
+ finally:
109
+ self._listen_key = None
110
+
111
+ self._logger.info("User websocket stopped")
112
+
113
+ async def restart(self) -> None:
114
+ """Перезапускает пользовательский вебсокет."""
115
+ await self.stop()
116
+ await self.start()
117
+
118
+ async def _start_ws(self, listen_key: str) -> None:
119
+ """Запускает WebSocket соединение."""
120
+ ws_url = f"{self._BASE_FUTURES_URL}/ws/{listen_key}"
121
+ self._ws_kwargs["no_message_reconnect_timeout"] = None
122
+ self._ws = Websocket(
123
+ callback=self._callback,
124
+ url=ws_url,
125
+ **self._ws_kwargs,
126
+ )
127
+ await self._ws.start()
128
+ self._logger.info(f"User websocket started: ...{ws_url[-5:]}")
129
+
130
+ async def _keepalive_loop(self) -> None:
131
+ """Фоновый цикл продления listenKey."""
132
+ while self._running:
133
+ try:
134
+ response = await self._renew_listen_key()
135
+ listen_key = response.get("listenKey") if isinstance(response, dict) else None
136
+
137
+ # Если сервер вернул новый listenKey - перезапускаем соединение.
138
+ if listen_key and listen_key != self._listen_key:
139
+ self._logger.info(
140
+ f"Listen key changed: {self._listen_key} -> {listen_key}. Restarting websocket"
141
+ )
142
+ asyncio.create_task(self.restart())
143
+ return
144
+
145
+ except Exception as exc:
146
+ self._logger.error(f"Error while keeping alive: {exc}")
147
+ asyncio.create_task(self.restart())
148
+ return
149
+
150
+ # Ждем до следующего продления с возможностью быстрого выхода.
151
+ for _ in range(self._RENEW_INTERVAL):
152
+ if not self._running:
153
+ return
154
+ await asyncio.sleep(1)
155
+
156
+ async def _stop_keepalive_task(self) -> None:
157
+ """Останавливает фоновую задачу продления listenKey."""
158
+ if not isinstance(self._keepalive_task, asyncio.Task):
159
+ return
160
+
161
+ current_task = asyncio.current_task()
162
+ keepalive_task = self._keepalive_task
163
+ self._keepalive_task = None
164
+ keepalive_task.cancel()
165
+
166
+ if keepalive_task is current_task:
167
+ return
168
+
169
+ try:
170
+ await keepalive_task
171
+ except asyncio.CancelledError:
172
+ pass
173
+ except Exception as exc:
174
+ self._logger.error(f"Error stopping keepalive task: {exc}")
175
+
176
+ async def _create_listen_key(self) -> str:
177
+ """Создает новый listenKey."""
178
+ response = await self._client.futures_listen_key()
179
+ key = response.get("listenKey") if isinstance(response, dict) else None
180
+ if not key:
181
+ raise RuntimeError(f"Can not create listenKey: {response}")
182
+ return key
183
+
184
+ async def _renew_listen_key(self) -> dict:
185
+ """Продлевает listenKey."""
186
+ response = await self._client.futures_renew_listen_key()
187
+ return response if isinstance(response, dict) else {}
188
+
189
+ async def _close_listen_key(self) -> None:
190
+ """Закрывает listenKey."""
191
+ await self._client.futures_close_listen_key()
@@ -0,0 +1,403 @@
1
+ __all__ = ["WebsocketManager"]
2
+
3
+ from collections.abc import Awaitable, Callable, Sequence
4
+ from typing import Any
5
+
6
+ from unicex._base import Websocket
7
+
8
+ from .client import Client
9
+
10
+ type CallbackType = Callable[[Any], Awaitable[None]]
11
+
12
+
13
+ class WebsocketManager:
14
+ """Менеджер асинхронных вебсокетов для Aster."""
15
+
16
+ _BASE_URL: str = "wss://fstream.asterdex.com"
17
+ """Базовый URL для вебсокетов Aster Futures."""
18
+
19
+ def __init__(self, client: Client | None = None, **ws_kwargs: Any) -> None:
20
+ """Инициализирует менеджер вебсокетов для Aster.
21
+
22
+ Параметры:
23
+ client (`Client | None`): Клиент для выполнения запросов. Нужен, чтобы открыть приватные вебсокеты.
24
+ ws_kwargs (`dict[str, Any]`): Дополнительные аргументы, которые прокидываются в `Websocket`.
25
+ """
26
+ self.client = client
27
+ self._ws_kwargs = ws_kwargs
28
+
29
+ def _generate_stream_url(
30
+ self,
31
+ type: str,
32
+ symbol: str | None = None,
33
+ symbols: Sequence[str] | None = None,
34
+ require_symbol: bool = False,
35
+ ) -> str:
36
+ """Генерирует URL для вебсокета Aster. Параметры symbol и symbols не могут быть использованы вместе.
37
+
38
+ Параметры:
39
+ type (`str`): Тип стрима.
40
+ symbol (`str | None`): Один символ для подписки.
41
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
42
+ require_symbol (`bool`): Требуется ли символ для подписки.
43
+
44
+ Возвращает:
45
+ `str`: URL для вебсокета.
46
+ """
47
+ if symbol and symbols:
48
+ raise ValueError("Parameters symbol and symbols cannot be used together")
49
+ if require_symbol and not (symbol or symbols):
50
+ raise ValueError("Either symbol or symbols must be provided")
51
+ if symbol:
52
+ return f"{self._BASE_URL}/ws/{symbol.lower()}@{type}"
53
+ if symbols:
54
+ streams = "/".join(f"{s.lower()}@{type}" for s in symbols)
55
+ return f"{self._BASE_URL}/stream?streams={streams}"
56
+ return f"{self._BASE_URL}/ws/{type}"
57
+
58
+ def futures_agg_trade(
59
+ self,
60
+ callback: CallbackType,
61
+ symbol: str | None = None,
62
+ symbols: Sequence[str] | None = None,
63
+ ) -> Websocket:
64
+ """Создает вебсокет для получения агрегированных сделок на фьючерсах.
65
+
66
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#aggregate-trade-streams
67
+
68
+ Параметры:
69
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
70
+ symbol (`str | None`): Один символ для подписки.
71
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
72
+
73
+ Возвращает:
74
+ `Websocket`: Объект для управления вебсокет соединением.
75
+ """
76
+ url = self._generate_stream_url(
77
+ type="aggTrade",
78
+ symbol=symbol,
79
+ symbols=symbols,
80
+ require_symbol=True,
81
+ )
82
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
83
+
84
+ def futures_symbol_mark_price(
85
+ self,
86
+ callback: CallbackType,
87
+ update_speed: str | None = None,
88
+ symbol: str | None = None,
89
+ symbols: Sequence[str] | None = None,
90
+ ) -> Websocket:
91
+ """Создает вебсокет для получения mark price и funding rate по символам на фьючерсах.
92
+
93
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#mark-price-stream
94
+
95
+ Параметры:
96
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
97
+ update_speed (`str | None`): Частота обновления ("1s" или пусто). По умолчанию "3s".
98
+ symbol (`str | None`): Один символ для подписки.
99
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
100
+
101
+ Возвращает:
102
+ `Websocket`: Объект для управления вебсокет соединением.
103
+ """
104
+ stream_type = "markPrice" + (f"@{update_speed}" if update_speed else "")
105
+ url = self._generate_stream_url(
106
+ type=stream_type,
107
+ symbol=symbol,
108
+ symbols=symbols,
109
+ require_symbol=True,
110
+ )
111
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
112
+
113
+ def futures_mark_price(
114
+ self, callback: CallbackType, update_speed: str | None = None
115
+ ) -> Websocket:
116
+ """Создает вебсокет для получения mark price и funding rate для всех символов на фьючерсах.
117
+
118
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#mark-price-stream-for-all-markets
119
+
120
+ Параметры:
121
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
122
+ update_speed (`str | None`): Частота обновления ("1s" или пусто). По умолчанию "3s".
123
+
124
+ Возвращает:
125
+ `Websocket`: Объект для управления вебсокет соединением.
126
+ """
127
+ stream_type = "!markPrice@arr" + (f"@{update_speed}" if update_speed else "")
128
+ url = self._generate_stream_url(type=stream_type)
129
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
130
+
131
+ def futures_klines(
132
+ self,
133
+ callback: CallbackType,
134
+ interval: str,
135
+ symbol: str | None = None,
136
+ symbols: Sequence[str] | None = None,
137
+ ) -> Websocket:
138
+ """Создает вебсокет для получения свечей на фьючерсах.
139
+
140
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#kline-candlestick-streams
141
+
142
+ Параметры:
143
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
144
+ interval (`str`): Временной интервал свечей.
145
+ symbol (`str | None`): Один символ для подписки.
146
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
147
+
148
+ Возвращает:
149
+ `Websocket`: Объект для управления вебсокет соединением.
150
+ """
151
+ url = self._generate_stream_url(
152
+ type=f"kline_{interval}",
153
+ symbol=symbol,
154
+ symbols=symbols,
155
+ require_symbol=True,
156
+ )
157
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
158
+
159
+ def futures_symbol_mini_ticker(
160
+ self,
161
+ callback: CallbackType,
162
+ symbol: str | None = None,
163
+ symbols: Sequence[str] | None = None,
164
+ ) -> Websocket:
165
+ """Создает вебсокет для мини‑статистики тикера за последние 24 часа на фьючерсах.
166
+
167
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#individual-symbol-mini-ticker-stream
168
+
169
+ Параметры:
170
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
171
+ symbol (`str | None`): Один символ для подписки.
172
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
173
+
174
+ Возвращает:
175
+ `Websocket`: Объект для управления вебсокет соединением.
176
+ """
177
+ url = self._generate_stream_url(
178
+ type="miniTicker",
179
+ symbol=symbol,
180
+ symbols=symbols,
181
+ require_symbol=True,
182
+ )
183
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
184
+
185
+ def futures_mini_ticker(self, callback: CallbackType) -> Websocket:
186
+ """Создает вебсокет для мини‑статистики всех тикеров за последние 24 часа на фьючерсах.
187
+
188
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#all-market-mini-tickers-stream
189
+
190
+ Параметры:
191
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
192
+
193
+ Возвращает:
194
+ `Websocket`: Объект для управления вебсокет соединением.
195
+ """
196
+ url = self._generate_stream_url(type="!miniTicker@arr")
197
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
198
+
199
+ def futures_symbol_ticker(
200
+ self,
201
+ callback: CallbackType,
202
+ symbol: str | None = None,
203
+ symbols: Sequence[str] | None = None,
204
+ ) -> Websocket:
205
+ """Создает вебсокет для расширенной статистики тикера за последние 24 часа на фьючерсах.
206
+
207
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#individual-symbol-ticker-streams
208
+
209
+ Параметры:
210
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
211
+ symbol (`str | None`): Один символ для подписки.
212
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
213
+
214
+ Возвращает:
215
+ `Websocket`: Объект для управления вебсокет соединением.
216
+ """
217
+ url = self._generate_stream_url(
218
+ type="ticker",
219
+ symbol=symbol,
220
+ symbols=symbols,
221
+ require_symbol=True,
222
+ )
223
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
224
+
225
+ def futures_ticker(self, callback: CallbackType) -> Websocket:
226
+ """Создает вебсокет для расширенной статистики всех тикеров за последние 24 часа на фьючерсах.
227
+
228
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#all-market-tickers-streams
229
+
230
+ Параметры:
231
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
232
+
233
+ Возвращает:
234
+ `Websocket`: Объект для управления вебсокет соединением.
235
+ """
236
+ url = self._generate_stream_url(type="!ticker@arr")
237
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
238
+
239
+ def futures_symbol_book_ticker(
240
+ self,
241
+ callback: CallbackType,
242
+ symbol: str | None = None,
243
+ symbols: Sequence[str] | None = None,
244
+ ) -> Websocket:
245
+ """Создает вебсокет для получения лучших бид/аск по символам на фьючерсах.
246
+
247
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#individual-symbol-book-ticker-streams
248
+
249
+ Параметры:
250
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
251
+ symbol (`str | None`): Один символ для подписки.
252
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
253
+
254
+ Возвращает:
255
+ `Websocket`: Объект для управления вебсокет соединением.
256
+ """
257
+ url = self._generate_stream_url(
258
+ type="bookTicker",
259
+ symbol=symbol,
260
+ symbols=symbols,
261
+ require_symbol=True,
262
+ )
263
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
264
+
265
+ def futures_book_ticker(self, callback: CallbackType) -> Websocket:
266
+ """Создает вебсокет для получения лучших бид/аск по всем символам на фьючерсах.
267
+
268
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#all-book-tickers-stream
269
+
270
+ Параметры:
271
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
272
+
273
+ Возвращает:
274
+ `Websocket`: Объект для управления вебсокет соединением.
275
+ """
276
+ url = self._generate_stream_url(type="!bookTicker")
277
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
278
+
279
+ def futures_liquidation_order(
280
+ self,
281
+ callback: CallbackType,
282
+ symbol: str | None = None,
283
+ symbols: Sequence[str] | None = None,
284
+ ) -> Websocket:
285
+ """Создает вебсокет для получения ликвидационных ордеров по символам на фьючерсах.
286
+
287
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#liquidation-order-streams
288
+
289
+ Параметры:
290
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
291
+ symbol (`str | None`): Один символ для подписки.
292
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
293
+
294
+ Возвращает:
295
+ `Websocket`: Объект для управления вебсокет соединением.
296
+ """
297
+ url = self._generate_stream_url(
298
+ type="forceOrder",
299
+ symbol=symbol,
300
+ symbols=symbols,
301
+ require_symbol=True,
302
+ )
303
+ return Websocket(
304
+ callback=callback,
305
+ url=url,
306
+ no_message_reconnect_timeout=60 * 15,
307
+ **self._ws_kwargs,
308
+ )
309
+
310
+ def futures_all_liquidation_orders(self, callback: CallbackType) -> Websocket:
311
+ """Создает вебсокет для получения всех ликвидационных ордеров по рынку на фьючерсах.
312
+
313
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#all-market-liquidation-order-streams
314
+
315
+ Параметры:
316
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
317
+
318
+ Возвращает:
319
+ `Websocket`: Объект для управления вебсокет соединением.
320
+ """
321
+ url = self._generate_stream_url(type="!forceOrder@arr")
322
+ return Websocket(
323
+ callback=callback,
324
+ url=url,
325
+ no_message_reconnect_timeout=60 * 15,
326
+ **self._ws_kwargs,
327
+ )
328
+
329
+ def futures_partial_book_depth(
330
+ self,
331
+ callback: CallbackType,
332
+ levels: str,
333
+ update_speed: str | None = None,
334
+ symbol: str | None = None,
335
+ symbols: Sequence[str] | None = None,
336
+ ) -> Websocket:
337
+ """Создает вебсокет для получения стакана глубиной N уровней на фьючерсах.
338
+
339
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#partial-book-depth-streams
340
+
341
+ Параметры:
342
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
343
+ levels (`str`): Глубина стакана (уровни).
344
+ update_speed (`str | None`): Скорость обновления стакана ("100ms" | "500ms"). По умолчанию: "250ms".
345
+ symbol (`str | None`): Один символ для подписки.
346
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
347
+
348
+ Возвращает:
349
+ `Websocket`: Объект для управления вебсокет соединением.
350
+ """
351
+ stream_type = f"depth{levels}" + (f"@{update_speed}" if update_speed else "")
352
+ url = self._generate_stream_url(
353
+ type=stream_type,
354
+ symbol=symbol,
355
+ symbols=symbols,
356
+ require_symbol=True,
357
+ )
358
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
359
+
360
+ def futures_diff_depth(
361
+ self,
362
+ callback: CallbackType,
363
+ update_speed: str | None = None,
364
+ symbol: str | None = None,
365
+ symbols: Sequence[str] | None = None,
366
+ ) -> Websocket:
367
+ """Создает вебсокет для получения событий изменения стакана (без лимита глубины) на фьючерсах.
368
+
369
+ https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation#diff-book-depth-streams
370
+
371
+ Параметры:
372
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
373
+ update_speed (`str | None`): Скорость обновления стакана ("100ms" | "500ms"). По умолчанию: "250ms".
374
+ symbol (`str | None`): Один символ для подписки.
375
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
376
+
377
+ Возвращает:
378
+ `Websocket`: Объект для управления вебсокет соединением.
379
+ """
380
+ stream_type = "depth" + (f"@{update_speed}" if update_speed else "")
381
+ url = self._generate_stream_url(
382
+ type=stream_type,
383
+ symbol=symbol,
384
+ symbols=symbols,
385
+ require_symbol=True,
386
+ )
387
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
388
+
389
+ def futures_multiplex_socket(self, callback: CallbackType, streams: str) -> Websocket:
390
+ """Создает вебсокет для мультиплексирования нескольких стримов в один на фьючерсах.
391
+
392
+ Параметры:
393
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
394
+ streams (`str`): Строка с перечислением стримов.
395
+
396
+ Возвращает:
397
+ `Websocket`: Объект для управления вебсокет соединением.
398
+ """
399
+ if streams.startswith("streams="):
400
+ url = f"{self._BASE_URL}/stream?{streams}"
401
+ else:
402
+ url = f"{self._BASE_URL}/stream?streams={streams}"
403
+ return Websocket(callback=callback, url=url, **self._ws_kwargs)
@@ -163,8 +163,8 @@ class UniClient(IUniClient[Client]):
163
163
  async def funding_rate(self, symbol: str | None = None) -> dict[str, float] | float:
164
164
  """Возвращает ставку финансирования для тикера или всех тикеров, если тикер не указан.
165
165
 
166
- - Параметры:
167
- symbol (`str | None`): Название тикера (Опционально).
166
+ Параметры:
167
+ symbol (`str | None`): Название тикера (Опционально).
168
168
 
169
169
  Возвращает:
170
170
  `dict[str, float] | float`: Ставка финансирования для тикера или словарь со ставками для всех тикеров.
unicex/enums.py CHANGED
@@ -24,6 +24,7 @@ class MarketType(StrEnum):
24
24
  class Exchange(StrEnum):
25
25
  """Перечисление бирж."""
26
26
 
27
+ ASTER = "ASTER"
27
28
  BINANCE = "BINANCE"
28
29
  BITGET = "BITGET"
29
30
  BYBIT = "BYBIT"
@@ -75,6 +76,23 @@ class Timeframe(StrEnum):
75
76
  def mapping(self) -> dict[Exchange | tuple[Exchange, MarketType], dict["Timeframe", str]]:
76
77
  """Возвращает словарь с маппингом таймфреймов для каждой биржи."""
77
78
  return {
79
+ Exchange.ASTER: {
80
+ Timeframe.MIN_1: "1m",
81
+ Timeframe.MIN_3: "3m",
82
+ Timeframe.MIN_5: "5m",
83
+ Timeframe.MIN_15: "15m",
84
+ Timeframe.MIN_30: "30m",
85
+ Timeframe.HOUR_1: "1h",
86
+ Timeframe.HOUR_2: "2h",
87
+ Timeframe.HOUR_4: "4h",
88
+ Timeframe.HOUR_6: "6h",
89
+ Timeframe.HOUR_8: "8h",
90
+ Timeframe.HOUR_12: "12h",
91
+ Timeframe.DAY_1: "1d",
92
+ Timeframe.DAY_3: "3d",
93
+ Timeframe.WEEK_1: "1w",
94
+ Timeframe.MONTH_1: "1M",
95
+ },
78
96
  (Exchange.BINANCE, MarketType.SPOT): {
79
97
  Timeframe.SECOND_1: "1s",
80
98
  Timeframe.MIN_1: "1m",
unicex/extra.py CHANGED
@@ -225,6 +225,11 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
225
225
  return f"https://bingx.com/en/perpetual/{ticker}-USDT"
226
226
  else:
227
227
  return f"https://bingx.com/en/spot/{ticker}USDT"
228
+ elif exchange == Exchange.ASTER:
229
+ if market_type == MarketType.FUTURES:
230
+ return f"https://www.asterdex.com/ru/trade/pro/futures/{symbol}"
231
+ else:
232
+ return f"https://www.asterdex.com/ru/trade/pro/spot/{symbol}"
228
233
  else:
229
234
  raise NotSupported(f"Exchange {exchange} is not supported")
230
235