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,100 @@
|
|
|
1
|
+
__all__ = ["ExchangeInfo"]
|
|
2
|
+
|
|
3
|
+
from unicex._abc import IExchangeInfo
|
|
4
|
+
|
|
5
|
+
from .client import Client
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ExchangeInfo(IExchangeInfo):
|
|
9
|
+
"""Предзагружает информацию о тикерах для биржи Hyperliquid."""
|
|
10
|
+
|
|
11
|
+
_spot_meta: dict = {}
|
|
12
|
+
"""Словарь с метаинформацией о спотовом рынке."""
|
|
13
|
+
|
|
14
|
+
_spot_ident_to_idx: dict = {}
|
|
15
|
+
"""Словарь, в котором ключ - индетефикатор тикера на бирже, например '@123', а значение - его индекс в _spot_meta."""
|
|
16
|
+
|
|
17
|
+
_spot_idx_to_name: dict = {}
|
|
18
|
+
"""Словарь, в котором ключ - индекс в _spot_meta, например "123", а значение - название тикера, например 'BTC'."""
|
|
19
|
+
|
|
20
|
+
_futures_meta: dict = {}
|
|
21
|
+
"""Словарь с метаинформацией о фьючерсном рынке."""
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
async def _load_exchange_info(cls) -> None:
|
|
25
|
+
"""Загружает информацию о бирже."""
|
|
26
|
+
client = await Client.create()
|
|
27
|
+
async with client as conn:
|
|
28
|
+
cls._spot_meta = await conn.spot_metadata()
|
|
29
|
+
cls._build_spot_mappings(cls._spot_meta)
|
|
30
|
+
cls._logger.debug("Hyperliquid spot exchange info loaded")
|
|
31
|
+
|
|
32
|
+
cls._futures_meta = await conn.perp_metadata()
|
|
33
|
+
cls._logger.debug("Hyperliquid futures exchange info loaded")
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _build_spot_mappings(cls, spot_meta: dict) -> None:
|
|
37
|
+
"""Строит словари соответствия '@индекс' ↔ индекс ↔ 'BTC'."""
|
|
38
|
+
universe = spot_meta["universe"]
|
|
39
|
+
tokens = spot_meta["tokens"]
|
|
40
|
+
|
|
41
|
+
number_to_idx = {}
|
|
42
|
+
for u in universe:
|
|
43
|
+
number_to_idx[u["name"]] = u["tokens"][0]
|
|
44
|
+
cls._spot_ident_to_idx = number_to_idx
|
|
45
|
+
|
|
46
|
+
idx_to_name = {}
|
|
47
|
+
for t in tokens:
|
|
48
|
+
idx_to_name[t["index"]] = t["name"]
|
|
49
|
+
cls._spot_idx_to_name = idx_to_name
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def get_spot_meta(cls) -> dict:
|
|
53
|
+
"""Возвращает метаинформацию о спотовом рынке."""
|
|
54
|
+
cls._check_loaded()
|
|
55
|
+
return cls._spot_meta
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def get_futures_meta(cls) -> dict:
|
|
59
|
+
"""Возвращает метаинформацию о фьючерсном рынке."""
|
|
60
|
+
cls._check_loaded()
|
|
61
|
+
return cls._futures_meta
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def resolve_spot_symbol(cls, ident: str) -> str:
|
|
65
|
+
"""Преобразует внутренний идентификатор вида '@142' в тикер, например 'BTC'.
|
|
66
|
+
Не рейзит KeyError, если тикер не найден.
|
|
67
|
+
|
|
68
|
+
Параметры:
|
|
69
|
+
token_name (str): Имя токена на бирже, например '@142' или 'BTC'.
|
|
70
|
+
|
|
71
|
+
Возвращает:
|
|
72
|
+
str | None: Название тикера (например 'BTC'), либо None, если не найден.
|
|
73
|
+
"""
|
|
74
|
+
cls._check_loaded()
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
return cls._spot_idx_to_name[cls._spot_ident_to_idx[ident]]
|
|
78
|
+
except KeyError:
|
|
79
|
+
return ident
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def resolve_spot_ident(cls, symbol: str) -> str:
|
|
83
|
+
"""Преобразует тикер (например, 'BTC') в внутренний идентификатор (например, '@142').
|
|
84
|
+
|
|
85
|
+
Параметры:
|
|
86
|
+
symbol (str): Название тикера, например 'BTC'.
|
|
87
|
+
|
|
88
|
+
Возвращает:
|
|
89
|
+
str: Внутренний идентификатор (например '@142').
|
|
90
|
+
|
|
91
|
+
Исключения:
|
|
92
|
+
KeyError: Если тикер не найден в локальном кэше биржи.
|
|
93
|
+
"""
|
|
94
|
+
cls._check_loaded()
|
|
95
|
+
|
|
96
|
+
# Находим индекс по тикеру
|
|
97
|
+
idx = next(k for k, v in cls._spot_idx_to_name.items() if v == symbol)
|
|
98
|
+
|
|
99
|
+
# Возвращаем внутренний идентификатор вида "@142"
|
|
100
|
+
return next(k for k, v in cls._spot_ident_to_idx.items() if v == idx)
|
unicex/hyperliquid/uni_client.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
__all__ = ["UniClient"]
|
|
2
2
|
|
|
3
|
+
import time
|
|
4
|
+
from typing import Self, overload
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
import aiohttp
|
|
5
7
|
|
|
6
8
|
from unicex._abc import IUniClient
|
|
7
|
-
from unicex.enums import Timeframe
|
|
8
|
-
from unicex.types import KlineDict, OpenInterestDict, OpenInterestItem, TickerDailyDict
|
|
9
|
+
from unicex.enums import Exchange, MarketType, Timeframe
|
|
10
|
+
from unicex.types import KlineDict, LoggerLike, OpenInterestDict, OpenInterestItem, TickerDailyDict
|
|
11
|
+
from unicex.utils import batched_list
|
|
9
12
|
|
|
10
13
|
from .adapter import Adapter
|
|
11
14
|
from .client import Client
|
|
@@ -14,6 +17,84 @@ from .client import Client
|
|
|
14
17
|
class UniClient(IUniClient[Client]):
|
|
15
18
|
"""Унифицированный клиент для работы с Hyperliquid API."""
|
|
16
19
|
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
session: aiohttp.ClientSession,
|
|
23
|
+
private_key: str | bytes | None = None,
|
|
24
|
+
wallet_address: str | None = None,
|
|
25
|
+
vault_address: str | None = None,
|
|
26
|
+
logger: LoggerLike | None = None,
|
|
27
|
+
max_retries: int = 3,
|
|
28
|
+
retry_delay: int | float = 0.1,
|
|
29
|
+
proxies: list[str] | None = None,
|
|
30
|
+
timeout: int = 10,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Инициализация клиента.
|
|
33
|
+
|
|
34
|
+
Параметры:
|
|
35
|
+
session (`aiohttp.ClientSession`): Сессия для выполнения HTTP‑запросов.
|
|
36
|
+
private_key (`str | bytes | None`): Приватный ключ API для аутентификации (Hyperliquid).
|
|
37
|
+
wallet_address (`str | None`): Адрес кошелька для аутентификации (Hyperliquid).
|
|
38
|
+
vault_address (`str | None`): Адрес хранилища для аутентификации (Hyperliquid).
|
|
39
|
+
logger (`LoggerLike | None`): Логгер для вывода информации.
|
|
40
|
+
max_retries (`int`): Максимальное количество повторных попыток запроса.
|
|
41
|
+
retry_delay (`int | float`): Задержка между повторными попытками, сек.
|
|
42
|
+
proxies (`list[str] | None`): Список HTTP(S)‑прокси для циклического использования.
|
|
43
|
+
timeout (`int`): Максимальное время ожидания ответа от сервера, сек.
|
|
44
|
+
"""
|
|
45
|
+
self._client: Client = self._client_cls(
|
|
46
|
+
private_key=private_key,
|
|
47
|
+
wallet_address=wallet_address,
|
|
48
|
+
vault_address=vault_address,
|
|
49
|
+
session=session,
|
|
50
|
+
logger=logger,
|
|
51
|
+
max_retries=max_retries,
|
|
52
|
+
retry_delay=retry_delay,
|
|
53
|
+
proxies=proxies,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
async def create(
|
|
59
|
+
cls,
|
|
60
|
+
private_key: str | bytes | None = None,
|
|
61
|
+
wallet_address: str | None = None,
|
|
62
|
+
vault_address: str | None = None,
|
|
63
|
+
logger: LoggerLike | None = None,
|
|
64
|
+
max_retries: int = 3,
|
|
65
|
+
retry_delay: int | float = 0.1,
|
|
66
|
+
proxies: list[str] | None = None,
|
|
67
|
+
timeout: int = 10,
|
|
68
|
+
) -> Self:
|
|
69
|
+
"""Создает инстанцию клиента.
|
|
70
|
+
Создать клиент можно и через __init__, но в таком случае session: `aiohttp.ClientSession` - обязательный параметр.
|
|
71
|
+
|
|
72
|
+
Параметры:
|
|
73
|
+
session (`aiohttp.ClientSession`): Сессия для выполнения HTTP‑запросов.
|
|
74
|
+
private_key (`str | bytes | None`): Приватный ключ API для аутентификации (Hyperliquid).
|
|
75
|
+
wallet_address (`str | None`): Адрес кошелька для аутентификации (Hyperliquid).
|
|
76
|
+
vault_address (`str | None`): Адрес хранилища для аутентификации (Hyperliquid).
|
|
77
|
+
logger (`LoggerLike | None`): Логгер для вывода информации.
|
|
78
|
+
max_retries (`int`): Максимальное количество повторных попыток запроса.
|
|
79
|
+
retry_delay (`int | float`): Задержка между повторными попытками, сек.
|
|
80
|
+
proxies (`list[str] | None`): Список HTTP(S)‑прокси для циклического использования.
|
|
81
|
+
timeout (`int`): Максимальное время ожидания ответа от сервера, сек.
|
|
82
|
+
|
|
83
|
+
Возвращает:
|
|
84
|
+
`IUniClient`: Созданный экземпляр клиента.
|
|
85
|
+
"""
|
|
86
|
+
return cls(
|
|
87
|
+
session=aiohttp.ClientSession(),
|
|
88
|
+
private_key=private_key,
|
|
89
|
+
wallet_address=wallet_address,
|
|
90
|
+
vault_address=vault_address,
|
|
91
|
+
logger=logger,
|
|
92
|
+
max_retries=max_retries,
|
|
93
|
+
retry_delay=retry_delay,
|
|
94
|
+
proxies=proxies,
|
|
95
|
+
timeout=timeout,
|
|
96
|
+
)
|
|
97
|
+
|
|
17
98
|
@property
|
|
18
99
|
def _client_cls(self) -> type[Client]:
|
|
19
100
|
"""Возвращает класс клиента для Hyperliquid.
|
|
@@ -23,36 +104,65 @@ class UniClient(IUniClient[Client]):
|
|
|
23
104
|
"""
|
|
24
105
|
return Client
|
|
25
106
|
|
|
26
|
-
async def tickers(self,
|
|
107
|
+
async def tickers(self, resolve_symbols: bool = False) -> list[str]:
|
|
27
108
|
"""Возвращает список тикеров.
|
|
28
109
|
|
|
29
110
|
Параметры:
|
|
30
|
-
|
|
111
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
31
112
|
|
|
32
113
|
Возвращает:
|
|
33
114
|
list[str]: Список тикеров.
|
|
34
115
|
"""
|
|
35
|
-
|
|
116
|
+
raw_data = await self._client.spot_metadata()
|
|
117
|
+
return Adapter.tickers(raw_data, resolve_symbols)
|
|
36
118
|
|
|
37
|
-
async def
|
|
38
|
-
|
|
119
|
+
async def tickers_batched(
|
|
120
|
+
self, resolve_symbols: bool = False, batch_size: int = 20
|
|
121
|
+
) -> list[list[str]]:
|
|
122
|
+
"""Возвращает список тикеров в чанках.
|
|
39
123
|
|
|
40
124
|
Параметры:
|
|
41
|
-
|
|
125
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
126
|
+
batch_size (`int`): Размер чанка.
|
|
127
|
+
|
|
128
|
+
Возвращает:
|
|
129
|
+
`list[list[str]]`: Список тикеров в чанках.
|
|
130
|
+
"""
|
|
131
|
+
tickers = await self.tickers(resolve_symbols)
|
|
132
|
+
return batched_list(tickers, batch_size)
|
|
133
|
+
|
|
134
|
+
async def futures_tickers(self) -> list[str]:
|
|
135
|
+
"""Возвращает список тикеров.
|
|
42
136
|
|
|
43
137
|
Возвращает:
|
|
44
138
|
list[str]: Список тикеров.
|
|
45
139
|
"""
|
|
46
|
-
raw_data = await self._client.
|
|
140
|
+
raw_data = await self._client.perp_metadata()
|
|
47
141
|
return Adapter.futures_tickers(raw_data)
|
|
48
142
|
|
|
49
|
-
async def
|
|
143
|
+
async def futures_tickers_batched(self, batch_size: int = 20) -> list[list[str]]:
|
|
144
|
+
"""Возвращает список тикеров в чанках.
|
|
145
|
+
|
|
146
|
+
Параметры:
|
|
147
|
+
batch_size (`int`): Размер чанка.
|
|
148
|
+
|
|
149
|
+
Возвращает:
|
|
150
|
+
`list[list[str]]`: Список тикеров в чанках.
|
|
151
|
+
"""
|
|
152
|
+
tickers = await self.futures_tickers()
|
|
153
|
+
return batched_list(tickers, batch_size)
|
|
154
|
+
|
|
155
|
+
async def last_price(self, resolve_symbols: bool = False) -> dict[str, float]:
|
|
50
156
|
"""Возвращает последнюю цену для каждого тикера.
|
|
51
157
|
|
|
158
|
+
Параметры:
|
|
159
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
160
|
+
|
|
52
161
|
Возвращает:
|
|
53
162
|
dict[str, float]: Словарь с последними ценами для каждого тикера.
|
|
54
163
|
"""
|
|
55
|
-
|
|
164
|
+
raw_data = await self._client.all_mids()
|
|
165
|
+
return Adapter.last_price(raw_data, resolve_symbols)
|
|
56
166
|
|
|
57
167
|
async def futures_last_price(self) -> dict[str, float]:
|
|
58
168
|
"""Возвращает последнюю цену для каждого тикера.
|
|
@@ -60,16 +170,20 @@ class UniClient(IUniClient[Client]):
|
|
|
60
170
|
Возвращает:
|
|
61
171
|
dict[str, float]: Словарь с последними ценами для каждого тикера.
|
|
62
172
|
"""
|
|
63
|
-
raw_data = await self._client.
|
|
173
|
+
raw_data = await self._client.all_mids()
|
|
64
174
|
return Adapter.futures_last_price(raw_data)
|
|
65
175
|
|
|
66
|
-
async def ticker_24hr(self) -> TickerDailyDict:
|
|
176
|
+
async def ticker_24hr(self, resolve_symbols: bool = False) -> TickerDailyDict:
|
|
67
177
|
"""Возвращает статистику за последние 24 часа для каждого тикера.
|
|
68
178
|
|
|
179
|
+
Параметры:
|
|
180
|
+
resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
|
|
181
|
+
|
|
69
182
|
Возвращает:
|
|
70
183
|
TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
|
|
71
184
|
"""
|
|
72
|
-
|
|
185
|
+
raw_data = await self._client.spot_meta_and_asset_contexts()
|
|
186
|
+
return Adapter.ticker_24hr(raw_data, resolve_symbols)
|
|
73
187
|
|
|
74
188
|
async def futures_ticker_24hr(self) -> TickerDailyDict:
|
|
75
189
|
"""Возвращает статистику за последние 24 часа для каждого тикера.
|
|
@@ -77,7 +191,7 @@ class UniClient(IUniClient[Client]):
|
|
|
77
191
|
Возвращает:
|
|
78
192
|
TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
|
|
79
193
|
"""
|
|
80
|
-
raw_data = await self._client.
|
|
194
|
+
raw_data = await self._client.perp_meta_and_asset_contexts()
|
|
81
195
|
return Adapter.futures_ticker_24hr(raw_data)
|
|
82
196
|
|
|
83
197
|
async def klines(
|
|
@@ -87,20 +201,41 @@ class UniClient(IUniClient[Client]):
|
|
|
87
201
|
limit: int | None = None,
|
|
88
202
|
start_time: int | None = None,
|
|
89
203
|
end_time: int | None = None,
|
|
204
|
+
resolve_symbols: bool = False,
|
|
90
205
|
) -> list[KlineDict]:
|
|
91
206
|
"""Возвращает список свечей для тикера.
|
|
92
207
|
|
|
93
208
|
Параметры:
|
|
94
|
-
symbol (str): Название тикера.
|
|
209
|
+
symbol (str): Название тикера. Например "@1".
|
|
95
210
|
limit (int | None): Количество свечей.
|
|
96
211
|
interval (Timeframe | str): Таймфрейм свечей.
|
|
97
212
|
start_time (int | None): Время начала периода в миллисекундах.
|
|
98
213
|
end_time (int | None): Время окончания периода в миллисекундах.
|
|
214
|
+
resolve_symbols (bool): Если True, тикер маппится из вида "@123" в "BTC".
|
|
99
215
|
|
|
100
216
|
Возвращает:
|
|
101
217
|
list[KlineDict]: Список свечей для тикера.
|
|
102
218
|
"""
|
|
103
|
-
|
|
219
|
+
if not limit and not all([start_time, end_time]):
|
|
220
|
+
raise ValueError("limit and (start_time and end_time) must be provided")
|
|
221
|
+
|
|
222
|
+
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
223
|
+
if not isinstance(interval, Timeframe):
|
|
224
|
+
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
225
|
+
end_time = int(time.time() * 1000)
|
|
226
|
+
start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
|
|
227
|
+
interval = (
|
|
228
|
+
interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.SPOT)
|
|
229
|
+
if isinstance(interval, Timeframe)
|
|
230
|
+
else interval
|
|
231
|
+
)
|
|
232
|
+
raw_data = await self._client.candle_snapshot(
|
|
233
|
+
coin=symbol,
|
|
234
|
+
interval=interval,
|
|
235
|
+
start_time=start_time, # type: ignore[reportArgumentType]
|
|
236
|
+
end_time=end_time, # type: ignore[reportArgumentType]
|
|
237
|
+
)
|
|
238
|
+
return Adapter.klines(raw_data=raw_data, resolve_symbols=resolve_symbols)
|
|
104
239
|
|
|
105
240
|
async def futures_klines(
|
|
106
241
|
self,
|
|
@@ -113,7 +248,7 @@ class UniClient(IUniClient[Client]):
|
|
|
113
248
|
"""Возвращает список свечей для тикера.
|
|
114
249
|
|
|
115
250
|
Параметры:
|
|
116
|
-
symbol (str): Название тикера.
|
|
251
|
+
symbol (str): Название тикера. Например "BTC".
|
|
117
252
|
limit (int | None): Количество свечей.
|
|
118
253
|
interval (Timeframe | str): Таймфрейм свечей.
|
|
119
254
|
start_time (int | None): Время начала периода в миллисекундах.
|
|
@@ -122,7 +257,26 @@ class UniClient(IUniClient[Client]):
|
|
|
122
257
|
Возвращает:
|
|
123
258
|
list[KlineDict]: Список свечей для тикера.
|
|
124
259
|
"""
|
|
125
|
-
|
|
260
|
+
if not limit and not all([start_time, end_time]):
|
|
261
|
+
raise ValueError("limit and (start_time and end_time) must be provided")
|
|
262
|
+
|
|
263
|
+
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
264
|
+
if not isinstance(interval, Timeframe):
|
|
265
|
+
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
266
|
+
end_time = int(time.time() * 1000)
|
|
267
|
+
start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
|
|
268
|
+
interval = (
|
|
269
|
+
interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.FUTURES)
|
|
270
|
+
if isinstance(interval, Timeframe)
|
|
271
|
+
else interval
|
|
272
|
+
)
|
|
273
|
+
raw_data = await self._client.candle_snapshot(
|
|
274
|
+
coin=symbol,
|
|
275
|
+
interval=interval,
|
|
276
|
+
start_time=start_time, # type: ignore[reportArgumentType]
|
|
277
|
+
end_time=end_time, # type: ignore[reportArgumentType]
|
|
278
|
+
)
|
|
279
|
+
return Adapter.futures_klines(raw_data)
|
|
126
280
|
|
|
127
281
|
@overload
|
|
128
282
|
async def funding_rate(self, symbol: str) -> float: ...
|
|
@@ -142,7 +296,7 @@ class UniClient(IUniClient[Client]):
|
|
|
142
296
|
Возвращает:
|
|
143
297
|
`dict[str, float] | float`: Ставка финансирования для тикера или словарь со ставками для всех тикеров.
|
|
144
298
|
"""
|
|
145
|
-
raw_data = await self._client.
|
|
299
|
+
raw_data = await self._client.perp_meta_and_asset_contexts()
|
|
146
300
|
adapted_data = Adapter.funding_rate(raw_data)
|
|
147
301
|
return adapted_data[symbol] if symbol else adapted_data
|
|
148
302
|
|
|
@@ -166,6 +320,6 @@ class UniClient(IUniClient[Client]):
|
|
|
166
320
|
открытого интереса в монетах. Если нет передан - то словарь, в котором ключ - тикер,
|
|
167
321
|
а значение - словарь с временем и объемом открытого интереса в монетах.
|
|
168
322
|
"""
|
|
169
|
-
raw_data = await self._client.
|
|
323
|
+
raw_data = await self._client.perp_meta_and_asset_contexts()
|
|
170
324
|
adapted_data = Adapter.open_interest(raw_data)
|
|
171
325
|
return adapted_data[symbol] if symbol else adapted_data
|
unicex/mapper.py
CHANGED
|
@@ -3,81 +3,68 @@
|
|
|
3
3
|
__all__ = [
|
|
4
4
|
"get_uni_client",
|
|
5
5
|
"get_uni_websocket_manager",
|
|
6
|
+
"get_exchange_info",
|
|
6
7
|
]
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
from ._abc import IUniClient, IUniWebsocketManager
|
|
10
|
+
from ._abc import IExchangeInfo, IUniClient, IUniWebsocketManager
|
|
11
|
+
from .binance import ExchangeInfo as BinanceExchangeInfo
|
|
10
12
|
from .binance import UniClient as BinanceUniClient
|
|
11
13
|
from .binance import UniWebsocketManager as BinanceUniWebsocketManager
|
|
14
|
+
from .bitget import ExchangeInfo as BitgetExchangeInfo
|
|
12
15
|
from .bitget import UniClient as BitgetUniClient
|
|
13
16
|
from .bitget import UniWebsocketManager as BitgetUniWebsocketManager
|
|
14
|
-
from .
|
|
15
|
-
from .bitrue import UniWebsocketManager as BitrueUniWebsocketManager
|
|
16
|
-
from .bitunix import UniClient as BitunixUniClient
|
|
17
|
-
from .bitunix import UniWebsocketManager as BitunixUniWebsocketManager
|
|
18
|
-
from .btse import UniClient as BtseUniClient
|
|
19
|
-
from .btse import UniWebsocketManager as BtseUniWebsocketManager
|
|
17
|
+
from .bybit import ExchangeInfo as BybitExchangeInfo
|
|
20
18
|
from .bybit import UniClient as BybitUniClient
|
|
21
19
|
from .bybit import UniWebsocketManager as BybitUniWebsocketManager
|
|
22
20
|
from .enums import Exchange
|
|
23
21
|
from .exceptions import NotSupported
|
|
22
|
+
from .gateio import ExchangeInfo as GateioExchangeInfo
|
|
24
23
|
from .gateio import UniClient as GateioUniClient
|
|
25
24
|
from .gateio import UniWebsocketManager as GateioUniWebsocketManager
|
|
25
|
+
from .hyperliquid import ExchangeInfo as HyperliquidExchangeInfo
|
|
26
26
|
from .hyperliquid import UniClient as HyperliquidUniClient
|
|
27
27
|
from .hyperliquid import UniWebsocketManager as HyperliquidUniWebsocketManager
|
|
28
|
-
from .
|
|
29
|
-
from .kcex import UniWebsocketManager as KcexUniWebsocketManager
|
|
30
|
-
from .kraken import UniClient as KrakenUniClient
|
|
31
|
-
from .kraken import UniWebsocketManager as KrakenUniWebsocketManager
|
|
32
|
-
from .kucoin import UniClient as KucoinUniClient
|
|
33
|
-
from .kucoin import UniWebsocketManager as KucoinUniWebsocketManager
|
|
28
|
+
from .mexc import ExchangeInfo as MexcExchangeInfo
|
|
34
29
|
from .mexc import UniClient as MexcUniClient
|
|
35
30
|
from .mexc import UniWebsocketManager as MexcUniWebsocketManager
|
|
31
|
+
from .okx import ExchangeInfo as OkxExchangeInfo
|
|
36
32
|
from .okx import UniClient as OkxUniClient
|
|
37
33
|
from .okx import UniWebsocketManager as OkxUniWebsocketManager
|
|
38
|
-
from .weex import UniClient as WeexUniClient
|
|
39
|
-
from .weex import UniWebsocketManager as WeexUniWebsocketManager
|
|
40
|
-
from .xt import UniClient as XtUniClient
|
|
41
|
-
from .xt import UniWebsocketManager as XtUniWebsocketManager
|
|
42
34
|
|
|
43
35
|
_UNI_CLIENT_MAPPER: dict[Exchange, type[IUniClient]] = {
|
|
44
36
|
Exchange.BINANCE: BinanceUniClient,
|
|
45
37
|
Exchange.BITGET: BitgetUniClient,
|
|
46
|
-
Exchange.BITRUE: BitrueUniClient,
|
|
47
|
-
Exchange.BITUNIX: BitunixUniClient,
|
|
48
|
-
Exchange.BTSE: BtseUniClient,
|
|
49
38
|
Exchange.BYBIT: BybitUniClient,
|
|
50
39
|
Exchange.GATEIO: GateioUniClient,
|
|
51
40
|
Exchange.HYPERLIQUID: HyperliquidUniClient,
|
|
52
|
-
Exchange.KCEX: KcexUniClient,
|
|
53
|
-
Exchange.KRAKEN: KrakenUniClient,
|
|
54
|
-
Exchange.KUCOIN: KucoinUniClient,
|
|
55
41
|
Exchange.MEXC: MexcUniClient,
|
|
56
42
|
Exchange.OKX: OkxUniClient,
|
|
57
|
-
Exchange.WEEX: WeexUniClient,
|
|
58
|
-
Exchange.XT: XtUniClient,
|
|
59
43
|
}
|
|
60
44
|
"""Маппер, который связывает биржу и реализацию унифицированного клиента."""
|
|
61
45
|
|
|
62
46
|
_UNI_WS_MANAGER_MAPPER: dict[Exchange, type[IUniWebsocketManager]] = {
|
|
63
47
|
Exchange.BINANCE: BinanceUniWebsocketManager,
|
|
64
48
|
Exchange.BITGET: BitgetUniWebsocketManager,
|
|
65
|
-
Exchange.BITRUE: BitrueUniWebsocketManager,
|
|
66
|
-
Exchange.BITUNIX: BitunixUniWebsocketManager,
|
|
67
|
-
Exchange.BTSE: BtseUniWebsocketManager,
|
|
68
49
|
Exchange.BYBIT: BybitUniWebsocketManager,
|
|
69
50
|
Exchange.GATEIO: GateioUniWebsocketManager,
|
|
70
51
|
Exchange.HYPERLIQUID: HyperliquidUniWebsocketManager,
|
|
71
|
-
Exchange.KCEX: KcexUniWebsocketManager,
|
|
72
|
-
Exchange.KRAKEN: KrakenUniWebsocketManager,
|
|
73
|
-
Exchange.KUCOIN: KucoinUniWebsocketManager,
|
|
74
52
|
Exchange.MEXC: MexcUniWebsocketManager,
|
|
75
53
|
Exchange.OKX: OkxUniWebsocketManager,
|
|
76
|
-
Exchange.WEEX: WeexUniWebsocketManager,
|
|
77
|
-
Exchange.XT: XtUniWebsocketManager,
|
|
78
54
|
}
|
|
79
55
|
"""Маппер, который связывает биржу и реализацию унифицированного вебсокет-менеджера."""
|
|
80
56
|
|
|
57
|
+
_EXCHANGE_INFO_MAPPER: dict[Exchange, type[IExchangeInfo]] = {
|
|
58
|
+
Exchange.BINANCE: BinanceExchangeInfo,
|
|
59
|
+
Exchange.BITGET: BitgetExchangeInfo,
|
|
60
|
+
Exchange.BYBIT: BybitExchangeInfo,
|
|
61
|
+
Exchange.GATEIO: GateioExchangeInfo,
|
|
62
|
+
Exchange.HYPERLIQUID: HyperliquidExchangeInfo,
|
|
63
|
+
Exchange.MEXC: MexcExchangeInfo,
|
|
64
|
+
Exchange.OKX: OkxExchangeInfo,
|
|
65
|
+
}
|
|
66
|
+
"""Маппер, который связывает биржу и реализацию сборщика информации о тикерах на бирже."""
|
|
67
|
+
|
|
81
68
|
|
|
82
69
|
def get_uni_client(exchange: Exchange) -> type[IUniClient]:
|
|
83
70
|
"""Возвращает унифицированный клиент для указанной биржи.
|
|
@@ -107,3 +94,18 @@ def get_uni_websocket_manager(exchange: Exchange) -> type[IUniWebsocketManager]:
|
|
|
107
94
|
return _UNI_WS_MANAGER_MAPPER[exchange]
|
|
108
95
|
except KeyError as e:
|
|
109
96
|
raise NotSupported(f"Unsupported exchange: {exchange}") from e
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_exchange_info(exchange: Exchange) -> type[IExchangeInfo]:
|
|
100
|
+
"""Возвращает унифицированный интерфейс для получения информации о бирже.
|
|
101
|
+
|
|
102
|
+
Параметры:
|
|
103
|
+
exchange (`Exchange`): Биржа.
|
|
104
|
+
|
|
105
|
+
Возвращает:
|
|
106
|
+
`type[IExchangeInfo]`: Унифицированный интерфейс для получения информации о бирже.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
return _EXCHANGE_INFO_MAPPER[exchange]
|
|
110
|
+
except KeyError as e:
|
|
111
|
+
raise NotSupported(f"Unsupported exchange: {exchange}") from e
|
unicex/mexc/__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
|
+
"""Загружает информацию о бирже Mexc."""
|
|
22
|
+
await ExchangeInfo.load_exchange_info()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
|
|
26
|
+
"""Запускает процесс обновления информации о бирже Mexc."""
|
|
27
|
+
await ExchangeInfo.start(parse_interval_seconds)
|
unicex/mexc/adapter.py
CHANGED
|
@@ -9,13 +9,15 @@ from unicex.types import (
|
|
|
9
9
|
)
|
|
10
10
|
from unicex.utils import catch_adapter_errors, decorate_all_methods
|
|
11
11
|
|
|
12
|
+
from .exchange_info import ExchangeInfo
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
@decorate_all_methods(catch_adapter_errors)
|
|
14
16
|
class Adapter:
|
|
15
17
|
"""Адаптер для унификации данных с Mexc API."""
|
|
16
18
|
|
|
17
19
|
@staticmethod
|
|
18
|
-
def tickers(raw_data: list[dict], only_usdt: bool
|
|
20
|
+
def tickers(raw_data: list[dict], only_usdt: bool) -> list[str]:
|
|
19
21
|
"""Преобразует сырой ответ, в котором содержатся данные о тикерах, в список тикеров.
|
|
20
22
|
|
|
21
23
|
Параметры:
|
|
@@ -25,10 +27,12 @@ class Adapter:
|
|
|
25
27
|
Возвращает:
|
|
26
28
|
list[str]: Список тикеров.
|
|
27
29
|
"""
|
|
28
|
-
return [
|
|
30
|
+
return [
|
|
31
|
+
item["symbol"] for item in raw_data if item["symbol"].endswith("USDT") or not only_usdt
|
|
32
|
+
]
|
|
29
33
|
|
|
30
34
|
@staticmethod
|
|
31
|
-
def futures_tickers(raw_data: dict, only_usdt: bool
|
|
35
|
+
def futures_tickers(raw_data: dict, only_usdt: bool) -> list[str]:
|
|
32
36
|
"""Преобразует сырой ответ, в котором содержатся данные о фьючерсных тикерах, в список тикеров.
|
|
33
37
|
|
|
34
38
|
Параметры:
|
|
@@ -41,7 +45,7 @@ class Adapter:
|
|
|
41
45
|
return [
|
|
42
46
|
item["symbol"]
|
|
43
47
|
for item in raw_data["data"]
|
|
44
|
-
if
|
|
48
|
+
if item["symbol"].endswith("USDT") or not only_usdt
|
|
45
49
|
]
|
|
46
50
|
|
|
47
51
|
@staticmethod
|
|
@@ -97,14 +101,15 @@ class Adapter:
|
|
|
97
101
|
Возвращает:
|
|
98
102
|
TickerDailyDict: Словарь, где ключ - тикер, а значение - статистика за последние 24 часа.
|
|
99
103
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
result = {}
|
|
105
|
+
for item in raw_data["data"]:
|
|
106
|
+
symbol = item["symbol"]
|
|
107
|
+
result[symbol] = TickerDailyItem(
|
|
102
108
|
p=float(item["riseFallRate"]) * 100,
|
|
103
|
-
v=float(item["volume24"]),
|
|
109
|
+
v=float(item["volume24"]) * Adapter._get_contract_size(symbol),
|
|
104
110
|
q=float(item["amount24"]),
|
|
105
111
|
)
|
|
106
|
-
|
|
107
|
-
}
|
|
112
|
+
return result
|
|
108
113
|
|
|
109
114
|
@staticmethod
|
|
110
115
|
def open_interest(raw_data: dict) -> OpenInterestDict:
|
|
@@ -116,10 +121,14 @@ class Adapter:
|
|
|
116
121
|
Возвращает:
|
|
117
122
|
OpenInterestDict: Словарь, где ключ - тикер, а значение - агрегированные данные открытого интереса.
|
|
118
123
|
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
result = {}
|
|
125
|
+
for item in raw_data["data"]:
|
|
126
|
+
symbol = item["symbol"]
|
|
127
|
+
result[symbol] = OpenInterestItem(
|
|
128
|
+
t=item["timestamp"],
|
|
129
|
+
v=float(item["holdVol"]) * Adapter._get_contract_size(symbol),
|
|
130
|
+
)
|
|
131
|
+
return result
|
|
123
132
|
|
|
124
133
|
@staticmethod
|
|
125
134
|
def funding_rate(raw_data: dict) -> dict[str, float]:
|
|
@@ -220,3 +229,11 @@ class Adapter:
|
|
|
220
229
|
)
|
|
221
230
|
|
|
222
231
|
return sorted(klines, key=lambda kline_item: kline_item["t"])
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _get_contract_size(symbol: str) -> float:
|
|
235
|
+
"""Возвращает размер контракта для указанного символа тикера."""
|
|
236
|
+
try:
|
|
237
|
+
return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
|
|
238
|
+
except: # noqa
|
|
239
|
+
return 1
|