unicex 0.14.8__py3-none-any.whl → 0.15.1__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 +18 -0
- unicex/_base/websocket.py +26 -9
- unicex/binance/adapter.py +0 -12
- unicex/bingx/__init__.py +27 -0
- unicex/bingx/adapter.py +284 -0
- unicex/bingx/client.py +521 -0
- unicex/bingx/exchange_info.py +22 -0
- unicex/bingx/uni_client.py +191 -0
- unicex/bingx/uni_websocket_manager.py +283 -0
- unicex/bingx/user_websocket.py +7 -0
- unicex/bingx/websocket_manager.py +120 -0
- unicex/bybit/client.py +4 -4
- unicex/enums.py +1 -0
- unicex/extra.py +7 -0
- unicex/mapper.py +59 -24
- unicex/mexc/_spot_ws_proto/__init__.py +335 -335
- unicex/mexc/websocket_manager.py +1 -1
- unicex/okx/websocket_manager.py +3 -3
- unicex/utils.py +17 -0
- {unicex-0.14.8.dist-info → unicex-0.15.1.dist-info}/METADATA +2 -1
- {unicex-0.14.8.dist-info → unicex-0.15.1.dist-info}/RECORD +24 -16
- {unicex-0.14.8.dist-info → unicex-0.15.1.dist-info}/WHEEL +0 -0
- {unicex-0.14.8.dist-info → unicex-0.15.1.dist-info}/licenses/LICENSE +0 -0
- {unicex-0.14.8.dist-info → unicex-0.15.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
__all__ = ["UniClient"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import overload
|
|
5
|
+
|
|
6
|
+
from unicex._abc import IUniClient
|
|
7
|
+
from unicex.enums import Timeframe
|
|
8
|
+
from unicex.types import KlineDict, OpenInterestDict, OpenInterestItem, TickerDailyDict
|
|
9
|
+
|
|
10
|
+
from .adapter import Adapter
|
|
11
|
+
from .client import Client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UniClient(IUniClient[Client]):
|
|
15
|
+
"""Унифицированный клиент для работы с BingX API."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def _client_cls(self) -> type[Client]:
|
|
19
|
+
"""Возвращает класс клиента для BingX.
|
|
20
|
+
|
|
21
|
+
Возвращает:
|
|
22
|
+
type[Client]: Класс клиента для BingX.
|
|
23
|
+
"""
|
|
24
|
+
return Client
|
|
25
|
+
|
|
26
|
+
async def tickers(self, only_usdt: bool = True) -> list[str]:
|
|
27
|
+
"""Возвращает список тикеров.
|
|
28
|
+
|
|
29
|
+
Параметры:
|
|
30
|
+
only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
|
|
31
|
+
|
|
32
|
+
Возвращает:
|
|
33
|
+
list[str]: Список тикеров.
|
|
34
|
+
"""
|
|
35
|
+
raw_data = await self._client.symbols()
|
|
36
|
+
return Adapter.tickers(raw_data, only_usdt)
|
|
37
|
+
|
|
38
|
+
async def futures_tickers(self, only_usdt: bool = True) -> list[str]:
|
|
39
|
+
"""Возвращает список тикеров.
|
|
40
|
+
|
|
41
|
+
Параметры:
|
|
42
|
+
only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
|
|
43
|
+
|
|
44
|
+
Возвращает:
|
|
45
|
+
list[str]: Список тикеров.
|
|
46
|
+
"""
|
|
47
|
+
raw_data = await self._client.futures_contracts()
|
|
48
|
+
return Adapter.tickers(raw_data, only_usdt)
|
|
49
|
+
|
|
50
|
+
async def last_price(self) -> dict[str, float]:
|
|
51
|
+
"""Возвращает последнюю цену для каждого тикера.
|
|
52
|
+
|
|
53
|
+
Возвращает:
|
|
54
|
+
dict[str, float]: Словарь с последними ценами для каждого тикера.
|
|
55
|
+
"""
|
|
56
|
+
raw_data = await self._client.ticker_24hr()
|
|
57
|
+
return Adapter.last_price(raw_data)
|
|
58
|
+
|
|
59
|
+
async def futures_last_price(self) -> dict[str, float]:
|
|
60
|
+
"""Возвращает последнюю цену для каждого тикера.
|
|
61
|
+
|
|
62
|
+
Возвращает:
|
|
63
|
+
dict[str, float]: Словарь с последними ценами для каждого тикера.
|
|
64
|
+
"""
|
|
65
|
+
raw_data = await self._client.futures_ticker_price()
|
|
66
|
+
return Adapter.last_price(raw_data)
|
|
67
|
+
|
|
68
|
+
async def ticker_24hr(self) -> TickerDailyDict:
|
|
69
|
+
"""Возвращает статистику за последние 24 часа для каждого тикера.
|
|
70
|
+
|
|
71
|
+
Возвращает:
|
|
72
|
+
TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
|
|
73
|
+
"""
|
|
74
|
+
raw_data = await self._client.ticker_24hr()
|
|
75
|
+
return Adapter.ticker_24hr(raw_data)
|
|
76
|
+
|
|
77
|
+
async def futures_ticker_24hr(self) -> TickerDailyDict:
|
|
78
|
+
"""Возвращает статистику за последние 24 часа для каждого тикера.
|
|
79
|
+
|
|
80
|
+
Возвращает:
|
|
81
|
+
TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
|
|
82
|
+
"""
|
|
83
|
+
raw_data = await self._client.futures_ticker_24hr()
|
|
84
|
+
return Adapter.ticker_24hr(raw_data)
|
|
85
|
+
|
|
86
|
+
async def klines(
|
|
87
|
+
self,
|
|
88
|
+
symbol: str,
|
|
89
|
+
interval: Timeframe | str,
|
|
90
|
+
limit: int | None = None,
|
|
91
|
+
start_time: int | None = None,
|
|
92
|
+
end_time: int | None = None,
|
|
93
|
+
) -> list[KlineDict]:
|
|
94
|
+
"""Возвращает список свечей для тикера.
|
|
95
|
+
|
|
96
|
+
Параметры:
|
|
97
|
+
symbol (str): Название тикера.
|
|
98
|
+
limit (int | None): Количество свечей.
|
|
99
|
+
interval (Timeframe | str): Таймфрейм свечей.
|
|
100
|
+
start_time (int | None): Время начала периода в миллисекундах.
|
|
101
|
+
end_time (int | None): Время окончания периода в миллисекундах.
|
|
102
|
+
|
|
103
|
+
Возвращает:
|
|
104
|
+
list[KlineDict]: Список свечей для тикера.
|
|
105
|
+
"""
|
|
106
|
+
interval = interval.value if isinstance(interval, Timeframe) else interval
|
|
107
|
+
raw_data = await self._client.klines(
|
|
108
|
+
symbol=symbol,
|
|
109
|
+
interval=interval,
|
|
110
|
+
limit=limit,
|
|
111
|
+
start_time=start_time,
|
|
112
|
+
end_time=end_time,
|
|
113
|
+
)
|
|
114
|
+
return Adapter.klines(raw_data)
|
|
115
|
+
|
|
116
|
+
async def futures_klines(
|
|
117
|
+
self,
|
|
118
|
+
symbol: str,
|
|
119
|
+
interval: Timeframe | str,
|
|
120
|
+
limit: int | None = None,
|
|
121
|
+
start_time: int | None = None,
|
|
122
|
+
end_time: int | None = None,
|
|
123
|
+
) -> list[KlineDict]:
|
|
124
|
+
"""Возвращает список свечей для тикера.
|
|
125
|
+
|
|
126
|
+
Параметры:
|
|
127
|
+
symbol (str): Название тикера.
|
|
128
|
+
limit (int | None): Количество свечей.
|
|
129
|
+
interval (Timeframe | str): Таймфрейм свечей.
|
|
130
|
+
start_time (int | None): Время начала периода в миллисекундах.
|
|
131
|
+
end_time (int | None): Время окончания периода в миллисекундах.
|
|
132
|
+
|
|
133
|
+
Возвращает:
|
|
134
|
+
list[KlineDict]: Список свечей для тикера.
|
|
135
|
+
"""
|
|
136
|
+
interval = interval.value if isinstance(interval, Timeframe) else interval
|
|
137
|
+
raw_data = await self._client.futures_klines(
|
|
138
|
+
symbol=symbol,
|
|
139
|
+
interval=interval,
|
|
140
|
+
limit=limit,
|
|
141
|
+
start_time=start_time,
|
|
142
|
+
end_time=end_time,
|
|
143
|
+
)
|
|
144
|
+
return Adapter.klines(raw_data)
|
|
145
|
+
|
|
146
|
+
@overload
|
|
147
|
+
async def funding_rate(self, symbol: str) -> float: ...
|
|
148
|
+
|
|
149
|
+
@overload
|
|
150
|
+
async def funding_rate(self, symbol: None) -> dict[str, float]: ...
|
|
151
|
+
|
|
152
|
+
@overload
|
|
153
|
+
async def funding_rate(self) -> dict[str, float]: ...
|
|
154
|
+
|
|
155
|
+
async def funding_rate(self, symbol: str | None = None) -> dict[str, float] | float:
|
|
156
|
+
"""Возвращает ставку финансирования для тикера или всех тикеров, если тикер не указан.
|
|
157
|
+
|
|
158
|
+
Параметры:
|
|
159
|
+
symbol (`str | None`): Название тикера (Опционально).
|
|
160
|
+
|
|
161
|
+
Возвращает:
|
|
162
|
+
dict[str, float] | float: Ставка финансирования для тикера или словарь со ставками.
|
|
163
|
+
"""
|
|
164
|
+
raw_data = await self._client.futures_mark_price(symbol=symbol)
|
|
165
|
+
adapted_data = Adapter.funding_rate(raw_data)
|
|
166
|
+
return adapted_data[symbol] if symbol else adapted_data
|
|
167
|
+
|
|
168
|
+
@overload
|
|
169
|
+
async def open_interest(self, symbol: str) -> OpenInterestItem: ...
|
|
170
|
+
|
|
171
|
+
@overload
|
|
172
|
+
async def open_interest(self, symbol: None) -> OpenInterestDict: ...
|
|
173
|
+
|
|
174
|
+
@overload
|
|
175
|
+
async def open_interest(self) -> OpenInterestDict: ...
|
|
176
|
+
|
|
177
|
+
async def open_interest(self, symbol: str | None = None) -> OpenInterestItem | OpenInterestDict:
|
|
178
|
+
"""Возвращает объем открытого интереса для тикера или всех тикеров, если тикер не указан.
|
|
179
|
+
|
|
180
|
+
Параметры:
|
|
181
|
+
symbol (`str | None`): Название тикера. (Опционально, но обязателен для следующих бирж: BINANCE).
|
|
182
|
+
|
|
183
|
+
Возвращает:
|
|
184
|
+
`OpenInterestItem | OpenInterestDict`: Если тикер передан - словарь со временем и объемом
|
|
185
|
+
открытого интереса в монетах. Если нет передан - то словарь, в котором ключ - тикер,
|
|
186
|
+
а значение - словарь с временем и объемом открытого интереса в монетах.
|
|
187
|
+
"""
|
|
188
|
+
if not symbol:
|
|
189
|
+
raise ValueError("Symbol is required for bingx open interest")
|
|
190
|
+
raw_data = await self._client.open_interest(symbol=symbol)
|
|
191
|
+
return Adapter.open_interest(raw_data)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
__all__ = ["IUniWebsocketManager"]
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
4
|
+
from typing import Any, overload
|
|
5
|
+
|
|
6
|
+
from unicex._abc import IUniWebsocketManager
|
|
7
|
+
from unicex._base import Websocket
|
|
8
|
+
from unicex.enums import Timeframe
|
|
9
|
+
from unicex.types import LoggerLike
|
|
10
|
+
|
|
11
|
+
from .adapter import Adapter
|
|
12
|
+
from .client import Client
|
|
13
|
+
from .uni_client import UniClient
|
|
14
|
+
from .websocket_manager import WebsocketManager
|
|
15
|
+
|
|
16
|
+
type CallbackType = Callable[[Any], Awaitable[None]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UniWebsocketManager(IUniWebsocketManager):
|
|
20
|
+
"""Реализация менеджера асинхронных унифицированных вебсокетов."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
client: Client | UniClient | None = None,
|
|
25
|
+
logger: LoggerLike | None = None,
|
|
26
|
+
**ws_kwargs: Any,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Инициализирует унифицированный менеджер вебсокетов.
|
|
29
|
+
|
|
30
|
+
Параметры:
|
|
31
|
+
client (`Client | UniClient | None`): Клиент BingX или унифицированный клиент. Нужен для подключения к приватным топикам.
|
|
32
|
+
logger (`LoggerLike | None`): Логгер для записи логов.
|
|
33
|
+
ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(client=client, logger=logger)
|
|
36
|
+
self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
|
|
37
|
+
self._adapter = Adapter()
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def klines(
|
|
41
|
+
self,
|
|
42
|
+
callback: CallbackType,
|
|
43
|
+
timeframe: Timeframe,
|
|
44
|
+
*,
|
|
45
|
+
symbol: str,
|
|
46
|
+
symbols: None = None,
|
|
47
|
+
) -> Websocket: ...
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
def klines(
|
|
51
|
+
self,
|
|
52
|
+
callback: CallbackType,
|
|
53
|
+
timeframe: Timeframe,
|
|
54
|
+
*,
|
|
55
|
+
symbol: None = None,
|
|
56
|
+
symbols: Sequence[str],
|
|
57
|
+
) -> Websocket: ...
|
|
58
|
+
|
|
59
|
+
def klines(
|
|
60
|
+
self,
|
|
61
|
+
callback: CallbackType,
|
|
62
|
+
timeframe: Timeframe,
|
|
63
|
+
symbol: str | None = None,
|
|
64
|
+
symbols: Sequence[str] | None = None,
|
|
65
|
+
) -> Websocket:
|
|
66
|
+
"""Открывает стрим свечей (spot) с унификацией сообщений.
|
|
67
|
+
|
|
68
|
+
Параметры:
|
|
69
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
70
|
+
timeframe (`Timeframe`): Временной интервал свечей.
|
|
71
|
+
symbol (`str | None`): Один символ для подписки.
|
|
72
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
73
|
+
|
|
74
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
75
|
+
|
|
76
|
+
Возвращает:
|
|
77
|
+
`Websocket`: Экземпляр вебсокета для управления соединением.
|
|
78
|
+
"""
|
|
79
|
+
raise NotImplementedError()
|
|
80
|
+
|
|
81
|
+
@overload
|
|
82
|
+
def futures_klines(
|
|
83
|
+
self,
|
|
84
|
+
callback: CallbackType,
|
|
85
|
+
timeframe: Timeframe,
|
|
86
|
+
*,
|
|
87
|
+
symbol: str,
|
|
88
|
+
symbols: None = None,
|
|
89
|
+
) -> Websocket: ...
|
|
90
|
+
|
|
91
|
+
@overload
|
|
92
|
+
def futures_klines(
|
|
93
|
+
self,
|
|
94
|
+
callback: CallbackType,
|
|
95
|
+
timeframe: Timeframe,
|
|
96
|
+
*,
|
|
97
|
+
symbol: None = None,
|
|
98
|
+
symbols: Sequence[str],
|
|
99
|
+
) -> Websocket: ...
|
|
100
|
+
|
|
101
|
+
def futures_klines(
|
|
102
|
+
self,
|
|
103
|
+
callback: CallbackType,
|
|
104
|
+
timeframe: Timeframe,
|
|
105
|
+
symbol: str | None = None,
|
|
106
|
+
symbols: Sequence[str] | None = None,
|
|
107
|
+
) -> Websocket:
|
|
108
|
+
"""Открывает стрим свечей (futures) с унификацией сообщений.
|
|
109
|
+
|
|
110
|
+
Параметры:
|
|
111
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
112
|
+
timeframe (`Timeframe`): Временной интервал свечей.
|
|
113
|
+
symbol (`str | None`): Один символ для подписки.
|
|
114
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
115
|
+
|
|
116
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
117
|
+
|
|
118
|
+
Возвращает:
|
|
119
|
+
`Websocket`: Экземпляр вебсокета.
|
|
120
|
+
"""
|
|
121
|
+
raise NotImplementedError()
|
|
122
|
+
|
|
123
|
+
@overload
|
|
124
|
+
def trades(
|
|
125
|
+
self,
|
|
126
|
+
callback: CallbackType,
|
|
127
|
+
*,
|
|
128
|
+
symbol: str,
|
|
129
|
+
symbols: None = None,
|
|
130
|
+
) -> Websocket: ...
|
|
131
|
+
|
|
132
|
+
@overload
|
|
133
|
+
def trades(
|
|
134
|
+
self,
|
|
135
|
+
callback: CallbackType,
|
|
136
|
+
*,
|
|
137
|
+
symbol: None = None,
|
|
138
|
+
symbols: Sequence[str],
|
|
139
|
+
) -> Websocket: ...
|
|
140
|
+
|
|
141
|
+
def trades(
|
|
142
|
+
self,
|
|
143
|
+
callback: CallbackType,
|
|
144
|
+
symbol: str | None = None,
|
|
145
|
+
symbols: Sequence[str] | None = None,
|
|
146
|
+
) -> Websocket:
|
|
147
|
+
"""Открывает стрим сделок (spot) с унификацией сообщений.
|
|
148
|
+
|
|
149
|
+
Параметры:
|
|
150
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
151
|
+
symbol (`str | None`): Один символ для подписки.
|
|
152
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
153
|
+
|
|
154
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
155
|
+
|
|
156
|
+
Возвращает:
|
|
157
|
+
`Websocket`: Экземпляр вебсокета.
|
|
158
|
+
"""
|
|
159
|
+
return self._websocket_manager.trade(
|
|
160
|
+
callback=self._make_wrapper(self._adapter.trades_message, callback),
|
|
161
|
+
symbol=symbol,
|
|
162
|
+
symbols=symbols,
|
|
163
|
+
market_type="SPOT",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@overload
|
|
167
|
+
def aggtrades(
|
|
168
|
+
self,
|
|
169
|
+
callback: CallbackType,
|
|
170
|
+
*,
|
|
171
|
+
symbol: str,
|
|
172
|
+
symbols: None = None,
|
|
173
|
+
) -> Websocket: ...
|
|
174
|
+
|
|
175
|
+
@overload
|
|
176
|
+
def aggtrades(
|
|
177
|
+
self,
|
|
178
|
+
callback: CallbackType,
|
|
179
|
+
*,
|
|
180
|
+
symbol: None = None,
|
|
181
|
+
symbols: Sequence[str],
|
|
182
|
+
) -> Websocket: ...
|
|
183
|
+
|
|
184
|
+
def aggtrades(
|
|
185
|
+
self,
|
|
186
|
+
callback: CallbackType,
|
|
187
|
+
symbol: str | None = None,
|
|
188
|
+
symbols: Sequence[str] | None = None,
|
|
189
|
+
) -> Websocket:
|
|
190
|
+
"""Открывает стрим агрегированных сделок (spot) с унификацией сообщений.
|
|
191
|
+
|
|
192
|
+
Параметры:
|
|
193
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
194
|
+
symbol (`str | None`): Один символ для подписки.
|
|
195
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
196
|
+
|
|
197
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
198
|
+
|
|
199
|
+
Возвращает:
|
|
200
|
+
`Websocket`: Экземпляр вебсокета.
|
|
201
|
+
"""
|
|
202
|
+
raise NotImplementedError()
|
|
203
|
+
|
|
204
|
+
@overload
|
|
205
|
+
def futures_trades(
|
|
206
|
+
self,
|
|
207
|
+
callback: CallbackType,
|
|
208
|
+
*,
|
|
209
|
+
symbol: str,
|
|
210
|
+
symbols: None = None,
|
|
211
|
+
) -> Websocket: ...
|
|
212
|
+
|
|
213
|
+
@overload
|
|
214
|
+
def futures_trades(
|
|
215
|
+
self,
|
|
216
|
+
callback: CallbackType,
|
|
217
|
+
*,
|
|
218
|
+
symbol: None = None,
|
|
219
|
+
symbols: Sequence[str],
|
|
220
|
+
) -> Websocket: ...
|
|
221
|
+
|
|
222
|
+
def futures_trades(
|
|
223
|
+
self,
|
|
224
|
+
callback: CallbackType,
|
|
225
|
+
symbol: str | None = None,
|
|
226
|
+
symbols: Sequence[str] | None = None,
|
|
227
|
+
) -> Websocket:
|
|
228
|
+
"""Открывает стрим сделок (futures) с унификацией сообщений.
|
|
229
|
+
|
|
230
|
+
Параметры:
|
|
231
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
232
|
+
symbol (`str | None`): Один символ для подписки.
|
|
233
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
234
|
+
|
|
235
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
236
|
+
|
|
237
|
+
Возвращает:
|
|
238
|
+
`Websocket`: Экземпляр вебсокета.
|
|
239
|
+
"""
|
|
240
|
+
return self._websocket_manager.trade(
|
|
241
|
+
callback=self._make_wrapper(self._adapter.futures_trades_message, callback),
|
|
242
|
+
symbol=symbol,
|
|
243
|
+
symbols=symbols,
|
|
244
|
+
market_type="FUTURES",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@overload
|
|
248
|
+
def futures_aggtrades(
|
|
249
|
+
self,
|
|
250
|
+
callback: CallbackType,
|
|
251
|
+
*,
|
|
252
|
+
symbol: str,
|
|
253
|
+
symbols: None = None,
|
|
254
|
+
) -> Websocket: ...
|
|
255
|
+
|
|
256
|
+
@overload
|
|
257
|
+
def futures_aggtrades(
|
|
258
|
+
self,
|
|
259
|
+
callback: CallbackType,
|
|
260
|
+
*,
|
|
261
|
+
symbol: None = None,
|
|
262
|
+
symbols: Sequence[str],
|
|
263
|
+
) -> Websocket: ...
|
|
264
|
+
|
|
265
|
+
def futures_aggtrades(
|
|
266
|
+
self,
|
|
267
|
+
callback: CallbackType,
|
|
268
|
+
symbol: str | None = None,
|
|
269
|
+
symbols: Sequence[str] | None = None,
|
|
270
|
+
) -> Websocket:
|
|
271
|
+
"""Открывает стрим агрегированных сделок (futures) с унификацией сообщений.
|
|
272
|
+
|
|
273
|
+
Параметры:
|
|
274
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
275
|
+
symbol (`str | None`): Один символ для подписки.
|
|
276
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
277
|
+
|
|
278
|
+
Должен быть указан либо `symbol`, либо `symbols`.
|
|
279
|
+
|
|
280
|
+
Возвращает:
|
|
281
|
+
`Websocket`: Экземпляр вебсокета.
|
|
282
|
+
"""
|
|
283
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
__all__ = ["WebsocketManager"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import gzip
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
import orjson
|
|
10
|
+
|
|
11
|
+
from unicex._base import Websocket
|
|
12
|
+
|
|
13
|
+
from .client import Client
|
|
14
|
+
|
|
15
|
+
type CallbackType = Callable[[Any], Awaitable[None]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WebsocketManager:
|
|
19
|
+
"""Менеджер асинхронных вебсокетов для BingX."""
|
|
20
|
+
|
|
21
|
+
_BASE_SPOT_URL: str = "wss://open-api-ws.bingx.com/market"
|
|
22
|
+
"""Базовый URL для вебсокета на спот."""
|
|
23
|
+
|
|
24
|
+
_BASE_FUTURES_URL: str = "wss://open-api-swap.bingx.com/swap-market"
|
|
25
|
+
"""Базовый URL для вебсокета на фьючерсы."""
|
|
26
|
+
|
|
27
|
+
class _BingXGzipDecoder:
|
|
28
|
+
"""Класс для декодирования gzip-сообщений WebSocket от BingX."""
|
|
29
|
+
|
|
30
|
+
def decode(self, message: Any) -> dict | Literal["ping"]:
|
|
31
|
+
if isinstance(message, bytes):
|
|
32
|
+
try:
|
|
33
|
+
message = gzip.decompress(message).decode("utf-8")
|
|
34
|
+
except OSError:
|
|
35
|
+
message = message.decode("utf-8")
|
|
36
|
+
|
|
37
|
+
if message == "Ping":
|
|
38
|
+
return "ping"
|
|
39
|
+
|
|
40
|
+
return orjson.loads(message)
|
|
41
|
+
|
|
42
|
+
def __init__(self, client: Client | None = None, **ws_kwargs: Any) -> None:
|
|
43
|
+
"""Инициализирует менеджер вебсокетов для BingX.
|
|
44
|
+
|
|
45
|
+
Параметры:
|
|
46
|
+
client (`Client | None`): Клиент для выполнения запросов. Нужен, чтобы открыть приватные вебсокеты.
|
|
47
|
+
ws_kwargs (`dict[str, Any]`): Дополнительные аргументы, которые прокидываются в `Websocket`.
|
|
48
|
+
"""
|
|
49
|
+
self.client = client
|
|
50
|
+
self._ws_kwargs = ws_kwargs
|
|
51
|
+
|
|
52
|
+
def _get_url(self, market_type: Literal["SPOT", "FUTURES"]) -> str:
|
|
53
|
+
"""Возвращает URL для указанного типа рынка."""
|
|
54
|
+
if market_type == "SPOT":
|
|
55
|
+
return self._BASE_SPOT_URL
|
|
56
|
+
if market_type == "FUTURES":
|
|
57
|
+
return self._BASE_FUTURES_URL
|
|
58
|
+
raise ValueError(f"Unsupported market type: {market_type}")
|
|
59
|
+
|
|
60
|
+
def _generate_subscription_messages(
|
|
61
|
+
self,
|
|
62
|
+
data_types: Sequence[str],
|
|
63
|
+
req_id: str | None = None,
|
|
64
|
+
) -> list[str]:
|
|
65
|
+
"""Сформировать сообщения для подписки на вебсокет.
|
|
66
|
+
|
|
67
|
+
Параметры:
|
|
68
|
+
data_types (`Sequence[str]`): Список топиков для подписки.
|
|
69
|
+
req_id (`str | None`): Опциональный идентификатор запроса.
|
|
70
|
+
|
|
71
|
+
Возвращает:
|
|
72
|
+
`list[str]`: Список JSON-строк с сообщениями для подписки.
|
|
73
|
+
"""
|
|
74
|
+
messages = []
|
|
75
|
+
for data_type in data_types:
|
|
76
|
+
message = {"reqType": "sub", "dataType": data_type}
|
|
77
|
+
if req_id:
|
|
78
|
+
message["id"] = req_id
|
|
79
|
+
messages.append(json.dumps(message))
|
|
80
|
+
return messages
|
|
81
|
+
|
|
82
|
+
def trade(
|
|
83
|
+
self,
|
|
84
|
+
callback: CallbackType,
|
|
85
|
+
market_type: Literal["SPOT", "FUTURES"],
|
|
86
|
+
symbol: str | None = None,
|
|
87
|
+
symbols: Sequence[str] | None = None,
|
|
88
|
+
req_id: str | None = None,
|
|
89
|
+
) -> Websocket:
|
|
90
|
+
"""Создает вебсокет для получения сделок.
|
|
91
|
+
|
|
92
|
+
https://bingx-api.github.io/docs-v3/#/en/Swap/Market%20Data/Subscribe%20to%20Tick-by-Tick%20Trades
|
|
93
|
+
|
|
94
|
+
Параметры:
|
|
95
|
+
callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
|
|
96
|
+
market_type (`Literal["SPOT", "FUTURES"]`): Тип рынка.
|
|
97
|
+
symbol (`str | None`): Один символ для подписки.
|
|
98
|
+
symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
|
|
99
|
+
req_id (`str | None`): Опциональный идентификатор запроса.
|
|
100
|
+
|
|
101
|
+
Возвращает:
|
|
102
|
+
`Websocket`: Объект для управления вебсокет соединением.
|
|
103
|
+
"""
|
|
104
|
+
if symbol and symbols:
|
|
105
|
+
raise ValueError("Parameters symbol and symbols cannot be used together")
|
|
106
|
+
if not (symbol or symbols):
|
|
107
|
+
raise ValueError("Either symbol or symbols must be provided")
|
|
108
|
+
|
|
109
|
+
tickers = [symbol] if symbol else symbols
|
|
110
|
+
data_types = [f"{ticker.upper()}@trade" for ticker in tickers] # type: ignore[arg-type]
|
|
111
|
+
|
|
112
|
+
subscription_messages = self._generate_subscription_messages(data_types, req_id)
|
|
113
|
+
return Websocket(
|
|
114
|
+
callback=callback,
|
|
115
|
+
url=self._get_url(market_type),
|
|
116
|
+
subscription_messages=subscription_messages,
|
|
117
|
+
decoder=self._BingXGzipDecoder,
|
|
118
|
+
pong_message="Pong",
|
|
119
|
+
**self._ws_kwargs,
|
|
120
|
+
)
|
unicex/bybit/client.py
CHANGED
|
@@ -610,7 +610,7 @@ class Client(BaseClient):
|
|
|
610
610
|
close_on_trigger: bool | None = None,
|
|
611
611
|
smp_type: str | None = None,
|
|
612
612
|
mmp: bool | None = None,
|
|
613
|
-
tpsl_mode:
|
|
613
|
+
tpsl_mode: Literal["Full", "Partial"] | None = None,
|
|
614
614
|
tp_limit_price: str | None = None,
|
|
615
615
|
sl_limit_price: str | None = None,
|
|
616
616
|
tp_order_type: str | None = None,
|
|
@@ -666,7 +666,7 @@ class Client(BaseClient):
|
|
|
666
666
|
trigger_price: str | None = None,
|
|
667
667
|
qty: str | None = None,
|
|
668
668
|
price: str | None = None,
|
|
669
|
-
tpsl_mode:
|
|
669
|
+
tpsl_mode: Literal["Full", "Partial"] | None = None,
|
|
670
670
|
take_profit: str | None = None,
|
|
671
671
|
stop_loss: str | None = None,
|
|
672
672
|
tp_trigger_by: str | None = None,
|
|
@@ -959,7 +959,7 @@ class Client(BaseClient):
|
|
|
959
959
|
close_on_trigger: bool | None = None,
|
|
960
960
|
smp_type: str | None = None,
|
|
961
961
|
mmp: bool | None = None,
|
|
962
|
-
tpsl_mode:
|
|
962
|
+
tpsl_mode: Literal["Full", "Partial"] | None = None,
|
|
963
963
|
tp_limit_price: str | None = None,
|
|
964
964
|
sl_limit_price: str | None = None,
|
|
965
965
|
tp_order_type: str | None = None,
|
|
@@ -1098,7 +1098,7 @@ class Client(BaseClient):
|
|
|
1098
1098
|
self,
|
|
1099
1099
|
category: Literal["linear", "inverse"],
|
|
1100
1100
|
symbol: str,
|
|
1101
|
-
tpsl_mode:
|
|
1101
|
+
tpsl_mode: Literal["Full", "Partial"],
|
|
1102
1102
|
position_idx: int,
|
|
1103
1103
|
take_profit: str | None = None,
|
|
1104
1104
|
stop_loss: str | None = None,
|
unicex/enums.py
CHANGED
unicex/extra.py
CHANGED
|
@@ -221,6 +221,11 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
|
|
|
221
221
|
return f"https://www.kucoin.com/trade/futures/{symbol}M"
|
|
222
222
|
else:
|
|
223
223
|
return f"https://www.kucoin.com/trade/{ticker}-USDT"
|
|
224
|
+
elif exchange == Exchange.BINGX:
|
|
225
|
+
if market_type == MarketType.FUTURES:
|
|
226
|
+
return f"https://bingx.com/en/spot/{symbol}"
|
|
227
|
+
else:
|
|
228
|
+
return f"https://bingx.com/en/perpetual/{ticker}-USDT"
|
|
224
229
|
else:
|
|
225
230
|
raise NotSupported(f"Exchange {exchange} is not supported")
|
|
226
231
|
|
|
@@ -273,6 +278,8 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
273
278
|
return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
|
|
274
279
|
case Exchange.KUCOIN:
|
|
275
280
|
return f"https://www.coinglass.com/tv/ru/KuCoin_{symbol}M"
|
|
281
|
+
case Exchange.BINGX:
|
|
282
|
+
return f"https://www.coinglass.com/tv/ru/BingX_{symbol.replace('USDT', '-USDT')}"
|
|
276
283
|
case _:
|
|
277
284
|
return f"{base_url}/{exchange.capitalize()}_{symbol}"
|
|
278
285
|
else:
|