unicex 0.10.5__py3-none-any.whl → 0.13.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.
- unicex/__init__.py +16 -1
- unicex/_abc/exchange_info.py +75 -35
- unicex/binance/exchange_info.py +53 -3
- unicex/bitget/exchange_info.py +39 -3
- unicex/bybit/adapter.py +57 -3
- unicex/bybit/exchange_info.py +42 -3
- unicex/bybit/uni_websocket_manager.py +14 -4
- unicex/bybit/websocket_manager.py +2 -2
- unicex/gateio/client.py +2 -2
- unicex/gateio/exchange_info.py +46 -3
- unicex/hyperliquid/exchange_info.py +29 -10
- unicex/mexc/exchange_info.py +35 -20
- unicex/okx/exchange_info.py +35 -38
- unicex/types.py +34 -15
- {unicex-0.10.5.dist-info → unicex-0.13.0.dist-info}/METADATA +57 -7
- {unicex-0.10.5.dist-info → unicex-0.13.0.dist-info}/RECORD +19 -19
- {unicex-0.10.5.dist-info → unicex-0.13.0.dist-info}/WHEEL +0 -0
- {unicex-0.10.5.dist-info → unicex-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {unicex-0.10.5.dist-info → unicex-0.13.0.dist-info}/top_level.txt +0 -0
unicex/__init__.py
CHANGED
|
@@ -26,6 +26,7 @@ __all__ = [
|
|
|
26
26
|
"OpenInterestItem",
|
|
27
27
|
"TickerInfoItem",
|
|
28
28
|
"TickersInfoDict",
|
|
29
|
+
"LiquidationDict",
|
|
29
30
|
# Interfaces
|
|
30
31
|
"IUniClient",
|
|
31
32
|
"IUniWebsocketManager",
|
|
@@ -93,7 +94,21 @@ from ._base import BaseClient, Websocket
|
|
|
93
94
|
# enums, mappers, types
|
|
94
95
|
from .enums import Exchange, MarketType, Side, Timeframe
|
|
95
96
|
from .mapper import get_uni_client, get_uni_websocket_manager, get_exchange_info
|
|
96
|
-
from .types import
|
|
97
|
+
from .types import (
|
|
98
|
+
TickerDailyDict,
|
|
99
|
+
TickerDailyItem,
|
|
100
|
+
KlineDict,
|
|
101
|
+
TradeDict,
|
|
102
|
+
AggTradeDict,
|
|
103
|
+
RequestMethod,
|
|
104
|
+
LoggerLike,
|
|
105
|
+
AccountType,
|
|
106
|
+
OpenInterestDict,
|
|
107
|
+
OpenInterestItem,
|
|
108
|
+
TickerInfoItem,
|
|
109
|
+
TickersInfoDict,
|
|
110
|
+
LiquidationDict,
|
|
111
|
+
)
|
|
97
112
|
|
|
98
113
|
# exchanges
|
|
99
114
|
|
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/adapter.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from unicex.types import (
|
|
6
6
|
KlineDict,
|
|
7
|
+
LiquidationDict,
|
|
7
8
|
OpenInterestDict,
|
|
8
9
|
OpenInterestItem,
|
|
9
10
|
TickerDailyDict,
|
|
@@ -130,6 +131,14 @@ class Adapter:
|
|
|
130
131
|
|
|
131
132
|
@staticmethod
|
|
132
133
|
def Klines_message(raw_msg: Any) -> list[KlineDict]:
|
|
134
|
+
"""Преобразует вебсокет-сообщение с данными о свечах в унифицированный формат.
|
|
135
|
+
|
|
136
|
+
Параметры:
|
|
137
|
+
raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
|
|
138
|
+
|
|
139
|
+
Возвращает:
|
|
140
|
+
`list[KlineDict]`: Список свечей в унифицированном формате.
|
|
141
|
+
"""
|
|
133
142
|
symbol = raw_msg["topic"].split(".")[-1]
|
|
134
143
|
return [
|
|
135
144
|
KlineDict(
|
|
@@ -144,11 +153,56 @@ class Adapter:
|
|
|
144
153
|
T=kline["end"],
|
|
145
154
|
x=kline["confirm"],
|
|
146
155
|
)
|
|
147
|
-
for kline in
|
|
156
|
+
for kline in sorted(
|
|
157
|
+
raw_msg["data"],
|
|
158
|
+
key=lambda x: int(x["start"]),
|
|
159
|
+
)
|
|
148
160
|
]
|
|
149
161
|
|
|
150
162
|
@staticmethod
|
|
151
|
-
def
|
|
163
|
+
def trades_message(raw_msg: Any) -> list[TradeDict]:
|
|
164
|
+
"""Преобразует вебсокет-сообщение с данными о сделках в унифицированный формат.
|
|
165
|
+
|
|
166
|
+
Параметры:
|
|
167
|
+
raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
|
|
168
|
+
|
|
169
|
+
Возвращает:
|
|
170
|
+
`list[TradeDict]`: Список сделок в унифицированном формате.
|
|
171
|
+
"""
|
|
172
|
+
return [
|
|
173
|
+
TradeDict(
|
|
174
|
+
t=trade["T"],
|
|
175
|
+
s=trade["s"],
|
|
176
|
+
S=trade["S"].upper(),
|
|
177
|
+
p=float(trade["p"]),
|
|
178
|
+
v=float(trade["v"]),
|
|
179
|
+
)
|
|
180
|
+
for trade in sorted(
|
|
181
|
+
raw_msg["data"],
|
|
182
|
+
key=lambda x: int(x["T"]),
|
|
183
|
+
)
|
|
184
|
+
]
|
|
152
185
|
|
|
153
186
|
@staticmethod
|
|
154
|
-
def
|
|
187
|
+
def liquidations_message(raw_msg: Any) -> list[LiquidationDict]:
|
|
188
|
+
"""Преобразует вебсокет-сообщение с данными о ликвидациях в унифицированный формат.
|
|
189
|
+
|
|
190
|
+
Параметры:
|
|
191
|
+
raw_msg (`Any`): Сырое сообщение из вебсокета Bybit.
|
|
192
|
+
|
|
193
|
+
Возвращает:
|
|
194
|
+
`list[LiquidationDict]`: Список ликвидаций в унифицированном формате.
|
|
195
|
+
"""
|
|
196
|
+
return [
|
|
197
|
+
LiquidationDict(
|
|
198
|
+
t=liquidation["T"],
|
|
199
|
+
s=liquidation["s"],
|
|
200
|
+
S=liquidation["S"].upper(),
|
|
201
|
+
v=float(liquidation["v"]),
|
|
202
|
+
p=float(liquidation["p"]),
|
|
203
|
+
)
|
|
204
|
+
for liquidation in sorted(
|
|
205
|
+
raw_msg["data"],
|
|
206
|
+
key=lambda x: int(x["T"]),
|
|
207
|
+
)
|
|
208
|
+
]
|
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
|
|
@@ -72,7 +72,7 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
72
72
|
Возвращает:
|
|
73
73
|
`Websocket`: Экземпляр вебсокета для управления соединением.
|
|
74
74
|
"""
|
|
75
|
-
return self._websocket_manager.
|
|
75
|
+
return self._websocket_manager.kline(
|
|
76
76
|
callback=self._make_wrapper(self._adapter.Klines_message, callback),
|
|
77
77
|
category="spot",
|
|
78
78
|
interval=timeframe.to_exchange_format(Exchange.BYBIT), # type: ignore
|
|
@@ -120,7 +120,7 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
120
120
|
Возвращает:
|
|
121
121
|
`Websocket`: Экземпляр вебсокета.
|
|
122
122
|
"""
|
|
123
|
-
return self._websocket_manager.
|
|
123
|
+
return self._websocket_manager.kline(
|
|
124
124
|
callback=self._make_wrapper(self._adapter.Klines_message, callback),
|
|
125
125
|
category="linear",
|
|
126
126
|
interval=timeframe.to_exchange_format(Exchange.BYBIT), # type: ignore
|
|
@@ -164,7 +164,12 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
164
164
|
Возвращает:
|
|
165
165
|
`Websocket`: Экземпляр вебсокета.
|
|
166
166
|
"""
|
|
167
|
-
|
|
167
|
+
return self._websocket_manager.trade(
|
|
168
|
+
callback=self._make_wrapper(self._adapter.trades_message, callback),
|
|
169
|
+
category="spot",
|
|
170
|
+
symbol=symbol,
|
|
171
|
+
symbols=symbols,
|
|
172
|
+
)
|
|
168
173
|
|
|
169
174
|
@overload
|
|
170
175
|
def aggtrades(
|
|
@@ -240,7 +245,12 @@ class UniWebsocketManager(IUniWebsocketManager):
|
|
|
240
245
|
Возвращает:
|
|
241
246
|
`Websocket`: Экземпляр вебсокета.
|
|
242
247
|
"""
|
|
243
|
-
|
|
248
|
+
return self._websocket_manager.trade(
|
|
249
|
+
callback=self._make_wrapper(self._adapter.trades_message, callback),
|
|
250
|
+
category="linear",
|
|
251
|
+
symbol=symbol,
|
|
252
|
+
symbols=symbols,
|
|
253
|
+
)
|
|
244
254
|
|
|
245
255
|
@overload
|
|
246
256
|
def futures_aggtrades(
|
|
@@ -126,7 +126,7 @@ class WebsocketManager:
|
|
|
126
126
|
**self._ws_kwargs,
|
|
127
127
|
)
|
|
128
128
|
|
|
129
|
-
def
|
|
129
|
+
def kline(
|
|
130
130
|
self,
|
|
131
131
|
callback: CallbackType,
|
|
132
132
|
category: Literal["spot", "linear", "inverse", "option"],
|
|
@@ -170,7 +170,7 @@ class WebsocketManager:
|
|
|
170
170
|
**self._ws_kwargs,
|
|
171
171
|
)
|
|
172
172
|
|
|
173
|
-
def
|
|
173
|
+
def trade(
|
|
174
174
|
self,
|
|
175
175
|
callback: CallbackType,
|
|
176
176
|
category: Literal["spot", "linear", "inverse", "option"],
|
unicex/gateio/client.py
CHANGED
|
@@ -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:
|
unicex/gateio/exchange_info.py
CHANGED
|
@@ -1,12 +1,55 @@
|
|
|
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
|
"""Предзагружает информацию о тикерах для биржи Gateio."""
|
|
8
13
|
|
|
14
|
+
exchange_name = "Gateio"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
9
17
|
@classmethod
|
|
10
|
-
async def
|
|
11
|
-
"""Загружает информацию о
|
|
12
|
-
|
|
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
|
|
@@ -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/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/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
|
@@ -13,6 +13,7 @@ __all__ = [
|
|
|
13
13
|
"OpenInterestItem",
|
|
14
14
|
"TickerInfoItem",
|
|
15
15
|
"TickersInfoDict",
|
|
16
|
+
"LiquidationDict",
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
from logging import Logger as LoggingLogger
|
|
@@ -117,34 +118,52 @@ type OpenInterestDict = dict[str, OpenInterestItem]
|
|
|
117
118
|
"""Модель открытого интереса."""
|
|
118
119
|
|
|
119
120
|
|
|
121
|
+
class LiquidationDict(TypedDict):
|
|
122
|
+
"""Модель ликвидации."""
|
|
123
|
+
|
|
124
|
+
t: int
|
|
125
|
+
"""Время. В миллисекундах."""
|
|
126
|
+
|
|
127
|
+
s: str
|
|
128
|
+
"""Символ."""
|
|
129
|
+
|
|
130
|
+
S: Literal["BUY", "SELL"]
|
|
131
|
+
"""Сторона."""
|
|
132
|
+
|
|
133
|
+
v: float
|
|
134
|
+
"""Объем ликвидации. В монетах."""
|
|
135
|
+
|
|
136
|
+
p: float
|
|
137
|
+
"""Цена ликвидации."""
|
|
138
|
+
|
|
139
|
+
|
|
120
140
|
type AccountType = Literal["SPOT", "FUTURES"]
|
|
121
141
|
"""Тип аккаунта."""
|
|
122
142
|
|
|
123
143
|
|
|
124
144
|
class TickerInfoItem(TypedDict):
|
|
125
|
-
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
|
|
145
|
+
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
На некоторых биржах удобнее делать округление через precisions, на некоторых через step,
|
|
148
|
+
потому что иногда встречаются шаги, которые не являются степенью 10. Поэтому обязательно
|
|
149
|
+
должны быть определены tick_precision ИЛИ tick_step, а так же size_precision ИЛИ size_step.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
tick_precision: int | None
|
|
128
153
|
"""Количество знаков после запятой для цены."""
|
|
129
154
|
|
|
130
|
-
|
|
155
|
+
tick_step: float | None
|
|
156
|
+
"""Шаг одного деления для цены."""
|
|
157
|
+
|
|
158
|
+
size_precision: int | None
|
|
131
159
|
"""Количество знаков после запятой для объема."""
|
|
132
160
|
|
|
161
|
+
size_step: float | None
|
|
162
|
+
"""Шаг одного деления для объема."""
|
|
163
|
+
|
|
133
164
|
contract_size: float | None
|
|
134
165
|
"""Множитель контракта (если есть)."""
|
|
135
166
|
|
|
136
|
-
min_market_size: float | None
|
|
137
|
-
"""Минимальный размер рыночного ордера в монетах (если есть)."""
|
|
138
|
-
|
|
139
|
-
max_market_size: float | None
|
|
140
|
-
"""Максимальный размер рыночного ордера в монетах (если есть)."""
|
|
141
|
-
|
|
142
|
-
min_limit_size: float | None
|
|
143
|
-
"""Минимальный размер лимитного ордера в монетах (если есть)."""
|
|
144
|
-
|
|
145
|
-
max_limit_size: float | None
|
|
146
|
-
"""Максимальный размер лимитного ордера в монетах (если есть)."""
|
|
147
|
-
|
|
148
167
|
|
|
149
168
|
type TickersInfoDict = dict[str, TickerInfoItem]
|
|
150
169
|
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для всех тикеров."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unicex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
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=
|
|
1
|
+
unicex/__init__.py,sha256=v3yO0P9GqC88cV4L-YSJ6h2YzlyOTnt1NPNLSJwhIRE,5806
|
|
2
2
|
unicex/enums.py,sha256=8E_Nb57kriOif57XSLnW8joFufbthZTJ7tcExKWf1Wg,9633
|
|
3
3
|
unicex/exceptions.py,sha256=r-xZzX78VuxVnI5pe99AM8FIiGcdIUDcF5CaTkQ4NE0,2213
|
|
4
4
|
unicex/extra.py,sha256=MZRSsDRok05KZCqKur-hjOexZuoZ-tC9J6e-EIZr_lw,13824
|
|
5
5
|
unicex/mapper.py,sha256=zOuInRQGJnSnwRI5yJ_axx-0svGn-nOqgLr7XXSlq14,4915
|
|
6
|
-
unicex/types.py,sha256=
|
|
6
|
+
unicex/types.py,sha256=quGNpQm-_lSkijRKz04A5lPVxQ2PYrFzggYSPiJGzXI,4728
|
|
7
7
|
unicex/utils.py,sha256=dwU1VYuP2xcMpzaETtNQerL1V8Y_JH8H8EsLJ__-M4s,8050
|
|
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,23 +23,23 @@ 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
|
|
30
30
|
unicex/bitget/websocket_manager.py,sha256=2OhH2gKy2gN03oYeCFvf3_l6RA29dDOvpajGhbXI9Zw,9886
|
|
31
31
|
unicex/bybit/__init__.py,sha256=SrMBh6K5zUt4JheWUpNUYNb1NCDr2ujTFv4IDguaGZI,926
|
|
32
|
-
unicex/bybit/adapter.py,sha256=
|
|
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
|
-
unicex/bybit/uni_websocket_manager.py,sha256=
|
|
36
|
+
unicex/bybit/uni_websocket_manager.py,sha256=OpnvWD0xZ8T_By0HZCSg3jWoZqScRATAsku4IIBqhlw,10252
|
|
37
37
|
unicex/bybit/user_websocket.py,sha256=IGGEnwyWs5jOppgK_R7SisBDvsiF1_piTswBrdQOgDg,128
|
|
38
|
-
unicex/bybit/websocket_manager.py,sha256=
|
|
38
|
+
unicex/bybit/websocket_manager.py,sha256=ePHvngoqoVPr6p-BayoEGhCuRK-cJ69CAPzF94qTalQ,15404
|
|
39
39
|
unicex/gateio/__init__.py,sha256=dsKvhQhDcw4_w0S4c9IjKkCoOg4DCUtSecEUOlfstug,929
|
|
40
40
|
unicex/gateio/adapter.py,sha256=PE5lXQORAKLhzFZBtxcarNIt2xEcDrPrphxWS0k0wEk,6842
|
|
41
|
-
unicex/gateio/client.py,sha256=
|
|
42
|
-
unicex/gateio/exchange_info.py,sha256=
|
|
41
|
+
unicex/gateio/client.py,sha256=bhWkYBn5B4JKpLRfQERTVzisiCSg7mMi6mM0kh4Hes4,53760
|
|
42
|
+
unicex/gateio/exchange_info.py,sha256=ANzfe4mqxtLnj2TBJJxoc31KUosvxdApp1_xYrRNQDs,2300
|
|
43
43
|
unicex/gateio/uni_client.py,sha256=btfI-dozBRsbjEMvSlUX8zp6SOvIS1xDxk_5hNqRfz4,9648
|
|
44
44
|
unicex/gateio/uni_websocket_manager.py,sha256=3KAHhgJNISTOZRBTwbZPigc8iq_IHS19NwnuAsnfeXg,9329
|
|
45
45
|
unicex/gateio/user_websocket.py,sha256=4qZX9N2RjlJ-e25Eszz12OeCM17j5DdXVimBVaLj53w,129
|
|
@@ -47,7 +47,7 @@ unicex/gateio/websocket_manager.py,sha256=phtHbvAGQD3mtewCUxBuuD1Nj0FXN6oZrd7tnm
|
|
|
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
|
|
@@ -81,13 +81,13 @@ unicex/mexc/_spot_ws_proto/__init__.py,sha256=L8Jft1713_M8CLR9drgSjLBdY_46sPT3O9
|
|
|
81
81
|
unicex/okx/__init__.py,sha256=Ljbw3AP0YrPF5bIPJi_3JP3B_czR9xurYHI24rgWk9M,920
|
|
82
82
|
unicex/okx/adapter.py,sha256=zXSLieRC80KY56ey1X-ss51uQ0QzouDxXppjcofLNRQ,5351
|
|
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.0.dist-info/licenses/LICENSE,sha256=lNNK4Vqak9cXm6qVJLhbqS7iR_BMj6k7fd7XQ6l1k54,1507
|
|
90
|
+
unicex-0.13.0.dist-info/METADATA,sha256=mcRAXzA8_-E8J4FPnOX8HStWwYigLbvL_6E9IyCvt90,11752
|
|
91
|
+
unicex-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
92
|
+
unicex-0.13.0.dist-info/top_level.txt,sha256=_7rar-0OENIg4KRy6cgjWiebFYAJhjKEcMggAocGWG4,7
|
|
93
|
+
unicex-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|