unicex 0.10.5__tar.gz → 0.13.0__tar.gz
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-0.10.5/unicex.egg-info → unicex-0.13.0}/PKG-INFO +57 -7
- {unicex-0.10.5 → unicex-0.13.0}/README.md +56 -6
- {unicex-0.10.5 → unicex-0.13.0}/pyproject.toml +1 -1
- {unicex-0.10.5 → unicex-0.13.0}/unicex/__init__.py +16 -1
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_abc/exchange_info.py +75 -35
- unicex-0.13.0/unicex/binance/exchange_info.py +62 -0
- unicex-0.13.0/unicex/bitget/exchange_info.py +48 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/adapter.py +57 -3
- unicex-0.13.0/unicex/bybit/exchange_info.py +51 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/uni_websocket_manager.py +14 -4
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/websocket_manager.py +2 -2
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/client.py +2 -2
- unicex-0.13.0/unicex/gateio/exchange_info.py +55 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/exchange_info.py +29 -10
- unicex-0.13.0/unicex/mexc/exchange_info.py +47 -0
- unicex-0.13.0/unicex/okx/exchange_info.py +47 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/types.py +34 -15
- {unicex-0.10.5 → unicex-0.13.0/unicex.egg-info}/PKG-INFO +57 -7
- unicex-0.10.5/unicex/binance/exchange_info.py +0 -12
- unicex-0.10.5/unicex/bitget/exchange_info.py +0 -12
- unicex-0.10.5/unicex/bybit/exchange_info.py +0 -12
- unicex-0.10.5/unicex/gateio/exchange_info.py +0 -12
- unicex-0.10.5/unicex/mexc/exchange_info.py +0 -32
- unicex-0.10.5/unicex/okx/exchange_info.py +0 -50
- {unicex-0.10.5 → unicex-0.13.0}/LICENSE +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/setup.cfg +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_abc/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_abc/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_abc/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_base/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_base/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/_base/websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/binance/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bitget/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/bybit/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/enums.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/exceptions.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/extra.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/gateio/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/hyperliquid/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mapper.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/_spot_ws_proto/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/mexc/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/__init__.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/adapter.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/uni_client.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/uni_websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/user_websocket.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/okx/websocket_manager.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex/utils.py +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex.egg-info/SOURCES.txt +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex.egg-info/dependency_links.txt +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex.egg-info/requires.txt +0 -0
- {unicex-0.10.5 → unicex-0.13.0}/unicex.egg-info/top_level.txt +0 -0
|
@@ -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,17 +1,17 @@
|
|
|
1
1
|
# Unified Crypto Exchange API
|
|
2
2
|
|
|
3
|
-
`unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж.
|
|
3
|
+
`unicex` — асинхронная библиотека для работы с криптовалютными биржами, реализующая унифицированный интерфейс поверх «сырых» REST и WebSocket API разных бирж. Поддерживает спотовый и USDT-фьючерсный рынки.
|
|
4
4
|
|
|
5
5
|
## ✅ Статус реализации
|
|
6
6
|
|
|
7
7
|
| Exchange | Client | Auth | WS Manager | User WS | Uni Client | Uni WS Manager | ExchangeInfo |
|
|
8
8
|
|-----------------|--------|------|------------|---------|------------|----------------|--------------|
|
|
9
|
-
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
10
|
-
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | |
|
|
11
|
-
| **Bybit** | ✓ | ✓ | ✓ | | ✓ |
|
|
12
|
-
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | |
|
|
9
|
+
| **Binance** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
10
|
+
| **Bitget** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
11
|
+
| **Bybit** | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
|
|
12
|
+
| **Gateio** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
13
13
|
| **Hyperliquid** | ✓ | ✓ | ✓ | ✓ | ✓ | | |
|
|
14
|
-
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | |
|
|
14
|
+
| **Mexc** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
15
15
|
| **Okx** | ✓ | ✓ | ✓ | | ✓ | | ✓ |
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -142,3 +142,53 @@ async def callback(trade: TradeDict) -> None:
|
|
|
142
142
|
if __name__ == "__main__":
|
|
143
143
|
asyncio.run(main())
|
|
144
144
|
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### Пример: Округление цен используя фоновый класс ExchangeInfo
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
import asyncio
|
|
152
|
+
from unicex import start_exchanges_info, get_exchange_info, Exchange
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def main() -> None:
|
|
156
|
+
# ⏳ Запускаем фоновые процессы, которые собирают рыночные параметры всех бирж:
|
|
157
|
+
# - количество знаков после точки для цены и объема
|
|
158
|
+
# - множители контрактов для фьючерсов
|
|
159
|
+
await start_exchanges_info()
|
|
160
|
+
|
|
161
|
+
# Небольшая пауза, чтобы данные успели подгрузиться
|
|
162
|
+
await asyncio.sleep(1)
|
|
163
|
+
|
|
164
|
+
# 1️⃣ Пример 1: Округление цены для фьючерсов OKX
|
|
165
|
+
okx_exchange_info = get_exchange_info(Exchange.OKX)
|
|
166
|
+
okx_rounded_price = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", 123456.1234567890)
|
|
167
|
+
print(okx_rounded_price) # >> 123456.1
|
|
168
|
+
|
|
169
|
+
# 2️⃣ Пример 2: Округление объема для спота Binance
|
|
170
|
+
binance_exchange_info = get_exchange_info(Exchange.BINANCE)
|
|
171
|
+
binance_rounded_quantity = binance_exchange_info.round_quantity("BTCUSDT", 1.123456789)
|
|
172
|
+
print(binance_rounded_quantity) # >> 1.12345
|
|
173
|
+
|
|
174
|
+
# 3️⃣ Пример 3: Получение множителя контракта (например, Mexc Futures)
|
|
175
|
+
mexc_exchange_info = get_exchange_info(Exchange.MEXC)
|
|
176
|
+
mexc_contract_multiplier = mexc_exchange_info.get_futures_ticker_info("BTC_USDT")["contract_size"]
|
|
177
|
+
print(mexc_contract_multiplier) # >> 0.0001
|
|
178
|
+
|
|
179
|
+
# 4️⃣ Пример 4: Реальное применение — вычисляем тейк-профит вручную
|
|
180
|
+
# Допустим, позиция открыта по 123123.1 USDT, хотим +3.5% тейк-профит:
|
|
181
|
+
take_profit_raw = 123123.1 * 1.035
|
|
182
|
+
print("До округления:", take_profit_raw) # >> 127432.40849999999
|
|
183
|
+
|
|
184
|
+
# Биржа требует цену в допустимом формате — округляем:
|
|
185
|
+
take_profit = okx_exchange_info.round_futures_price("BTC-USDT-SWAP", take_profit_raw)
|
|
186
|
+
print("После округления:", take_profit) # >> 127432.4
|
|
187
|
+
|
|
188
|
+
# Теперь это число можно безопасно передать в API без ошибок:
|
|
189
|
+
# await client.create_order(symbol="BTC-USDT-SWAP", price=take_profit, ...)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
asyncio.run(main())
|
|
194
|
+
```
|
|
@@ -4,7 +4,7 @@ name = "unicex"
|
|
|
4
4
|
# • PATCH (x.y.Z) → увеличивается при багфиксе, который не ломает совместимость.
|
|
5
5
|
# • MINOR (x.Y.z) → увеличивается при добавлении новой функциональности, но без ломающих изменений (backward-compatible).
|
|
6
6
|
# • MAJOR (X.y.z) → увеличивается при изменениях, которые ломают обратную совместимость.
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.13.0"
|
|
8
8
|
|
|
9
9
|
description = "Unified Crypto Exchange API "
|
|
10
10
|
readme = "README.md"
|
|
@@ -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
|
|
|
@@ -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:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
__all__ = ["ExchangeInfo"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
|
|
6
|
+
from unicex._abc import IExchangeInfo
|
|
7
|
+
from unicex.types import TickerInfoItem
|
|
8
|
+
|
|
9
|
+
from .client import Client
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExchangeInfo(IExchangeInfo):
|
|
13
|
+
"""Предзагружает информацию о тикерах для биржи Binance."""
|
|
14
|
+
|
|
15
|
+
exchange_name = "Binance"
|
|
16
|
+
"""Название биржи, на которой работает класс."""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
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
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
"""Предзагружает информацию о тикерах для биржи Bitget."""
|
|
13
|
+
|
|
14
|
+
exchange_name = "Bitget"
|
|
15
|
+
"""Название биржи, на которой работает класс."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
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
|
|
@@ -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
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
"""Предзагружает информацию о тикерах для биржи Bybit."""
|
|
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
|
+
|
|
33
|
+
@classmethod
|
|
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
|