unicex 0.13.17__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 (93) hide show
  1. unicex/__init__.py +200 -0
  2. unicex/_abc/__init__.py +11 -0
  3. unicex/_abc/exchange_info.py +216 -0
  4. unicex/_abc/uni_client.py +329 -0
  5. unicex/_abc/uni_websocket_manager.py +294 -0
  6. unicex/_base/__init__.py +9 -0
  7. unicex/_base/client.py +214 -0
  8. unicex/_base/websocket.py +261 -0
  9. unicex/binance/__init__.py +27 -0
  10. unicex/binance/adapter.py +202 -0
  11. unicex/binance/client.py +1577 -0
  12. unicex/binance/exchange_info.py +62 -0
  13. unicex/binance/uni_client.py +188 -0
  14. unicex/binance/uni_websocket_manager.py +166 -0
  15. unicex/binance/user_websocket.py +186 -0
  16. unicex/binance/websocket_manager.py +912 -0
  17. unicex/bitget/__init__.py +27 -0
  18. unicex/bitget/adapter.py +188 -0
  19. unicex/bitget/client.py +2514 -0
  20. unicex/bitget/exchange_info.py +48 -0
  21. unicex/bitget/uni_client.py +198 -0
  22. unicex/bitget/uni_websocket_manager.py +275 -0
  23. unicex/bitget/user_websocket.py +7 -0
  24. unicex/bitget/websocket_manager.py +232 -0
  25. unicex/bybit/__init__.py +27 -0
  26. unicex/bybit/adapter.py +208 -0
  27. unicex/bybit/client.py +1876 -0
  28. unicex/bybit/exchange_info.py +53 -0
  29. unicex/bybit/uni_client.py +200 -0
  30. unicex/bybit/uni_websocket_manager.py +291 -0
  31. unicex/bybit/user_websocket.py +7 -0
  32. unicex/bybit/websocket_manager.py +339 -0
  33. unicex/enums.py +273 -0
  34. unicex/exceptions.py +64 -0
  35. unicex/extra.py +335 -0
  36. unicex/gate/__init__.py +27 -0
  37. unicex/gate/adapter.py +178 -0
  38. unicex/gate/client.py +1667 -0
  39. unicex/gate/exchange_info.py +55 -0
  40. unicex/gate/uni_client.py +214 -0
  41. unicex/gate/uni_websocket_manager.py +269 -0
  42. unicex/gate/user_websocket.py +7 -0
  43. unicex/gate/websocket_manager.py +513 -0
  44. unicex/hyperliquid/__init__.py +27 -0
  45. unicex/hyperliquid/adapter.py +261 -0
  46. unicex/hyperliquid/client.py +2315 -0
  47. unicex/hyperliquid/exchange_info.py +119 -0
  48. unicex/hyperliquid/uni_client.py +325 -0
  49. unicex/hyperliquid/uni_websocket_manager.py +269 -0
  50. unicex/hyperliquid/user_websocket.py +7 -0
  51. unicex/hyperliquid/websocket_manager.py +393 -0
  52. unicex/mapper.py +111 -0
  53. unicex/mexc/__init__.py +27 -0
  54. unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
  55. unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
  56. unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
  57. unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
  58. unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
  59. unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
  60. unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
  61. unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
  62. unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
  63. unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
  64. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
  65. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
  66. unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
  67. unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
  68. unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
  69. unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
  70. unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
  71. unicex/mexc/_spot_ws_proto/__init__.py +335 -0
  72. unicex/mexc/adapter.py +239 -0
  73. unicex/mexc/client.py +846 -0
  74. unicex/mexc/exchange_info.py +47 -0
  75. unicex/mexc/uni_client.py +211 -0
  76. unicex/mexc/uni_websocket_manager.py +269 -0
  77. unicex/mexc/user_websocket.py +7 -0
  78. unicex/mexc/websocket_manager.py +456 -0
  79. unicex/okx/__init__.py +27 -0
  80. unicex/okx/adapter.py +150 -0
  81. unicex/okx/client.py +2864 -0
  82. unicex/okx/exchange_info.py +47 -0
  83. unicex/okx/uni_client.py +202 -0
  84. unicex/okx/uni_websocket_manager.py +269 -0
  85. unicex/okx/user_websocket.py +7 -0
  86. unicex/okx/websocket_manager.py +743 -0
  87. unicex/types.py +164 -0
  88. unicex/utils.py +218 -0
  89. unicex-0.13.17.dist-info/METADATA +243 -0
  90. unicex-0.13.17.dist-info/RECORD +93 -0
  91. unicex-0.13.17.dist-info/WHEEL +5 -0
  92. unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
  93. unicex-0.13.17.dist-info/top_level.txt +1 -0
unicex/__init__.py ADDED
@@ -0,0 +1,200 @@
1
+ """unicex - библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс для работы с различными криптовалютными биржами."""
2
+
3
+ __all__ = [
4
+ # Mappers
5
+ "get_uni_client",
6
+ "get_uni_websocket_manager",
7
+ "get_exchange_info",
8
+ # Exchanges info
9
+ "load_exchanges_info",
10
+ "start_exchanges_info",
11
+ # Enums
12
+ "MarketType",
13
+ "Exchange",
14
+ "Timeframe",
15
+ "Side",
16
+ # Types
17
+ "TickerDailyDict",
18
+ "TickerDailyItem",
19
+ "KlineDict",
20
+ "TradeDict",
21
+ "AggTradeDict",
22
+ "RequestMethod",
23
+ "LoggerLike",
24
+ "OpenInterestDict",
25
+ "OpenInterestItem",
26
+ "TickerInfoItem",
27
+ "TickersInfoDict",
28
+ "LiquidationDict",
29
+ # Interfaces
30
+ "IUniClient",
31
+ "IUniWebsocketManager",
32
+ # Base clients and websockets
33
+ "Websocket",
34
+ "BaseClient",
35
+ # Binance
36
+ "BinanceClient",
37
+ "BinanceUniClient",
38
+ "BinanceWebsocketManager",
39
+ "BinanceUniWebsocketManager",
40
+ "BinanceUserWebsocket",
41
+ "BinanceExchangeInfo",
42
+ # Bitget
43
+ "BitgetClient",
44
+ "BitgetUniClient",
45
+ "BitgetUniWebsocketManager",
46
+ "BitgetWebsocketManager",
47
+ "BitgetUserWebsocket",
48
+ "BitgetExchangeInfo",
49
+ # Mexc
50
+ "MexcClient",
51
+ "MexcUniClient",
52
+ "MexcUniWebsocketManager",
53
+ "MexcWebsocketManager",
54
+ "MexcUserWebsocket",
55
+ "MexcExchangeInfo",
56
+ # Bybit
57
+ "BybitClient",
58
+ "BybitUniClient",
59
+ "BybitUniWebsocketManager",
60
+ "BybitWebsocketManager",
61
+ "BybitUserWebsocket",
62
+ "BybitExchangeInfo",
63
+ # Okx
64
+ "OkxClient",
65
+ "OkxUniClient",
66
+ "OkxUniWebsocketManager",
67
+ "OkxWebsocketManager",
68
+ "OkxUserWebsocket",
69
+ "OkxExchangeInfo",
70
+ # Hyperliquid
71
+ "HyperliquidClient",
72
+ "HyperliquidUniClient",
73
+ "HyperliquidUniWebsocketManager",
74
+ "HyperliquidWebsocketManager",
75
+ "HyperliquidUserWebsocket",
76
+ "HyperliquidExchangeInfo",
77
+ # Gateio
78
+ "GateioClient",
79
+ "GateioUniClient",
80
+ "GateioUniWebsocketManager",
81
+ "GateioWebsocketManager",
82
+ "GateioUserWebsocket",
83
+ "GateioExchangeInfo",
84
+ ]
85
+
86
+ # ruff: noqa
87
+
88
+ # abstract & base
89
+ import asyncio
90
+ from ._abc import IUniClient, IUniWebsocketManager
91
+ from ._base import BaseClient, Websocket
92
+
93
+ # enums, mappers, types
94
+ from .enums import Exchange, MarketType, Side, Timeframe
95
+ from .mapper import get_uni_client, get_uni_websocket_manager, get_exchange_info
96
+ from .types import (
97
+ TickerDailyDict,
98
+ TickerDailyItem,
99
+ KlineDict,
100
+ TradeDict,
101
+ AggTradeDict,
102
+ RequestMethod,
103
+ LoggerLike,
104
+ OpenInterestDict,
105
+ OpenInterestItem,
106
+ TickerInfoItem,
107
+ TickersInfoDict,
108
+ LiquidationDict,
109
+ )
110
+
111
+ # exchanges
112
+
113
+ from .binance import (
114
+ Client as BinanceClient,
115
+ UniClient as BinanceUniClient,
116
+ UniWebsocketManager as BinanceUniWebsocketManager,
117
+ UserWebsocket as BinanceUserWebsocket,
118
+ WebsocketManager as BinanceWebsocketManager,
119
+ ExchangeInfo as BinanceExchangeInfo,
120
+ )
121
+
122
+ from .bitget import (
123
+ Client as BitgetClient,
124
+ UniClient as BitgetUniClient,
125
+ UniWebsocketManager as BitgetUniWebsocketManager,
126
+ UserWebsocket as BitgetUserWebsocket,
127
+ WebsocketManager as BitgetWebsocketManager,
128
+ ExchangeInfo as BitgetExchangeInfo,
129
+ )
130
+
131
+ from .bybit import (
132
+ Client as BybitClient,
133
+ UniClient as BybitUniClient,
134
+ UniWebsocketManager as BybitUniWebsocketManager,
135
+ UserWebsocket as BybitUserWebsocket,
136
+ WebsocketManager as BybitWebsocketManager,
137
+ ExchangeInfo as BybitExchangeInfo,
138
+ )
139
+
140
+ from .gate import (
141
+ Client as GateioClient,
142
+ UniClient as GateioUniClient,
143
+ UniWebsocketManager as GateioUniWebsocketManager,
144
+ UserWebsocket as GateioUserWebsocket,
145
+ WebsocketManager as GateioWebsocketManager,
146
+ ExchangeInfo as GateioExchangeInfo,
147
+ )
148
+
149
+ from .hyperliquid import (
150
+ Client as HyperliquidClient,
151
+ UniClient as HyperliquidUniClient,
152
+ UniWebsocketManager as HyperliquidUniWebsocketManager,
153
+ UserWebsocket as HyperliquidUserWebsocket,
154
+ WebsocketManager as HyperliquidWebsocketManager,
155
+ ExchangeInfo as HyperliquidExchangeInfo,
156
+ )
157
+
158
+ from .mexc import (
159
+ Client as MexcClient,
160
+ UniClient as MexcUniClient,
161
+ UniWebsocketManager as MexcUniWebsocketManager,
162
+ UserWebsocket as MexcUserWebsocket,
163
+ WebsocketManager as MexcWebsocketManager,
164
+ ExchangeInfo as MexcExchangeInfo,
165
+ )
166
+
167
+ from .okx import (
168
+ Client as OkxClient,
169
+ UniClient as OkxUniClient,
170
+ UniWebsocketManager as OkxUniWebsocketManager,
171
+ UserWebsocket as OkxUserWebsocket,
172
+ WebsocketManager as OkxWebsocketManager,
173
+ ExchangeInfo as OkxExchangeInfo,
174
+ )
175
+
176
+
177
+ async def load_exchanges_info() -> None:
178
+ """Единожды загружает информацию о тикерах на всех биржах."""
179
+ await asyncio.gather(
180
+ BinanceExchangeInfo.load_exchange_info(),
181
+ BitgetExchangeInfo.load_exchange_info(),
182
+ BybitExchangeInfo.load_exchange_info(),
183
+ GateioExchangeInfo.load_exchange_info(),
184
+ HyperliquidExchangeInfo.load_exchange_info(),
185
+ MexcExchangeInfo.load_exchange_info(),
186
+ OkxExchangeInfo.load_exchange_info(),
187
+ )
188
+
189
+
190
+ async def start_exchanges_info(parse_interval_seconds: int = 60 * 60) -> None:
191
+ """Запускает цикл обновления информации о тикерах на всех биржах."""
192
+ asyncio.gather(
193
+ BinanceExchangeInfo.start(parse_interval_seconds),
194
+ BitgetExchangeInfo.start(parse_interval_seconds),
195
+ BybitExchangeInfo.start(parse_interval_seconds),
196
+ GateioExchangeInfo.start(parse_interval_seconds),
197
+ HyperliquidExchangeInfo.start(parse_interval_seconds),
198
+ MexcExchangeInfo.start(parse_interval_seconds),
199
+ OkxExchangeInfo.start(parse_interval_seconds),
200
+ )
@@ -0,0 +1,11 @@
1
+ """Пакет с абстракциями и интерфейсами."""
2
+
3
+ __all__ = [
4
+ "IUniClient",
5
+ "IUniWebsocketManager",
6
+ "IExchangeInfo",
7
+ ]
8
+
9
+ from .exchange_info import IExchangeInfo
10
+ from .uni_client import IUniClient
11
+ from .uni_websocket_manager import IUniWebsocketManager
@@ -0,0 +1,216 @@
1
+ __all__ = ["IExchangeInfo"]
2
+
3
+ import asyncio
4
+ import math
5
+ from abc import ABC, abstractmethod
6
+ from decimal import Decimal
7
+ from typing import TYPE_CHECKING, ClassVar
8
+
9
+ import aiohttp
10
+ from loguru import logger
11
+
12
+ from unicex.enums import MarketType
13
+ from unicex.types import TickerInfoItem, TickersInfoDict
14
+
15
+ if TYPE_CHECKING:
16
+ import loguru
17
+
18
+
19
+ class IExchangeInfo(ABC):
20
+ """Интерфейс для наследников, которые предзагружают и обновляют информацию о бирже."""
21
+
22
+ _loaded: bool
23
+ """Флаг, указывающий, была ли информация о бирже загружена."""
24
+
25
+ _running: bool
26
+ """Флаг, указывающий, запущена ли фоновая задача для обновления информации о бирже."""
27
+
28
+ _tickers_info: TickersInfoDict
29
+ """Словарь с информацией о округлении для каждого тикера."""
30
+
31
+ _futures_tickers_info: TickersInfoDict
32
+ """Словарь с информацией о округлении и размере контракта (если есть) для каждого тикера."""
33
+
34
+ _logger: "loguru.Logger"
35
+ """Логгер для записи сообщений о работе с биржей."""
36
+
37
+ exchange_name: ClassVar[str] = "not_defined_exchange"
38
+ """Название биржи, на которой работает класс."""
39
+
40
+ def __init_subclass__(cls, **kwargs):
41
+ """Инициализация подкласса. Функция нужна, чтобы у каждого наследника была своя копия атрибутов."""
42
+ super().__init_subclass__(**kwargs)
43
+ cls._tickers_info = {}
44
+ cls._loaded = False
45
+ cls._running = False
46
+ cls._logger = logger
47
+
48
+ @classmethod
49
+ async def start(cls, update_interval_seconds: int = 60 * 60) -> None:
50
+ """Запускает фоновую задачу с бесконечным циклом для загрузки данных."""
51
+ cls._running = True
52
+ asyncio.create_task(cls._load_exchange_info_loop(update_interval_seconds))
53
+
54
+ @classmethod
55
+ async def stop(cls) -> None:
56
+ """Останавливает фоновую задачу для обновления информации о бирже."""
57
+ cls._running = False
58
+
59
+ @classmethod
60
+ async def set_logger(cls, logger: "loguru.Logger") -> None:
61
+ """Устанавливает логгер для записи сообщений о работе с биржей."""
62
+ cls._logger = logger
63
+
64
+ @classmethod
65
+ async def _load_exchange_info_loop(cls, update_interval_seconds: int) -> None:
66
+ """Запускает бесконечный цикл для загрузки данных о бирже."""
67
+ while cls._running:
68
+ try:
69
+ await cls.load_exchange_info()
70
+ except Exception as e:
71
+ cls._logger.error(f"Error loading exchange data for {cls.exchange_name}: {e}")
72
+ for _ in range(update_interval_seconds):
73
+ if not cls._running:
74
+ break
75
+ await asyncio.sleep(1)
76
+
77
+ @classmethod
78
+ async def load_exchange_info(cls) -> None:
79
+ """Принудительно вызывает загрузку информации о бирже."""
80
+ async with aiohttp.ClientSession() as session:
81
+ try:
82
+ await cls._load_spot_exchange_info(session)
83
+ cls._logger.debug(f"Loaded spot exchange data for {cls.exchange_name} ")
84
+ except Exception as e:
85
+ cls._logger.error(f"Error loading spot exchange data for {cls.exchange_name}: {e}")
86
+ try:
87
+ await cls._load_futures_exchange_info(session)
88
+ cls._logger.debug(f"Loaded futures exchange data for {cls.exchange_name} ")
89
+ except Exception as e:
90
+ cls._logger.error(
91
+ f"Error loading futures exchange data for {cls.exchange_name}: {e}"
92
+ )
93
+ cls._loaded = True
94
+
95
+ @classmethod
96
+ @abstractmethod
97
+ async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
98
+ """Загружает информацию о бирже для спотового рынка."""
99
+ ...
100
+
101
+ @classmethod
102
+ @abstractmethod
103
+ async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
104
+ """Загружает информацию о бирже для фьючерсного рынка."""
105
+ ...
106
+
107
+ @classmethod
108
+ def get_ticker_info(
109
+ cls, symbol: str, market_type: MarketType = MarketType.SPOT
110
+ ) -> TickerInfoItem: # type: ignore[reportReturnType]
111
+ """Возвращает информацию о тикере по его символу."""
112
+ try:
113
+ if market_type == MarketType.SPOT:
114
+ return cls._tickers_info[symbol]
115
+ return cls._futures_tickers_info[symbol]
116
+ except KeyError as e:
117
+ cls._handle_key_error(e, symbol)
118
+
119
+ @classmethod
120
+ def get_futures_ticker_info(cls, symbol: str) -> TickerInfoItem:
121
+ """Возвращает информацию о тикере фьючерсов по его символу."""
122
+ return cls.get_ticker_info(symbol, MarketType.FUTURES)
123
+
124
+ @classmethod
125
+ def round_price(
126
+ cls, symbol: str, price: float, market_type: MarketType = MarketType.SPOT
127
+ ) -> float: # type: ignore
128
+ """Округляет цену до ближайшего возможного значения."""
129
+ try:
130
+ if market_type == MarketType.SPOT:
131
+ precision = cls._tickers_info[symbol]["tick_precision"]
132
+ step = cls._tickers_info[symbol]["tick_step"]
133
+ else:
134
+ precision = cls._futures_tickers_info[symbol]["tick_precision"]
135
+ step = cls._futures_tickers_info[symbol]["tick_step"]
136
+ if precision:
137
+ return cls._floor_round(price, precision)
138
+ return cls._floor_to_step(price, step) # type: ignore
139
+ except KeyError as e:
140
+ cls._handle_key_error(e, symbol)
141
+
142
+ @classmethod
143
+ def round_quantity(
144
+ cls, symbol: str, quantity: float, market_type: MarketType = MarketType.SPOT
145
+ ) -> float: # type: ignore
146
+ """Округляет объем до ближайшего возможного значения."""
147
+ try:
148
+ if market_type == MarketType.SPOT:
149
+ precision = cls._tickers_info[symbol]["size_precision"]
150
+ step = cls._tickers_info[symbol]["size_step"]
151
+ else:
152
+ precision = cls._futures_tickers_info[symbol]["size_precision"]
153
+ step = cls._futures_tickers_info[symbol]["size_step"]
154
+ if precision:
155
+ return cls._floor_round(quantity, precision)
156
+ return cls._floor_to_step(quantity, step) # type: ignore
157
+ except KeyError as e:
158
+ cls._handle_key_error(e, symbol)
159
+
160
+ @classmethod
161
+ def round_futures_price(cls, symbol: str, price: float) -> float:
162
+ """Округляет цену до ближайшего возможного значения на фьючерсах."""
163
+ return cls.round_price(symbol, price, MarketType.FUTURES)
164
+
165
+ @classmethod
166
+ def round_futures_quantity(cls, symbol: str, quantity: float) -> float:
167
+ """Округляет объем до ближайшего возможного значения на фьючерсах."""
168
+ return cls.round_quantity(symbol, quantity, MarketType.FUTURES)
169
+
170
+ @staticmethod
171
+ def _floor_to_step(value: float, step: float) -> float:
172
+ """Округляет число вниз до ближайшего кратного шага.
173
+
174
+ Принимает:
175
+ value (float): Исходное число.
176
+ step: (float): Шаг округления (> 0).
177
+
178
+ Возвращает:
179
+ Число, округлённое вниз до кратного step.
180
+
181
+ Примеры:
182
+ >>> floor_to_step(0.16, 0.05)
183
+ 0.15
184
+ >>> floor_to_step(16, 5)
185
+ 15
186
+ >>> floor_to_step(1.2345, 0.01)
187
+ 1.23
188
+ >>> floor_to_step(-1.23, 0.1)
189
+ -1.3
190
+ >>> floor_to_step(100, 25)
191
+ 100
192
+
193
+ """
194
+ if step <= 0:
195
+ raise ValueError("step must be > 0")
196
+ result = math.floor(value / step) * step
197
+ digits = abs(Decimal(str(step)).as_tuple().exponent) # type: ignore
198
+ return round(result, digits)
199
+
200
+ @staticmethod
201
+ def _floor_round(value: float, digits: int) -> float:
202
+ """Округляет число вниз до указанного количества знаков после запятой."""
203
+ factor = 10**digits
204
+ return math.floor(value * factor) / factor
205
+
206
+ @classmethod
207
+ def _handle_key_error(cls, exception: KeyError, symbol: str) -> None:
208
+ """Обрабатывает KeyError при получении информации о тикере."""
209
+ cls._check_loaded()
210
+ raise KeyError(f"Symbol {symbol} not found") from exception
211
+
212
+ @classmethod
213
+ def _check_loaded(cls) -> None:
214
+ """Проверяет, загружены ли данные об обмене."""
215
+ if not cls._loaded:
216
+ raise ValueError("Exchange data not loaded") from None