unicex 0.11.0__py3-none-any.whl → 0.13.1__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 +1 -1
- unicex/_abc/exchange_info.py +75 -35
- unicex/binance/exchange_info.py +53 -3
- unicex/bitget/exchange_info.py +39 -3
- unicex/bybit/exchange_info.py +42 -3
- unicex/enums.py +3 -3
- unicex/extra.py +10 -9
- unicex/{gateio → gate}/client.py +2 -2
- unicex/gate/exchange_info.py +55 -0
- unicex/{gateio → gate}/uni_client.py +2 -2
- unicex/hyperliquid/exchange_info.py +29 -10
- unicex/mapper.py +6 -6
- unicex/mexc/exchange_info.py +35 -20
- unicex/okx/adapter.py +23 -11
- unicex/okx/exchange_info.py +35 -38
- unicex/types.py +16 -15
- unicex/utils.py +1 -1
- {unicex-0.11.0.dist-info → unicex-0.13.1.dist-info}/METADATA +57 -7
- {unicex-0.11.0.dist-info → unicex-0.13.1.dist-info}/RECORD +27 -27
- unicex/gateio/exchange_info.py +0 -12
- /unicex/{gateio → gate}/__init__.py +0 -0
- /unicex/{gateio → gate}/adapter.py +0 -0
- /unicex/{gateio → gate}/uni_websocket_manager.py +0 -0
- /unicex/{gateio → gate}/user_websocket.py +0 -0
- /unicex/{gateio → gate}/websocket_manager.py +0 -0
- {unicex-0.11.0.dist-info → unicex-0.13.1.dist-info}/WHEEL +0 -0
- {unicex-0.11.0.dist-info → unicex-0.13.1.dist-info}/licenses/LICENSE +0 -0
- {unicex-0.11.0.dist-info → unicex-0.13.1.dist-info}/top_level.txt +0 -0
unicex/__init__.py
CHANGED
unicex/_abc/exchange_info.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
__all__ = ["IExchangeInfo"]
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import math
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from decimal import Decimal
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
7
8
|
|
|
9
|
+
import aiohttp
|
|
8
10
|
from loguru import logger
|
|
9
11
|
|
|
10
12
|
from unicex.enums import MarketType
|
|
@@ -32,6 +34,9 @@ class IExchangeInfo(ABC):
|
|
|
32
34
|
_logger: "loguru.Logger"
|
|
33
35
|
"""Логгер для записи сообщений о работе с биржей."""
|
|
34
36
|
|
|
37
|
+
exchange_name: ClassVar[str] = "not_defined_exchange"
|
|
38
|
+
"""Название биржи, на которой работает класс."""
|
|
39
|
+
|
|
35
40
|
def __init_subclass__(cls, **kwargs):
|
|
36
41
|
"""Инициализация подкласса. Функция нужна, чтобы у каждого наследника была своя копия атрибутов."""
|
|
37
42
|
super().__init_subclass__(**kwargs)
|
|
@@ -63,7 +68,7 @@ class IExchangeInfo(ABC):
|
|
|
63
68
|
try:
|
|
64
69
|
await cls.load_exchange_info()
|
|
65
70
|
except Exception as e:
|
|
66
|
-
cls._logger.error(f"Error loading exchange data: {e}")
|
|
71
|
+
cls._logger.error(f"Error loading exchange data for {cls.exchange_name}: {e}")
|
|
67
72
|
for _ in range(update_interval_seconds):
|
|
68
73
|
if not cls._running:
|
|
69
74
|
break
|
|
@@ -72,9 +77,33 @@ class IExchangeInfo(ABC):
|
|
|
72
77
|
@classmethod
|
|
73
78
|
async def load_exchange_info(cls) -> None:
|
|
74
79
|
"""Принудительно вызывает загрузку информации о бирже."""
|
|
75
|
-
|
|
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
|
+
)
|
|
76
93
|
cls._loaded = True
|
|
77
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
|
+
|
|
78
107
|
@classmethod
|
|
79
108
|
def get_ticker_info(
|
|
80
109
|
cls, symbol: str, market_type: MarketType = MarketType.SPOT
|
|
@@ -92,39 +121,41 @@ class IExchangeInfo(ABC):
|
|
|
92
121
|
"""Возвращает информацию о тикере фьючерсов по его символу."""
|
|
93
122
|
return cls.get_ticker_info(symbol, MarketType.FUTURES)
|
|
94
123
|
|
|
95
|
-
@classmethod
|
|
96
|
-
@abstractmethod
|
|
97
|
-
async def _load_exchange_info(cls) -> None:
|
|
98
|
-
"""Загружает информацию о бирже."""
|
|
99
|
-
...
|
|
100
|
-
|
|
101
124
|
@classmethod
|
|
102
125
|
def round_price(
|
|
103
126
|
cls, symbol: str, price: float, market_type: MarketType = MarketType.SPOT
|
|
104
|
-
) -> float:
|
|
127
|
+
) -> float: # type: ignore
|
|
105
128
|
"""Округляет цену до ближайшего возможного значения."""
|
|
106
129
|
try:
|
|
107
130
|
if market_type == MarketType.SPOT:
|
|
108
131
|
precision = cls._tickers_info[symbol]["tick_precision"]
|
|
132
|
+
step = cls._tickers_info[symbol]["tick_step"]
|
|
109
133
|
else:
|
|
110
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
|
|
111
139
|
except KeyError as e:
|
|
112
140
|
cls._handle_key_error(e, symbol)
|
|
113
|
-
return round(price, precision)
|
|
114
141
|
|
|
115
142
|
@classmethod
|
|
116
143
|
def round_quantity(
|
|
117
144
|
cls, symbol: str, quantity: float, market_type: MarketType = MarketType.SPOT
|
|
118
|
-
) -> float:
|
|
145
|
+
) -> float: # type: ignore
|
|
119
146
|
"""Округляет объем до ближайшего возможного значения."""
|
|
120
147
|
try:
|
|
121
148
|
if market_type == MarketType.SPOT:
|
|
122
149
|
precision = cls._tickers_info[symbol]["size_precision"]
|
|
150
|
+
step = cls._tickers_info[symbol]["size_step"]
|
|
123
151
|
else:
|
|
124
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
|
|
125
157
|
except KeyError as e:
|
|
126
158
|
cls._handle_key_error(e, symbol)
|
|
127
|
-
return round(quantity, precision)
|
|
128
159
|
|
|
129
160
|
@classmethod
|
|
130
161
|
def round_futures_price(cls, symbol: str, price: float) -> float:
|
|
@@ -137,31 +168,40 @@ class IExchangeInfo(ABC):
|
|
|
137
168
|
return cls.round_quantity(symbol, quantity, MarketType.FUTURES)
|
|
138
169
|
|
|
139
170
|
@staticmethod
|
|
140
|
-
def
|
|
141
|
-
"""
|
|
171
|
+
def _floor_to_step(value: float, step: float) -> float:
|
|
172
|
+
"""Округляет число вниз до ближайшего кратного шага.
|
|
173
|
+
|
|
174
|
+
Принимает:
|
|
175
|
+
value (float): Исходное число.
|
|
176
|
+
step: (float): Шаг округления (> 0).
|
|
177
|
+
|
|
178
|
+
Возвращает:
|
|
179
|
+
Число, округлённое вниз до кратного step.
|
|
142
180
|
|
|
143
|
-
Работает только для шагов — степеней 10.
|
|
144
181
|
Примеры:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
|
|
151
193
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
f"tick_size={tick_size} is not a power of 10; cannot map to round() precision."
|
|
164
|
-
)
|
|
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
|
|
165
205
|
|
|
166
206
|
@classmethod
|
|
167
207
|
def _handle_key_error(cls, exception: KeyError, symbol: str) -> None:
|
unicex/binance/exchange_info.py
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
__all__ = ["ExchangeInfo"]
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
|
|
3
6
|
from unicex._abc import IExchangeInfo
|
|
7
|
+
from unicex.types import TickerInfoItem
|
|
8
|
+
|
|
9
|
+
from .client import Client
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
class ExchangeInfo(IExchangeInfo):
|
|
7
13
|
"""Предзагружает информацию о тикерах для биржи Binance."""
|
|
8
14
|
|
|
15
|
+
exchange_name = "Binance"
|
|
16
|
+
"""Название биржи, на которой работает класс."""
|
|
17
|
+
|
|
9
18
|
@classmethod
|
|
10
|
-
async def
|
|
11
|
-
"""Загружает информацию о
|
|
12
|
-
|
|
19
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
20
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
21
|
+
exchange_info = await Client(session).exchange_info()
|
|
22
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
23
|
+
for symbol_info in exchange_info["symbols"]:
|
|
24
|
+
filters = {
|
|
25
|
+
flt["filterType"]: flt
|
|
26
|
+
for flt in symbol_info.get("filters", [])
|
|
27
|
+
if "filterType" in flt
|
|
28
|
+
}
|
|
29
|
+
price_filter = filters["PRICE_FILTER"]
|
|
30
|
+
lot_size_filter = filters["LOT_SIZE"]
|
|
31
|
+
tickers_info[symbol_info["symbol"]] = TickerInfoItem(
|
|
32
|
+
tick_step=float(price_filter["tickSize"]),
|
|
33
|
+
tick_precision=None,
|
|
34
|
+
size_step=float(lot_size_filter["stepSize"]),
|
|
35
|
+
size_precision=None,
|
|
36
|
+
contract_size=1,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
cls._tickers_info = tickers_info
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
43
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
44
|
+
exchange_info = await Client(session).futures_exchange_info()
|
|
45
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
46
|
+
for symbol_info in exchange_info["symbols"]:
|
|
47
|
+
filters = {
|
|
48
|
+
flt["filterType"]: flt
|
|
49
|
+
for flt in symbol_info.get("filters", [])
|
|
50
|
+
if "filterType" in flt
|
|
51
|
+
}
|
|
52
|
+
price_filter = filters["PRICE_FILTER"]
|
|
53
|
+
lot_size_filter = filters["LOT_SIZE"]
|
|
54
|
+
tickers_info[symbol_info["symbol"]] = TickerInfoItem(
|
|
55
|
+
tick_step=float(price_filter["tickSize"]),
|
|
56
|
+
tick_precision=None,
|
|
57
|
+
size_step=float(lot_size_filter["stepSize"]),
|
|
58
|
+
size_precision=None,
|
|
59
|
+
contract_size=1,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
cls._futures_tickers_info = tickers_info
|
unicex/bitget/exchange_info.py
CHANGED
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
__all__ = ["ExchangeInfo"]
|
|
2
2
|
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
3
5
|
from unicex._abc import IExchangeInfo
|
|
6
|
+
from unicex.types import TickerInfoItem
|
|
7
|
+
|
|
8
|
+
from .client import Client
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
class ExchangeInfo(IExchangeInfo):
|
|
7
12
|
"""Предзагружает информацию о тикерах для биржи Bitget."""
|
|
8
13
|
|
|
14
|
+
exchange_name = "Bitget"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
9
17
|
@classmethod
|
|
10
|
-
async def
|
|
11
|
-
"""Загружает информацию о
|
|
12
|
-
|
|
18
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
19
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
20
|
+
exchange_info = await Client(session).get_symbol_info()
|
|
21
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
22
|
+
for symbol_info in exchange_info["data"]:
|
|
23
|
+
tickers_info[symbol_info["symbol"]] = TickerInfoItem(
|
|
24
|
+
tick_precision=int(symbol_info["pricePrecision"]),
|
|
25
|
+
tick_step=None,
|
|
26
|
+
size_precision=int(symbol_info["quantityPrecision"]),
|
|
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: dict[str, TickerInfoItem] = {}
|
|
37
|
+
exchange_info = await Client(session).futures_get_contracts("USDT-FUTURES")
|
|
38
|
+
for symbol_info in exchange_info["data"]:
|
|
39
|
+
symbol = symbol_info["symbol"]
|
|
40
|
+
tickers_info[symbol] = TickerInfoItem(
|
|
41
|
+
tick_precision=int(symbol_info["pricePlace"]),
|
|
42
|
+
tick_step=None,
|
|
43
|
+
size_precision=int(symbol_info["volumePlace"]),
|
|
44
|
+
size_step=None,
|
|
45
|
+
contract_size=float(symbol_info["sizeMultiplier"]),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
cls._futures_tickers_info = tickers_info
|
unicex/bybit/exchange_info.py
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
__all__ = ["ExchangeInfo"]
|
|
2
2
|
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
3
5
|
from unicex._abc import IExchangeInfo
|
|
6
|
+
from unicex.types import TickerInfoItem
|
|
7
|
+
|
|
8
|
+
from .client import Client
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
class ExchangeInfo(IExchangeInfo):
|
|
7
12
|
"""Предзагружает информацию о тикерах для биржи Bybit."""
|
|
8
13
|
|
|
14
|
+
exchange_name = "Bybit"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
19
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
20
|
+
exchange_info = await Client(session).instruments_info("spot")
|
|
21
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
22
|
+
for symbol_info in exchange_info["result"]["list"]:
|
|
23
|
+
tickers_info[symbol_info["symbol"]] = TickerInfoItem(
|
|
24
|
+
tick_step=float(symbol_info["priceFilter"]["tickSize"]),
|
|
25
|
+
tick_precision=None,
|
|
26
|
+
size_step=float(symbol_info["lotSizeFilter"]["basePrecision"]),
|
|
27
|
+
size_precision=None,
|
|
28
|
+
contract_size=1,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
cls._tickers_info = tickers_info
|
|
32
|
+
|
|
9
33
|
@classmethod
|
|
10
|
-
async def
|
|
11
|
-
"""Загружает информацию о
|
|
12
|
-
|
|
34
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
35
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
36
|
+
exchange_info = await Client(session).instruments_info("linear", limit=1000)
|
|
37
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
38
|
+
for symbol_info in exchange_info["result"]["list"]:
|
|
39
|
+
try:
|
|
40
|
+
tickers_info[symbol_info["symbol"]] = TickerInfoItem(
|
|
41
|
+
tick_step=float(symbol_info["priceFilter"]["tickSize"]),
|
|
42
|
+
tick_precision=None,
|
|
43
|
+
size_step=float(symbol_info["lotSizeFilter"]["qtyStep"]),
|
|
44
|
+
size_precision=None,
|
|
45
|
+
contract_size=1,
|
|
46
|
+
)
|
|
47
|
+
except ValueError as e:
|
|
48
|
+
cls._logger.trace(
|
|
49
|
+
f"ValueError on {cls.exchange_name} by {symbol_info['symbol']}: {e}"
|
|
50
|
+
)
|
|
51
|
+
cls._futures_tickers_info = tickers_info
|
unicex/enums.py
CHANGED
|
@@ -28,7 +28,7 @@ class Exchange(StrEnum):
|
|
|
28
28
|
BITGET = "BITGET"
|
|
29
29
|
BITUNIX = "BITUNIX"
|
|
30
30
|
BYBIT = "BYBIT"
|
|
31
|
-
|
|
31
|
+
GATE = "GATE"
|
|
32
32
|
HYPERLIQUID = "HYPERLIQUID"
|
|
33
33
|
KCEX = "KCEX"
|
|
34
34
|
MEXC = "MEXC"
|
|
@@ -194,7 +194,7 @@ class Timeframe(StrEnum):
|
|
|
194
194
|
Timeframe.WEEK_1: "1W",
|
|
195
195
|
Timeframe.MONTH_1: "1M",
|
|
196
196
|
},
|
|
197
|
-
(Exchange.
|
|
197
|
+
(Exchange.GATE, MarketType.FUTURES): {
|
|
198
198
|
Timeframe.SECOND_1: "1s",
|
|
199
199
|
Timeframe.MIN_1: "1m",
|
|
200
200
|
Timeframe.MIN_5: "5m",
|
|
@@ -210,7 +210,7 @@ class Timeframe(StrEnum):
|
|
|
210
210
|
Timeframe.WEEK_1: "1w",
|
|
211
211
|
Timeframe.MONTH_1: "30d",
|
|
212
212
|
},
|
|
213
|
-
(Exchange.
|
|
213
|
+
(Exchange.GATE, MarketType.SPOT): {
|
|
214
214
|
Timeframe.MIN_1: "1m",
|
|
215
215
|
Timeframe.MIN_5: "5m",
|
|
216
216
|
Timeframe.MIN_15: "15m",
|
unicex/extra.py
CHANGED
|
@@ -201,7 +201,7 @@ def generate_ex_link(exchange: Exchange, market_type: MarketType, symbol: str):
|
|
|
201
201
|
return f"https://www.mexc.com/ru-RU/futures/{ticker}_USDT?type=linear_swap"
|
|
202
202
|
else:
|
|
203
203
|
return f"https://www.mexc.com/ru-RU/exchange/{ticker}_USDT"
|
|
204
|
-
elif exchange == Exchange.
|
|
204
|
+
elif exchange == Exchange.GATE:
|
|
205
205
|
if market_type == MarketType.FUTURES:
|
|
206
206
|
return f"https://www.gate.com/ru/futures/USDT/{ticker}_USDT"
|
|
207
207
|
else:
|
|
@@ -242,10 +242,11 @@ def generate_tv_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
242
242
|
`str`: Ссылка для TradingView.
|
|
243
243
|
"""
|
|
244
244
|
symbol = normalize_symbol(symbol)
|
|
245
|
+
exchange_str = "GATEIO" if exchange == Exchange.GATE else str(exchange)
|
|
245
246
|
if market_type == MarketType.FUTURES:
|
|
246
|
-
return f"https://www.tradingview.com/chart/?symbol={
|
|
247
|
+
return f"https://www.tradingview.com/chart/?symbol={exchange_str}:{symbol}.P"
|
|
247
248
|
else:
|
|
248
|
-
return f"https://www.tradingview.com/chart/?symbol={
|
|
249
|
+
return f"https://www.tradingview.com/chart/?symbol={exchange_str}:{symbol}"
|
|
249
250
|
|
|
250
251
|
|
|
251
252
|
def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -> str:
|
|
@@ -266,17 +267,17 @@ def generate_cg_link(exchange: Exchange, market_type: MarketType, symbol: str) -
|
|
|
266
267
|
if market_type == MarketType.FUTURES:
|
|
267
268
|
match exchange:
|
|
268
269
|
case Exchange.OKX:
|
|
269
|
-
return f"{base_url}/{
|
|
270
|
+
return f"{base_url}/OKX_{symbol.replace('USDT', '-USDT')}-SWAP"
|
|
270
271
|
case Exchange.MEXC:
|
|
271
|
-
return f"{base_url}/{
|
|
272
|
+
return f"{base_url}/MEXC_{symbol.replace('USDT', '_USDT')}"
|
|
272
273
|
case Exchange.BITGET:
|
|
273
|
-
return f"{base_url}/{
|
|
274
|
-
case Exchange.
|
|
274
|
+
return f"{base_url}/Bitget_{symbol}_UMCBL"
|
|
275
|
+
case Exchange.GATE:
|
|
275
276
|
return f"{base_url}/Gate_{symbol.replace('USDT', '_USDT')}"
|
|
276
277
|
case Exchange.BITUNIX:
|
|
277
|
-
return f"{base_url}/{
|
|
278
|
+
return f"{base_url}/Bitunix_{symbol}"
|
|
278
279
|
case Exchange.HYPERLIQUID:
|
|
279
|
-
return f"{base_url}/{
|
|
280
|
+
return f"{base_url}/Hyperliquid_{symbol.replace('USDT', '-USD')}"
|
|
280
281
|
case _:
|
|
281
282
|
return f"{base_url}/{exchange.capitalize()}_{symbol}"
|
|
282
283
|
else:
|
unicex/{gateio → gate}/client.py
RENAMED
|
@@ -4,7 +4,7 @@ import hashlib
|
|
|
4
4
|
import hmac
|
|
5
5
|
import json
|
|
6
6
|
import time
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Literal
|
|
8
8
|
|
|
9
9
|
from unicex._base import BaseClient
|
|
10
10
|
from unicex.exceptions import NotAuthorized
|
|
@@ -691,7 +691,7 @@ class Client(BaseClient):
|
|
|
691
691
|
|
|
692
692
|
async def futures_contracts(
|
|
693
693
|
self,
|
|
694
|
-
settle:
|
|
694
|
+
settle: Literal["usdt", "btc"],
|
|
695
695
|
limit: int | None = None,
|
|
696
696
|
offset: int | None = None,
|
|
697
697
|
) -> dict:
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
"""Предзагружает информацию о тикерах для биржи Gateio."""
|
|
13
|
+
|
|
14
|
+
exchange_name = "Gateio"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
19
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
20
|
+
currency_pairs = await Client(session).currency_pairs()
|
|
21
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
22
|
+
for symbol_info in currency_pairs:
|
|
23
|
+
try:
|
|
24
|
+
tickers_info[symbol_info.get("id")] = TickerInfoItem(
|
|
25
|
+
tick_precision=int(symbol_info["precision"]),
|
|
26
|
+
tick_step=None,
|
|
27
|
+
size_precision=int(symbol_info["amount_precision"]),
|
|
28
|
+
size_step=None,
|
|
29
|
+
contract_size=1,
|
|
30
|
+
)
|
|
31
|
+
except ValueError as e:
|
|
32
|
+
cls._logger.trace(
|
|
33
|
+
f"ValueError on {cls.exchange_name} by {symbol_info['symbol']}: {e}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
cls._tickers_info = tickers_info
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
40
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
41
|
+
contracts = await Client(session).futures_contracts("usdt")
|
|
42
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
43
|
+
for contract in contracts:
|
|
44
|
+
try:
|
|
45
|
+
tickers_info[contract.get("name")] = TickerInfoItem(
|
|
46
|
+
tick_precision=None,
|
|
47
|
+
tick_step=float(contract["order_price_round"]),
|
|
48
|
+
size_precision=None,
|
|
49
|
+
size_step=float(contract["quanto_multiplier"]),
|
|
50
|
+
contract_size=float(contract["quanto_multiplier"]),
|
|
51
|
+
)
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
cls._logger.trace(f"ValueError on {cls.exchange_name} by {contract['name']}: {e}")
|
|
54
|
+
|
|
55
|
+
cls._futures_tickers_info = tickers_info
|
|
@@ -104,7 +104,7 @@ class UniClient(IUniClient[Client]):
|
|
|
104
104
|
list[KlineDict]: Список свечей для тикера.
|
|
105
105
|
"""
|
|
106
106
|
interval = (
|
|
107
|
-
interval.to_exchange_format(Exchange.
|
|
107
|
+
interval.to_exchange_format(Exchange.GATE, MarketType.SPOT)
|
|
108
108
|
if isinstance(interval, Timeframe)
|
|
109
109
|
else interval
|
|
110
110
|
)
|
|
@@ -138,7 +138,7 @@ class UniClient(IUniClient[Client]):
|
|
|
138
138
|
list[KlineDict]: Список свечей для тикера.
|
|
139
139
|
"""
|
|
140
140
|
interval = (
|
|
141
|
-
interval.to_exchange_format(Exchange.
|
|
141
|
+
interval.to_exchange_format(Exchange.GATE, MarketType.FUTURES)
|
|
142
142
|
if isinstance(interval, Timeframe)
|
|
143
143
|
else interval
|
|
144
144
|
)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
__all__ = ["ExchangeInfo"]
|
|
2
2
|
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
3
5
|
from unicex._abc import IExchangeInfo
|
|
6
|
+
from unicex.types import TickerInfoItem
|
|
4
7
|
|
|
5
8
|
from .client import Client
|
|
6
9
|
|
|
@@ -8,6 +11,9 @@ from .client import Client
|
|
|
8
11
|
class ExchangeInfo(IExchangeInfo):
|
|
9
12
|
"""Предзагружает информацию о тикерах для биржи Hyperliquid."""
|
|
10
13
|
|
|
14
|
+
exchange_name = "Hyperliquid"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
11
17
|
_spot_meta: dict = {}
|
|
12
18
|
"""Словарь с метаинформацией о спотовом рынке."""
|
|
13
19
|
|
|
@@ -20,17 +26,30 @@ class ExchangeInfo(IExchangeInfo):
|
|
|
20
26
|
_futures_meta: dict = {}
|
|
21
27
|
"""Словарь с метаинформацией о фьючерсном рынке."""
|
|
22
28
|
|
|
29
|
+
# DOCS: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
33
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
34
|
+
cls._spot_meta = await Client(session).spot_metadata()
|
|
35
|
+
cls._build_spot_mappings(cls._spot_meta)
|
|
36
|
+
|
|
37
|
+
tickers_info: dict[str, TickerInfoItem] = {}
|
|
38
|
+
for symbol_info in cls._spot_meta["tokens"]:
|
|
39
|
+
tickers_info[symbol_info["name"]] = TickerInfoItem(
|
|
40
|
+
tick_step=None,
|
|
41
|
+
tick_precision=int(symbol_info["weiDecimals"]),
|
|
42
|
+
size_step=None,
|
|
43
|
+
size_precision=int(symbol_info["szDecimals"]),
|
|
44
|
+
contract_size=1,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
cls._tickers_info = tickers_info
|
|
48
|
+
|
|
23
49
|
@classmethod
|
|
24
|
-
async def
|
|
25
|
-
"""Загружает информацию о
|
|
26
|
-
|
|
27
|
-
async with client as conn:
|
|
28
|
-
cls._spot_meta = await conn.spot_metadata()
|
|
29
|
-
cls._build_spot_mappings(cls._spot_meta)
|
|
30
|
-
cls._logger.debug("Hyperliquid spot exchange info loaded")
|
|
31
|
-
|
|
32
|
-
cls._futures_meta = await conn.perp_metadata()
|
|
33
|
-
cls._logger.debug("Hyperliquid futures exchange info loaded")
|
|
50
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
51
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
52
|
+
cls._futures_meta = await Client(session).perp_metadata()
|
|
34
53
|
|
|
35
54
|
@classmethod
|
|
36
55
|
def _build_spot_mappings(cls, spot_meta: dict) -> None:
|
unicex/mapper.py
CHANGED
|
@@ -19,9 +19,9 @@ from .bybit import UniClient as BybitUniClient
|
|
|
19
19
|
from .bybit import UniWebsocketManager as BybitUniWebsocketManager
|
|
20
20
|
from .enums import Exchange
|
|
21
21
|
from .exceptions import NotSupported
|
|
22
|
-
from .
|
|
23
|
-
from .
|
|
24
|
-
from .
|
|
22
|
+
from .gate import ExchangeInfo as GateioExchangeInfo
|
|
23
|
+
from .gate import UniClient as GateioUniClient
|
|
24
|
+
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
|
|
@@ -36,7 +36,7 @@ _UNI_CLIENT_MAPPER: dict[Exchange, type[IUniClient]] = {
|
|
|
36
36
|
Exchange.BINANCE: BinanceUniClient,
|
|
37
37
|
Exchange.BITGET: BitgetUniClient,
|
|
38
38
|
Exchange.BYBIT: BybitUniClient,
|
|
39
|
-
Exchange.
|
|
39
|
+
Exchange.GATE: GateioUniClient,
|
|
40
40
|
Exchange.HYPERLIQUID: HyperliquidUniClient,
|
|
41
41
|
Exchange.MEXC: MexcUniClient,
|
|
42
42
|
Exchange.OKX: OkxUniClient,
|
|
@@ -47,7 +47,7 @@ _UNI_WS_MANAGER_MAPPER: dict[Exchange, type[IUniWebsocketManager]] = {
|
|
|
47
47
|
Exchange.BINANCE: BinanceUniWebsocketManager,
|
|
48
48
|
Exchange.BITGET: BitgetUniWebsocketManager,
|
|
49
49
|
Exchange.BYBIT: BybitUniWebsocketManager,
|
|
50
|
-
Exchange.
|
|
50
|
+
Exchange.GATE: GateioUniWebsocketManager,
|
|
51
51
|
Exchange.HYPERLIQUID: HyperliquidUniWebsocketManager,
|
|
52
52
|
Exchange.MEXC: MexcUniWebsocketManager,
|
|
53
53
|
Exchange.OKX: OkxUniWebsocketManager,
|
|
@@ -58,7 +58,7 @@ _EXCHANGE_INFO_MAPPER: dict[Exchange, type[IExchangeInfo]] = {
|
|
|
58
58
|
Exchange.BINANCE: BinanceExchangeInfo,
|
|
59
59
|
Exchange.BITGET: BitgetExchangeInfo,
|
|
60
60
|
Exchange.BYBIT: BybitExchangeInfo,
|
|
61
|
-
Exchange.
|
|
61
|
+
Exchange.GATE: GateioExchangeInfo,
|
|
62
62
|
Exchange.HYPERLIQUID: HyperliquidExchangeInfo,
|
|
63
63
|
Exchange.MEXC: MexcExchangeInfo,
|
|
64
64
|
Exchange.OKX: OkxExchangeInfo,
|
unicex/mexc/exchange_info.py
CHANGED
|
@@ -5,28 +5,43 @@ import aiohttp
|
|
|
5
5
|
from unicex._abc import IExchangeInfo
|
|
6
6
|
from unicex.types import TickerInfoItem
|
|
7
7
|
|
|
8
|
+
from .client import Client
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class ExchangeInfo(IExchangeInfo):
|
|
10
12
|
"""Предзагружает информацию о тикерах для биржи Mexc."""
|
|
11
13
|
|
|
14
|
+
exchange_name = "Mexc"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
async def _load_spot_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
19
|
+
"""Загружает информацию о бирже для спотового рынка."""
|
|
20
|
+
exchange_info = await Client(session).exchange_info()
|
|
21
|
+
tickers_info = {}
|
|
22
|
+
for el in exchange_info["symbols"]:
|
|
23
|
+
tickers_info[el["symbol"]] = TickerInfoItem(
|
|
24
|
+
tick_precision=int(el["quotePrecision"]),
|
|
25
|
+
tick_step=None,
|
|
26
|
+
size_precision=int(el["baseAssetPrecision"]),
|
|
27
|
+
size_step=None,
|
|
28
|
+
contract_size=1,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
cls._tickers_info = tickers_info
|
|
32
|
+
|
|
12
33
|
@classmethod
|
|
13
|
-
async def
|
|
14
|
-
"""Загружает информацию о
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
min_limit_size=el["minVol"],
|
|
28
|
-
max_limit_size=el["maxVol"],
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
cls._futures_tickers_info = futures_tickers_info
|
|
32
|
-
cls._logger.debug("Mexc futures exchange info loaded")
|
|
34
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
35
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
36
|
+
exchange_info = await Client(session).futures_contract_detail()
|
|
37
|
+
tickers_info = {}
|
|
38
|
+
for el in exchange_info["data"]:
|
|
39
|
+
tickers_info[el["symbol"]] = TickerInfoItem(
|
|
40
|
+
tick_precision=None,
|
|
41
|
+
tick_step=el["priceUnit"],
|
|
42
|
+
size_precision=None,
|
|
43
|
+
size_step=el["contractSize"],
|
|
44
|
+
contract_size=el["contractSize"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
cls._futures_tickers_info = tickers_info
|
unicex/okx/adapter.py
CHANGED
|
@@ -57,16 +57,22 @@ class Adapter:
|
|
|
57
57
|
|
|
58
58
|
# NOTE: Обратите внимание, изменение цены в случае с OKX возвращается относительно открытия 1 day свечи.
|
|
59
59
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
result = {}
|
|
61
|
+
for item in raw_data["data"]:
|
|
62
|
+
try:
|
|
63
|
+
result[item["instId"]] = TickerDailyItem(
|
|
64
|
+
p=round(
|
|
65
|
+
(float(item["last"]) - float(item["open24h"]))
|
|
66
|
+
/ float(item["open24h"])
|
|
67
|
+
* 100,
|
|
68
|
+
2,
|
|
69
|
+
),
|
|
70
|
+
v=float(item["vol24h"]),
|
|
71
|
+
q=float(item["volCcy24h"]),
|
|
72
|
+
)
|
|
73
|
+
except (ValueError, TypeError, KeyError):
|
|
74
|
+
continue
|
|
75
|
+
return result
|
|
70
76
|
|
|
71
77
|
@staticmethod
|
|
72
78
|
def futures_ticker_24hr(raw_data: dict) -> TickerDailyDict:
|
|
@@ -88,7 +94,13 @@ class Adapter:
|
|
|
88
94
|
@staticmethod
|
|
89
95
|
def last_price(raw_data: dict) -> dict[str, float]:
|
|
90
96
|
"""Преобразует данные о последней цене в унифицированный формат."""
|
|
91
|
-
|
|
97
|
+
result = {}
|
|
98
|
+
for item in raw_data["data"]:
|
|
99
|
+
try:
|
|
100
|
+
result[item["instId"]] = float(item["last"])
|
|
101
|
+
except (ValueError, TypeError, KeyError):
|
|
102
|
+
continue
|
|
103
|
+
return result
|
|
92
104
|
|
|
93
105
|
@staticmethod
|
|
94
106
|
def klines(raw_data: dict, symbol: str) -> list[KlineDict]:
|
unicex/okx/exchange_info.py
CHANGED
|
@@ -5,46 +5,43 @@ import aiohttp
|
|
|
5
5
|
from unicex._abc import IExchangeInfo
|
|
6
6
|
from unicex.types import TickerInfoItem
|
|
7
7
|
|
|
8
|
+
from .client import Client
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class ExchangeInfo(IExchangeInfo):
|
|
10
12
|
"""Предзагружает информацию о тикерах для биржи Okx."""
|
|
11
13
|
|
|
14
|
+
exchange_name = "Okx"
|
|
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).get_instruments("SPOT")
|
|
22
|
+
for el in exchange_info["data"]:
|
|
23
|
+
tickers_info[el["instId"]] = TickerInfoItem(
|
|
24
|
+
tick_precision=None,
|
|
25
|
+
tick_step=float(el["tickSz"]),
|
|
26
|
+
size_precision=None,
|
|
27
|
+
size_step=float(el["lotSz"]),
|
|
28
|
+
contract_size=1,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
cls._tickers_info = tickers_info
|
|
32
|
+
|
|
12
33
|
@classmethod
|
|
13
|
-
async def
|
|
14
|
-
"""Загружает информацию о
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
min_limit_size=float(el["minSz"]),
|
|
28
|
-
max_limit_size=float(el["maxLmtSz"]),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
cls._tickers_info = tickers_info
|
|
32
|
-
cls._logger.debug("Okx spot exchange info loaded")
|
|
33
|
-
|
|
34
|
-
futures_tickers_info = {}
|
|
35
|
-
url = "https://www.okx.com/api/v5/public/instruments?instType=SWAP"
|
|
36
|
-
async with session.get(url) as response:
|
|
37
|
-
data = await response.json()
|
|
38
|
-
for el in data["data"]:
|
|
39
|
-
futures_tickers_info[el["instId"]] = TickerInfoItem(
|
|
40
|
-
tick_precision=cls._step_size_to_precision(el["tickSz"]),
|
|
41
|
-
size_precision=cls._step_size_to_precision(el["lotSz"]),
|
|
42
|
-
contract_size=float(el["ctVal"]),
|
|
43
|
-
min_market_size=el["minSz"],
|
|
44
|
-
max_market_size=el["maxMktSz"],
|
|
45
|
-
min_limit_size=el["minSz"],
|
|
46
|
-
max_limit_size=el["maxLmtSz"],
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
cls._futures_tickers_info = futures_tickers_info
|
|
50
|
-
cls._logger.debug("Okx futures exchange info loaded")
|
|
34
|
+
async def _load_futures_exchange_info(cls, session: aiohttp.ClientSession) -> None:
|
|
35
|
+
"""Загружает информацию о бирже для фьючерсного рынка."""
|
|
36
|
+
tickers_info = {}
|
|
37
|
+
exchange_info = await Client(session).get_instruments("SWAP")
|
|
38
|
+
for el in exchange_info["data"]:
|
|
39
|
+
tickers_info[el["instId"]] = TickerInfoItem(
|
|
40
|
+
tick_precision=None,
|
|
41
|
+
tick_step=float(el["tickSz"]),
|
|
42
|
+
size_precision=None,
|
|
43
|
+
size_step=float(el["lotSz"]) * float(el["ctVal"]),
|
|
44
|
+
contract_size=float(el["ctVal"]),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
cls._futures_tickers_info = tickers_info
|
unicex/types.py
CHANGED
|
@@ -119,6 +119,8 @@ type OpenInterestDict = dict[str, OpenInterestItem]
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
class LiquidationDict(TypedDict):
|
|
122
|
+
"""Модель ликвидации."""
|
|
123
|
+
|
|
122
124
|
t: int
|
|
123
125
|
"""Время. В миллисекундах."""
|
|
124
126
|
|
|
@@ -140,29 +142,28 @@ type AccountType = Literal["SPOT", "FUTURES"]
|
|
|
140
142
|
|
|
141
143
|
|
|
142
144
|
class TickerInfoItem(TypedDict):
|
|
143
|
-
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
|
|
145
|
+
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
|
|
146
|
+
|
|
147
|
+
На некоторых биржах удобнее делать округление через precisions, на некоторых через step,
|
|
148
|
+
потому что иногда встречаются шаги, которые не являются степенью 10. Поэтому обязательно
|
|
149
|
+
должны быть определены tick_precision ИЛИ tick_step, а так же size_precision ИЛИ size_step.
|
|
150
|
+
"""
|
|
144
151
|
|
|
145
|
-
tick_precision: int
|
|
152
|
+
tick_precision: int | None
|
|
146
153
|
"""Количество знаков после запятой для цены."""
|
|
147
154
|
|
|
148
|
-
|
|
155
|
+
tick_step: float | None
|
|
156
|
+
"""Шаг одного деления для цены."""
|
|
157
|
+
|
|
158
|
+
size_precision: int | None
|
|
149
159
|
"""Количество знаков после запятой для объема."""
|
|
150
160
|
|
|
161
|
+
size_step: float | None
|
|
162
|
+
"""Шаг одного деления для объема."""
|
|
163
|
+
|
|
151
164
|
contract_size: float | None
|
|
152
165
|
"""Множитель контракта (если есть)."""
|
|
153
166
|
|
|
154
|
-
min_market_size: float | None
|
|
155
|
-
"""Минимальный размер рыночного ордера в монетах (если есть)."""
|
|
156
|
-
|
|
157
|
-
max_market_size: float | None
|
|
158
|
-
"""Максимальный размер рыночного ордера в монетах (если есть)."""
|
|
159
|
-
|
|
160
|
-
min_limit_size: float | None
|
|
161
|
-
"""Минимальный размер лимитного ордера в монетах (если есть)."""
|
|
162
|
-
|
|
163
|
-
max_limit_size: float | None
|
|
164
|
-
"""Максимальный размер лимитного ордера в монетах (если есть)."""
|
|
165
|
-
|
|
166
167
|
|
|
167
168
|
type TickersInfoDict = dict[str, TickerInfoItem]
|
|
168
169
|
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для всех тикеров."""
|
unicex/utils.py
CHANGED
|
@@ -208,7 +208,7 @@ def symbol_to_exchange_format(
|
|
|
208
208
|
return symbol_upper.replace("USDT", "-USDT-SWAP")
|
|
209
209
|
elif market_type == MarketType.SPOT:
|
|
210
210
|
return symbol_upper.replace("USDT", "-USDT")
|
|
211
|
-
elif exchange == Exchange.
|
|
211
|
+
elif exchange == Exchange.GATE:
|
|
212
212
|
return symbol_upper.replace("USDT", "_USDT")
|
|
213
213
|
elif exchange == Exchange.HYPERLIQUID:
|
|
214
214
|
if market_type == MarketType.FUTURES:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unicex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.1
|
|
4
4
|
Summary: Unified Crypto Exchange API
|
|
5
5
|
Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -49,18 +49,18 @@ Dynamic: license-file
|
|
|
49
49
|
|
|
50
50
|
# Unified Crypto Exchange API
|
|
51
51
|
|
|
52
|
-
`unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж.
|
|
52
|
+
`unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж. Поддерживает спотовый и USDT-фьючерсный рынки.
|
|
53
53
|
|
|
54
54
|
## ✅ Статус реализации
|
|
55
55
|
|
|
56
56
|
| Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
|
|
57
57
|
|-----------------|--------|------|------------|---------|------------|----------------|--------------|
|
|
58
|
-
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
59
|
-
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | |
|
|
60
|
-
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ |
|
|
61
|
-
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | |
|
|
58
|
+
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
59
|
+
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
60
|
+
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
|
|
61
|
+
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
62
62
|
| **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
|
|
63
|
-
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | |
|
|
63
|
+
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
64
64
|
| **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -191,3 +191,53 @@ async def callback(trade: TradeDict) -> None:
|
|
|
191
191
|
if __name__ == "__main__":
|
|
192
192
|
asyncio.run(main())
|
|
193
193
|
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
### Пример: Округление цен используя фоновый класс ExchangeInfo
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
import asyncio
|
|
201
|
+
from unicex import start_exchanges_info, get_exchange_info, Exchange
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def main() -> None:
|
|
205
|
+
# ⏳ Запускаем фоновые процессы, которые собирают рыночные параметры всех бирж:
|
|
206
|
+
# - количество знаков после точки для цены и объема
|
|
207
|
+
# - множители контрактов для фьючерсов
|
|
208
|
+
await start_exchanges_info()
|
|
209
|
+
|
|
210
|
+
# Небольшая пауза, чтобы данные успели подгрузиться
|
|
211
|
+
await asyncio.sleep(1)
|
|
212
|
+
|
|
213
|
+
# 1️⃣ Пример 1: Округление цены для фьючерсов OKX
|
|
214
|
+
okx_exchange_info = get_exchange_info(Exchange.OKX)
|
|
215
|
+
okx_rounded_price = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", 123456.1234567890)
|
|
216
|
+
print(okx_rounded_price) # >> 123456.1
|
|
217
|
+
|
|
218
|
+
# 2️⃣ Пример 2: Округление объема для спота Binance
|
|
219
|
+
binance_exchange_info = get_exchange_info(Exchange.BINANCE)
|
|
220
|
+
binance_rounded_quantity = binance_exchange_info.round_quantity("BTCUSDT", 1.123456789)
|
|
221
|
+
print(binance_rounded_quantity) # >> 1.12345
|
|
222
|
+
|
|
223
|
+
# 3️⃣ Пример 3: Получение множителя контракта (например, Mexc Futures)
|
|
224
|
+
mexc_exchange_info = get_exchange_info(Exchange.MEXC)
|
|
225
|
+
mexc_contract_multiplier = mexc_exchange_info.get_futures_ticker_info("BTC_USDT")["contract_size"]
|
|
226
|
+
print(mexc_contract_multiplier) # >> 0.0001
|
|
227
|
+
|
|
228
|
+
# 4️⃣ Пример 4: Реальное применение — вычисляем тейк-профит вручную
|
|
229
|
+
# Допустим, позиция открыта по 123123.1 USDT, хотим +3.5% тейк-профит:
|
|
230
|
+
take_profit_raw = 123123.1 * 1.035
|
|
231
|
+
print("До округления:", take_profit_raw) # >> 127432.40849999999
|
|
232
|
+
|
|
233
|
+
# Биржа требует цену в допустимом формате — округляем:
|
|
234
|
+
take_profit = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", take_profit_raw)
|
|
235
|
+
print("После округления:", take_profit) # >> 127432.4
|
|
236
|
+
|
|
237
|
+
# Теперь это число можно безопасно передать в API без ошибок:
|
|
238
|
+
# await client.create_order(symbol="BTC-USDT-SWAP", price=take_profit, ...)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
asyncio.run(main())
|
|
243
|
+
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
unicex/__init__.py,sha256=
|
|
2
|
-
unicex/enums.py,sha256=
|
|
1
|
+
unicex/__init__.py,sha256=fnJ6Z68MRZKLY3SSz8Q2yhraq5t22QDagIvZKbVq3Uk,5804
|
|
2
|
+
unicex/enums.py,sha256=8S72iJvD1Hd8i9EmF8KshJEEfAYe9dITVcIxBLREjn0,9625
|
|
3
3
|
unicex/exceptions.py,sha256=r-xZzX78VuxVnI5pe99AM8FIiGcdIUDcF5CaTkQ4NE0,2213
|
|
4
|
-
unicex/extra.py,sha256=
|
|
5
|
-
unicex/mapper.py,sha256=
|
|
6
|
-
unicex/types.py,sha256=
|
|
7
|
-
unicex/utils.py,sha256=
|
|
4
|
+
unicex/extra.py,sha256=bU88Oh4-9Cvc2vuuf2fDGksUR7gvdluDh99O0m-vDFo,13830
|
|
5
|
+
unicex/mapper.py,sha256=PPMC5E8smjmhXwiOCQG3WIc_-8wZ3iZGey6HiLiN7TQ,4903
|
|
6
|
+
unicex/types.py,sha256=quGNpQm-_lSkijRKz04A5lPVxQ2PYrFzggYSPiJGzXI,4728
|
|
7
|
+
unicex/utils.py,sha256=xzeP8E2bBoie8GvxW97FPEu_QPYMxiNgc9zX7yrdsfk,8048
|
|
8
8
|
unicex/_abc/__init__.py,sha256=fxZjNFJFeFwWTXz8iSDe7eCWwE6xfFwFwAuG6l-TI8A,289
|
|
9
|
-
unicex/_abc/exchange_info.py,sha256=
|
|
9
|
+
unicex/_abc/exchange_info.py,sha256=NHBQAjknIRebyjl3musrvneN5u9vKYXjAca5PmTvJTw,9545
|
|
10
10
|
unicex/_abc/uni_client.py,sha256=ZjxK8aqCGLUUYy1UQTM9EvWn1IXwMkH2Db8sZrs1e_I,13728
|
|
11
11
|
unicex/_abc/uni_websocket_manager.py,sha256=yYKypPkIe3rKfWBuTsS8rkwIPljpd1588CYDkeTOYqE,9905
|
|
12
12
|
unicex/_base/__init__.py,sha256=0TmevATGnRB3qow6tkCR8dQKNZCWKeib6YQjNJ4a1b0,236
|
|
@@ -15,7 +15,7 @@ unicex/_base/websocket.py,sha256=7IUIO2-KyjTswK3nTkRI7uQkNq6NYS_EhaG7tJQCPeg,114
|
|
|
15
15
|
unicex/binance/__init__.py,sha256=sDk4ZjakRdpFMaMSpOCfqjf6ZPfAS9tlrt4WlDHtDkw,932
|
|
16
16
|
unicex/binance/adapter.py,sha256=JbUFyjnDAFtyuYYrh90YeOvQOZQ6faim0nWS6U0NxXw,8799
|
|
17
17
|
unicex/binance/client.py,sha256=1qPx0uRT4prC6saLBQ55pXDWcWTCKhYEwVIysiihPgU,60984
|
|
18
|
-
unicex/binance/exchange_info.py,sha256=
|
|
18
|
+
unicex/binance/exchange_info.py,sha256=LNDkgBC5HB3JxtIBi39puqDg6LIVWqIWjT-6akDxtMs,2437
|
|
19
19
|
unicex/binance/uni_client.py,sha256=W4yxiU0kkJKPJjimhv4KAWreuEBwt7GgrWXefcw5BEA,8365
|
|
20
20
|
unicex/binance/uni_websocket_manager.py,sha256=FywEuUt3CiDcAQPo3ItdW2pPgbLDvVpVYFM2D_QscPU,8679
|
|
21
21
|
unicex/binance/user_websocket.py,sha256=HJ_3VZV0Bil0vfsdLrZElXm-gUqd8mGzXzfQ0_sHjFc,7861
|
|
@@ -23,7 +23,7 @@ unicex/binance/websocket_manager.py,sha256=Idrq0Qzi14QgpIG5d77S-r1h_BvRXphcxY2BT
|
|
|
23
23
|
unicex/bitget/__init__.py,sha256=8govSOEyWjA62js-ZTQIiSYWSmcEUFSC9hVTpS8eosk,929
|
|
24
24
|
unicex/bitget/adapter.py,sha256=frQBOKFsIB8mXc_Ime2-Iby_nRQlSpzXjshC5AxoDTA,7741
|
|
25
25
|
unicex/bitget/client.py,sha256=RbOrW8Q21OwYFac2xrKQURdYSAkVQ0jj18cDoLj5Edk,90155
|
|
26
|
-
unicex/bitget/exchange_info.py,sha256=
|
|
26
|
+
unicex/bitget/exchange_info.py,sha256=_UMvAqP0zcpmv9dkovkFxrXLlol6q8_v7-0sy6FSfrE,1959
|
|
27
27
|
unicex/bitget/uni_client.py,sha256=MrXAmthTDTEQZ1ZY3LuqkCKL1bw_mKHMdiV4XiRFO-M,8641
|
|
28
28
|
unicex/bitget/uni_websocket_manager.py,sha256=y-HXrREy_ruNeUzdRv5nHwBZQgbupxzp1UV-IFOpN_8,9793
|
|
29
29
|
unicex/bitget/user_websocket.py,sha256=tlkv7Rmsw_FSfCJnEMOK_9jRsXRk2Ah_slqG8C-uhuo,129
|
|
@@ -31,23 +31,23 @@ unicex/bitget/websocket_manager.py,sha256=2OhH2gKy2gN03oYeCFvf3_l6RA29dDOvpajGhb
|
|
|
31
31
|
unicex/bybit/__init__.py,sha256=SrMBh6K5zUt4JheWUpNUYNb1NCDr2ujTFv4IDguaGZI,926
|
|
32
32
|
unicex/bybit/adapter.py,sha256=U6QP2eGefZqfXq7H4P1G9M23HieAjmylb5FnrHISn5s,8147
|
|
33
33
|
unicex/bybit/client.py,sha256=U1mCcF-Mg2spiKlp0ucIA-kDOisYLO0edRS6XC16iNo,61014
|
|
34
|
-
unicex/bybit/exchange_info.py,sha256=
|
|
34
|
+
unicex/bybit/exchange_info.py,sha256=6DySi-61kyU1Wpfp7G-5Zu7G1bFfcR4qo2oO5d0YutI,2200
|
|
35
35
|
unicex/bybit/uni_client.py,sha256=0wmIRRgofuJXWvZMB1gHwuIEfoWuPhLJXthmK1f9w40,8382
|
|
36
36
|
unicex/bybit/uni_websocket_manager.py,sha256=OpnvWD0xZ8T_By0HZCSg3jWoZqScRATAsku4IIBqhlw,10252
|
|
37
37
|
unicex/bybit/user_websocket.py,sha256=IGGEnwyWs5jOppgK_R7SisBDvsiF1_piTswBrdQOgDg,128
|
|
38
38
|
unicex/bybit/websocket_manager.py,sha256=ePHvngoqoVPr6p-BayoEGhCuRK-cJ69CAPzF94qTalQ,15404
|
|
39
|
-
unicex/
|
|
40
|
-
unicex/
|
|
41
|
-
unicex/
|
|
42
|
-
unicex/
|
|
43
|
-
unicex/
|
|
44
|
-
unicex/
|
|
45
|
-
unicex/
|
|
46
|
-
unicex/
|
|
39
|
+
unicex/gate/__init__.py,sha256=dsKvhQhDcw4_w0S4c9IjKkCoOg4DCUtSecEUOlfstug,929
|
|
40
|
+
unicex/gate/adapter.py,sha256=PE5lXQORAKLhzFZBtxcarNIt2xEcDrPrphxWS0k0wEk,6842
|
|
41
|
+
unicex/gate/client.py,sha256=bhWkYBn5B4JKpLRfQERTVzisiCSg7mMi6mM0kh4Hes4,53760
|
|
42
|
+
unicex/gate/exchange_info.py,sha256=ANzfe4mqxtLnj2TBJJxoc31KUosvxdApp1_xYrRNQDs,2300
|
|
43
|
+
unicex/gate/uni_client.py,sha256=zJrxzWqp0g4hYV0kUKQRfWxMHcWoAnhgB4B5t_MfOng,9644
|
|
44
|
+
unicex/gate/uni_websocket_manager.py,sha256=3KAHhgJNISTOZRBTwbZPigc8iq_IHS19NwnuAsnfeXg,9329
|
|
45
|
+
unicex/gate/user_websocket.py,sha256=4qZX9N2RjlJ-e25Eszz12OeCM17j5DdXVimBVaLj53w,129
|
|
46
|
+
unicex/gate/websocket_manager.py,sha256=phtHbvAGQD3mtewCUxBuuD1Nj0FXN6oZrd7tnmT7c2c,25832
|
|
47
47
|
unicex/hyperliquid/__init__.py,sha256=qGTAkwfXLvknvHET_iA7Qml3jkxxxA0moU_98nGTcVU,944
|
|
48
48
|
unicex/hyperliquid/adapter.py,sha256=0aULPGDhppjbRvShbR49GNt6qmw4o_CmrFi_xGVRLHg,10612
|
|
49
49
|
unicex/hyperliquid/client.py,sha256=8YnPh_6Hj8vFYcPbMaPae6OfroFzn28VI6iL3FQfa84,84255
|
|
50
|
-
unicex/hyperliquid/exchange_info.py,sha256=
|
|
50
|
+
unicex/hyperliquid/exchange_info.py,sha256=D1h1fvH9DycRq-xDTZ_-Y1NC2JCp165IyWMXs39VjFI,4936
|
|
51
51
|
unicex/hyperliquid/uni_client.py,sha256=4jv2uC076PBeq-EzCKvwaEEMk_M3HpsWA6iwNepJg7E,15674
|
|
52
52
|
unicex/hyperliquid/uni_websocket_manager.py,sha256=AzR_8Aq98NkCA1oc2IiH02ysMOYLaisEmcuuaafeaJw,9334
|
|
53
53
|
unicex/hyperliquid/user_websocket.py,sha256=BKD9ap2bx5DwpkkwwecfOTVedrZBR9eMAITgCBgg02w,134
|
|
@@ -55,7 +55,7 @@ unicex/hyperliquid/websocket_manager.py,sha256=GNBQT4ihk1XsTAaas8lxAfo3ASBGs_8EV
|
|
|
55
55
|
unicex/mexc/__init__.py,sha256=lltANqM_2P-fmF5j8o5-pjmORPuK6C5sVjcQhuUU_R0,923
|
|
56
56
|
unicex/mexc/adapter.py,sha256=uaJ6157wTrMuf72thIggDJHiaeRVYyH0qMIn1x5Mrp8,9820
|
|
57
57
|
unicex/mexc/client.py,sha256=oJSpz-8hvA1WMCN2B7oqI2lXEx1_VJLAn8jbzhW8mF0,30950
|
|
58
|
-
unicex/mexc/exchange_info.py,sha256=
|
|
58
|
+
unicex/mexc/exchange_info.py,sha256=z2bQsVU0ciXV2_DFkueZHo1X35KIK2alD-7ZZjNg5Kc,1763
|
|
59
59
|
unicex/mexc/uni_client.py,sha256=0unhbU3WfqrMsV_y15VnIFjwUzQLVtp1tx6spV6cHKI,9477
|
|
60
60
|
unicex/mexc/uni_websocket_manager.py,sha256=MAKyGyit8Ve4doM7y8cTQbVVbpzlJx2HcHaSkLG4Jp4,9327
|
|
61
61
|
unicex/mexc/user_websocket.py,sha256=l77-e6i0B2btd7a5IcCytbgswnV171NqOhunTcbaq48,127
|
|
@@ -79,15 +79,15 @@ unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py,sha256=0FOMsSO4jzMQ6446yZ
|
|
|
79
79
|
unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py,sha256=_V0StMrDE7-bsdRPQdTC2XLGDGqJ5U_scx8ZjcyCGgk,3735
|
|
80
80
|
unicex/mexc/_spot_ws_proto/__init__.py,sha256=L8Jft1713_M8CLR9drgSjLBdY_46sPT3O9zDFlxYvgc,11431
|
|
81
81
|
unicex/okx/__init__.py,sha256=Ljbw3AP0YrPF5bIPJi_3JP3B_czR9xurYHI24rgWk9M,920
|
|
82
|
-
unicex/okx/adapter.py,sha256=
|
|
82
|
+
unicex/okx/adapter.py,sha256=0sQ_7pYdUEBfXMUtNj8wJ9ECt-DJQ9ozUXi0WPAYevU,5724
|
|
83
83
|
unicex/okx/client.py,sha256=2XL4YWPdRZYRMfLF1vSpkmwSd8Dxn8uyw5vCwDJab48,90969
|
|
84
|
-
unicex/okx/exchange_info.py,sha256=
|
|
84
|
+
unicex/okx/exchange_info.py,sha256=gkTwYnXgswa1FGLXdKo9qLYqZA0BS9VefpALhR4_t-Q,1772
|
|
85
85
|
unicex/okx/uni_client.py,sha256=E_Wod0JSGt1K6k1mAIWnOv350pELbv-nic7g1KgOuos,8694
|
|
86
86
|
unicex/okx/uni_websocket_manager.py,sha256=b4f_QjA64DJmENQdIGb5IOVc7kvit7KMCdWeCmRbxGY,9326
|
|
87
87
|
unicex/okx/user_websocket.py,sha256=8c9kpm-xVa729pW93OKUGLHaE9MY0uzEpjIgNIFRF80,126
|
|
88
88
|
unicex/okx/websocket_manager.py,sha256=wROXTUDqKzOE-wDnCtXso_MC4SzfPuPols5aPg_Z3y4,26027
|
|
89
|
-
unicex-0.
|
|
90
|
-
unicex-0.
|
|
91
|
-
unicex-0.
|
|
92
|
-
unicex-0.
|
|
93
|
-
unicex-0.
|
|
89
|
+
unicex-0.13.1.dist-info/licenses/LICENSE,sha256=lNNK4Vqak9cXm6qVJLhbqS7iR_BMj6k7fd7XQ6l1k54,1507
|
|
90
|
+
unicex-0.13.1.dist-info/METADATA,sha256=POGdHm6PX4PV12dOa5GmLlDSQ9W9-WxPI1unqjm0itY,11752
|
|
91
|
+
unicex-0.13.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
92
|
+
unicex-0.13.1.dist-info/top_level.txt,sha256=_7rar-0OENIg4KRy6cgjWiebFYAJhjKEcMggAocGWG4,7
|
|
93
|
+
unicex-0.13.1.dist-info/RECORD,,
|
unicex/gateio/exchange_info.py
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|