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.
- unicex/__init__.py +200 -0
- unicex/_abc/__init__.py +11 -0
- unicex/_abc/exchange_info.py +216 -0
- unicex/_abc/uni_client.py +329 -0
- unicex/_abc/uni_websocket_manager.py +294 -0
- unicex/_base/__init__.py +9 -0
- unicex/_base/client.py +214 -0
- unicex/_base/websocket.py +261 -0
- unicex/binance/__init__.py +27 -0
- unicex/binance/adapter.py +202 -0
- unicex/binance/client.py +1577 -0
- unicex/binance/exchange_info.py +62 -0
- unicex/binance/uni_client.py +188 -0
- unicex/binance/uni_websocket_manager.py +166 -0
- unicex/binance/user_websocket.py +186 -0
- unicex/binance/websocket_manager.py +912 -0
- unicex/bitget/__init__.py +27 -0
- unicex/bitget/adapter.py +188 -0
- unicex/bitget/client.py +2514 -0
- unicex/bitget/exchange_info.py +48 -0
- unicex/bitget/uni_client.py +198 -0
- unicex/bitget/uni_websocket_manager.py +275 -0
- unicex/bitget/user_websocket.py +7 -0
- unicex/bitget/websocket_manager.py +232 -0
- unicex/bybit/__init__.py +27 -0
- unicex/bybit/adapter.py +208 -0
- unicex/bybit/client.py +1876 -0
- unicex/bybit/exchange_info.py +53 -0
- unicex/bybit/uni_client.py +200 -0
- unicex/bybit/uni_websocket_manager.py +291 -0
- unicex/bybit/user_websocket.py +7 -0
- unicex/bybit/websocket_manager.py +339 -0
- unicex/enums.py +273 -0
- unicex/exceptions.py +64 -0
- unicex/extra.py +335 -0
- unicex/gate/__init__.py +27 -0
- unicex/gate/adapter.py +178 -0
- unicex/gate/client.py +1667 -0
- unicex/gate/exchange_info.py +55 -0
- unicex/gate/uni_client.py +214 -0
- unicex/gate/uni_websocket_manager.py +269 -0
- unicex/gate/user_websocket.py +7 -0
- unicex/gate/websocket_manager.py +513 -0
- unicex/hyperliquid/__init__.py +27 -0
- unicex/hyperliquid/adapter.py +261 -0
- unicex/hyperliquid/client.py +2315 -0
- unicex/hyperliquid/exchange_info.py +119 -0
- unicex/hyperliquid/uni_client.py +325 -0
- unicex/hyperliquid/uni_websocket_manager.py +269 -0
- unicex/hyperliquid/user_websocket.py +7 -0
- unicex/hyperliquid/websocket_manager.py +393 -0
- unicex/mapper.py +111 -0
- unicex/mexc/__init__.py +27 -0
- unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/__init__.py +335 -0
- unicex/mexc/adapter.py +239 -0
- unicex/mexc/client.py +846 -0
- unicex/mexc/exchange_info.py +47 -0
- unicex/mexc/uni_client.py +211 -0
- unicex/mexc/uni_websocket_manager.py +269 -0
- unicex/mexc/user_websocket.py +7 -0
- unicex/mexc/websocket_manager.py +456 -0
- unicex/okx/__init__.py +27 -0
- unicex/okx/adapter.py +150 -0
- unicex/okx/client.py +2864 -0
- unicex/okx/exchange_info.py +47 -0
- unicex/okx/uni_client.py +202 -0
- unicex/okx/uni_websocket_manager.py +269 -0
- unicex/okx/user_websocket.py +7 -0
- unicex/okx/websocket_manager.py +743 -0
- unicex/types.py +164 -0
- unicex/utils.py +218 -0
- unicex-0.13.17.dist-info/METADATA +243 -0
- unicex-0.13.17.dist-info/RECORD +93 -0
- unicex-0.13.17.dist-info/WHEEL +5 -0
- unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
- 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
|
+
)
|
unicex/_abc/__init__.py
ADDED
|
@@ -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
|