unicex 0.14.1__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/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: str,
332
- price: str,
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: str,
356
+ amount: NumberLike,
357
357
  text: str | None = None,
358
358
  type: str | None = None,
359
359
  account: str | None = None,
360
- price: str | None = None,
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: str | None = None,
483
- price: str | None = None,
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: int | None = None,
1348
- price: str | None = None,
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, client: Client | UniClient | None = None, logger: LoggerLike | None = None
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
@@ -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: str,
1187
+ size: NumberLike,
1188
1188
  reduce_only: bool,
1189
1189
  order_type: Literal["limit", "trigger"],
1190
1190
  order_body: dict,
1191
- price: str | None = None,
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: str | float,
1482
- size: str | float,
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: str,
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: str,
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: str,
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: str,
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: str,
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: int,
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: str | float,
2150
+ size: NumberLike,
2151
2151
  reduce_only: bool,
2152
2152
  minutes: int,
2153
2153
  randomize: bool,
@@ -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 and (start_time and end_time) must be provided")
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 = int(time.time() * 1000)
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, MarketType.SPOT)
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 = int(time.time() * 1000)
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, client: Client | UniClient | None = None, logger: LoggerLike | None = None
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,9 +2,17 @@ __all__ = ["Adapter"]
2
2
 
3
3
  from typing import Any
4
4
 
5
- from unicex.types import OpenInterestDict, OpenInterestItem, TickerDailyDict, TickerDailyItem
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
 
14
+ from .exchange_info import ExchangeInfo
15
+
8
16
 
9
17
  @decorate_all_methods(catch_adapter_errors)
10
18
  class Adapter:
@@ -14,6 +22,23 @@ class Adapter:
14
22
  def tickers(raw_data: dict, only_usdt: bool) -> list[str]:
15
23
  """Преобразует сырой ответ, в котором содержатся данные о тикерах в список тикеров.
16
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
+
17
42
  Параметры:
18
43
  raw_data (dict): Сырой ответ с биржи.
19
44
  only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре к USDT.
@@ -60,7 +85,11 @@ class Adapter:
60
85
  Возвращает:
61
86
  dict[str, float]: Словарь, где ключ - тикер, а значение - последняя цена.
62
87
  """
63
- return {item["symbol"]: float(item["lastPrice"]) for item in raw_data["data"]["list"]}
88
+ return {
89
+ item["symbol"]: float(item["lastPrice"])
90
+ for item in raw_data["data"]["list"]
91
+ if item["lastPrice"]
92
+ }
64
93
 
65
94
  @staticmethod
66
95
  def open_interest(raw_data: dict[str, Any]) -> OpenInterestDict:
@@ -75,7 +104,62 @@ class Adapter:
75
104
  return {
76
105
  item["symbol"]: OpenInterestItem(
77
106
  t=item["ts"],
78
- v=float(item["openInterest"]),
107
+ v=float(item["openInterest"]) * Adapter._get_contract_size(item["symbol"]),
79
108
  )
80
109
  for item in raw_data["data"]
81
110
  }
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
+
130
+ @staticmethod
131
+ def _get_contract_size(symbol: str) -> float:
132
+ """Возвращает размер контракта для указанного символа тикера."""
133
+ try:
134
+ return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
135
+ except: # noqa
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,14 +14,48 @@ class Client(BaseClient):
12
14
  _BASE_URL: str = "https://api.kucoin.com"
13
15
  """Базовый URL для запросов."""
14
16
 
15
- async def open_interest(self) -> dict[str, Any]:
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.
17
25
 
18
- https://www.kucoin.com/docs-new/3476287e0
26
+ Параметры:
27
+ method (str): HTTP метод запроса ("GET", "POST", "DELETE" и т.д.).
28
+ endpoint (str): URL эндпоинта Kucoin API.
29
+ params (dict | None): Параметры запроса.
30
+
31
+ Возвращает:
32
+ dict: Ответ в формате JSON.
19
33
  """
20
- url = self._BASE_URL + "/api/ua/v1/market/open-interest"
34
+ # Составляем URL для запроса
35
+ url = self._BASE_URL + endpoint
36
+
37
+ # Фильтруем параметры от None значений
38
+ params = filter_params(params) if params else {}
21
39
 
22
- return await self._make_request("GET", url)
40
+ # Выполняем запрос
41
+ return await super()._make_request(
42
+ method=method,
43
+ url=url,
44
+ params=params,
45
+ )
46
+
47
+ async def symbol(
48
+ self,
49
+ trade_type: Literal["SPOT", "FUTURES", "ISOLATED", "CROSS"],
50
+ symbol: str | None = None,
51
+ ) -> dict[str, Any]:
52
+ """Получение символов и информации о них.
53
+
54
+ https://www.kucoin.com/docs-new/rest/ua/get-symbol
55
+ """
56
+ params = {"tradeType": trade_type, "symbol": symbol}
57
+
58
+ return await self._make_request("GET", "/api/ua/v1/market/instrument", params=params)
23
59
 
24
60
  async def ticker(
25
61
  self,
@@ -30,9 +66,57 @@ class Client(BaseClient):
30
66
 
31
67
  https://www.kucoin.com/docs-new/rest/ua/get-ticker
32
68
  """
33
- url = self._BASE_URL + "/api/ua/v1/market/ticker"
34
- params = {"tradeType": trade_type}
35
- if symbol:
36
- params["symbol"] = symbol
69
+ params = {"tradeType": trade_type, "symbol": symbol}
70
+
71
+ return await self._make_request("GET", "/api/ua/v1/market/ticker", params=params)
72
+
73
+ async def open_interest(self) -> dict[str, Any]:
74
+ """Получение открытого интереса.
75
+
76
+ https://www.kucoin.com/docs-new/3476287e0
77
+ """
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
+ }
37
117
 
38
- return await self._make_request("GET", url, params=params)
118
+ return await self._make_request(
119
+ "GET",
120
+ "/api/ua/v1/market/funding-rate-history",
121
+ params=params,
122
+ )
@@ -3,6 +3,9 @@ __all__ = ["ExchangeInfo"]
3
3
  import aiohttp
4
4
 
5
5
  from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+ from .client import Client
6
9
 
7
10
 
8
11
  class ExchangeInfo(IExchangeInfo):
@@ -14,9 +17,34 @@ class ExchangeInfo(IExchangeInfo):
14
17
  @classmethod
15
18
  async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
16
19
  """Загружает информацию о бирже для спотового рынка."""
17
- ...
20
+ tickers_info = {}
21
+ exchange_info = await Client(session).symbol("SPOT")
22
+ for el in exchange_info["data"]["list"]:
23
+ tickers_info[el["symbol"]] = TickerInfoItem(
24
+ tick_precision=None,
25
+ tick_step=float(el["tickSize"]),
26
+ size_precision=None,
27
+ size_step=float(el["baseOrderStep"]),
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
18
32
 
19
33
  @classmethod
20
34
  async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
21
35
  """Загружает информацию о бирже для фьючерсного рынка."""
22
- ...
36
+ tickers_info = {}
37
+ exchange_info = await Client(session).symbol("FUTURES")
38
+ for el in exchange_info["data"]["list"]:
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}")
49
+
50
+ cls._futures_tickers_info = tickers_info
@@ -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.tickers(raw_data, only_usdt)
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
- raise NotImplementedError()
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
- raise NotImplementedError()
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
- async def funding_rate(self) -> dict[str, float]:
131
- """Возвращает ставку финансирования для всех тикеров.
176
+ Параметры:
177
+ symbol (`str | None`): Название тикера. На Kucoin параметр обязателен.
132
178
 
133
179
  Возвращает:
134
- dict[str, float]: Ставка финансирования для каждого тикера.
180
+ `dict[str, float] | float`: Ставка финансирования в процентах.
135
181
  """
136
- raise NotImplementedError()
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: ...