unicex 0.4.0__py3-none-any.whl → 0.7.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.
Files changed (98) hide show
  1. unicex/__init__.py +56 -124
  2. unicex/_abc/__init__.py +2 -0
  3. unicex/_abc/exchange_info.py +176 -0
  4. unicex/_abc/uni_client.py +2 -2
  5. unicex/_base/client.py +2 -2
  6. unicex/binance/__init__.py +12 -0
  7. unicex/binance/adapter.py +1 -1
  8. unicex/binance/exchange_info.py +12 -0
  9. unicex/bitget/__init__.py +14 -4
  10. unicex/bitget/adapter.py +1 -1
  11. unicex/bitget/exchange_info.py +12 -0
  12. unicex/bitget/uni_websocket_manager.py +1 -1
  13. unicex/bybit/__init__.py +12 -0
  14. unicex/bybit/adapter.py +1 -1
  15. unicex/bybit/exchange_info.py +12 -0
  16. unicex/enums.py +16 -5
  17. unicex/extra.py +49 -0
  18. unicex/gateio/__init__.py +12 -0
  19. unicex/gateio/adapter.py +2 -2
  20. unicex/gateio/exchange_info.py +12 -0
  21. unicex/hyperliquid/__init__.py +12 -0
  22. unicex/hyperliquid/adapter.py +140 -30
  23. unicex/hyperliquid/client.py +2208 -125
  24. unicex/hyperliquid/exchange_info.py +100 -0
  25. unicex/hyperliquid/uni_client.py +176 -22
  26. unicex/mapper.py +35 -33
  27. unicex/mexc/__init__.py +12 -0
  28. unicex/mexc/adapter.py +28 -11
  29. unicex/mexc/exchange_info.py +32 -0
  30. unicex/mexc/uni_client.py +6 -0
  31. unicex/okx/__init__.py +12 -0
  32. unicex/okx/adapter.py +23 -7
  33. unicex/okx/exchange_info.py +50 -0
  34. unicex/okx/uni_client.py +2 -2
  35. unicex/types.py +31 -0
  36. unicex-0.7.0.dist-info/METADATA +192 -0
  37. unicex-0.7.0.dist-info/RECORD +75 -0
  38. unicex/bitrue/__init__.py +0 -15
  39. unicex/bitrue/adapter.py +0 -8
  40. unicex/bitrue/client.py +0 -128
  41. unicex/bitrue/uni_client.py +0 -151
  42. unicex/bitrue/uni_websocket_manager.py +0 -269
  43. unicex/bitrue/user_websocket.py +0 -7
  44. unicex/bitrue/websocket_manager.py +0 -11
  45. unicex/bitunix/__init__.py +0 -15
  46. unicex/bitunix/adapter.py +0 -8
  47. unicex/bitunix/client.py +0 -8
  48. unicex/bitunix/uni_client.py +0 -151
  49. unicex/bitunix/uni_websocket_manager.py +0 -269
  50. unicex/bitunix/user_websocket.py +0 -7
  51. unicex/bitunix/websocket_manager.py +0 -11
  52. unicex/btse/__init__.py +0 -15
  53. unicex/btse/adapter.py +0 -8
  54. unicex/btse/client.py +0 -123
  55. unicex/btse/uni_client.py +0 -151
  56. unicex/btse/uni_websocket_manager.py +0 -269
  57. unicex/btse/user_websocket.py +0 -7
  58. unicex/btse/websocket_manager.py +0 -11
  59. unicex/kcex/__init__.py +0 -15
  60. unicex/kcex/adapter.py +0 -8
  61. unicex/kcex/client.py +0 -8
  62. unicex/kcex/uni_client.py +0 -151
  63. unicex/kcex/uni_websocket_manager.py +0 -269
  64. unicex/kcex/user_websocket.py +0 -7
  65. unicex/kcex/websocket_manager.py +0 -11
  66. unicex/kraken/__init__.py +0 -15
  67. unicex/kraken/adapter.py +0 -8
  68. unicex/kraken/client.py +0 -165
  69. unicex/kraken/uni_client.py +0 -151
  70. unicex/kraken/uni_websocket_manager.py +0 -269
  71. unicex/kraken/user_websocket.py +0 -7
  72. unicex/kraken/websocket_manager.py +0 -11
  73. unicex/kucoin/__init__.py +0 -15
  74. unicex/kucoin/adapter.py +0 -8
  75. unicex/kucoin/client.py +0 -120
  76. unicex/kucoin/uni_client.py +0 -151
  77. unicex/kucoin/uni_websocket_manager.py +0 -269
  78. unicex/kucoin/user_websocket.py +0 -7
  79. unicex/kucoin/websocket_manager.py +0 -11
  80. unicex/weex/__init__.py +0 -15
  81. unicex/weex/adapter.py +0 -8
  82. unicex/weex/client.py +0 -8
  83. unicex/weex/uni_client.py +0 -151
  84. unicex/weex/uni_websocket_manager.py +0 -269
  85. unicex/weex/user_websocket.py +0 -7
  86. unicex/weex/websocket_manager.py +0 -11
  87. unicex/xt/__init__.py +0 -15
  88. unicex/xt/adapter.py +0 -8
  89. unicex/xt/client.py +0 -8
  90. unicex/xt/uni_client.py +0 -151
  91. unicex/xt/uni_websocket_manager.py +0 -269
  92. unicex/xt/user_websocket.py +0 -7
  93. unicex/xt/websocket_manager.py +0 -11
  94. unicex-0.4.0.dist-info/METADATA +0 -170
  95. unicex-0.4.0.dist-info/RECORD +0 -123
  96. {unicex-0.4.0.dist-info → unicex-0.7.0.dist-info}/WHEEL +0 -0
  97. {unicex-0.4.0.dist-info → unicex-0.7.0.dist-info}/licenses/LICENSE +0 -0
  98. {unicex-0.4.0.dist-info → unicex-0.7.0.dist-info}/top_level.txt +0 -0
unicex/enums.py CHANGED
@@ -26,18 +26,13 @@ class Exchange(StrEnum):
26
26
 
27
27
  BINANCE = "BINANCE"
28
28
  BITGET = "BITGET"
29
- BITRUE = "BITRUE"
30
29
  BITUNIX = "BITUNIX"
31
- BTSE = "BTSE"
32
30
  BYBIT = "BYBIT"
33
31
  GATEIO = "GATEIO"
34
32
  HYPERLIQUID = "HYPERLIQUID"
35
33
  KCEX = "KCEX"
36
- KRAKEN = "KRAKEN"
37
- KUCOIN = "KUCOIN"
38
34
  MEXC = "MEXC"
39
35
  OKX = "OKX"
40
- WEEX = "WEEX"
41
36
  XT = "XT"
42
37
 
43
38
  def __add__(self, market_type: "MarketType") -> tuple["Exchange", "MarketType"]:
@@ -227,6 +222,22 @@ class Timeframe(StrEnum):
227
222
  Timeframe.WEEK_1: "7d",
228
223
  Timeframe.MONTH_1: "30d",
229
224
  },
225
+ Exchange.HYPERLIQUID: {
226
+ Timeframe.MIN_1: "1m",
227
+ Timeframe.MIN_3: "3m",
228
+ Timeframe.MIN_5: "5m",
229
+ Timeframe.MIN_15: "15m",
230
+ Timeframe.MIN_30: "30m",
231
+ Timeframe.HOUR_1: "1h",
232
+ Timeframe.HOUR_2: "2h",
233
+ Timeframe.HOUR_4: "4h",
234
+ Timeframe.HOUR_8: "8h",
235
+ Timeframe.HOUR_12: "12h",
236
+ Timeframe.DAY_1: "1d",
237
+ Timeframe.DAY_3: "3d",
238
+ Timeframe.WEEK_1: "1w",
239
+ Timeframe.MONTH_1: "1M",
240
+ },
230
241
  }
231
242
 
232
243
  def to_exchange_format(self, exchange: Exchange, market_type: MarketType | None = None) -> str:
unicex/extra.py CHANGED
@@ -7,9 +7,11 @@ __all__ = [
7
7
  "generate_ex_link",
8
8
  "generate_tv_link",
9
9
  "generate_cg_link",
10
+ "make_humanreadable",
10
11
  ]
11
12
 
12
13
  import time
14
+ from typing import Literal
13
15
 
14
16
  from .enums import Exchange, MarketType
15
17
  from .exceptions import NotSupported
@@ -205,3 +207,50 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
205
207
  return f"{base_url}/SPOT_{exchange.upper()}_{symbol.replace('USDT', '-USDT')}"
206
208
  # Для остальных бирж ссылки нет → возвращаем заглушку
207
209
  return base_url
210
+
211
+
212
+ def make_humanreadable(value: float, locale: Literal["ru", "en"] = "ru") -> str:
213
+ """Функция превращает большие числа в удобочитаемый вид.
214
+
215
+ Принимает:
216
+ value (float): число для преобразования
217
+ locale (Literal["ru", "en"]): язык для форматирования числа
218
+
219
+ Возвращает:
220
+ str: Человеческое представление числа
221
+ """
222
+ suffixes = {
223
+ "ru": {
224
+ 1_000: "тыс.",
225
+ 1_000_000: "млн.",
226
+ 1_000_000_000: "млрд.",
227
+ 1_000_000_000_000: "трлн.",
228
+ 1_000_000_000_000_000: "квдрлн.",
229
+ 1_000_000_000_000_000_000: "квнтлн.",
230
+ },
231
+ "en": {
232
+ 1_000: "K",
233
+ 1_000_000: "M",
234
+ 1_000_000_000: "B",
235
+ 1_000_000_000_000: "T",
236
+ 1_000_000_000_000_000: "Qa", # Quadrillion
237
+ 1_000_000_000_000_000_000: "Qi", # Quintillion
238
+ },
239
+ }
240
+
241
+ selected_suffixes = suffixes[locale]
242
+
243
+ for divisor in sorted(selected_suffixes.keys(), reverse=True):
244
+ if abs(value) >= divisor:
245
+ number = value / divisor
246
+ if locale == "ru":
247
+ return (
248
+ f"{number:,.2f}".replace(",", " ").replace(".", ",")
249
+ + f" {selected_suffixes[divisor]}"
250
+ )
251
+ return f"{number:,.2f} {selected_suffixes[divisor]}"
252
+
253
+ # Форматирование "малых" чисел
254
+ if locale == "ru":
255
+ return f"{value:,.2f}".replace(",", " ").replace(".", ",")
256
+ return f"{value:,.2f}"
unicex/gateio/__init__.py CHANGED
@@ -6,10 +6,22 @@ __all__ = [
6
6
  "UserWebsocket",
7
7
  "WebsocketManager",
8
8
  "UniWebsocketManager",
9
+ "ExchangeInfo",
9
10
  ]
10
11
 
11
12
  from .client import Client
13
+ from .exchange_info import ExchangeInfo
12
14
  from .uni_client import UniClient
13
15
  from .uni_websocket_manager import UniWebsocketManager
14
16
  from .user_websocket import UserWebsocket
15
17
  from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже Gateio."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Gateio."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
unicex/gateio/adapter.py CHANGED
@@ -32,7 +32,7 @@ class Adapter:
32
32
  return [
33
33
  item["currency_pair"]
34
34
  for item in raw_data
35
- if not only_usdt or item["currency_pair"].endswith("USDT")
35
+ if item["currency_pair"].endswith("USDT") or not only_usdt
36
36
  ]
37
37
 
38
38
  @staticmethod
@@ -49,7 +49,7 @@ class Adapter:
49
49
  return [
50
50
  item["contract"]
51
51
  for item in raw_data
52
- if not only_usdt or item["contract"].endswith("USDT")
52
+ if item["contract"].endswith("USDT") or not only_usdt
53
53
  ]
54
54
 
55
55
  @staticmethod
@@ -0,0 +1,12 @@
1
+ __all__ = ["ExchangeInfo"]
2
+
3
+ from unicex._abc import IExchangeInfo
4
+
5
+
6
+ class ExchangeInfo(IExchangeInfo):
7
+ """Предзагружает информацию о тикерах для биржи Gateio."""
8
+
9
+ @classmethod
10
+ async def _load_exchange_info(cls) -> None:
11
+ """Загружает информацию о бирже."""
12
+ ...
@@ -6,10 +6,22 @@ __all__ = [
6
6
  "UserWebsocket",
7
7
  "WebsocketManager",
8
8
  "UniWebsocketManager",
9
+ "ExchangeInfo",
9
10
  ]
10
11
 
11
12
  from .client import Client
13
+ from .exchange_info import ExchangeInfo
12
14
  from .uni_client import UniClient
13
15
  from .uni_websocket_manager import UniWebsocketManager
14
16
  from .user_websocket import UserWebsocket
15
17
  from .websocket_manager import WebsocketManager
18
+
19
+
20
+ async def load_exchange_info() -> None:
21
+ """Загружает информацию о бирже Hyperliquid."""
22
+ await ExchangeInfo.load_exchange_info()
23
+
24
+
25
+ async def start_exchange_info(parse_interval_seconds: int = 60 * 60) -> None:
26
+ """Запускает процесс обновления информации о бирже Hyperliquid."""
27
+ await ExchangeInfo.start(parse_interval_seconds)
@@ -3,80 +3,119 @@ __all__ = ["Adapter"]
3
3
  import time
4
4
 
5
5
  from unicex.types import (
6
+ KlineDict,
6
7
  OpenInterestDict,
7
8
  OpenInterestItem,
8
9
  TickerDailyDict,
9
10
  TickerDailyItem,
10
11
  )
11
- from unicex.utils import catch_adapter_errors, decorate_all_methods
12
12
 
13
+ # from unicex.utils import catch_adapter_errors, decorate_all_methods
14
+ from .exchange_info import ExchangeInfo
13
15
 
14
- @decorate_all_methods(catch_adapter_errors)
16
+
17
+ # @decorate_all_methods(catch_adapter_errors)
15
18
  class Adapter:
16
19
  """Адаптер для унификации данных с Hyperliquid API."""
17
20
 
18
21
  @staticmethod
19
- def tickers(raw_data: list) -> list[str]:
22
+ def tickers(raw_data: dict, resolve_symbols: bool) -> list[str]:
20
23
  """Преобразует данные Hyperliquid в список спотовых тикеров.
21
24
 
22
25
  Параметры:
23
- raw_data (list): Сырой ответ с биржи.
26
+ raw_data (dict): Сырой ответ с биржи.
27
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
24
28
 
25
29
  Возвращает:
26
- list[str]: Список тикеров (например, "PURR/USDC").
30
+ list[str]: Список тикеров (например, "@123").
27
31
  """
28
- raise NotImplementedError()
32
+ if resolve_symbols:
33
+ return [ExchangeInfo.resolve_spot_symbol(item["name"]) for item in raw_data["universe"]]
34
+ else:
35
+ return [item["name"] for item in raw_data["universe"]]
29
36
 
30
37
  @staticmethod
31
- def futures_tickers(raw_data: list) -> list[str]:
38
+ def futures_tickers(raw_data: dict) -> list[str]:
32
39
  """Преобразует данные Hyperliquid в список фьючерсных тикеров.
33
40
 
34
41
  Параметры:
35
- raw_data (list): Сырой ответ с биржи.
42
+ raw_data (dict): Сырой ответ с биржи.
36
43
 
37
44
  Возвращает:
38
- list[str]: Список тикеров.
45
+ list[str]: Список тикеров (например, "@123").
39
46
  """
40
- universe = raw_data[0]["universe"]
41
- return [item["name"] for item in universe]
47
+ return [item["name"] for item in raw_data["universe"]]
42
48
 
43
49
  @staticmethod
44
- def last_price(raw_data: list) -> dict[str, float]:
50
+ def last_price(raw_data: dict, resolve_symbols: bool) -> dict[str, float]:
45
51
  """Преобразует данные о последних ценах (spot) в унифицированный формат.
46
52
 
47
53
  Параметры:
48
- raw_data (list): Сырой ответ с биржи.
54
+ raw_data (dict): Сырой ответ с биржи.
55
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
49
56
 
50
57
  Возвращает:
51
58
  dict[str, float]: Словарь тикеров и последних цен.
52
59
  """
53
- raise NotImplementedError()
60
+ if resolve_symbols:
61
+ return {
62
+ ExchangeInfo.resolve_spot_symbol(token): float(price)
63
+ for token, price in raw_data.items()
64
+ if token.startswith("@")
65
+ }
66
+ else:
67
+ return {
68
+ token: float(price) for token, price in raw_data.items() if token.startswith("@")
69
+ }
54
70
 
55
71
  @staticmethod
56
- def futures_last_price(raw_data: list) -> dict[str, float]:
72
+ def futures_last_price(raw_data: dict) -> dict[str, float]:
57
73
  """Преобразует данные о последних ценах (futures) в унифицированный формат.
58
74
 
59
75
  Параметры:
60
- raw_data (list): Сырой ответ с биржи.
76
+ raw_data (dict): Сырой ответ с биржи.
61
77
 
62
78
  Возвращает:
63
79
  dict[str, float]: Словарь тикеров и последних цен.
64
80
  """
65
- universe = raw_data[0]["universe"]
66
- metrics = raw_data[1]
67
- return {universe[i]["name"]: float(item["markPx"]) for i, item in enumerate(metrics)}
81
+ return {k: v for k, v in raw_data.items() if not k.startswith("@")}
68
82
 
69
83
  @staticmethod
70
- def ticker_24hr(raw_data: list) -> TickerDailyDict:
84
+ def ticker_24hr(raw_data: list, resolve_symbols: bool) -> TickerDailyDict:
71
85
  """Преобразует 24-часовую статистику (spot) в унифицированный формат.
72
86
 
73
87
  Параметры:
74
88
  raw_data (list): Сырой ответ с биржи.
89
+ resolve_symbols (bool): Если True, тикеры маппятся из вида "@123" в "BTC".
75
90
 
76
91
  Возвращает:
77
92
  TickerDailyDict: Словарь тикеров и их статистики.
78
93
  """
79
- raise NotImplementedError()
94
+ metrics = raw_data[1]
95
+ result: TickerDailyDict = {}
96
+
97
+ for item in metrics:
98
+ try:
99
+ coin = item["coin"]
100
+
101
+ if resolve_symbols:
102
+ coin = ExchangeInfo.resolve_spot_symbol(coin) or coin
103
+
104
+ prev_day_px = float(item.get("prevDayPx") or "0")
105
+ mid_px = float(item.get("midPx") or "0")
106
+ mark_px = float(item.get("markPx") or "0")
107
+ day_ntl_vlm = float(item.get("dayNtlVlm") or "0")
108
+
109
+ p = ((mark_px - prev_day_px) / prev_day_px * 100) if prev_day_px else 0.0
110
+ v = (day_ntl_vlm / mid_px) if mid_px else 0.0
111
+ q = day_ntl_vlm
112
+
113
+ result[coin] = TickerDailyItem(p=p, v=v, q=q)
114
+
115
+ except (KeyError, TypeError, ValueError):
116
+ continue
117
+
118
+ return result
80
119
 
81
120
  @staticmethod
82
121
  def futures_ticker_24hr(raw_data: list) -> TickerDailyDict:
@@ -90,16 +129,87 @@ class Adapter:
90
129
  """
91
130
  universe = raw_data[0]["universe"]
92
131
  metrics = raw_data[1]
93
- return {
94
- universe[i]["name"]: TickerDailyItem(
95
- p=(float(item["markPx"]) - float(item["prevDayPx"]))
96
- / float(item["prevDayPx"])
97
- * 100,
98
- v=float(item["dayNtlVlm"]) / float(item["oraclePx"]),
99
- q=float(item["dayNtlVlm"]),
132
+
133
+ result: TickerDailyDict = {}
134
+
135
+ for i, item in enumerate(metrics):
136
+ try:
137
+ prev_day_px = float(item.get("prevDayPx", 0) or "0")
138
+ oracle_px = float(item.get("oraclePx", 0) or "0")
139
+ mark_px = float(item.get("markPx", 0) or "0")
140
+ day_ntl_vlm = float(item.get("dayNtlVlm", 0) or "0")
141
+
142
+ p = ((mark_px - prev_day_px) / prev_day_px * 100) if prev_day_px else 0.0
143
+ v = (day_ntl_vlm / oracle_px) if oracle_px else 0.0
144
+ q = day_ntl_vlm
145
+
146
+ result[universe[i]["name"]] = TickerDailyItem(p=p, v=v, q=q)
147
+ except (KeyError, TypeError, ValueError):
148
+ continue
149
+
150
+ return result
151
+
152
+ @staticmethod
153
+ def klines(raw_data: list[dict], resolve_symbols: bool) -> list[KlineDict]:
154
+ """Преобразует сырой ответ, в котором содержатся данные о свечах, в унифицированный формат.
155
+
156
+ Параметры:
157
+ raw_data (list[dict]): Сырой ответ с биржи.
158
+ resolve_symbols (bool): Если True, тикер маппится из вида "@123" в "BTC".
159
+
160
+ Возвращает:
161
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
162
+ """
163
+ return [
164
+ KlineDict(
165
+ s=kline["s"]
166
+ if not resolve_symbols
167
+ else ExchangeInfo.resolve_spot_symbol(kline["s"]),
168
+ t=kline["t"],
169
+ o=float(kline["o"]),
170
+ h=float(kline["h"]),
171
+ l=float(kline["l"]),
172
+ c=float(kline["c"]),
173
+ v=float(kline["v"]),
174
+ q=float(kline["v"]) * float(kline["c"]),
175
+ T=kline["T"],
176
+ x=None,
100
177
  )
101
- for i, item in enumerate(metrics)
102
- }
178
+ for kline in sorted(
179
+ raw_data,
180
+ key=lambda x: int(x["t"]),
181
+ )
182
+ ]
183
+
184
+ @staticmethod
185
+ def futures_klines(raw_data: list[dict]) -> list[KlineDict]:
186
+ """Преобразует сырой ответ, в котором содержатся данные о свечах, в унифицированный формат.
187
+
188
+ Параметры:
189
+ raw_data (list[dict]): Сырой ответ с биржи.
190
+ symbol (str): Символ тикера.
191
+
192
+ Возвращает:
193
+ list[KlineDict]: Список словарей, где каждый словарь содержит данные о свече.
194
+ """
195
+ return [
196
+ KlineDict(
197
+ s=kline["s"],
198
+ t=kline["t"],
199
+ o=float(kline["o"]),
200
+ h=float(kline["h"]),
201
+ l=float(kline["l"]),
202
+ c=float(kline["c"]),
203
+ v=float(kline["v"]),
204
+ q=float(kline["v"]) * float(kline["c"]),
205
+ T=kline["T"],
206
+ x=None,
207
+ )
208
+ for kline in sorted(
209
+ raw_data,
210
+ key=lambda x: int(x["t"]),
211
+ )
212
+ ]
103
213
 
104
214
  @staticmethod
105
215
  def funding_rate(raw_data: list) -> dict[str, float]: