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