unicex 0.14.2__py3-none-any.whl → 0.14.5__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/_abc/uni_client.py +30 -0
- unicex/binance/client.py +25 -25
- unicex/binance/uni_websocket_manager.py +6 -2
- unicex/bitget/client.py +64 -64
- unicex/bitget/uni_websocket_manager.py +6 -2
- unicex/bybit/client.py +29 -29
- unicex/bybit/uni_websocket_manager.py +6 -2
- unicex/enums.py +17 -0
- unicex/gate/client.py +9 -9
- unicex/gate/uni_client.py +1 -2
- unicex/gate/uni_websocket_manager.py +6 -2
- unicex/hyperliquid/client.py +12 -12
- unicex/hyperliquid/uni_client.py +4 -7
- unicex/hyperliquid/uni_websocket_manager.py +6 -2
- unicex/kucoin/adapter.py +76 -2
- unicex/kucoin/client.py +80 -12
- unicex/kucoin/exchange_info.py +12 -9
- unicex/kucoin/uni_client.py +68 -8
- unicex/kucoin/uni_websocket_manager.py +6 -2
- unicex/mexc/client.py +7 -7
- unicex/mexc/uni_client.py +1 -2
- unicex/mexc/uni_websocket_manager.py +6 -2
- unicex/okx/client.py +15 -15
- unicex/okx/uni_websocket_manager.py +6 -2
- unicex/types.py +8 -0
- unicex/utils.py +5 -0
- {unicex-0.14.2.dist-info → unicex-0.14.5.dist-info}/METADATA +2 -1
- {unicex-0.14.2.dist-info → unicex-0.14.5.dist-info}/RECORD +31 -31
- {unicex-0.14.2.dist-info → unicex-0.14.5.dist-info}/WHEEL +0 -0
- {unicex-0.14.2.dist-info → unicex-0.14.5.dist-info}/licenses/LICENSE +0 -0
- {unicex-0.14.2.dist-info → unicex-0.14.5.dist-info}/top_level.txt +0 -0
unicex/enums.py
CHANGED
|
@@ -176,6 +176,23 @@ class Timeframe(StrEnum):
|
|
|
176
176
|
Timeframe.WEEK_1: "Week1",
|
|
177
177
|
Timeframe.MONTH_1: "Month1",
|
|
178
178
|
},
|
|
179
|
+
Exchange.KUCOIN: {
|
|
180
|
+
Timeframe.MIN_1: "1min",
|
|
181
|
+
Timeframe.MIN_3: "3min",
|
|
182
|
+
Timeframe.MIN_5: "5min",
|
|
183
|
+
Timeframe.MIN_15: "15min",
|
|
184
|
+
Timeframe.MIN_30: "30min",
|
|
185
|
+
Timeframe.HOUR_1: "1hour",
|
|
186
|
+
Timeframe.HOUR_2: "2hour",
|
|
187
|
+
Timeframe.HOUR_4: "4hour",
|
|
188
|
+
Timeframe.HOUR_6: "6hour",
|
|
189
|
+
Timeframe.HOUR_8: "8hour",
|
|
190
|
+
Timeframe.HOUR_12: "12hour",
|
|
191
|
+
Timeframe.DAY_1: "1day",
|
|
192
|
+
Timeframe.DAY_3: "3day",
|
|
193
|
+
Timeframe.WEEK_1: "1week",
|
|
194
|
+
Timeframe.MONTH_1: "1month",
|
|
195
|
+
},
|
|
179
196
|
Exchange.OKX: {
|
|
180
197
|
Timeframe.MIN_1: "1m",
|
|
181
198
|
Timeframe.MIN_3: "3m",
|
unicex/gate/client.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Any, Literal
|
|
|
8
8
|
|
|
9
9
|
from unicex._base import BaseClient
|
|
10
10
|
from unicex.exceptions import NotAuthorized
|
|
11
|
-
from unicex.types import RequestMethod
|
|
11
|
+
from unicex.types import NumberLike, RequestMethod
|
|
12
12
|
from unicex.utils import dict_to_query_string, filter_params
|
|
13
13
|
|
|
14
14
|
|
|
@@ -328,8 +328,8 @@ class Client(BaseClient):
|
|
|
328
328
|
async def cross_liquidate_orders(
|
|
329
329
|
self,
|
|
330
330
|
currency_pair: str,
|
|
331
|
-
amount:
|
|
332
|
-
price:
|
|
331
|
+
amount: NumberLike,
|
|
332
|
+
price: NumberLike,
|
|
333
333
|
text: str | None = None,
|
|
334
334
|
action_mode: str | None = None,
|
|
335
335
|
) -> dict:
|
|
@@ -353,11 +353,11 @@ class Client(BaseClient):
|
|
|
353
353
|
self,
|
|
354
354
|
currency_pair: str,
|
|
355
355
|
side: str,
|
|
356
|
-
amount:
|
|
356
|
+
amount: NumberLike,
|
|
357
357
|
text: str | None = None,
|
|
358
358
|
type: str | None = None,
|
|
359
359
|
account: str | None = None,
|
|
360
|
-
price:
|
|
360
|
+
price: NumberLike | None = None,
|
|
361
361
|
time_in_force: str | None = None,
|
|
362
362
|
iceberg: str | None = None,
|
|
363
363
|
auto_borrow: bool | None = None,
|
|
@@ -479,8 +479,8 @@ class Client(BaseClient):
|
|
|
479
479
|
order_id: str,
|
|
480
480
|
currency_pair: str | None = None,
|
|
481
481
|
account: str | None = None,
|
|
482
|
-
amount:
|
|
483
|
-
price:
|
|
482
|
+
amount: NumberLike | None = None,
|
|
483
|
+
price: NumberLike | None = None,
|
|
484
484
|
amend_text: str | None = None,
|
|
485
485
|
action_mode: str | None = None,
|
|
486
486
|
) -> dict:
|
|
@@ -1344,8 +1344,8 @@ class Client(BaseClient):
|
|
|
1344
1344
|
self,
|
|
1345
1345
|
settle: str,
|
|
1346
1346
|
order_id: str,
|
|
1347
|
-
size:
|
|
1348
|
-
price:
|
|
1347
|
+
size: NumberLike | None = None,
|
|
1348
|
+
price: NumberLike | None = None,
|
|
1349
1349
|
amend_text: str | None = None,
|
|
1350
1350
|
text: str | None = None,
|
|
1351
1351
|
) -> dict:
|
unicex/gate/uni_client.py
CHANGED
|
@@ -173,8 +173,7 @@ class UniClient(IUniClient[Client]):
|
|
|
173
173
|
raw_data = await self._client.futures_tickers(settle="usdt", contract=symbol)
|
|
174
174
|
items = raw_data if isinstance(raw_data, list) else [raw_data]
|
|
175
175
|
adapted_data = Adapter.funding_rate(raw_data=items) # type: ignore[reportArgumentType]
|
|
176
|
-
if symbol
|
|
177
|
-
return adapted_data[symbol]
|
|
176
|
+
return adapted_data[symbol] if symbol else adapted_data
|
|
178
177
|
return adapted_data
|
|
179
178
|
|
|
180
179
|
@overload
|
|
@@ -20,16 +20,20 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
20
20
|
"""Реализация менеджера асинхронных унифицированных вебсокетов."""
|
|
21
21
|
|
|
22
22
|
def __init__(
|
|
23
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
client: Client | UniClient | None = None,
|
|
25
|
+
logger: LoggerLike | None = None,
|
|
26
|
+
**ws_kwargs: Any,
|
|
24
27
|
) -> None:
|
|
25
28
|
"""Инициализирует унифицированный менеджер вебсокетов.
|
|
26
29
|
|
|
27
30
|
Параметры:
|
|
28
31
|
client (`Client | UniClient | None`): Клиент Gateio или унифицированный клиент. Нужен для подключения к приватным топикам.
|
|
29
32
|
logger (`LoggerLike | None`): Логгер для записи логов.
|
|
33
|
+
ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
|
|
30
34
|
"""
|
|
31
35
|
super().__init__(client=client, logger=logger)
|
|
32
|
-
self._websocket_manager = WebsocketManager(self._client) # type: ignore
|
|
36
|
+
self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
|
|
33
37
|
self._adapter = Adapter()
|
|
34
38
|
|
|
35
39
|
@overload
|
unicex/hyperliquid/client.py
CHANGED
|
@@ -13,7 +13,7 @@ from eth_utils.crypto import keccak
|
|
|
13
13
|
|
|
14
14
|
from unicex._base import BaseClient
|
|
15
15
|
from unicex.exceptions import NotAuthorized
|
|
16
|
-
from unicex.types import LoggerLike
|
|
16
|
+
from unicex.types import LoggerLike, NumberLike
|
|
17
17
|
from unicex.utils import filter_params
|
|
18
18
|
|
|
19
19
|
# Authentication
|
|
@@ -1184,11 +1184,11 @@ class Client(BaseClient):
|
|
|
1184
1184
|
self,
|
|
1185
1185
|
asset: int,
|
|
1186
1186
|
is_buy: bool,
|
|
1187
|
-
size:
|
|
1187
|
+
size: NumberLike,
|
|
1188
1188
|
reduce_only: bool,
|
|
1189
1189
|
order_type: Literal["limit", "trigger"],
|
|
1190
1190
|
order_body: dict,
|
|
1191
|
-
price:
|
|
1191
|
+
price: NumberLike | None = None,
|
|
1192
1192
|
client_order_id: str | None = None,
|
|
1193
1193
|
grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
|
|
1194
1194
|
builder_address: str | None = None,
|
|
@@ -1478,8 +1478,8 @@ class Client(BaseClient):
|
|
|
1478
1478
|
order_id: int | str,
|
|
1479
1479
|
asset: int,
|
|
1480
1480
|
is_buy: bool,
|
|
1481
|
-
price:
|
|
1482
|
-
size:
|
|
1481
|
+
price: NumberLike,
|
|
1482
|
+
size: NumberLike,
|
|
1483
1483
|
reduce_only: bool,
|
|
1484
1484
|
order_type: Literal["limit", "trigger"],
|
|
1485
1485
|
order_body: dict[str, Any],
|
|
@@ -1668,7 +1668,7 @@ class Client(BaseClient):
|
|
|
1668
1668
|
hyperliquid_chain: Literal["Mainnet", "Testnet"],
|
|
1669
1669
|
signature_chain_id: str,
|
|
1670
1670
|
destination: str,
|
|
1671
|
-
amount:
|
|
1671
|
+
amount: NumberLike,
|
|
1672
1672
|
time_ms: int,
|
|
1673
1673
|
nonce: int | None = None,
|
|
1674
1674
|
) -> dict:
|
|
@@ -1711,7 +1711,7 @@ class Client(BaseClient):
|
|
|
1711
1711
|
signature_chain_id: str,
|
|
1712
1712
|
destination: str,
|
|
1713
1713
|
token: str,
|
|
1714
|
-
amount:
|
|
1714
|
+
amount: NumberLike,
|
|
1715
1715
|
time_ms: int,
|
|
1716
1716
|
nonce: int | None = None,
|
|
1717
1717
|
) -> dict:
|
|
@@ -1753,7 +1753,7 @@ class Client(BaseClient):
|
|
|
1753
1753
|
self,
|
|
1754
1754
|
hyperliquid_chain: Literal["Mainnet", "Testnet"],
|
|
1755
1755
|
signature_chain_id: str,
|
|
1756
|
-
amount:
|
|
1756
|
+
amount: NumberLike,
|
|
1757
1757
|
time_ms: int,
|
|
1758
1758
|
destination: str,
|
|
1759
1759
|
nonce: int | None = None,
|
|
@@ -1795,7 +1795,7 @@ class Client(BaseClient):
|
|
|
1795
1795
|
self,
|
|
1796
1796
|
hyperliquid_chain: Literal["Mainnet", "Testnet"],
|
|
1797
1797
|
signature_chain_id: str,
|
|
1798
|
-
amount:
|
|
1798
|
+
amount: NumberLike,
|
|
1799
1799
|
to_perp: bool,
|
|
1800
1800
|
subaccount: str | None = None,
|
|
1801
1801
|
) -> dict:
|
|
@@ -1845,7 +1845,7 @@ class Client(BaseClient):
|
|
|
1845
1845
|
source_dex: str,
|
|
1846
1846
|
destination_dex: str,
|
|
1847
1847
|
token: str,
|
|
1848
|
-
amount:
|
|
1848
|
+
amount: NumberLike,
|
|
1849
1849
|
from_subaccount: str,
|
|
1850
1850
|
nonce_value: int,
|
|
1851
1851
|
nonce: int | None = None,
|
|
@@ -2015,7 +2015,7 @@ class Client(BaseClient):
|
|
|
2015
2015
|
self,
|
|
2016
2016
|
vault_address: str,
|
|
2017
2017
|
is_deposit: bool,
|
|
2018
|
-
usd:
|
|
2018
|
+
usd: NumberLike,
|
|
2019
2019
|
nonce: int | None = None,
|
|
2020
2020
|
expires_after: int | None = None,
|
|
2021
2021
|
signing_vault_address: str | None = None,
|
|
@@ -2147,7 +2147,7 @@ class Client(BaseClient):
|
|
|
2147
2147
|
self,
|
|
2148
2148
|
asset: int,
|
|
2149
2149
|
is_buy: bool,
|
|
2150
|
-
size:
|
|
2150
|
+
size: NumberLike,
|
|
2151
2151
|
reduce_only: bool,
|
|
2152
2152
|
minutes: int,
|
|
2153
2153
|
randomize: bool,
|
unicex/hyperliquid/uni_client.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
__all__ = ["UniClient"]
|
|
2
2
|
|
|
3
|
-
import time
|
|
4
3
|
from typing import Self, overload
|
|
5
4
|
|
|
6
5
|
import aiohttp
|
|
@@ -217,15 +216,14 @@ class UniClient(IUniClient[Client]):
|
|
|
217
216
|
list[KlineDict]: Список свечей для тикера.
|
|
218
217
|
"""
|
|
219
218
|
if not limit and not all([start_time, end_time]):
|
|
220
|
-
raise ValueError("limit
|
|
219
|
+
raise ValueError("limit or (start_time and end_time) must be provided")
|
|
221
220
|
|
|
222
221
|
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
223
222
|
if not isinstance(interval, Timeframe):
|
|
224
223
|
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
225
|
-
end_time =
|
|
226
|
-
start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
|
|
224
|
+
start_time, end_time = self.limit_to_start_and_end_time(interval, limit)
|
|
227
225
|
interval = (
|
|
228
|
-
interval.to_exchange_format(Exchange.HYPERLIQUID
|
|
226
|
+
interval.to_exchange_format(Exchange.HYPERLIQUID)
|
|
229
227
|
if isinstance(interval, Timeframe)
|
|
230
228
|
else interval
|
|
231
229
|
)
|
|
@@ -263,8 +261,7 @@ class UniClient(IUniClient[Client]):
|
|
|
263
261
|
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
264
262
|
if not isinstance(interval, Timeframe):
|
|
265
263
|
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
266
|
-
end_time =
|
|
267
|
-
start_time = end_time - (limit * interval.to_seconds * 1000) # type: ignore[reportOptionalOperand]
|
|
264
|
+
start_time, end_time = self.limit_to_start_and_end_time(interval, limit)
|
|
268
265
|
interval = (
|
|
269
266
|
interval.to_exchange_format(Exchange.HYPERLIQUID, MarketType.FUTURES)
|
|
270
267
|
if isinstance(interval, Timeframe)
|
|
@@ -20,16 +20,20 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
20
20
|
"""Реализация менеджера асинхронных унифицированных вебсокетов."""
|
|
21
21
|
|
|
22
22
|
def __init__(
|
|
23
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
client: Client | UniClient | None = None,
|
|
25
|
+
logger: LoggerLike | None = None,
|
|
26
|
+
**ws_kwargs: Any,
|
|
24
27
|
) -> None:
|
|
25
28
|
"""Инициализирует унифицированный менеджер вебсокетов.
|
|
26
29
|
|
|
27
30
|
Параметры:
|
|
28
31
|
client (`Client | UniClient | None`): Клиент Hyperliquid или унифицированный клиент. Нужен для подключения к приватным топикам.
|
|
29
32
|
logger (`LoggerLike | None`): Логгер для записи логов.
|
|
33
|
+
ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
|
|
30
34
|
"""
|
|
31
35
|
super().__init__(client=client, logger=logger)
|
|
32
|
-
self._websocket_manager = WebsocketManager(self._client) # type: ignore
|
|
36
|
+
self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
|
|
33
37
|
self._adapter = Adapter()
|
|
34
38
|
|
|
35
39
|
@overload
|
unicex/kucoin/adapter.py
CHANGED
|
@@ -2,7 +2,13 @@ __all__ = ["Adapter"]
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from unicex.types import
|
|
5
|
+
from unicex.types import (
|
|
6
|
+
KlineDict,
|
|
7
|
+
OpenInterestDict,
|
|
8
|
+
OpenInterestItem,
|
|
9
|
+
TickerDailyDict,
|
|
10
|
+
TickerDailyItem,
|
|
11
|
+
)
|
|
6
12
|
from unicex.utils import catch_adapter_errors, decorate_all_methods
|
|
7
13
|
|
|
8
14
|
from .exchange_info import ExchangeInfo
|
|
@@ -16,6 +22,23 @@ class Adapter:
|
|
|
16
22
|
def tickers(raw_data: dict, only_usdt: bool) -> list[str]:
|
|
17
23
|
"""Преобразует сырой ответ, в котором содержатся данные о тикерах в список тикеров.
|
|
18
24
|
|
|
25
|
+
Параметры:
|
|
26
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
27
|
+
only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре к USDT.
|
|
28
|
+
|
|
29
|
+
Возвращает:
|
|
30
|
+
list[str]: Список тикеров.
|
|
31
|
+
"""
|
|
32
|
+
return [
|
|
33
|
+
item["symbol"]
|
|
34
|
+
for item in raw_data["data"]["list"]
|
|
35
|
+
if item["symbol"].endswith("USDT") or not only_usdt
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def futures_tickers(raw_data: dict, only_usdt: bool) -> list[str]:
|
|
40
|
+
"""Преобразует сырой ответ, в котором содержатся данные о тикерах в список тикеров.
|
|
41
|
+
|
|
19
42
|
Параметры:
|
|
20
43
|
raw_data (dict): Сырой ответ с биржи.
|
|
21
44
|
only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре к USDT.
|
|
@@ -62,7 +85,11 @@ class Adapter:
|
|
|
62
85
|
Возвращает:
|
|
63
86
|
dict[str, float]: Словарь, где ключ - тикер, а значение - последняя цена.
|
|
64
87
|
"""
|
|
65
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
item["symbol"]: float(item["lastPrice"])
|
|
90
|
+
for item in raw_data["data"]["list"]
|
|
91
|
+
if item["lastPrice"]
|
|
92
|
+
}
|
|
66
93
|
|
|
67
94
|
@staticmethod
|
|
68
95
|
def open_interest(raw_data: dict[str, Any]) -> OpenInterestDict:
|
|
@@ -82,6 +109,24 @@ class Adapter:
|
|
|
82
109
|
for item in raw_data["data"]
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
@staticmethod
|
|
113
|
+
def funding_rate(raw_data: dict) -> dict[str, float]:
|
|
114
|
+
"""Преобразует историю ставок финансирования в унифицированный формат.
|
|
115
|
+
|
|
116
|
+
Параметры:
|
|
117
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
118
|
+
|
|
119
|
+
Возвращает:
|
|
120
|
+
dict[str, float]: Словарь, где ключ - тикер, а значение - актуальная ставка финансирования.
|
|
121
|
+
"""
|
|
122
|
+
symbol = raw_data["data"]["symbol"]
|
|
123
|
+
history = raw_data["data"]["list"]
|
|
124
|
+
if not history:
|
|
125
|
+
return {}
|
|
126
|
+
|
|
127
|
+
last_point = max(history, key=lambda item: int(item["ts"]))
|
|
128
|
+
return {symbol: float(last_point["fundingRate"]) * 100}
|
|
129
|
+
|
|
85
130
|
@staticmethod
|
|
86
131
|
def _get_contract_size(symbol: str) -> float:
|
|
87
132
|
"""Возвращает размер контракта для указанного символа тикера."""
|
|
@@ -89,3 +134,32 @@ class Adapter:
|
|
|
89
134
|
return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
|
|
90
135
|
except: # noqa
|
|
91
136
|
return 1
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def klines(raw_data: dict, symbol: str) -> list[KlineDict]:
|
|
140
|
+
"""Преобразует данные о свечах в унифицированный формат.
|
|
141
|
+
|
|
142
|
+
Параметры:
|
|
143
|
+
raw_data (dict): Сырой ответ с биржи.
|
|
144
|
+
symbol (str): Символ тикера.
|
|
145
|
+
|
|
146
|
+
Возвращает:
|
|
147
|
+
list[KlineDict]: Список свечей.
|
|
148
|
+
"""
|
|
149
|
+
klines: list[KlineDict] = []
|
|
150
|
+
for item in sorted(raw_data["data"]["list"], key=lambda x: int(float(x[0]))):
|
|
151
|
+
klines.append( # noqa: PERF401
|
|
152
|
+
KlineDict(
|
|
153
|
+
s=symbol,
|
|
154
|
+
t=item[0],
|
|
155
|
+
o=float(item[1]),
|
|
156
|
+
h=float(item[3]),
|
|
157
|
+
l=float(item[4]),
|
|
158
|
+
c=float(item[2]),
|
|
159
|
+
v=float(item[5]),
|
|
160
|
+
q=float(item[6]),
|
|
161
|
+
T=None,
|
|
162
|
+
x=None,
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
return klines
|
unicex/kucoin/client.py
CHANGED
|
@@ -4,6 +4,8 @@ __all__ = ["Client"]
|
|
|
4
4
|
from typing import Any, Literal
|
|
5
5
|
|
|
6
6
|
from unicex._base import BaseClient
|
|
7
|
+
from unicex.types import RequestMethod
|
|
8
|
+
from unicex.utils import filter_params
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class Client(BaseClient):
|
|
@@ -12,6 +14,36 @@ class Client(BaseClient):
|
|
|
12
14
|
_BASE_URL: str = "https://api.kucoin.com"
|
|
13
15
|
"""Базовый URL для запросов."""
|
|
14
16
|
|
|
17
|
+
async def _make_request(
|
|
18
|
+
self,
|
|
19
|
+
method: RequestMethod,
|
|
20
|
+
endpoint: str,
|
|
21
|
+
*,
|
|
22
|
+
params: dict[str, Any] | None = None,
|
|
23
|
+
) -> dict[str, Any]:
|
|
24
|
+
"""Выполняет HTTP-запрос к эндпоинтам Kucoin API.
|
|
25
|
+
|
|
26
|
+
Параметры:
|
|
27
|
+
method (str): HTTP метод запроса ("GET", "POST", "DELETE" и т.д.).
|
|
28
|
+
endpoint (str): URL эндпоинта Kucoin API.
|
|
29
|
+
params (dict | None): Параметры запроса.
|
|
30
|
+
|
|
31
|
+
Возвращает:
|
|
32
|
+
dict: Ответ в формате JSON.
|
|
33
|
+
"""
|
|
34
|
+
# Составляем URL для запроса
|
|
35
|
+
url = self._BASE_URL + endpoint
|
|
36
|
+
|
|
37
|
+
# Фильтруем параметры от None значений
|
|
38
|
+
params = filter_params(params) if params else {}
|
|
39
|
+
|
|
40
|
+
# Выполняем запрос
|
|
41
|
+
return await super()._make_request(
|
|
42
|
+
method=method,
|
|
43
|
+
url=url,
|
|
44
|
+
params=params,
|
|
45
|
+
)
|
|
46
|
+
|
|
15
47
|
async def symbol(
|
|
16
48
|
self,
|
|
17
49
|
trade_type: Literal["SPOT", "FUTURES", "ISOLATED", "CROSS"],
|
|
@@ -21,12 +53,9 @@ class Client(BaseClient):
|
|
|
21
53
|
|
|
22
54
|
https://www.kucoin.com/docs-new/rest/ua/get-symbol
|
|
23
55
|
"""
|
|
24
|
-
|
|
25
|
-
params = {"tradeType": trade_type}
|
|
26
|
-
if symbol:
|
|
27
|
-
params["symbol"] = symbol
|
|
56
|
+
params = {"tradeType": trade_type, "symbol": symbol}
|
|
28
57
|
|
|
29
|
-
return await self._make_request("GET",
|
|
58
|
+
return await self._make_request("GET", "/api/ua/v1/market/instrument", params=params)
|
|
30
59
|
|
|
31
60
|
async def ticker(
|
|
32
61
|
self,
|
|
@@ -37,18 +66,57 @@ class Client(BaseClient):
|
|
|
37
66
|
|
|
38
67
|
https://www.kucoin.com/docs-new/rest/ua/get-ticker
|
|
39
68
|
"""
|
|
40
|
-
|
|
41
|
-
params = {"tradeType": trade_type}
|
|
42
|
-
if symbol:
|
|
43
|
-
params["symbol"] = symbol
|
|
69
|
+
params = {"tradeType": trade_type, "symbol": symbol}
|
|
44
70
|
|
|
45
|
-
return await self._make_request("GET",
|
|
71
|
+
return await self._make_request("GET", "/api/ua/v1/market/ticker", params=params)
|
|
46
72
|
|
|
47
73
|
async def open_interest(self) -> dict[str, Any]:
|
|
48
74
|
"""Получение открытого интереса.
|
|
49
75
|
|
|
50
76
|
https://www.kucoin.com/docs-new/3476287e0
|
|
51
77
|
"""
|
|
52
|
-
|
|
78
|
+
return await self._make_request("GET", "/api/ua/v1/market/open-interest")
|
|
79
|
+
|
|
80
|
+
async def kline(
|
|
81
|
+
self,
|
|
82
|
+
trade_type: Literal["SPOT", "FUTURES"],
|
|
83
|
+
symbol: str,
|
|
84
|
+
interval: str,
|
|
85
|
+
start_at: int | None = None,
|
|
86
|
+
end_at: int | None = None,
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""Получение списка свечей.
|
|
89
|
+
|
|
90
|
+
https://www.kucoin.com/docs-new/rest/ua/get-klines
|
|
91
|
+
"""
|
|
92
|
+
params = {
|
|
93
|
+
"tradeType": trade_type,
|
|
94
|
+
"symbol": symbol,
|
|
95
|
+
"interval": interval,
|
|
96
|
+
"startAt": start_at,
|
|
97
|
+
"endAt": end_at,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return await self._make_request("GET", "/api/ua/v1/market/kline", params=params)
|
|
101
|
+
|
|
102
|
+
async def funding_rate_history(
|
|
103
|
+
self,
|
|
104
|
+
symbol: str,
|
|
105
|
+
start_at: int,
|
|
106
|
+
end_at: int,
|
|
107
|
+
) -> dict[str, Any]:
|
|
108
|
+
"""Получение истории ставок финансирования.
|
|
109
|
+
|
|
110
|
+
https://www.kucoin.com/docs-new/rest/ua/get-history-funding-rate
|
|
111
|
+
"""
|
|
112
|
+
params = {
|
|
113
|
+
"symbol": symbol,
|
|
114
|
+
"startAt": start_at,
|
|
115
|
+
"endAt": end_at,
|
|
116
|
+
}
|
|
53
117
|
|
|
54
|
-
return await self._make_request(
|
|
118
|
+
return await self._make_request(
|
|
119
|
+
"GET",
|
|
120
|
+
"/api/ua/v1/market/funding-rate-history",
|
|
121
|
+
params=params,
|
|
122
|
+
)
|
unicex/kucoin/exchange_info.py
CHANGED
|
@@ -22,9 +22,9 @@ class ExchangeInfo(IExchangeInfo):
|
|
|
22
22
|
for el in exchange_info["data"]["list"]:
|
|
23
23
|
tickers_info[el["symbol"]] = TickerInfoItem(
|
|
24
24
|
tick_precision=None,
|
|
25
|
-
tick_step=
|
|
25
|
+
tick_step=float(el["tickSize"]),
|
|
26
26
|
size_precision=None,
|
|
27
|
-
size_step=
|
|
27
|
+
size_step=float(el["baseOrderStep"]),
|
|
28
28
|
contract_size=1,
|
|
29
29
|
)
|
|
30
30
|
|
|
@@ -36,12 +36,15 @@ class ExchangeInfo(IExchangeInfo):
|
|
|
36
36
|
tickers_info = {}
|
|
37
37
|
exchange_info = await Client(session).symbol("FUTURES")
|
|
38
38
|
for el in exchange_info["data"]["list"]:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
try:
|
|
40
|
+
tickers_info[el["symbol"]] = TickerInfoItem(
|
|
41
|
+
tick_precision=None,
|
|
42
|
+
tick_step=float(el["tickSize"]),
|
|
43
|
+
size_precision=None,
|
|
44
|
+
size_step=float(el["lotSize"]),
|
|
45
|
+
contract_size=float(el["unitSize"]),
|
|
46
|
+
)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
cls._logger.error(f"Error loading ticker info for {el}: {e}")
|
|
46
49
|
|
|
47
50
|
cls._futures_tickers_info = tickers_info
|
unicex/kucoin/uni_client.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
__all__ = ["UniClient"]
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
import time
|
|
4
5
|
from typing import overload
|
|
5
6
|
|
|
6
7
|
from unicex._abc import IUniClient
|
|
7
|
-
from unicex.enums import Timeframe
|
|
8
|
+
from unicex.enums import Exchange, Timeframe
|
|
8
9
|
from unicex.types import KlineDict, OpenInterestDict, OpenInterestItem, TickerDailyDict
|
|
9
10
|
|
|
10
11
|
from .adapter import Adapter
|
|
@@ -45,7 +46,7 @@ class UniClient(IUniClient[Client]):
|
|
|
45
46
|
list[str]: Список тикеров.
|
|
46
47
|
"""
|
|
47
48
|
raw_data = await self._client.ticker("FUTURES")
|
|
48
|
-
return Adapter.
|
|
49
|
+
return Adapter.futures_tickers(raw_data, only_usdt)
|
|
49
50
|
|
|
50
51
|
async def last_price(self) -> dict[str, float]:
|
|
51
52
|
"""Возвращает последнюю цену для каждого тикера.
|
|
@@ -103,7 +104,28 @@ class UniClient(IUniClient[Client]):
|
|
|
103
104
|
Возвращает:
|
|
104
105
|
list[KlineDict]: Список свечей для тикера.
|
|
105
106
|
"""
|
|
106
|
-
|
|
107
|
+
if not limit and not all([start_time, end_time]):
|
|
108
|
+
raise ValueError("limit or (start_time and end_time) must be provided")
|
|
109
|
+
|
|
110
|
+
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
111
|
+
if not isinstance(interval, Timeframe):
|
|
112
|
+
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
113
|
+
start_time, end_time = self.limit_to_start_and_end_time(
|
|
114
|
+
interval, limit, use_milliseconds=False
|
|
115
|
+
)
|
|
116
|
+
interval = (
|
|
117
|
+
interval.to_exchange_format(Exchange.KUCOIN)
|
|
118
|
+
if isinstance(interval, Timeframe)
|
|
119
|
+
else interval
|
|
120
|
+
)
|
|
121
|
+
raw_data = await self._client.kline(
|
|
122
|
+
trade_type="SPOT",
|
|
123
|
+
symbol=symbol,
|
|
124
|
+
interval=interval,
|
|
125
|
+
start_at=self.to_seconds(start_time),
|
|
126
|
+
end_at=self.to_seconds(end_time),
|
|
127
|
+
)
|
|
128
|
+
return Adapter.klines(raw_data=raw_data, symbol=symbol)
|
|
107
129
|
|
|
108
130
|
async def futures_klines(
|
|
109
131
|
self,
|
|
@@ -125,15 +147,53 @@ class UniClient(IUniClient[Client]):
|
|
|
125
147
|
Возвращает:
|
|
126
148
|
list[KlineDict]: Список свечей для тикера.
|
|
127
149
|
"""
|
|
128
|
-
|
|
150
|
+
if not limit and not all([start_time, end_time]):
|
|
151
|
+
raise ValueError("limit or (start_time and end_time) must be provided")
|
|
152
|
+
|
|
153
|
+
if limit: # Перезаписываем start_time и end_time если указан limit, т.к. по умолчанию HyperLiquid не принимают этот параметр
|
|
154
|
+
if not isinstance(interval, Timeframe):
|
|
155
|
+
raise ValueError("interval must be a Timeframe if limit param provided")
|
|
156
|
+
start_time, end_time = self.limit_to_start_and_end_time(
|
|
157
|
+
interval, limit, use_milliseconds=False
|
|
158
|
+
)
|
|
159
|
+
interval = (
|
|
160
|
+
interval.to_exchange_format(Exchange.KUCOIN)
|
|
161
|
+
if isinstance(interval, Timeframe)
|
|
162
|
+
else interval
|
|
163
|
+
)
|
|
164
|
+
raw_data = await self._client.kline(
|
|
165
|
+
trade_type="FUTURES",
|
|
166
|
+
symbol=symbol,
|
|
167
|
+
interval=interval,
|
|
168
|
+
start_at=self.to_seconds(start_time),
|
|
169
|
+
end_at=self.to_seconds(end_time),
|
|
170
|
+
)
|
|
171
|
+
return Adapter.klines(raw_data=raw_data, symbol=symbol)
|
|
172
|
+
|
|
173
|
+
async def funding_rate(self, symbol: str | None = None) -> dict[str, float] | float:
|
|
174
|
+
"""Возвращает ставку финансирования для тикера.
|
|
129
175
|
|
|
130
|
-
|
|
131
|
-
|
|
176
|
+
Параметры:
|
|
177
|
+
symbol (`str | None`): Название тикера. На Kucoin параметр обязателен.
|
|
132
178
|
|
|
133
179
|
Возвращает:
|
|
134
|
-
dict[str, float]
|
|
180
|
+
`dict[str, float] | float`: Ставка финансирования в процентах.
|
|
135
181
|
"""
|
|
136
|
-
|
|
182
|
+
if not symbol:
|
|
183
|
+
raise ValueError("Symbol is required to fetch Kucoin funding rate")
|
|
184
|
+
|
|
185
|
+
end_time = int(time.time() * 1000)
|
|
186
|
+
# Kucoin публикует ставку каждые 8 часов, берем окно в 24 часа для гарантии наличия записи.
|
|
187
|
+
start_time = end_time - 24 * 60 * 60 * 1000
|
|
188
|
+
raw_data = await self._client.funding_rate_history(
|
|
189
|
+
symbol=symbol,
|
|
190
|
+
start_at=start_time,
|
|
191
|
+
end_at=end_time,
|
|
192
|
+
)
|
|
193
|
+
adapted_data = Adapter.funding_rate(raw_data)
|
|
194
|
+
if symbol not in adapted_data:
|
|
195
|
+
raise ValueError(f"Kucoin funding rate history is empty for {symbol}")
|
|
196
|
+
return adapted_data[symbol]
|
|
137
197
|
|
|
138
198
|
@overload
|
|
139
199
|
async def open_interest(self, symbol: str) -> OpenInterestItem: ...
|
|
@@ -20,16 +20,20 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
20
20
|
"""Реализация менеджера асинхронных унифицированных вебсокетов."""
|
|
21
21
|
|
|
22
22
|
def __init__(
|
|
23
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
client: Client | UniClient | None = None,
|
|
25
|
+
logger: LoggerLike | None = None,
|
|
26
|
+
**ws_kwargs: Any,
|
|
24
27
|
) -> None:
|
|
25
28
|
"""Инициализирует унифицированный менеджер вебсокетов.
|
|
26
29
|
|
|
27
30
|
Параметры:
|
|
28
31
|
client (`Client | UniClient | None`): Клиент Kucoin или унифицированный клиент. Нужен для подключения к приватным топикам.
|
|
29
32
|
logger (`LoggerLike | None`): Логгер для записи логов.
|
|
33
|
+
ws_kwargs (`dict[str, Any]`): Дополнительные параметры инициализации, которые будут переданы WebsocketManager/Websocket.
|
|
30
34
|
"""
|
|
31
35
|
super().__init__(client=client, logger=logger)
|
|
32
|
-
self._websocket_manager = WebsocketManager(self._client) # type: ignore
|
|
36
|
+
self._websocket_manager = WebsocketManager(self._client, **ws_kwargs) # type: ignore
|
|
33
37
|
self._adapter = Adapter()
|
|
34
38
|
|
|
35
39
|
@overload
|