unicex 0.13.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unicex/__init__.py +200 -0
- unicex/_abc/__init__.py +11 -0
- unicex/_abc/exchange_info.py +216 -0
- unicex/_abc/uni_client.py +329 -0
- unicex/_abc/uni_websocket_manager.py +294 -0
- unicex/_base/__init__.py +9 -0
- unicex/_base/client.py +214 -0
- unicex/_base/websocket.py +261 -0
- unicex/binance/__init__.py +27 -0
- unicex/binance/adapter.py +202 -0
- unicex/binance/client.py +1577 -0
- unicex/binance/exchange_info.py +62 -0
- unicex/binance/uni_client.py +188 -0
- unicex/binance/uni_websocket_manager.py +166 -0
- unicex/binance/user_websocket.py +186 -0
- unicex/binance/websocket_manager.py +912 -0
- unicex/bitget/__init__.py +27 -0
- unicex/bitget/adapter.py +188 -0
- unicex/bitget/client.py +2514 -0
- unicex/bitget/exchange_info.py +48 -0
- unicex/bitget/uni_client.py +198 -0
- unicex/bitget/uni_websocket_manager.py +275 -0
- unicex/bitget/user_websocket.py +7 -0
- unicex/bitget/websocket_manager.py +232 -0
- unicex/bybit/__init__.py +27 -0
- unicex/bybit/adapter.py +208 -0
- unicex/bybit/client.py +1876 -0
- unicex/bybit/exchange_info.py +53 -0
- unicex/bybit/uni_client.py +200 -0
- unicex/bybit/uni_websocket_manager.py +291 -0
- unicex/bybit/user_websocket.py +7 -0
- unicex/bybit/websocket_manager.py +339 -0
- unicex/enums.py +273 -0
- unicex/exceptions.py +64 -0
- unicex/extra.py +335 -0
- unicex/gate/__init__.py +27 -0
- unicex/gate/adapter.py +178 -0
- unicex/gate/client.py +1667 -0
- unicex/gate/exchange_info.py +55 -0
- unicex/gate/uni_client.py +214 -0
- unicex/gate/uni_websocket_manager.py +269 -0
- unicex/gate/user_websocket.py +7 -0
- unicex/gate/websocket_manager.py +513 -0
- unicex/hyperliquid/__init__.py +27 -0
- unicex/hyperliquid/adapter.py +261 -0
- unicex/hyperliquid/client.py +2315 -0
- unicex/hyperliquid/exchange_info.py +119 -0
- unicex/hyperliquid/uni_client.py +325 -0
- unicex/hyperliquid/uni_websocket_manager.py +269 -0
- unicex/hyperliquid/user_websocket.py +7 -0
- unicex/hyperliquid/websocket_manager.py +393 -0
- unicex/mapper.py +111 -0
- unicex/mexc/__init__.py +27 -0
- unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
- unicex/mexc/_spot_ws_proto/__init__.py +335 -0
- unicex/mexc/adapter.py +239 -0
- unicex/mexc/client.py +846 -0
- unicex/mexc/exchange_info.py +47 -0
- unicex/mexc/uni_client.py +211 -0
- unicex/mexc/uni_websocket_manager.py +269 -0
- unicex/mexc/user_websocket.py +7 -0
- unicex/mexc/websocket_manager.py +456 -0
- unicex/okx/__init__.py +27 -0
- unicex/okx/adapter.py +150 -0
- unicex/okx/client.py +2864 -0
- unicex/okx/exchange_info.py +47 -0
- unicex/okx/uni_client.py +202 -0
- unicex/okx/uni_websocket_manager.py +269 -0
- unicex/okx/user_websocket.py +7 -0
- unicex/okx/websocket_manager.py +743 -0
- unicex/types.py +164 -0
- unicex/utils.py +218 -0
- unicex-0.13.17.dist-info/METADATA +243 -0
- unicex-0.13.17.dist-info/RECORD +93 -0
- unicex-0.13.17.dist-info/WHEEL +5 -0
- unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
- unicex-0.13.17.dist-info/top_level.txt +1 -0
unicex/types.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Модуль, который предоставляет типы данных для работы с библиотекой."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"TickerDailyDict",
|
|
5
|
+
"TickerDailyItem",
|
|
6
|
+
"KlineDict",
|
|
7
|
+
"TradeDict",
|
|
8
|
+
"AggTradeDict",
|
|
9
|
+
"RequestMethod",
|
|
10
|
+
"LoggerLike",
|
|
11
|
+
"OpenInterestDict",
|
|
12
|
+
"OpenInterestItem",
|
|
13
|
+
"TickerInfoItem",
|
|
14
|
+
"TickersInfoDict",
|
|
15
|
+
"LiquidationDict",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
from logging import Logger as LoggingLogger
|
|
19
|
+
from typing import Literal, TypedDict
|
|
20
|
+
|
|
21
|
+
import loguru
|
|
22
|
+
|
|
23
|
+
type LoggerLike = LoggingLogger | loguru.Logger
|
|
24
|
+
"""Объединение логгеров: loguru._logger.Logger или logging.Logger."""
|
|
25
|
+
|
|
26
|
+
type RequestMethod = Literal["GET", "POST", "PUT", "DELETE", "PATCH"]
|
|
27
|
+
"""Типы методов HTTP запросов."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TickerDailyItem(TypedDict):
|
|
31
|
+
"""Статистика одного тикера за последние 24 часа."""
|
|
32
|
+
|
|
33
|
+
p: float
|
|
34
|
+
"""Изменение цены за 24 ч."""
|
|
35
|
+
|
|
36
|
+
v: float
|
|
37
|
+
"""Объем торгов за 24 ч. в монетах."""
|
|
38
|
+
|
|
39
|
+
q: float
|
|
40
|
+
"""Объем торгов за 24 ч. в долларах."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
type TickerDailyDict = dict[str, TickerDailyItem]
|
|
44
|
+
"""Статистика тикеров за последние 24 часа."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class KlineDict(TypedDict):
|
|
48
|
+
"""Модель свечи."""
|
|
49
|
+
|
|
50
|
+
s: str
|
|
51
|
+
"""Символ."""
|
|
52
|
+
|
|
53
|
+
t: int
|
|
54
|
+
"""Время открытия. В миллисекундах."""
|
|
55
|
+
|
|
56
|
+
o: float
|
|
57
|
+
"""Цена открытия свечи."""
|
|
58
|
+
|
|
59
|
+
h: float
|
|
60
|
+
"""Верхняя точка свечи."""
|
|
61
|
+
|
|
62
|
+
l: float # noqa
|
|
63
|
+
"""Нижняя точка свечи."""
|
|
64
|
+
|
|
65
|
+
c: float
|
|
66
|
+
"""Цена закрытия свечи."""
|
|
67
|
+
|
|
68
|
+
v: float
|
|
69
|
+
"""Объем свечи. В монетах."""
|
|
70
|
+
|
|
71
|
+
q: float
|
|
72
|
+
"""Объем свечи. В долларах."""
|
|
73
|
+
|
|
74
|
+
T: int | None
|
|
75
|
+
"""Время закрытия. В миллисекундах."""
|
|
76
|
+
|
|
77
|
+
x: bool | None
|
|
78
|
+
"""Флаг закрыта ли свеча."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TradeDict(TypedDict):
|
|
82
|
+
"""Модель сделки."""
|
|
83
|
+
|
|
84
|
+
t: int
|
|
85
|
+
"""Время сделки. В миллисекундах."""
|
|
86
|
+
|
|
87
|
+
s: str
|
|
88
|
+
"""Символ."""
|
|
89
|
+
|
|
90
|
+
S: Literal["BUY", "SELL"]
|
|
91
|
+
"""Направление сделки."""
|
|
92
|
+
|
|
93
|
+
p: float
|
|
94
|
+
"""Цена сделки."""
|
|
95
|
+
|
|
96
|
+
v: float
|
|
97
|
+
"""Объем сделки. В монетах."""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AggTradeDict(TradeDict):
|
|
101
|
+
"""Модель агрегированной сделки."""
|
|
102
|
+
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class OpenInterestItem(TypedDict):
|
|
107
|
+
"""Модель одного элемента открытого интереса."""
|
|
108
|
+
|
|
109
|
+
t: int
|
|
110
|
+
"""Время. В миллисекундах."""
|
|
111
|
+
|
|
112
|
+
v: float
|
|
113
|
+
"""Открытый интерес. В монетах."""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
type OpenInterestDict = dict[str, OpenInterestItem]
|
|
117
|
+
"""Модель открытого интереса."""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class LiquidationDict(TypedDict):
|
|
121
|
+
"""Модель ликвидации."""
|
|
122
|
+
|
|
123
|
+
t: int
|
|
124
|
+
"""Время. В миллисекундах."""
|
|
125
|
+
|
|
126
|
+
s: str
|
|
127
|
+
"""Символ."""
|
|
128
|
+
|
|
129
|
+
S: Literal["BUY", "SELL"]
|
|
130
|
+
"""Сторона."""
|
|
131
|
+
|
|
132
|
+
v: float
|
|
133
|
+
"""Объем ликвидации. В монетах."""
|
|
134
|
+
|
|
135
|
+
p: float
|
|
136
|
+
"""Цена ликвидации."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TickerInfoItem(TypedDict):
|
|
140
|
+
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для тикера.
|
|
141
|
+
|
|
142
|
+
На некоторых биржах удобнее делать округление через precisions, на некоторых через step,
|
|
143
|
+
потому что иногда встречаются шаги, которые не являются степенью 10. Поэтому обязательно
|
|
144
|
+
должны быть определены tick_precision ИЛИ tick_step, а так же size_precision ИЛИ size_step.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
tick_precision: int | None
|
|
148
|
+
"""Количество знаков после запятой для цены."""
|
|
149
|
+
|
|
150
|
+
tick_step: float | None
|
|
151
|
+
"""Шаг одного деления для цены."""
|
|
152
|
+
|
|
153
|
+
size_precision: int | None
|
|
154
|
+
"""Количество знаков после запятой для объема."""
|
|
155
|
+
|
|
156
|
+
size_step: float | None
|
|
157
|
+
"""Шаг одного деления для объема."""
|
|
158
|
+
|
|
159
|
+
contract_size: float | None
|
|
160
|
+
"""Множитель контракта (если есть)."""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
type TickersInfoDict = dict[str, TickerInfoItem]
|
|
164
|
+
"""Информация о размерах тиков, ступеней цены и множителя контракта (если есть) для всех тикеров."""
|
unicex/utils.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Модуль, который предоставляет дополнительные функции, которые нужны для внутренного использования в библиотеке."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"dict_to_query_string",
|
|
5
|
+
"generate_hmac_sha256_signature",
|
|
6
|
+
"sort_params_by_alphabetical_order",
|
|
7
|
+
"filter_params",
|
|
8
|
+
"batched_list",
|
|
9
|
+
"catch_adapter_errors",
|
|
10
|
+
"decorate_all_methods",
|
|
11
|
+
"symbol_to_exchange_format",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import hashlib
|
|
16
|
+
import hmac
|
|
17
|
+
import json
|
|
18
|
+
from collections.abc import Callable, Iterable
|
|
19
|
+
from functools import wraps
|
|
20
|
+
from typing import Any, Literal
|
|
21
|
+
from urllib.parse import urlencode
|
|
22
|
+
|
|
23
|
+
from unicex.enums import Exchange, MarketType
|
|
24
|
+
from unicex.exceptions import AdapterError
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def filter_params(params: dict) -> dict:
|
|
28
|
+
"""Фильтрует параметры запроса, удаляя None-значения.
|
|
29
|
+
|
|
30
|
+
Параметры:
|
|
31
|
+
params (`dict`): Словарь параметров запроса.
|
|
32
|
+
|
|
33
|
+
Возвращает:
|
|
34
|
+
`dict`: Отфильтрованный словарь параметров запроса.
|
|
35
|
+
"""
|
|
36
|
+
return {k: v for k, v in params.items() if v is not None}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sort_params_by_alphabetical_order(params: dict) -> dict:
|
|
40
|
+
"""Сортирует параметры запроса по алфавиту.
|
|
41
|
+
|
|
42
|
+
Параметры:
|
|
43
|
+
params (`dict`): Словарь параметров запроса.
|
|
44
|
+
|
|
45
|
+
Возвращает:
|
|
46
|
+
`dict`: Отсортированный словарь параметров запроса.
|
|
47
|
+
"""
|
|
48
|
+
return dict(sorted(params.items()))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def dict_to_query_string(params: dict) -> str:
|
|
52
|
+
"""Преобразует словарь параметров в query string для URL.
|
|
53
|
+
|
|
54
|
+
- Списки и словари автоматически сериализуются в JSON.
|
|
55
|
+
- Используется стандартная urlencode кодировка.
|
|
56
|
+
|
|
57
|
+
Параметры:
|
|
58
|
+
params (`dict`): Словарь параметров запроса.
|
|
59
|
+
|
|
60
|
+
Возвращает:
|
|
61
|
+
`str`: Строка параметров, готовая для использования в URL.
|
|
62
|
+
"""
|
|
63
|
+
processed = {
|
|
64
|
+
k: json.dumps(v, separators=(",", ":")) if isinstance(v, list | dict) else v
|
|
65
|
+
for k, v in params.items()
|
|
66
|
+
}
|
|
67
|
+
return urlencode(processed, doseq=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def generate_hmac_sha256_signature(
|
|
71
|
+
secret_key: str,
|
|
72
|
+
payload: str,
|
|
73
|
+
encoding: Literal["hex", "base64"] = "hex",
|
|
74
|
+
) -> str:
|
|
75
|
+
"""Генерирует HMAC-SHA256 подпись.
|
|
76
|
+
|
|
77
|
+
encoding:
|
|
78
|
+
- "hex" → шестнадцатеричная строка
|
|
79
|
+
- "base64" → base64-строка
|
|
80
|
+
"""
|
|
81
|
+
digest = hmac.new(secret_key.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).digest()
|
|
82
|
+
if encoding == "hex":
|
|
83
|
+
return digest.hex()
|
|
84
|
+
elif encoding == "base64":
|
|
85
|
+
return base64.b64encode(digest).decode()
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError("encoding must be 'hex' or 'base64'")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def batched_list[T](iterable: Iterable[T], n: int) -> list[list[T]]:
|
|
91
|
+
"""Разбивает последовательность на чанки фиксированного размера.
|
|
92
|
+
|
|
93
|
+
Всегда возвращает список списков (list[list[T]]).
|
|
94
|
+
"""
|
|
95
|
+
if n <= 0:
|
|
96
|
+
raise ValueError("n must be greater than 0")
|
|
97
|
+
|
|
98
|
+
result: list[list[T]] = []
|
|
99
|
+
batch: list[T] = []
|
|
100
|
+
for item in iterable:
|
|
101
|
+
batch.append(item)
|
|
102
|
+
if len(batch) == n:
|
|
103
|
+
result.append(batch)
|
|
104
|
+
batch = []
|
|
105
|
+
if batch:
|
|
106
|
+
result.append(batch)
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def catch_adapter_errors(func: Callable):
|
|
111
|
+
"""Декоратор для унификации обработки ошибок в адаптерах.
|
|
112
|
+
|
|
113
|
+
Перехватывает все исключения внутри функции и выбрасывает AdapterError
|
|
114
|
+
с подробным сообщением, включающим тип и текст исходного исключения,
|
|
115
|
+
а также имя функции.
|
|
116
|
+
|
|
117
|
+
Параметры:
|
|
118
|
+
func (Callable): Декорируемая функция.
|
|
119
|
+
|
|
120
|
+
Возвращает:
|
|
121
|
+
Callable: Обёрнутую функцию, выбрасывающую AdapterError при ошибке.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@wraps(func)
|
|
125
|
+
def wrapper(*args, **kwargs):
|
|
126
|
+
try:
|
|
127
|
+
return func(*args, **kwargs)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
args_repr = repr(args)
|
|
130
|
+
if len(args_repr) > 400:
|
|
131
|
+
args_preview = args_repr[:400] + "... (truncated)"
|
|
132
|
+
else:
|
|
133
|
+
args_preview = args_repr
|
|
134
|
+
|
|
135
|
+
kwargs_repr = repr(kwargs)
|
|
136
|
+
if len(kwargs_repr) > 400:
|
|
137
|
+
kwargs_preview = kwargs_repr[:400] + "... (truncated)"
|
|
138
|
+
else:
|
|
139
|
+
kwargs_preview = kwargs_repr
|
|
140
|
+
|
|
141
|
+
raise AdapterError(
|
|
142
|
+
f"({type(e).__name__}): {e}. Can not convert input (args={args_preview}, kwargs={kwargs_preview}) in function `{func.__name__}`."
|
|
143
|
+
) from None
|
|
144
|
+
|
|
145
|
+
return wrapper
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def decorate_all_methods(decorator: Callable[[Callable[..., Any]], Callable[..., Any]]) -> Callable:
|
|
149
|
+
"""Класс-декоратор, который оборачивает все методы класса указанным декоратором.
|
|
150
|
+
|
|
151
|
+
Декоратор применяется только к методам/функциям, не начинающимся с "__".
|
|
152
|
+
|
|
153
|
+
Парамтеры:
|
|
154
|
+
decorator: Декоратор, который нужно применить ко всем методам.
|
|
155
|
+
|
|
156
|
+
Возвращает:
|
|
157
|
+
Callable: Декоратор для классов.
|
|
158
|
+
|
|
159
|
+
Пример:
|
|
160
|
+
>>> def debug(func):
|
|
161
|
+
... def wrapper(*args, **kwargs):
|
|
162
|
+
... print(f"Call {func.__name__}")
|
|
163
|
+
... return func(*args, **kwargs)
|
|
164
|
+
...
|
|
165
|
+
... return wrapper
|
|
166
|
+
>>> @decorate_all_methods(debug)
|
|
167
|
+
... class Test:
|
|
168
|
+
... def hello(self):
|
|
169
|
+
... return "hi"
|
|
170
|
+
>>> Test().hello()
|
|
171
|
+
Call hello
|
|
172
|
+
'hi'
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def wrapper(cls: type) -> type:
|
|
177
|
+
for k, v in cls.__dict__.items():
|
|
178
|
+
if isinstance(v, staticmethod):
|
|
179
|
+
func = v.__func__
|
|
180
|
+
setattr(cls, k, staticmethod(decorator(func)))
|
|
181
|
+
elif isinstance(v, classmethod):
|
|
182
|
+
func = v.__func__
|
|
183
|
+
setattr(cls, k, classmethod(decorator(func)))
|
|
184
|
+
elif callable(v) and not k.startswith("__"):
|
|
185
|
+
setattr(cls, k, decorator(v))
|
|
186
|
+
return cls
|
|
187
|
+
|
|
188
|
+
return wrapper
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def symbol_to_exchange_format(
|
|
192
|
+
symbol: str, exchange: Exchange, market_type: MarketType | None = None
|
|
193
|
+
) -> str:
|
|
194
|
+
"""Преобразует символ в формат, который используется на бирже.
|
|
195
|
+
|
|
196
|
+
Параметры:
|
|
197
|
+
symbol (str): Символ, обязательно в формате 'BTCUSDT' или 'btcusdt'.
|
|
198
|
+
|
|
199
|
+
Возвращает:
|
|
200
|
+
str: Символ в формате, который используется на бирже, заглавными буквами.
|
|
201
|
+
"""
|
|
202
|
+
symbol_upper = symbol.upper()
|
|
203
|
+
if exchange == Exchange.MEXC:
|
|
204
|
+
if market_type == MarketType.FUTURES:
|
|
205
|
+
return symbol_upper.replace("USDT", "_USDT")
|
|
206
|
+
elif exchange == Exchange.OKX:
|
|
207
|
+
if market_type == MarketType.FUTURES:
|
|
208
|
+
return symbol_upper.replace("USDT", "-USDT-SWAP")
|
|
209
|
+
elif market_type == MarketType.SPOT:
|
|
210
|
+
return symbol_upper.replace("USDT", "-USDT")
|
|
211
|
+
elif exchange == Exchange.GATE:
|
|
212
|
+
return symbol_upper.replace("USDT", "_USDT")
|
|
213
|
+
elif exchange == Exchange.HYPERLIQUID:
|
|
214
|
+
if market_type == MarketType.FUTURES:
|
|
215
|
+
return symbol.removesuffix("USDT") # Вот тут мб и не так, там вроде что-то к USDC
|
|
216
|
+
else:
|
|
217
|
+
return symbol.removesuffix("USDT") # Вот тут мб и не так, там вроде что-то к USDC
|
|
218
|
+
return symbol_upper
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unicex
|
|
3
|
+
Version: 0.13.17
|
|
4
|
+
Summary: Unified Crypto Exchange API
|
|
5
|
+
Author-email: LoveBloodAndDiamonds <ayazshakirzyanov27@gmail.com>
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025, LoveBloodAndDiamonds
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
34
|
+
|
|
35
|
+
Project-URL: Github, https://github.com/LoveBloodAndDiamonds/uni-cex-api
|
|
36
|
+
Project-URL: Author, https://t.me/LoveBloodAndDiamonds
|
|
37
|
+
Project-URL: Readthedocs, https://unicex.readthedocs.io/ru/latest/
|
|
38
|
+
Requires-Python: >=3.12
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: aiohttp>=3.12.15
|
|
42
|
+
Requires-Dist: eth-account>=0.13.7
|
|
43
|
+
Requires-Dist: loguru>=0.7.3
|
|
44
|
+
Requires-Dist: msgpack>=1.1.1
|
|
45
|
+
Requires-Dist: orjson>=3.11.3
|
|
46
|
+
Requires-Dist: protobuf>=6.32.1
|
|
47
|
+
Requires-Dist: websockets>=15.0.1
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# Unified Crypto Exchange API
|
|
51
|
+
|
|
52
|
+
`unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж. Поддерживает спотовый и USDT-фьючерсный рынки.
|
|
53
|
+
|
|
54
|
+
## ✅ Статус реализации
|
|
55
|
+
|
|
56
|
+
| Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
|
|
57
|
+
|-----------------|--------|------|------------|---------|------------|----------------|--------------|
|
|
58
|
+
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
59
|
+
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
60
|
+
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
|
|
61
|
+
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
62
|
+
| **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
|
|
63
|
+
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
64
|
+
| **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### 📖 Описание колонок
|
|
69
|
+
|
|
70
|
+
- **Client** – Обертки над HTTP методами следующих разделов: market, order, position, account.
|
|
71
|
+
- **Auth** – Поддержка авторизации и приватных эндпоинтов.
|
|
72
|
+
- **WS Manager** – Обертки над вебсокетами биржи.
|
|
73
|
+
- **User WS** – Поддержка пользовательских вебсокетов.
|
|
74
|
+
- **UniClient** –Унифированный клиент.
|
|
75
|
+
- **UniWebsocketManager** – Унифированный менеджер вебсокетов.
|
|
76
|
+
- **ExchangeInfo** - Информация о бирже для округления цен и объемов
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🚀 Быстрый старт
|
|
80
|
+
|
|
81
|
+
- Установка: `pip install unicex` или из исходников: `pip install -e .`
|
|
82
|
+
- Библиотека полностью асинхронная. Примеры импорта:
|
|
83
|
+
- Сырые клиенты: `from unicex.binance import Client`
|
|
84
|
+
- Унифицированные клиенты: `from unicex.binance import UniClient`
|
|
85
|
+
- Вебсокет менеджеры: `from unicex.binance import WebsocketManager, UniWebsocketManager`
|
|
86
|
+
|
|
87
|
+
### Пример: Получение рыночных данных через API
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import asyncio
|
|
91
|
+
|
|
92
|
+
from unicex import Exchange, Timeframe, get_uni_client
|
|
93
|
+
|
|
94
|
+
# Выбираем биржу, с которой хотим работать.
|
|
95
|
+
# Поддерживаются: Binance, Bybit, Bitget, Mexc, Gateio, Hyperliquid и другие.
|
|
96
|
+
exchange = Exchange.BYBIT
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def main() -> None:
|
|
100
|
+
"""Пример простого использования унифицированного клиента unicex."""
|
|
101
|
+
# 1️⃣ Создаём клиент для выбранной биржи
|
|
102
|
+
client = await get_uni_client(exchange).create()
|
|
103
|
+
|
|
104
|
+
# 2️⃣ Получаем открытый интерес по всем контрактам
|
|
105
|
+
open_interest = await client.open_interest()
|
|
106
|
+
print(open_interest)
|
|
107
|
+
|
|
108
|
+
# Пример вывода:
|
|
109
|
+
# {
|
|
110
|
+
# "BTCUSDT": {"t": 1759669833728, "v": 61099320.0},
|
|
111
|
+
# "ETHUSDT": {"t": 1759669833728, "v": 16302340.0},
|
|
112
|
+
# "SOLUSDT": {"t": 1759669833728, "v": 3427780.0},
|
|
113
|
+
# ...
|
|
114
|
+
# }
|
|
115
|
+
|
|
116
|
+
# 3️⃣ Можно точно так же получать другие данные в едином формате:
|
|
117
|
+
await client.tickers() # список всех тикеров
|
|
118
|
+
await client.futures_tickers() # тикеры фьючерсов
|
|
119
|
+
await client.ticker_24hr() # статистика за 24 часа (spot)
|
|
120
|
+
await client.futures_ticker_24hr() # статистика за 24 часа (futures)
|
|
121
|
+
await client.klines("BTCUSDT", Timeframe.MIN_5) # свечи спота
|
|
122
|
+
await client.futures_klines("BTCUSDT", Timeframe.HOUR_1) # свечи фьючерсов
|
|
123
|
+
await client.funding_rate() # ставка финансирования
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
asyncio.run(main())
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Пример: Получение данных в реальном времени через Websocket API
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import asyncio
|
|
135
|
+
from unicex import Exchange, TradeDict, get_uni_websocket_manager
|
|
136
|
+
from unicex.enums import Timeframe
|
|
137
|
+
|
|
138
|
+
# Выбираем биржу, с которой хотим работать.
|
|
139
|
+
# Поддерживаются: Binance, Bybit, Bitget, Mexc, Gateio, Hyperliquid и другие.
|
|
140
|
+
exchange = Exchange.BITGET
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def main() -> None:
|
|
144
|
+
"""Пример простого использования унифицированного менеджера Websocket от UniCEX."""
|
|
145
|
+
|
|
146
|
+
# 1️⃣ Создаём WebSocket-менеджер для выбранной биржи
|
|
147
|
+
ws_manager = get_uni_websocket_manager(exchange)()
|
|
148
|
+
|
|
149
|
+
# 2️⃣ Подключаемся к потоку сделок (aggTrades)
|
|
150
|
+
aggtrades_ws = ws_manager.aggtrades(
|
|
151
|
+
callback=callback,
|
|
152
|
+
symbols=["BTCUSDT", "ETHUSDT"],
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Запускаем получение данных
|
|
156
|
+
await aggtrades_ws.start()
|
|
157
|
+
|
|
158
|
+
# 3️⃣ Примеры других типов потоков:
|
|
159
|
+
futures_aggtrades_ws = ws_manager.futures_aggtrades(
|
|
160
|
+
callback=callback,
|
|
161
|
+
symbols=["BTCUSDT", "ETHUSDT"],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
klines_ws = ws_manager.klines(
|
|
165
|
+
callback=callback,
|
|
166
|
+
symbols=["BTCUSDT", "ETHUSDT"],
|
|
167
|
+
timeframe=Timeframe.MIN_5,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
futures_klines_ws = ws_manager.futures_klines(
|
|
171
|
+
callback=callback,
|
|
172
|
+
symbols=["BTCUSDT", "ETHUSDT"],
|
|
173
|
+
timeframe=Timeframe.MIN_1,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# 💡 Также у каждой биржи есть свой WebsocketManager:
|
|
177
|
+
# unicex.<exchange>.websocket_manager.WebsocketManager
|
|
178
|
+
# В нём реализованы остальные методы для работы с WS API.
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def callback(trade: TradeDict) -> None:
|
|
182
|
+
"""Обработка входящих данных из Websocket."""
|
|
183
|
+
print(trade)
|
|
184
|
+
# Пример вывода:
|
|
185
|
+
# {'t': 1759670527594, 's': 'BTCUSDT', 'S': 'BUY', 'p': 123238.87, 'v': 0.05}
|
|
186
|
+
# {'t': 1759670527594, 's': 'BTCUSDT', 'S': 'BUY', 'p': 123238.87, 'v': 0.04}
|
|
187
|
+
# {'t': 1759670346828, 's': 'ETHUSDT', 'S': 'SELL', 'p': 4535.0, 'v': 0.0044}
|
|
188
|
+
# {'t': 1759670347087, 's': 'ETHUSDT', 'S': 'BUY', 'p': 4534.91, 'v': 0.2712}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
asyncio.run(main())
|
|
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
|
+
```
|