unicex 0.5.0__py3-none-any.whl → 0.8.0__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 +56 -124
- unicex/_abc/__init__.py +2 -0
- unicex/_abc/exchange_info.py +176 -0
- unicex/_abc/uni_client.py +2 -2
- unicex/_base/client.py +2 -2
- unicex/binance/__init__.py +12 -0
- unicex/binance/adapter.py +2 -2
- unicex/binance/exchange_info.py +12 -0
- unicex/bitget/__init__.py +14 -4
- unicex/bitget/adapter.py +1 -1
- unicex/bitget/exchange_info.py +12 -0
- unicex/bitget/uni_websocket_manager.py +1 -1
- unicex/bybit/__init__.py +12 -0
- unicex/bybit/adapter.py +2 -2
- unicex/bybit/exchange_info.py +12 -0
- unicex/bybit/uni_client.py +2 -2
- unicex/enums.py +16 -5
- unicex/extra.py +84 -6
- unicex/gateio/__init__.py +12 -0
- unicex/gateio/adapter.py +4 -4
- unicex/gateio/exchange_info.py +12 -0
- unicex/hyperliquid/__init__.py +12 -0
- unicex/hyperliquid/adapter.py +151 -30
- unicex/hyperliquid/client.py +2208 -125
- unicex/hyperliquid/exchange_info.py +100 -0
- unicex/hyperliquid/uni_client.py +176 -22
- unicex/mapper.py +35 -33
- unicex/mexc/__init__.py +12 -0
- unicex/mexc/adapter.py +30 -13
- unicex/mexc/exchange_info.py +32 -0
- unicex/mexc/uni_client.py +6 -0
- unicex/okx/__init__.py +12 -0
- unicex/okx/adapter.py +25 -9
- unicex/okx/client.py +2599 -26
- unicex/okx/exchange_info.py +50 -0
- unicex/okx/uni_client.py +10 -10
- unicex/types.py +31 -0
- unicex-0.8.0.dist-info/METADATA +192 -0
- unicex-0.8.0.dist-info/RECORD +75 -0
- unicex/bitrue/__init__.py +0 -15
- unicex/bitrue/adapter.py +0 -8
- unicex/bitrue/client.py +0 -128
- unicex/bitrue/uni_client.py +0 -151
- unicex/bitrue/uni_websocket_manager.py +0 -269
- unicex/bitrue/user_websocket.py +0 -7
- unicex/bitrue/websocket_manager.py +0 -11
- unicex/bitunix/__init__.py +0 -15
- unicex/bitunix/adapter.py +0 -8
- unicex/bitunix/client.py +0 -8
- unicex/bitunix/uni_client.py +0 -151
- unicex/bitunix/uni_websocket_manager.py +0 -269
- unicex/bitunix/user_websocket.py +0 -7
- unicex/bitunix/websocket_manager.py +0 -11
- unicex/btse/__init__.py +0 -15
- unicex/btse/adapter.py +0 -8
- unicex/btse/client.py +0 -123
- unicex/btse/uni_client.py +0 -151
- unicex/btse/uni_websocket_manager.py +0 -269
- unicex/btse/user_websocket.py +0 -7
- unicex/btse/websocket_manager.py +0 -11
- unicex/kcex/__init__.py +0 -15
- unicex/kcex/adapter.py +0 -8
- unicex/kcex/client.py +0 -8
- unicex/kcex/uni_client.py +0 -151
- unicex/kcex/uni_websocket_manager.py +0 -269
- unicex/kcex/user_websocket.py +0 -7
- unicex/kcex/websocket_manager.py +0 -11
- unicex/kraken/__init__.py +0 -15
- unicex/kraken/adapter.py +0 -8
- unicex/kraken/client.py +0 -165
- unicex/kraken/uni_client.py +0 -151
- unicex/kraken/uni_websocket_manager.py +0 -269
- unicex/kraken/user_websocket.py +0 -7
- unicex/kraken/websocket_manager.py +0 -11
- unicex/kucoin/__init__.py +0 -15
- unicex/kucoin/adapter.py +0 -8
- unicex/kucoin/client.py +0 -120
- unicex/kucoin/uni_client.py +0 -151
- unicex/kucoin/uni_websocket_manager.py +0 -269
- unicex/kucoin/user_websocket.py +0 -7
- unicex/kucoin/websocket_manager.py +0 -11
- unicex/weex/__init__.py +0 -15
- unicex/weex/adapter.py +0 -8
- unicex/weex/client.py +0 -8
- unicex/weex/uni_client.py +0 -151
- unicex/weex/uni_websocket_manager.py +0 -269
- unicex/weex/user_websocket.py +0 -7
- unicex/weex/websocket_manager.py +0 -11
- unicex/xt/__init__.py +0 -15
- unicex/xt/adapter.py +0 -8
- unicex/xt/client.py +0 -8
- unicex/xt/uni_client.py +0 -151
- unicex/xt/uni_websocket_manager.py +0 -269
- unicex/xt/user_websocket.py +0 -7
- unicex/xt/websocket_manager.py +0 -11
- unicex-0.5.0.dist-info/METADATA +0 -170
- unicex-0.5.0.dist-info/RECORD +0 -123
- {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/WHEEL +0 -0
- {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
__all__ = ["ExchangeInfo"]
|
|
2
|
+
|
|
3
|
+
from unicex._abc import IExchangeInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExchangeInfo(IExchangeInfo):
|
|
7
|
+
"""Предзагружает информацию о тикерах для биржи Bybit."""
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
async def _load_exchange_info(cls) -> None:
|
|
11
|
+
"""Загружает информацию о бирже."""
|
|
12
|
+
...
|
unicex/bybit/uni_client.py
CHANGED
|
@@ -33,7 +33,7 @@ class UniClient(IUniClient[Client]):
|
|
|
33
33
|
list[str]: Список тикеров.
|
|
34
34
|
"""
|
|
35
35
|
raw_data = await self._client.tickers("spot")
|
|
36
|
-
return Adapter.tickers(raw_data)
|
|
36
|
+
return Adapter.tickers(raw_data, only_usdt)
|
|
37
37
|
|
|
38
38
|
async def futures_tickers(self, only_usdt: bool = True) -> list[str]:
|
|
39
39
|
"""Возвращает список тикеров.
|
|
@@ -45,7 +45,7 @@ class UniClient(IUniClient[Client]):
|
|
|
45
45
|
list[str]: Список тикеров.
|
|
46
46
|
"""
|
|
47
47
|
raw_data = await self._client.tickers("linear")
|
|
48
|
-
return Adapter.tickers(raw_data)
|
|
48
|
+
return Adapter.tickers(raw_data, only_usdt)
|
|
49
49
|
|
|
50
50
|
async def last_price(self) -> dict[str, float]:
|
|
51
51
|
"""Возвращает последнюю цену для каждого тикера.
|
unicex/enums.py
CHANGED
|
@@ -26,18 +26,13 @@ class Exchange(StrEnum):
|
|
|
26
26
|
|
|
27
27
|
BINANCE = "BINANCE"
|
|
28
28
|
BITGET = "BITGET"
|
|
29
|
-
BITRUE = "BITRUE"
|
|
30
29
|
BITUNIX = "BITUNIX"
|
|
31
|
-
BTSE = "BTSE"
|
|
32
30
|
BYBIT = "BYBIT"
|
|
33
31
|
GATEIO = "GATEIO"
|
|
34
32
|
HYPERLIQUID = "HYPERLIQUID"
|
|
35
33
|
KCEX = "KCEX"
|
|
36
|
-
KRAKEN = "KRAKEN"
|
|
37
|
-
KUCOIN = "KUCOIN"
|
|
38
34
|
MEXC = "MEXC"
|
|
39
35
|
OKX = "OKX"
|
|
40
|
-
WEEX = "WEEX"
|
|
41
36
|
XT = "XT"
|
|
42
37
|
|
|
43
38
|
def __add__(self, market_type: "MarketType") -> tuple["Exchange", "MarketType"]:
|
|
@@ -227,6 +222,22 @@ class Timeframe(StrEnum):
|
|
|
227
222
|
Timeframe.WEEK_1: "7d",
|
|
228
223
|
Timeframe.MONTH_1: "30d",
|
|
229
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
|
+
},
|
|
230
241
|
}
|
|
231
242
|
|
|
232
243
|
def to_exchange_format(self, exchange: Exchange, market_type: MarketType | None = None) -> str:
|
unicex/extra.py
CHANGED
|
@@ -8,6 +8,8 @@ __all__ = [
|
|
|
8
8
|
"generate_tv_link",
|
|
9
9
|
"generate_cg_link",
|
|
10
10
|
"make_humanreadable",
|
|
11
|
+
"normalize_ticker",
|
|
12
|
+
"normalize_symbol",
|
|
11
13
|
]
|
|
12
14
|
|
|
13
15
|
import time
|
|
@@ -32,7 +34,7 @@ def percent_greater(higher: float, lower: float) -> float:
|
|
|
32
34
|
`float`: На сколько процентов `higher` больше `lower`.
|
|
33
35
|
"""
|
|
34
36
|
if lower == 0:
|
|
35
|
-
return float(
|
|
37
|
+
return 0.0 # Не будем возвращать float('inf'), чтобы не ломать логику приложения
|
|
36
38
|
return (higher / lower - 1) * 100
|
|
37
39
|
|
|
38
40
|
|
|
@@ -51,7 +53,7 @@ def percent_less(higher: float, lower: float) -> float:
|
|
|
51
53
|
`float`: На сколько процентов `lower` меньше `higher`.
|
|
52
54
|
"""
|
|
53
55
|
if lower == 0:
|
|
54
|
-
return float(
|
|
56
|
+
return 0.0 # Не будем возвращать float('inf'), чтобы не ломать логику приложения
|
|
55
57
|
return (1 - lower / higher) * 100
|
|
56
58
|
|
|
57
59
|
|
|
@@ -91,6 +93,76 @@ class TimeoutTracker[T]:
|
|
|
91
93
|
self._blocked_items[item] = time.time() + duration
|
|
92
94
|
|
|
93
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
|
+
|
|
94
166
|
def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
|
|
95
167
|
"""Генерирует ссылку на биржу.
|
|
96
168
|
|
|
@@ -102,7 +174,8 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
|
|
|
102
174
|
Возвращает:
|
|
103
175
|
`str`: Ссылка на биржу.
|
|
104
176
|
"""
|
|
105
|
-
|
|
177
|
+
symbol = normalize_symbol(symbol)
|
|
178
|
+
ticker = normalize_ticker(symbol)
|
|
106
179
|
if exchange == Exchange.BINANCE:
|
|
107
180
|
if market_type == MarketType.FUTURES:
|
|
108
181
|
return f"https://www.binance.com/en/futures/{symbol}"
|
|
@@ -152,7 +225,7 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
|
|
|
152
225
|
if market_type == MarketType.FUTURES:
|
|
153
226
|
return f"https://app.hyperliquid.xyz/trade/{ticker}"
|
|
154
227
|
else:
|
|
155
|
-
return f"https://
|
|
228
|
+
return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
|
|
156
229
|
else:
|
|
157
230
|
raise NotSupported(f"Exchange {exchange} is not supported")
|
|
158
231
|
|
|
@@ -168,6 +241,7 @@ def generate_tv_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
168
241
|
Возвращает:
|
|
169
242
|
`str`: Ссылка для TradingView.
|
|
170
243
|
"""
|
|
244
|
+
symbol = normalize_symbol(symbol)
|
|
171
245
|
if market_type == MarketType.FUTURES:
|
|
172
246
|
return f"https://www.tradingview.com/chart/?symbol={exchange}:{symbol}.P"
|
|
173
247
|
else:
|
|
@@ -187,6 +261,8 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
187
261
|
"""
|
|
188
262
|
base_url = "https://www.coinglass.com/tv/ru"
|
|
189
263
|
|
|
264
|
+
symbol = normalize_symbol(symbol)
|
|
265
|
+
|
|
190
266
|
if market_type == MarketType.FUTURES:
|
|
191
267
|
match exchange:
|
|
192
268
|
case Exchange.OKX:
|
|
@@ -196,9 +272,11 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
196
272
|
case Exchange.BITGET:
|
|
197
273
|
return f"{base_url}/{exchange.capitalize()}_{symbol}_UMCBL"
|
|
198
274
|
case Exchange.GATEIO:
|
|
199
|
-
return f"{base_url}/{
|
|
275
|
+
return f"{base_url}/Gate_{symbol.replace('USDT', '_USDT')}"
|
|
200
276
|
case Exchange.BITUNIX:
|
|
201
277
|
return f"{base_url}/{exchange.capitalize()}_{symbol}"
|
|
278
|
+
case Exchange.HYPERLIQUID:
|
|
279
|
+
return f"{base_url}/{exchange.capitalize()}_{symbol.replace('USDT', '-USD')}"
|
|
202
280
|
case _:
|
|
203
281
|
return f"{base_url}/{exchange.capitalize()}_{symbol}"
|
|
204
282
|
else:
|
|
@@ -206,7 +284,7 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
206
284
|
if exchange == Exchange.OKX:
|
|
207
285
|
return f"{base_url}/SPOT_{exchange.upper()}_{symbol.replace('USDT', '-USDT')}"
|
|
208
286
|
# Для остальных бирж ссылки нет → возвращаем заглушку
|
|
209
|
-
return
|
|
287
|
+
return generate_cg_link(exchange, MarketType.FUTURES, symbol)
|
|
210
288
|
|
|
211
289
|
|
|
212
290
|
def make_humanreadable(value: float, locale: Literal["ru", "en"] = "ru") -> str:
|
unicex/gateio/__init__.py
CHANGED
|
@@ -6,10 +6,22 @@ __all__ = [
|
|
|
6
6
|
"UserWebsocket",
|
|
7
7
|
"WebsocketManager",
|
|
8
8
|
"UniWebsocketManager",
|
|
9
|
+
"ExchangeInfo",
|
|
9
10
|
]
|
|
10
11
|
|
|
11
12
|
from .client import Client
|
|
13
|
+
from .exchange_info import ExchangeInfo
|
|
12
14
|
from .uni_client import UniClient
|
|
13
15
|
from .uni_websocket_manager import UniWebsocketManager
|
|
14
16
|
from .user_websocket import UserWebsocket
|
|
15
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/gateio/adapter.py
CHANGED
|
@@ -19,7 +19,7 @@ class Adapter:
|
|
|
19
19
|
"""Адаптер для унификации данных с Gateio API."""
|
|
20
20
|
|
|
21
21
|
@staticmethod
|
|
22
|
-
def tickers(raw_data: list[dict], only_usdt: bool
|
|
22
|
+
def tickers(raw_data: list[dict], only_usdt: bool) -> list[str]:
|
|
23
23
|
"""Преобразует сырой ответ о тикерах в список символов.
|
|
24
24
|
|
|
25
25
|
Параметры:
|
|
@@ -32,11 +32,11 @@ class Adapter:
|
|
|
32
32
|
return [
|
|
33
33
|
item["currency_pair"]
|
|
34
34
|
for item in raw_data
|
|
35
|
-
if
|
|
35
|
+
if item["currency_pair"].endswith("USDT") or not only_usdt
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
@staticmethod
|
|
39
|
-
def futures_tickers(raw_data: list[dict], only_usdt: bool
|
|
39
|
+
def futures_tickers(raw_data: list[dict], only_usdt: bool) -> list[str]:
|
|
40
40
|
"""Преобразует сырой ответ о фьючерсных тикерах в список символов.
|
|
41
41
|
|
|
42
42
|
Параметры:
|
|
@@ -49,7 +49,7 @@ class Adapter:
|
|
|
49
49
|
return [
|
|
50
50
|
item["contract"]
|
|
51
51
|
for item in raw_data
|
|
52
|
-
if
|
|
52
|
+
if item["contract"].endswith("USDT") or not only_usdt
|
|
53
53
|
]
|
|
54
54
|
|
|
55
55
|
@staticmethod
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
__all__ = ["ExchangeInfo"]
|
|
2
|
+
|
|
3
|
+
from unicex._abc import IExchangeInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExchangeInfo(IExchangeInfo):
|
|
7
|
+
"""Предзагружает информацию о тикерах для биржи Gateio."""
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
async def _load_exchange_info(cls) -> None:
|
|
11
|
+
"""Загружает информацию о бирже."""
|
|
12
|
+
...
|
unicex/hyperliquid/__init__.py
CHANGED
|
@@ -6,10 +6,22 @@ __all__ = [
|
|
|
6
6
|
"UserWebsocket",
|
|
7
7
|
"WebsocketManager",
|
|
8
8
|
"UniWebsocketManager",
|
|
9
|
+
"ExchangeInfo",
|
|
9
10
|
]
|
|
10
11
|
|
|
11
12
|
from .client import Client
|
|
13
|
+
from .exchange_info import ExchangeInfo
|
|
12
14
|
from .uni_client import UniClient
|
|
13
15
|
from .uni_websocket_manager import UniWebsocketManager
|
|
14
16
|
from .user_websocket import UserWebsocket
|
|
15
17
|
from .websocket_manager import WebsocketManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def load_exchange_info() -> None:
|
|
21
|
+
"""Загружает информацию о бирже Hyperliquid."""
|
|
22
|
+
await ExchangeInfo.load_exchange_info()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
|
|
26
|
+
"""Запускает процесс обновления информации о бирже Hyperliquid."""
|
|
27
|
+
await ExchangeInfo.start(parse_interval_seconds)
|
unicex/hyperliquid/adapter.py
CHANGED
|
@@ -3,80 +3,130 @@ __all__ = ["Adapter"]
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
5
|
from unicex.types import (
|
|
6
|
+
KlineDict,
|
|
6
7
|
OpenInterestDict,
|
|
7
8
|
OpenInterestItem,
|
|
8
9
|
TickerDailyDict,
|
|
9
10
|
TickerDailyItem,
|
|
10
11
|
)
|
|
11
|
-
from unicex.utils import catch_adapter_errors, decorate_all_methods
|
|
12
12
|
|
|
13
|
+
# from unicex.utils import catch_adapter_errors, decorate_all_methods
|
|
14
|
+
from .exchange_info import ExchangeInfo
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
# @decorate_all_methods(catch_adapter_errors)
|
|
15
18
|
class Adapter:
|
|
16
19
|
"""Адаптер для унификации данных с Hyperliquid API."""
|
|
17
20
|
|
|
18
21
|
@staticmethod
|
|
19
|
-
def tickers(raw_data:
|
|
22
|
+
def tickers(raw_data: dict, resolve_symbols: bool) -> list[str]:
|
|
20
23
|
"""Преобразует данные Hyperliquid в список спотовых тикеров.
|
|
21
24
|
|
|
22
25
|
Параметры:
|
|
23
|
-
raw_data (
|
|
26
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
27
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
24
28
|
|
|
25
29
|
Возвращает:
|
|
26
|
-
list[str]: Список тикеров (например, "
|
|
30
|
+
list[str]: Список тикеров (например, "@123").
|
|
27
31
|
"""
|
|
28
|
-
|
|
32
|
+
if resolve_symbols:
|
|
33
|
+
return [ExchangeInfo.resolve_spot_symbol(item["name"]) for item in raw_data["universe"]]
|
|
34
|
+
else:
|
|
35
|
+
return [item["name"] for item in raw_data["universe"]]
|
|
29
36
|
|
|
30
37
|
@staticmethod
|
|
31
|
-
def futures_tickers(raw_data:
|
|
38
|
+
def futures_tickers(raw_data: dict) -> list[str]:
|
|
32
39
|
"""Преобразует данные Hyperliquid в список фьючерсных тикеров.
|
|
33
40
|
|
|
34
41
|
Параметры:
|
|
35
|
-
raw_data (
|
|
42
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
36
43
|
|
|
37
44
|
Возвращает:
|
|
38
|
-
list[str]: Список
|
|
45
|
+
list[str]: Список тикеров (например, "@123").
|
|
39
46
|
"""
|
|
40
|
-
|
|
41
|
-
return [item["name"] for item in universe]
|
|
47
|
+
return [item["name"] for item in raw_data["universe"]]
|
|
42
48
|
|
|
43
49
|
@staticmethod
|
|
44
|
-
def last_price(raw_data:
|
|
50
|
+
def last_price(raw_data: dict, resolve_symbols: bool) -> dict[str, float]:
|
|
45
51
|
"""Преобразует данные о последних ценах (spot) в унифицированный формат.
|
|
46
52
|
|
|
47
53
|
Параметры:
|
|
48
|
-
raw_data (
|
|
54
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
55
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
49
56
|
|
|
50
57
|
Возвращает:
|
|
51
58
|
dict[str, float]: Словарь тикеров и последних цен.
|
|
52
59
|
"""
|
|
53
|
-
|
|
60
|
+
if resolve_symbols:
|
|
61
|
+
return {
|
|
62
|
+
ExchangeInfo.resolve_spot_symbol(token): float(price)
|
|
63
|
+
for token, price in raw_data.items()
|
|
64
|
+
if token.startswith("@")
|
|
65
|
+
}
|
|
66
|
+
else:
|
|
67
|
+
return {
|
|
68
|
+
token: float(price) for token, price in raw_data.items() if token.startswith("@")
|
|
69
|
+
}
|
|
54
70
|
|
|
55
71
|
@staticmethod
|
|
56
|
-
def futures_last_price(raw_data:
|
|
72
|
+
def futures_last_price(raw_data: dict) -> dict[str, float]:
|
|
57
73
|
"""Преобразует данные о последних ценах (futures) в унифицированный формат.
|
|
58
74
|
|
|
59
75
|
Параметры:
|
|
60
|
-
raw_data (
|
|
76
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
61
77
|
|
|
62
78
|
Возвращает:
|
|
63
79
|
dict[str, float]: Словарь тикеров и последних цен.
|
|
64
80
|
"""
|
|
65
|
-
|
|
66
|
-
metrics = raw_data[1]
|
|
67
|
-
return {universe[i]["name"]: float(item["markPx"]) for i, item in enumerate(metrics)}
|
|
81
|
+
return {k: v for k, v in raw_data.items() if not k.startswith("@")}
|
|
68
82
|
|
|
69
83
|
@staticmethod
|
|
70
|
-
def ticker_24hr(raw_data: list) -> TickerDailyDict:
|
|
84
|
+
def ticker_24hr(raw_data: list, resolve_symbols: bool) -> TickerDailyDict:
|
|
71
85
|
"""Преобразует 24-часовую статистику (spot) в унифицированный формат.
|
|
72
86
|
|
|
73
87
|
Параметры:
|
|
74
88
|
raw_data (list): Сырой ответ с биржи.
|
|
89
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
75
90
|
|
|
76
91
|
Возвращает:
|
|
77
92
|
TickerDailyDict: Словарь тикеров и их статистики.
|
|
78
93
|
"""
|
|
79
|
-
|
|
94
|
+
metrics = raw_data[1]
|
|
95
|
+
result: TickerDailyDict = {}
|
|
96
|
+
|
|
97
|
+
for item in metrics:
|
|
98
|
+
try:
|
|
99
|
+
coin = item["coin"]
|
|
100
|
+
|
|
101
|
+
if resolve_symbols:
|
|
102
|
+
coin = ExchangeInfo.resolve_spot_symbol(coin) or coin
|
|
103
|
+
|
|
104
|
+
prev_day_px = float(item.get("prevDayPx") or "0")
|
|
105
|
+
mid_px = float(item.get("midPx") or "0")
|
|
106
|
+
mark_px = float(item.get("markPx") or "0")
|
|
107
|
+
day_ntl_vlm = float(item.get("dayNtlVlm") or "0")
|
|
108
|
+
|
|
109
|
+
p = ((mark_px - prev_day_px) / prev_day_px * 100) if prev_day_px else 0.0
|
|
110
|
+
v = (day_ntl_vlm / mid_px) if mid_px else 0.0
|
|
111
|
+
q = day_ntl_vlm
|
|
112
|
+
|
|
113
|
+
if coin in result:
|
|
114
|
+
# В случае с конфликтом оставляем ту монету, в которой больше дневного объема, т.к
|
|
115
|
+
# для 6 монет (на 07.10.2025) встречаются пары к USDH, которые повторяют идентификатор.
|
|
116
|
+
# Проблема не критичная - т.к. флаг resolve_symbols по идее использовать должен редко.
|
|
117
|
+
prev_ticker_daily = result[coin]
|
|
118
|
+
curr_ticker_daily = TickerDailyItem(p=p, v=v, q=q)
|
|
119
|
+
if prev_ticker_daily["q"] > curr_ticker_daily["q"]:
|
|
120
|
+
result[coin] = prev_ticker_daily
|
|
121
|
+
else:
|
|
122
|
+
result[coin] = curr_ticker_daily
|
|
123
|
+
else:
|
|
124
|
+
result[coin] = TickerDailyItem(p=p, v=v, q=q)
|
|
125
|
+
|
|
126
|
+
except (KeyError, TypeError, ValueError):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
return result
|
|
80
130
|
|
|
81
131
|
@staticmethod
|
|
82
132
|
def futures_ticker_24hr(raw_data: list) -> TickerDailyDict:
|
|
@@ -90,16 +140,87 @@ class Adapter:
|
|
|
90
140
|
"""
|
|
91
141
|
universe = raw_data[0]["universe"]
|
|
92
142
|
metrics = raw_data[1]
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
143
|
+
|
|
144
|
+
result: TickerDailyDict = {}
|
|
145
|
+
|
|
146
|
+
for i, item in enumerate(metrics):
|
|
147
|
+
try:
|
|
148
|
+
prev_day_px = float(item.get("prevDayPx", 0) or "0")
|
|
149
|
+
oracle_px = float(item.get("oraclePx", 0) or "0")
|
|
150
|
+
mark_px = float(item.get("markPx", 0) or "0")
|
|
151
|
+
day_ntl_vlm = float(item.get("dayNtlVlm", 0) or "0")
|
|
152
|
+
|
|
153
|
+
p = ((mark_px - prev_day_px) / prev_day_px * 100) if prev_day_px else 0.0
|
|
154
|
+
v = (day_ntl_vlm / oracle_px) if oracle_px else 0.0
|
|
155
|
+
q = day_ntl_vlm
|
|
156
|
+
|
|
157
|
+
result[universe[i]["name"]] = TickerDailyItem(p=p, v=v, q=q)
|
|
158
|
+
except (KeyError, TypeError, ValueError):
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def klines(raw_data: list[dict], resolve_symbols: bool) -> list[KlineDict]:
|
|
165
|
+
"""Преобразует сырой ответ, в котором содержатся данные о свечах, в унифицированный формат.
|
|
166
|
+
|
|
167
|
+
Параметры:
|
|
168
|
+
raw_data (list[dict]): Сырой ответ с биржи.
|
|
169
|
+
resolve_symbols (bool): Если True, тикер маппится из вида "@123" в "BTC".
|
|
170
|
+
|
|
171
|
+
Возвращает:
|
|
172
|
+
list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
|
|
173
|
+
"""
|
|
174
|
+
return [
|
|
175
|
+
KlineDict(
|
|
176
|
+
s=kline["s"]
|
|
177
|
+
if not resolve_symbols
|
|
178
|
+
else ExchangeInfo.resolve_spot_symbol(kline["s"]),
|
|
179
|
+
t=kline["t"],
|
|
180
|
+
o=float(kline["o"]),
|
|
181
|
+
h=float(kline["h"]),
|
|
182
|
+
l=float(kline["l"]),
|
|
183
|
+
c=float(kline["c"]),
|
|
184
|
+
v=float(kline["v"]),
|
|
185
|
+
q=float(kline["v"]) * float(kline["c"]),
|
|
186
|
+
T=kline["T"],
|
|
187
|
+
x=None,
|
|
100
188
|
)
|
|
101
|
-
for
|
|
102
|
-
|
|
189
|
+
for kline in sorted(
|
|
190
|
+
raw_data,
|
|
191
|
+
key=lambda x: int(x["t"]),
|
|
192
|
+
)
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def futures_klines(raw_data: list[dict]) -> list[KlineDict]:
|
|
197
|
+
"""Преобразует сырой ответ, в котором содержатся данные о свечах, в унифицированный формат.
|
|
198
|
+
|
|
199
|
+
Параметры:
|
|
200
|
+
raw_data (list[dict]): Сырой ответ с биржи.
|
|
201
|
+
symbol (str): Символ тикера.
|
|
202
|
+
|
|
203
|
+
Возвращает:
|
|
204
|
+
list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
|
|
205
|
+
"""
|
|
206
|
+
return [
|
|
207
|
+
KlineDict(
|
|
208
|
+
s=kline["s"],
|
|
209
|
+
t=kline["t"],
|
|
210
|
+
o=float(kline["o"]),
|
|
211
|
+
h=float(kline["h"]),
|
|
212
|
+
l=float(kline["l"]),
|
|
213
|
+
c=float(kline["c"]),
|
|
214
|
+
v=float(kline["v"]),
|
|
215
|
+
q=float(kline["v"]) * float(kline["c"]),
|
|
216
|
+
T=kline["T"],
|
|
217
|
+
x=None,
|
|
218
|
+
)
|
|
219
|
+
for kline in sorted(
|
|
220
|
+
raw_data,
|
|
221
|
+
key=lambda x: int(x["t"]),
|
|
222
|
+
)
|
|
223
|
+
]
|
|
103
224
|
|
|
104
225
|
@staticmethod
|
|
105
226
|
def funding_rate(raw_data: list) -> dict[str, float]:
|