unicex 0.13.18__tar.gz → 0.14.2__tar.gz

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.
Files changed (106) hide show
  1. {unicex-0.13.18/unicex.egg-info → unicex-0.14.2}/PKG-INFO +1 -1
  2. {unicex-0.13.18 → unicex-0.14.2}/pyproject.toml +1 -1
  3. {unicex-0.13.18 → unicex-0.14.2}/unicex/__init__.py +18 -0
  4. {unicex-0.13.18 → unicex-0.14.2}/unicex/enums.py +1 -3
  5. {unicex-0.13.18 → unicex-0.14.2}/unicex/extra.py +12 -17
  6. unicex-0.14.2/unicex/kucoin/__init__.py +27 -0
  7. unicex-0.14.2/unicex/kucoin/adapter.py +91 -0
  8. unicex-0.14.2/unicex/kucoin/client.py +54 -0
  9. unicex-0.14.2/unicex/kucoin/exchange_info.py +47 -0
  10. unicex-0.14.2/unicex/kucoin/uni_client.py +160 -0
  11. unicex-0.14.2/unicex/kucoin/uni_websocket_manager.py +269 -0
  12. unicex-0.14.2/unicex/kucoin/user_websocket.py +7 -0
  13. unicex-0.14.2/unicex/kucoin/websocket_manager.py +11 -0
  14. {unicex-0.13.18 → unicex-0.14.2}/unicex/mapper.py +6 -0
  15. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/adapter.py +0 -10
  16. {unicex-0.13.18 → unicex-0.14.2/unicex.egg-info}/PKG-INFO +1 -1
  17. {unicex-0.13.18 → unicex-0.14.2}/unicex.egg-info/SOURCES.txt +8 -0
  18. {unicex-0.13.18 → unicex-0.14.2}/LICENSE +0 -0
  19. {unicex-0.13.18 → unicex-0.14.2}/README.md +0 -0
  20. {unicex-0.13.18 → unicex-0.14.2}/setup.cfg +0 -0
  21. {unicex-0.13.18 → unicex-0.14.2}/unicex/_abc/__init__.py +0 -0
  22. {unicex-0.13.18 → unicex-0.14.2}/unicex/_abc/exchange_info.py +0 -0
  23. {unicex-0.13.18 → unicex-0.14.2}/unicex/_abc/uni_client.py +0 -0
  24. {unicex-0.13.18 → unicex-0.14.2}/unicex/_abc/uni_websocket_manager.py +0 -0
  25. {unicex-0.13.18 → unicex-0.14.2}/unicex/_base/__init__.py +0 -0
  26. {unicex-0.13.18 → unicex-0.14.2}/unicex/_base/client.py +0 -0
  27. {unicex-0.13.18 → unicex-0.14.2}/unicex/_base/websocket.py +0 -0
  28. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/__init__.py +0 -0
  29. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/adapter.py +0 -0
  30. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/client.py +0 -0
  31. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/exchange_info.py +0 -0
  32. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/uni_client.py +0 -0
  33. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/uni_websocket_manager.py +0 -0
  34. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/user_websocket.py +0 -0
  35. {unicex-0.13.18 → unicex-0.14.2}/unicex/binance/websocket_manager.py +0 -0
  36. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/__init__.py +0 -0
  37. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/adapter.py +0 -0
  38. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/client.py +0 -0
  39. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/exchange_info.py +0 -0
  40. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/uni_client.py +0 -0
  41. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/uni_websocket_manager.py +0 -0
  42. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/user_websocket.py +0 -0
  43. {unicex-0.13.18 → unicex-0.14.2}/unicex/bitget/websocket_manager.py +0 -0
  44. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/__init__.py +0 -0
  45. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/adapter.py +0 -0
  46. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/client.py +0 -0
  47. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/exchange_info.py +0 -0
  48. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/uni_client.py +0 -0
  49. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/uni_websocket_manager.py +0 -0
  50. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/user_websocket.py +0 -0
  51. {unicex-0.13.18 → unicex-0.14.2}/unicex/bybit/websocket_manager.py +0 -0
  52. {unicex-0.13.18 → unicex-0.14.2}/unicex/exceptions.py +0 -0
  53. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/__init__.py +0 -0
  54. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/adapter.py +0 -0
  55. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/client.py +0 -0
  56. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/exchange_info.py +0 -0
  57. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/uni_client.py +0 -0
  58. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/uni_websocket_manager.py +0 -0
  59. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/user_websocket.py +0 -0
  60. {unicex-0.13.18 → unicex-0.14.2}/unicex/gate/websocket_manager.py +0 -0
  61. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/__init__.py +0 -0
  62. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/adapter.py +0 -0
  63. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/client.py +0 -0
  64. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/exchange_info.py +0 -0
  65. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/uni_client.py +0 -0
  66. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
  67. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/user_websocket.py +0 -0
  68. {unicex-0.13.18 → unicex-0.14.2}/unicex/hyperliquid/websocket_manager.py +0 -0
  69. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/__init__.py +0 -0
  70. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +0 -0
  71. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +0 -0
  72. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +0 -0
  73. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +0 -0
  74. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +0 -0
  75. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +0 -0
  76. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +0 -0
  77. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +0 -0
  78. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +0 -0
  79. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +0 -0
  80. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +0 -0
  81. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +0 -0
  82. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +0 -0
  83. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +0 -0
  84. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +0 -0
  85. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +0 -0
  86. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +0 -0
  87. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/_spot_ws_proto/__init__.py +0 -0
  88. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/adapter.py +0 -0
  89. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/client.py +0 -0
  90. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/exchange_info.py +0 -0
  91. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/uni_client.py +0 -0
  92. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/uni_websocket_manager.py +0 -0
  93. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/user_websocket.py +0 -0
  94. {unicex-0.13.18 → unicex-0.14.2}/unicex/mexc/websocket_manager.py +0 -0
  95. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/__init__.py +0 -0
  96. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/client.py +0 -0
  97. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/exchange_info.py +0 -0
  98. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/uni_client.py +0 -0
  99. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/uni_websocket_manager.py +0 -0
  100. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/user_websocket.py +0 -0
  101. {unicex-0.13.18 → unicex-0.14.2}/unicex/okx/websocket_manager.py +0 -0
  102. {unicex-0.13.18 → unicex-0.14.2}/unicex/types.py +0 -0
  103. {unicex-0.13.18 → unicex-0.14.2}/unicex/utils.py +0 -0
  104. {unicex-0.13.18 → unicex-0.14.2}/unicex.egg-info/dependency_links.txt +0 -0
  105. {unicex-0.13.18 → unicex-0.14.2}/unicex.egg-info/requires.txt +0 -0
  106. {unicex-0.13.18 → unicex-0.14.2}/unicex.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unicex
3
- Version: 0.13.18
3
+ Version: 0.14.2
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -4,7 +4,7 @@ name = "unicex"
4
4
  # • PATCH (x.y.Z) → увеличивается при багфиксе, который не ломает совместимость.
5
5
  # • MINOR (x.Y.z) → увеличивается при добавлении новой функциональности, но без ломающих изменений (backward-compatible).
6
6
  # • MAJOR (X.y.z) → увеличивается при изменениях, которые ломают обратную совместимость.
7
- version = "0.13.18"
7
+ version = "0.14.2"
8
8
 
9
9
  description = "Unified Crypto Exchange API "
10
10
  readme = "README.md"
@@ -81,6 +81,13 @@ __all__ = [
81
81
  "GateioWebsocketManager",
82
82
  "GateioUserWebsocket",
83
83
  "GateioExchangeInfo",
84
+ # Kucoin
85
+ "KucoinClient",
86
+ "KucoinUniClient",
87
+ "KucoinUniWebsocketManager",
88
+ "KucoinWebsocketManager",
89
+ "KucoinUserWebsocket",
90
+ "KucoinExchangeInfo",
84
91
  ]
85
92
 
86
93
  # ruff: noqa
@@ -173,6 +180,15 @@ from .okx import (
173
180
  ExchangeInfo as OkxExchangeInfo,
174
181
  )
175
182
 
183
+ from .kucoin import (
184
+ Client as KucoinClient,
185
+ UniClient as KucoinUniClient,
186
+ UniWebsocketManager as KucoinUniWebsocketManager,
187
+ UserWebsocket as KucoinUserWebsocket,
188
+ WebsocketManager as KucoinWebsocketManager,
189
+ ExchangeInfo as KucoinExchangeInfo,
190
+ )
191
+
176
192
 
177
193
  async def load_exchanges_info() -> None:
178
194
  """Единожды загружает информацию о тикерах на всех биржах."""
@@ -184,6 +200,7 @@ async def load_exchanges_info() -> None:
184
200
  HyperliquidExchangeInfo.load_exchange_info(),
185
201
  MexcExchangeInfo.load_exchange_info(),
186
202
  OkxExchangeInfo.load_exchange_info(),
203
+ KucoinExchangeInfo.load_exchange_info(),
187
204
  )
188
205
 
189
206
 
@@ -197,4 +214,5 @@ async def start_exchanges_info(parse_interval_seconds: int = 60 * 60) -> None:
197
214
  HyperliquidExchangeInfo.start(parse_interval_seconds),
198
215
  MexcExchangeInfo.start(parse_interval_seconds),
199
216
  OkxExchangeInfo.start(parse_interval_seconds),
217
+ KucoinExchangeInfo.start(parse_interval_seconds),
200
218
  )
@@ -26,14 +26,12 @@ class Exchange(StrEnum):
26
26
 
27
27
  BINANCE = "BINANCE"
28
28
  BITGET = "BITGET"
29
- BITUNIX = "BITUNIX"
30
29
  BYBIT = "BYBIT"
31
30
  GATE = "GATE"
32
31
  HYPERLIQUID = "HYPERLIQUID"
33
- KCEX = "KCEX"
34
32
  MEXC = "MEXC"
35
33
  OKX = "OKX"
36
- XT = "XT"
34
+ KUCOIN = "KUCOIN"
37
35
 
38
36
  def __add__(self, market_type: "MarketType") -> tuple["Exchange", "MarketType"]:
39
37
  """Возвращает кортеж из биржи и типа рынка."""
@@ -125,6 +125,10 @@ def normalize_ticker(raw_ticker: str) -> str:
125
125
  .removesuffix("SWAP")
126
126
  )
127
127
 
128
+ # Меняем постфикс USDTM на USDT
129
+ if ticker.endswith("USDTM"):
130
+ ticker = ticker.removesuffix("USDTM") + "USDT"
131
+
128
132
  # Удаляем разделители
129
133
  ticker = ticker.translate(str.maketrans("", "", "-_."))
130
134
 
@@ -149,6 +153,7 @@ def normalize_symbol(raw_ticker: str, quote: Literal["USDT", "USDC"] = "USDT") -
149
153
  normalize_symbol("BTC") # "BTCUSDT"
150
154
  normalize_symbol("btc_usdt_swap") # "BTCUSDT"
151
155
  normalize_symbol("ETH", "USDC") # "ETHUSDC"
156
+ normalize_symbol("BTCUSDTM") # "BTCUSDT"
152
157
  ```
153
158
 
154
159
  Параметры:
@@ -206,26 +211,16 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
206
211
  return f"https://www.gate.com/ru/futures/USDT/{ticker}_USDT"
207
212
  else:
208
213
  return f"https://www.gate.com/ru/trade/{ticker}_USDT"
209
- elif exchange == Exchange.XT:
210
- if market_type == MarketType.FUTURES:
211
- return f"https://www.xt.com/ru/futures/trade/{ticker.lower()}_usdt"
212
- else:
213
- return f"https://www.xt.com/ru/trade/{ticker.lower()}_usdt"
214
- elif exchange == Exchange.BITUNIX:
215
- if market_type == MarketType.FUTURES:
216
- return f"https://www.bitunix.com/ru-ru/contract-trade/{ticker.upper()}USDT"
217
- else:
218
- return f"https://www.bitunix.com/ru-ru/spot-trade/{ticker.upper()}USDT"
219
- elif exchange == Exchange.KCEX:
220
- if market_type == MarketType.FUTURES:
221
- return f"https://www.kcex.com/ru-RU/futures/exchange/{ticker.upper()}_USDT"
222
- else:
223
- return f"https://www.kcex.com/ru-RU/exchange/{ticker.upper()}_USDT"
224
214
  elif exchange == Exchange.HYPERLIQUID:
225
215
  if market_type == MarketType.FUTURES:
226
216
  return f"https://app.hyperliquid.xyz/trade/{ticker}"
227
217
  else:
228
218
  return f"https://app.hyperliquid.xyz/trade/{ticker}/USDC"
219
+ elif exchange == Exchange.KUCOIN:
220
+ if market_type == MarketType.FUTURES:
221
+ return f"https://www.kucoin.com/trade/futures/{symbol}M"
222
+ else:
223
+ return f"https://www.kucoin.com/trade/{ticker}-USDT"
229
224
  else:
230
225
  raise NotSupported(f"Exchange {exchange} is not supported")
231
226
 
@@ -274,10 +269,10 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
274
269
  return f"{base_url}/Bitget_{symbol}_UMCBL"
275
270
  case Exchange.GATE:
276
271
  return f"{base_url}/Gate_{symbol.replace('USDT', '_USDT')}"
277
- case Exchange.BITUNIX:
278
- return f"{base_url}/Bitunix_{symbol}"
279
272
  case Exchange.HYPERLIQUID:
280
273
  return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
274
+ case Exchange.KUCOIN:
275
+ return f"https://www.coinglass.com/tv/ru/KuCoin_{symbol}M"
281
276
  case _:
282
277
  return f"{base_url}/{exchange.capitalize()}_{symbol}"
283
278
  else:
@@ -0,0 +1,27 @@
1
+ """Пакет, содержащий реализации клиентов и менеджеров для работы с биржей Kucoin."""
2
+
3
+ __all__ = [
4
+ "Client",
5
+ "UniClient",
6
+ "UserWebsocket",
7
+ "WebsocketManager",
8
+ "UniWebsocketManager",
9
+ "ExchangeInfo",
10
+ ]
11
+
12
+ from .client import Client
13
+ from .exchange_info import ExchangeInfo
14
+ from .uni_client import UniClient
15
+ from .uni_websocket_manager import UniWebsocketManager
16
+ from .user_websocket import UserWebsocket
17
+ from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже Kucoin."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Kucoin."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
@@ -0,0 +1,91 @@
1
+ __all__ = ["Adapter"]
2
+
3
+ from typing import Any
4
+
5
+ from unicex.types import OpenInterestDict, OpenInterestItem, TickerDailyDict, TickerDailyItem
6
+ from unicex.utils import catch_adapter_errors, decorate_all_methods
7
+
8
+ from .exchange_info import ExchangeInfo
9
+
10
+
11
+ @decorate_all_methods(catch_adapter_errors)
12
+ class Adapter:
13
+ """Адаптер для унификации данных с Kucoin API."""
14
+
15
+ @staticmethod
16
+ def tickers(raw_data: dict, only_usdt: bool) -> list[str]:
17
+ """Преобразует сырой ответ, в котором содержатся данные о тикерах в список тикеров.
18
+
19
+ Параметры:
20
+ raw_data (dict): Сырой ответ с биржи.
21
+ only_usdt (bool): Флаг, указывающий, нужно ли включать только тикеры в паре к USDT.
22
+
23
+ Возвращает:
24
+ list[str]: Список тикеров.
25
+ """
26
+ return [
27
+ item["symbol"]
28
+ for item in raw_data["data"]["list"]
29
+ if item["symbol"].endswith("USDTM") or not only_usdt
30
+ ]
31
+
32
+ @staticmethod
33
+ def ticker_24hr(raw_data: dict) -> TickerDailyDict:
34
+ """Преобразует сырой ответ, в котором содержатся данные о тикере за последние 24 часа в унифицированный формат.
35
+
36
+ Параметры:
37
+ raw_data (dict): Сырой ответ с биржи.
38
+
39
+ Возвращает:
40
+ TickerDailyDict: Словарь, где ключ - тикер, а значение - статистика за последние 24 часа.
41
+ """
42
+ return {
43
+ item["symbol"]: TickerDailyItem(
44
+ p=(
45
+ round((float(item["lastPrice"]) / float(item["open"]) - 1) * 100, 2)
46
+ if float(item["open"]) != 0
47
+ else 0.0
48
+ ),
49
+ v=float(item["baseVolume"]),
50
+ q=float(item["quoteVolume"]),
51
+ )
52
+ for item in raw_data["data"]["list"]
53
+ }
54
+
55
+ @staticmethod
56
+ def last_price(raw_data: dict) -> dict[str, float]:
57
+ """Преобразует сырой ответ, в котором содержатся данные о последней цене, в унифицированный формат.
58
+
59
+ Параметры:
60
+ raw_data (dict): Сырой ответ с биржи.
61
+
62
+ Возвращает:
63
+ dict[str, float]: Словарь, где ключ - тикер, а значение - последняя цена.
64
+ """
65
+ return {item["symbol"]: float(item["lastPrice"]) for item in raw_data["data"]["list"]}
66
+
67
+ @staticmethod
68
+ def open_interest(raw_data: dict[str, Any]) -> OpenInterestDict:
69
+ """Преобразует сырой ответ, в котором содержатся данные об открытом интересе, в унифицированный формат.
70
+
71
+ Параметры:
72
+ raw_data (dict): Сырой ответ с биржи.
73
+
74
+ Возвращает:
75
+ OpenInterestDict: Словарь, где ключ - тикер, а значение - агрегированные данные открытого интереса.
76
+ """
77
+ return {
78
+ item["symbol"]: OpenInterestItem(
79
+ t=item["ts"],
80
+ v=float(item["openInterest"]) * Adapter._get_contract_size(item["symbol"]),
81
+ )
82
+ for item in raw_data["data"]
83
+ }
84
+
85
+ @staticmethod
86
+ def _get_contract_size(symbol: str) -> float:
87
+ """Возвращает размер контракта для указанного символа тикера."""
88
+ try:
89
+ return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
90
+ except: # noqa
91
+ return 1
@@ -0,0 +1,54 @@
1
+ __all__ = ["Client"]
2
+
3
+
4
+ from typing import Any, Literal
5
+
6
+ from unicex._base import BaseClient
7
+
8
+
9
+ class Client(BaseClient):
10
+ """Клиент для работы с Kucoin API."""
11
+
12
+ _BASE_URL: str = "https://api.kucoin.com"
13
+ """Базовый URL для запросов."""
14
+
15
+ async def symbol(
16
+ self,
17
+ trade_type: Literal["SPOT", "FUTURES", "ISOLATED", "CROSS"],
18
+ symbol: str | None = None,
19
+ ) -> dict[str, Any]:
20
+ """Получение символов и информации о них.
21
+
22
+ https://www.kucoin.com/docs-new/rest/ua/get-symbol
23
+ """
24
+ url = self._BASE_URL + "/api/ua/v1/market/instrument"
25
+ params = {"tradeType": trade_type}
26
+ if symbol:
27
+ params["symbol"] = symbol
28
+
29
+ return await self._make_request("GET", url, params=params)
30
+
31
+ async def ticker(
32
+ self,
33
+ trade_type: Literal["SPOT", "FUTURES"],
34
+ symbol: str | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Получение тикеров и информации о них.
37
+
38
+ https://www.kucoin.com/docs-new/rest/ua/get-ticker
39
+ """
40
+ url = self._BASE_URL + "/api/ua/v1/market/ticker"
41
+ params = {"tradeType": trade_type}
42
+ if symbol:
43
+ params["symbol"] = symbol
44
+
45
+ return await self._make_request("GET", url, params=params)
46
+
47
+ async def open_interest(self) -> dict[str, Any]:
48
+ """Получение открытого интереса.
49
+
50
+ https://www.kucoin.com/docs-new/3476287e0
51
+ """
52
+ url = self._BASE_URL + "/api/ua/v1/market/open-interest"
53
+
54
+ return await self._make_request("GET", url)
@@ -0,0 +1,47 @@
1
+ __all__ = ["ExchangeInfo"]
2
+
3
+ import aiohttp
4
+
5
+ from unicex._abc import IExchangeInfo
6
+ from unicex.types import TickerInfoItem
7
+
8
+ from .client import Client
9
+
10
+
11
+ class ExchangeInfo(IExchangeInfo):
12
+ """Предзагружает информацию о тикерах для биржи Kucoin."""
13
+
14
+ exchange_name = "Kucoin"
15
+ """Название биржи, на которой работает класс."""
16
+
17
+ @classmethod
18
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
19
+ """Загружает информацию о бирже для спотового рынка."""
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=None,
26
+ size_precision=None,
27
+ size_step=None,
28
+ contract_size=1,
29
+ )
30
+
31
+ cls._tickers_info = tickers_info
32
+
33
+ @classmethod
34
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
35
+ """Загружает информацию о бирже для фьючерсного рынка."""
36
+ tickers_info = {}
37
+ exchange_info = await Client(session).symbol("FUTURES")
38
+ for el in exchange_info["data"]["list"]:
39
+ tickers_info[el["symbol"]] = TickerInfoItem(
40
+ tick_precision=None,
41
+ tick_step=None,
42
+ size_precision=None,
43
+ size_step=None,
44
+ contract_size=float(el["unitSize"]),
45
+ )
46
+
47
+ cls._futures_tickers_info = tickers_info
@@ -0,0 +1,160 @@
1
+ __all__ = ["UniClient"]
2
+
3
+
4
+ from typing import overload
5
+
6
+ from unicex._abc import IUniClient
7
+ from unicex.enums import Timeframe
8
+ from unicex.types import KlineDict, OpenInterestDict, OpenInterestItem, TickerDailyDict
9
+
10
+ from .adapter import Adapter
11
+ from .client import Client
12
+
13
+
14
+ class UniClient(IUniClient[Client]):
15
+ """Унифицированный клиент для работы с Kucoin API."""
16
+
17
+ @property
18
+ def _client_cls(self) -> type[Client]:
19
+ """Возвращает класс клиента для Kucoin.
20
+
21
+ Возвращает:
22
+ type[Client]: Класс клиента для Kucoin.
23
+ """
24
+ return Client
25
+
26
+ async def tickers(self, only_usdt: bool = True) -> list[str]:
27
+ """Возвращает список тикеров.
28
+
29
+ Параметры:
30
+ only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
31
+
32
+ Возвращает:
33
+ list[str]: Список тикеров.
34
+ """
35
+ raw_data = await self._client.ticker("SPOT")
36
+ return Adapter.tickers(raw_data, only_usdt)
37
+
38
+ async def futures_tickers(self, only_usdt: bool = True) -> list[str]:
39
+ """Возвращает список тикеров.
40
+
41
+ Параметры:
42
+ only_usdt (bool): Если True, возвращает только тикеры в паре к USDT.
43
+
44
+ Возвращает:
45
+ list[str]: Список тикеров.
46
+ """
47
+ raw_data = await self._client.ticker("FUTURES")
48
+ return Adapter.tickers(raw_data, only_usdt)
49
+
50
+ async def last_price(self) -> dict[str, float]:
51
+ """Возвращает последнюю цену для каждого тикера.
52
+
53
+ Возвращает:
54
+ dict[str, float]: Словарь с последними ценами для каждого тикера.
55
+ """
56
+ raw_data = await self._client.ticker("SPOT")
57
+ return Adapter.last_price(raw_data)
58
+
59
+ async def futures_last_price(self) -> dict[str, float]:
60
+ """Возвращает последнюю цену для каждого тикера.
61
+
62
+ Возвращает:
63
+ dict[str, float]: Словарь с последними ценами для каждого тикера.
64
+ """
65
+ raw_data = await self._client.ticker("FUTURES")
66
+ return Adapter.last_price(raw_data)
67
+
68
+ async def ticker_24hr(self) -> TickerDailyDict:
69
+ """Возвращает статистику за последние 24 часа для каждого тикера.
70
+
71
+ Возвращает:
72
+ TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
73
+ """
74
+ raw_data = await self._client.ticker("SPOT")
75
+ return Adapter.ticker_24hr(raw_data)
76
+
77
+ async def futures_ticker_24hr(self) -> TickerDailyDict:
78
+ """Возвращает статистику за последние 24 часа для каждого тикера.
79
+
80
+ Возвращает:
81
+ TickerDailyDict: Словарь с статистикой за последние 24 часа для каждого тикера.
82
+ """
83
+ raw_data = await self._client.ticker("FUTURES")
84
+ return Adapter.ticker_24hr(raw_data)
85
+
86
+ async def klines(
87
+ self,
88
+ symbol: str,
89
+ interval: Timeframe | str,
90
+ limit: int | None = None,
91
+ start_time: int | None = None,
92
+ end_time: int | None = None,
93
+ ) -> list[KlineDict]:
94
+ """Возвращает список свечей для тикера.
95
+
96
+ Параметры:
97
+ symbol (str): Название тикера.
98
+ limit (int | None): Количество свечей.
99
+ interval (Timeframe | str): Таймфрейм свечей.
100
+ start_time (int | None): Время начала периода в миллисекундах.
101
+ end_time (int | None): Время окончания периода в миллисекундах.
102
+
103
+ Возвращает:
104
+ list[KlineDict]: Список свечей для тикера.
105
+ """
106
+ raise NotImplementedError()
107
+
108
+ async def futures_klines(
109
+ self,
110
+ symbol: str,
111
+ interval: Timeframe | str,
112
+ limit: int | None = None,
113
+ start_time: int | None = None,
114
+ end_time: int | None = None,
115
+ ) -> list[KlineDict]:
116
+ """Возвращает список свечей для тикера.
117
+
118
+ Параметры:
119
+ symbol (str): Название тикера.
120
+ limit (int | None): Количество свечей.
121
+ interval (Timeframe | str): Таймфрейм свечей.
122
+ start_time (int | None): Время начала периода в миллисекундах.
123
+ end_time (int | None): Время окончания периода в миллисекундах.
124
+
125
+ Возвращает:
126
+ list[KlineDict]: Список свечей для тикера.
127
+ """
128
+ raise NotImplementedError()
129
+
130
+ async def funding_rate(self) -> dict[str, float]:
131
+ """Возвращает ставку финансирования для всех тикеров.
132
+
133
+ Возвращает:
134
+ dict[str, float]: Ставка финансирования для каждого тикера.
135
+ """
136
+ raise NotImplementedError()
137
+
138
+ @overload
139
+ async def open_interest(self, symbol: str) -> OpenInterestItem: ...
140
+
141
+ @overload
142
+ async def open_interest(self, symbol: None) -> OpenInterestDict: ...
143
+
144
+ @overload
145
+ async def open_interest(self) -> OpenInterestDict: ...
146
+
147
+ async def open_interest(self, symbol: str | None = None) -> OpenInterestItem | OpenInterestDict:
148
+ """Возвращает объем открытого интереса для тикера или всех тикеров, если тикер не указан.
149
+
150
+ Параметры:
151
+ symbol (`str | None`): Название тикера. (Опционально, но обязателен для следующих бирж: BINANCE).
152
+
153
+ Возвращает:
154
+ `OpenInterestItem | OpenInterestDict`: Если тикер передан - словарь со временем и объемом
155
+ открытого интереса в монетах. Если нет передан - то словарь, в котором ключ - тикер,
156
+ а значение - словарь с временем и объемом открытого интереса в монетах.
157
+ """
158
+ raw_data = await self._client.open_interest()
159
+ adapted_data = Adapter.open_interest(raw_data)
160
+ return adapted_data[symbol] if symbol else adapted_data
@@ -0,0 +1,269 @@
1
+ __all__ = ["IUniWebsocketManager"]
2
+
3
+ from collections.abc import Awaitable, Callable, Sequence
4
+ from typing import Any, overload
5
+
6
+ from unicex._abc import IUniWebsocketManager
7
+ from unicex._base import Websocket
8
+ from unicex.enums import Timeframe
9
+ from unicex.types import LoggerLike
10
+
11
+ from .adapter import Adapter
12
+ from .client import Client
13
+ from .uni_client import UniClient
14
+ from .websocket_manager import WebsocketManager
15
+
16
+ type CallbackType = Callable[[Any], Awaitable[None]]
17
+
18
+
19
+ class UniWebsocketManager(IUniWebsocketManager):
20
+ """Реализация менеджера асинхронных унифицированных вебсокетов."""
21
+
22
+ def __init__(
23
+ self, client: Client | UniClient | None = None, logger: LoggerLike | None = None
24
+ ) -> None:
25
+ """Инициализирует унифицированный менеджер вебсокетов.
26
+
27
+ Параметры:
28
+ client (`Client | UniClient | None`): Клиент Kucoin или унифицированный клиент. Нужен для подключения к приватным топикам.
29
+ logger (`LoggerLike | None`): Логгер для записи логов.
30
+ """
31
+ super().__init__(client=client, logger=logger)
32
+ self._websocket_manager = WebsocketManager(self._client) # type: ignore
33
+ self._adapter = Adapter()
34
+
35
+ @overload
36
+ def klines(
37
+ self,
38
+ callback: CallbackType,
39
+ timeframe: Timeframe,
40
+ *,
41
+ symbol: str,
42
+ symbols: None = None,
43
+ ) -> Websocket: ...
44
+
45
+ @overload
46
+ def klines(
47
+ self,
48
+ callback: CallbackType,
49
+ timeframe: Timeframe,
50
+ *,
51
+ symbol: None = None,
52
+ symbols: Sequence[str],
53
+ ) -> Websocket: ...
54
+
55
+ def klines(
56
+ self,
57
+ callback: CallbackType,
58
+ timeframe: Timeframe,
59
+ symbol: str | None = None,
60
+ symbols: Sequence[str] | None = None,
61
+ ) -> Websocket:
62
+ """Открывает стрим свечей (spot) с унификацией сообщений.
63
+
64
+ Параметры:
65
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
66
+ timeframe (`Timeframe`): Временной интервал свечей.
67
+ symbol (`str | None`): Один символ для подписки.
68
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
69
+
70
+ Должен быть указан либо `symbol`, либо `symbols`.
71
+
72
+ Возвращает:
73
+ `Websocket`: Экземпляр вебсокета для управления соединением.
74
+ """
75
+ raise NotImplementedError()
76
+
77
+ @overload
78
+ def futures_klines(
79
+ self,
80
+ callback: CallbackType,
81
+ timeframe: Timeframe,
82
+ *,
83
+ symbol: str,
84
+ symbols: None = None,
85
+ ) -> Websocket: ...
86
+
87
+ @overload
88
+ def futures_klines(
89
+ self,
90
+ callback: CallbackType,
91
+ timeframe: Timeframe,
92
+ *,
93
+ symbol: None = None,
94
+ symbols: Sequence[str],
95
+ ) -> Websocket: ...
96
+
97
+ def futures_klines(
98
+ self,
99
+ callback: CallbackType,
100
+ timeframe: Timeframe,
101
+ symbol: str | None = None,
102
+ symbols: Sequence[str] | None = None,
103
+ ) -> Websocket:
104
+ """Открывает стрим свечей (futures) с унификацией сообщений.
105
+
106
+ Параметры:
107
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
108
+ timeframe (`Timeframe`): Временной интервал свечей.
109
+ symbol (`str | None`): Один символ для подписки.
110
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
111
+
112
+ Должен быть указан либо `symbol`, либо `symbols`.
113
+
114
+ Возвращает:
115
+ `Websocket`: Экземпляр вебсокета.
116
+ """
117
+ raise NotImplementedError()
118
+
119
+ @overload
120
+ def trades(
121
+ self,
122
+ callback: CallbackType,
123
+ *,
124
+ symbol: str,
125
+ symbols: None = None,
126
+ ) -> Websocket: ...
127
+
128
+ @overload
129
+ def trades(
130
+ self,
131
+ callback: CallbackType,
132
+ *,
133
+ symbol: None = None,
134
+ symbols: Sequence[str],
135
+ ) -> Websocket: ...
136
+
137
+ def trades(
138
+ self,
139
+ callback: CallbackType,
140
+ symbol: str | None = None,
141
+ symbols: Sequence[str] | None = None,
142
+ ) -> Websocket:
143
+ """Открывает стрим сделок (spot) с унификацией сообщений.
144
+
145
+ Параметры:
146
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
147
+ symbol (`str | None`): Один символ для подписки.
148
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
149
+
150
+ Должен быть указан либо `symbol`, либо `symbols`.
151
+
152
+ Возвращает:
153
+ `Websocket`: Экземпляр вебсокета.
154
+ """
155
+ raise NotImplementedError()
156
+
157
+ @overload
158
+ def aggtrades(
159
+ self,
160
+ callback: CallbackType,
161
+ *,
162
+ symbol: str,
163
+ symbols: None = None,
164
+ ) -> Websocket: ...
165
+
166
+ @overload
167
+ def aggtrades(
168
+ self,
169
+ callback: CallbackType,
170
+ *,
171
+ symbol: None = None,
172
+ symbols: Sequence[str],
173
+ ) -> Websocket: ...
174
+
175
+ def aggtrades(
176
+ self,
177
+ callback: CallbackType,
178
+ symbol: str | None = None,
179
+ symbols: Sequence[str] | None = None,
180
+ ) -> Websocket:
181
+ """Открывает стрим агрегированных сделок (spot) с унификацией сообщений.
182
+
183
+ Параметры:
184
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
185
+ symbol (`str | None`): Один символ для подписки.
186
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
187
+
188
+ Должен быть указан либо `symbol`, либо `symbols`.
189
+
190
+ Возвращает:
191
+ `Websocket`: Экземпляр вебсокета.
192
+ """
193
+ raise NotImplementedError()
194
+
195
+ @overload
196
+ def futures_trades(
197
+ self,
198
+ callback: CallbackType,
199
+ *,
200
+ symbol: str,
201
+ symbols: None = None,
202
+ ) -> Websocket: ...
203
+
204
+ @overload
205
+ def futures_trades(
206
+ self,
207
+ callback: CallbackType,
208
+ *,
209
+ symbol: None = None,
210
+ symbols: Sequence[str],
211
+ ) -> Websocket: ...
212
+
213
+ def futures_trades(
214
+ self,
215
+ callback: CallbackType,
216
+ symbol: str | None = None,
217
+ symbols: Sequence[str] | None = None,
218
+ ) -> Websocket:
219
+ """Открывает стрим сделок (futures) с унификацией сообщений.
220
+
221
+ Параметры:
222
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
223
+ symbol (`str | None`): Один символ для подписки.
224
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
225
+
226
+ Должен быть указан либо `symbol`, либо `symbols`.
227
+
228
+ Возвращает:
229
+ `Websocket`: Экземпляр вебсокета.
230
+ """
231
+ raise NotImplementedError()
232
+
233
+ @overload
234
+ def futures_aggtrades(
235
+ self,
236
+ callback: CallbackType,
237
+ *,
238
+ symbol: str,
239
+ symbols: None = None,
240
+ ) -> Websocket: ...
241
+
242
+ @overload
243
+ def futures_aggtrades(
244
+ self,
245
+ callback: CallbackType,
246
+ *,
247
+ symbol: None = None,
248
+ symbols: Sequence[str],
249
+ ) -> Websocket: ...
250
+
251
+ def futures_aggtrades(
252
+ self,
253
+ callback: CallbackType,
254
+ symbol: str | None = None,
255
+ symbols: Sequence[str] | None = None,
256
+ ) -> Websocket:
257
+ """Открывает стрим агрегированных сделок (futures) с унификацией сообщений.
258
+
259
+ Параметры:
260
+ callback (`CallbackType`): Асинхронная функция обратного вызова для обработки сообщений.
261
+ symbol (`str | None`): Один символ для подписки.
262
+ symbols (`Sequence[str] | None`): Список символов для мультиплекс‑подключения.
263
+
264
+ Должен быть указан либо `symbol`, либо `symbols`.
265
+
266
+ Возвращает:
267
+ `Websocket`: Экземпляр вебсокета.
268
+ """
269
+ raise NotImplementedError()
@@ -0,0 +1,7 @@
1
+ __all__ = ["UserWebsocket"]
2
+
3
+
4
+ class UserWebsocket:
5
+ """Пользовательский вебсокет Kucoin."""
6
+
7
+ pass
@@ -0,0 +1,11 @@
1
+ __all__ = ["WebsocketManager"]
2
+
3
+
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import Any
6
+
7
+ type CallbackType = Callable[[Any], Awaitable[None]]
8
+
9
+
10
+ class WebsocketManager:
11
+ """Менеджер асинхронных вебсокетов для Kucoin."""
@@ -25,6 +25,9 @@ from .gate import UniWebsocketManager as GateioUniWebsocketManager
25
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 .kucoin import ExchangeInfo as KucoinExchangeInfo
29
+ from .kucoin import UniClient as KucoinUniClient
30
+ from .kucoin import UniWebsocketManager as KucoinUniWebsocketManager
28
31
  from .mexc import ExchangeInfo as MexcExchangeInfo
29
32
  from .mexc import UniClient as MexcUniClient
30
33
  from .mexc import UniWebsocketManager as MexcUniWebsocketManager
@@ -40,6 +43,7 @@ _UNI_CLIENT_MAPPER: dict[Exchange, type[IUniClient]] = {
40
43
  Exchange.HYPERLIQUID: HyperliquidUniClient,
41
44
  Exchange.MEXC: MexcUniClient,
42
45
  Exchange.OKX: OkxUniClient,
46
+ Exchange.KUCOIN: KucoinUniClient,
43
47
  }
44
48
  """Маппер, который связывает биржу и реализацию унифицированного клиента."""
45
49
 
@@ -51,6 +55,7 @@ _UNI_WS_MANAGER_MAPPER: dict[Exchange, type[IUniWebsocketManager]] = {
51
55
  Exchange.HYPERLIQUID: HyperliquidUniWebsocketManager,
52
56
  Exchange.MEXC: MexcUniWebsocketManager,
53
57
  Exchange.OKX: OkxUniWebsocketManager,
58
+ Exchange.KUCOIN: KucoinUniWebsocketManager,
54
59
  }
55
60
  """Маппер, который связывает биржу и реализацию унифицированного вебсокет-менеджера."""
56
61
 
@@ -62,6 +67,7 @@ _EXCHANGE_INFO_MAPPER: dict[Exchange, type[IExchangeInfo]] = {
62
67
  Exchange.HYPERLIQUID: HyperliquidExchangeInfo,
63
68
  Exchange.MEXC: MexcExchangeInfo,
64
69
  Exchange.OKX: OkxExchangeInfo,
70
+ Exchange.KUCOIN: KucoinExchangeInfo,
65
71
  }
66
72
  """Маппер, который связывает биржу и реализацию сборщика информации о тикерах на бирже."""
67
73
 
@@ -10,8 +10,6 @@ from unicex.types import (
10
10
  )
11
11
  from unicex.utils import catch_adapter_errors, decorate_all_methods
12
12
 
13
- from .exchange_info import ExchangeInfo
14
-
15
13
 
16
14
  @decorate_all_methods(catch_adapter_errors)
17
15
  class Adapter:
@@ -140,11 +138,3 @@ class Adapter:
140
138
  )
141
139
  for item in raw_data["data"]
142
140
  }
143
-
144
- @staticmethod
145
- def _get_contract_size(symbol: str) -> float:
146
- """Возвращает размер контракта для указанного символа тикера."""
147
- try:
148
- return ExchangeInfo.get_futures_ticker_info(symbol)["contract_size"] or 1
149
- except: # noqa
150
- return 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unicex
3
- Version: 0.13.18
3
+ Version: 0.14.2
4
4
  Summary: Unified Crypto Exchange API
5
5
  Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
6
6
  License: BSD 3-Clause License
@@ -60,6 +60,14 @@ unicex/hyperliquid/uni_client.py
60
60
  unicex/hyperliquid/uni_websocket_manager.py
61
61
  unicex/hyperliquid/user_websocket.py
62
62
  unicex/hyperliquid/websocket_manager.py
63
+ unicex/kucoin/__init__.py
64
+ unicex/kucoin/adapter.py
65
+ unicex/kucoin/client.py
66
+ unicex/kucoin/exchange_info.py
67
+ unicex/kucoin/uni_client.py
68
+ unicex/kucoin/uni_websocket_manager.py
69
+ unicex/kucoin/user_websocket.py
70
+ unicex/kucoin/websocket_manager.py
63
71
  unicex/mexc/__init__.py
64
72
  unicex/mexc/adapter.py
65
73
  unicex/mexc/client.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes