trd-utils 0.0.32__py3-none-any.whl → 0.0.33__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.
Potentially problematic release.
This version of trd-utils might be problematic. Click here for more details.
- trd_utils/__init__.py +1 -1
- trd_utils/exchanges/bx_ultra/bx_types.py +2 -16
- trd_utils/exchanges/bx_ultra/bx_ultra_client.py +62 -42
- trd_utils/exchanges/exchange_base.py +7 -11
- trd_utils/exchanges/price_fetcher.py +38 -0
- {trd_utils-0.0.32.dist-info → trd_utils-0.0.33.dist-info}/METADATA +1 -1
- {trd_utils-0.0.32.dist-info → trd_utils-0.0.33.dist-info}/RECORD +9 -8
- {trd_utils-0.0.32.dist-info → trd_utils-0.0.33.dist-info}/LICENSE +0 -0
- {trd_utils-0.0.32.dist-info → trd_utils-0.0.33.dist-info}/WHEEL +0 -0
trd_utils/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
import pytz
|
|
5
5
|
|
|
6
6
|
from trd_utils.exchanges.errors import ExchangeError
|
|
7
|
+
from trd_utils.exchanges.price_fetcher import MinimalCandleInfo
|
|
7
8
|
from trd_utils.types_helper import BaseModel
|
|
8
9
|
|
|
9
10
|
from trd_utils.common_utils.float_utils import (
|
|
@@ -1239,22 +1240,7 @@ class CreateOrderDelegationResponse(BxApiResponse):
|
|
|
1239
1240
|
# region candle types
|
|
1240
1241
|
|
|
1241
1242
|
|
|
1242
|
-
class SingleCandleInfo(
|
|
1243
|
-
# The pair in format of BTC/USDT.
|
|
1244
|
-
pair: str = None
|
|
1245
|
-
|
|
1246
|
-
# This candle's open price.
|
|
1247
|
-
open_price: Decimal = None
|
|
1248
|
-
|
|
1249
|
-
# The close price.
|
|
1250
|
-
close_price: Decimal = None
|
|
1251
|
-
|
|
1252
|
-
# volume in the first pair (e.g. BTC).
|
|
1253
|
-
volume: Decimal = None
|
|
1254
|
-
|
|
1255
|
-
# volume in the second part of the pair (e.g. USDT).
|
|
1256
|
-
quote_volume: Decimal = None
|
|
1257
|
-
|
|
1243
|
+
class SingleCandleInfo(MinimalCandleInfo):
|
|
1258
1244
|
@staticmethod
|
|
1259
1245
|
def deserialize_short(data: dict) -> "SingleCandleInfo":
|
|
1260
1246
|
info = SingleCandleInfo()
|
|
@@ -52,6 +52,7 @@ from trd_utils.cipher import AESCipher
|
|
|
52
52
|
|
|
53
53
|
from trd_utils.exchanges.errors import ExchangeError
|
|
54
54
|
from trd_utils.exchanges.exchange_base import ExchangeBase, JWTManager
|
|
55
|
+
from trd_utils.exchanges.price_fetcher import IPriceFetcher
|
|
55
56
|
|
|
56
57
|
PLATFORM_ID_ANDROID = "10"
|
|
57
58
|
PLATFORM_ID_WEB = "30"
|
|
@@ -78,7 +79,7 @@ logger = logging.getLogger(__name__)
|
|
|
78
79
|
user_api_identity_cache: dict[int, int] = {}
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
class BXUltraClient(ExchangeBase):
|
|
82
|
+
class BXUltraClient(ExchangeBase, IPriceFetcher):
|
|
82
83
|
###########################################################
|
|
83
84
|
# region client parameters
|
|
84
85
|
we_api_base_host: str = "\u0061pi-\u0061pp.w\u0065-\u0061pi.com"
|
|
@@ -295,9 +296,23 @@ class BXUltraClient(ExchangeBase):
|
|
|
295
296
|
"traceId": self.trace_id,
|
|
296
297
|
}
|
|
297
298
|
url = f"{self.ws_we_api_base_url}?{urlencode(params, doseq=True)}"
|
|
299
|
+
while True:
|
|
300
|
+
try:
|
|
301
|
+
await self._do_price_ws(
|
|
302
|
+
url=url,
|
|
303
|
+
)
|
|
304
|
+
except Exception as ex:
|
|
305
|
+
err_str = f"{ex}"
|
|
306
|
+
if err_str.find("Event loop is closed") != -1:
|
|
307
|
+
# just return
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
logger.warning(f"error at _do_price_ws: {err_str}")
|
|
311
|
+
|
|
312
|
+
async def _do_price_ws(self, url: str):
|
|
298
313
|
async with websockets.connect(url, ping_interval=None) as ws:
|
|
299
314
|
await self._internal_lock.acquire()
|
|
300
|
-
self.
|
|
315
|
+
self.price_ws_connection = ws
|
|
301
316
|
self._internal_lock.release()
|
|
302
317
|
|
|
303
318
|
await ws.send(json.dumps({
|
|
@@ -309,48 +324,53 @@ class BXUltraClient(ExchangeBase):
|
|
|
309
324
|
try:
|
|
310
325
|
decompressed_message = gzip.decompress(msg)
|
|
311
326
|
str_msg = decompressed_message.decode("utf-8")
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
data: dict = json.loads(str_msg, parse_float=Decimal)
|
|
317
|
-
if not isinstance(data, dict):
|
|
318
|
-
logger.warning(f"invalid data instance: {type(data)}")
|
|
319
|
-
continue
|
|
320
|
-
|
|
321
|
-
if data.get("code", 0) == 0 and data.get("data", None) is None:
|
|
322
|
-
# it's all fine
|
|
323
|
-
continue
|
|
324
|
-
|
|
325
|
-
if data.get("ping", None):
|
|
326
|
-
target_id = data["ping"]
|
|
327
|
-
target_time = data.get(
|
|
328
|
-
"time",
|
|
329
|
-
datetime.now(
|
|
330
|
-
timezone(timedelta(hours=8))
|
|
331
|
-
).isoformat(timespec="seconds")
|
|
332
|
-
)
|
|
333
|
-
await ws.send(json.dumps({
|
|
334
|
-
"pong": target_id,
|
|
335
|
-
"time": target_time,
|
|
336
|
-
}))
|
|
337
|
-
continue
|
|
338
|
-
|
|
339
|
-
inner_data = data.get("data", None)
|
|
340
|
-
if isinstance(inner_data, dict):
|
|
341
|
-
if data.get("dataType", None) == "swap.market.v2.contracts":
|
|
342
|
-
list_data = inner_data.get("l", None)
|
|
343
|
-
await self.__last_candle_lock.acquire()
|
|
344
|
-
for current in list_data:
|
|
345
|
-
info = SingleCandleInfo.deserialize_short(current)
|
|
346
|
-
if info:
|
|
347
|
-
self.__last_candle_storage[info.pair.lower()] = info
|
|
348
|
-
self.__last_candle_lock.release()
|
|
349
|
-
continue
|
|
350
|
-
|
|
351
|
-
logger.info(f"we got some unknown data: {data}")
|
|
327
|
+
await self._handle_price_ws_msg(
|
|
328
|
+
str_msg=str_msg,
|
|
329
|
+
)
|
|
352
330
|
except Exception as ex:
|
|
353
331
|
logger.info(f"failed to handle ws message from exchange: {msg}; {ex}")
|
|
332
|
+
|
|
333
|
+
async def _handle_price_ws_msg(self, str_msg: str):
|
|
334
|
+
if str_msg.lower() == "ping":
|
|
335
|
+
await self.price_ws_connection.send("Pong")
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
data: dict = json.loads(str_msg, parse_float=Decimal)
|
|
339
|
+
if not isinstance(data, dict):
|
|
340
|
+
logger.warning(f"invalid data instance: {type(data)}")
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if data.get("code", 0) == 0 and data.get("data", None) is None:
|
|
344
|
+
# it's all fine
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
if data.get("ping", None):
|
|
348
|
+
target_id = data["ping"]
|
|
349
|
+
target_time = data.get(
|
|
350
|
+
"time",
|
|
351
|
+
datetime.now(
|
|
352
|
+
timezone(timedelta(hours=8))
|
|
353
|
+
).isoformat(timespec="seconds")
|
|
354
|
+
)
|
|
355
|
+
await self.price_ws_connection.send(json.dumps({
|
|
356
|
+
"pong": target_id,
|
|
357
|
+
"time": target_time,
|
|
358
|
+
}))
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
inner_data = data.get("data", None)
|
|
362
|
+
if isinstance(inner_data, dict):
|
|
363
|
+
if data.get("dataType", None) == "swap.market.v2.contracts":
|
|
364
|
+
list_data = inner_data.get("l", None)
|
|
365
|
+
await self.__last_candle_lock.acquire()
|
|
366
|
+
for current in list_data:
|
|
367
|
+
info = SingleCandleInfo.deserialize_short(current)
|
|
368
|
+
if info:
|
|
369
|
+
self.__last_candle_storage[info.pair.lower()] = info
|
|
370
|
+
self.__last_candle_lock.release()
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
logger.info(f"we got some unknown data: {data}")
|
|
354
374
|
|
|
355
375
|
async def get_last_candle(self, pair: str) -> SingleCandleInfo:
|
|
356
376
|
"""
|
|
@@ -62,8 +62,8 @@ class ExchangeBase(ABC):
|
|
|
62
62
|
# extra tasks to be cancelled when the client closes.
|
|
63
63
|
extra_tasks: list[asyncio.Task] = None
|
|
64
64
|
|
|
65
|
-
# the ws
|
|
66
|
-
|
|
65
|
+
# the price ws connection to be closed when this client is closed.
|
|
66
|
+
price_ws_connection: WSConnection = None
|
|
67
67
|
# endregion
|
|
68
68
|
###########################################################
|
|
69
69
|
# region constructor method
|
|
@@ -71,7 +71,6 @@ class ExchangeBase(ABC):
|
|
|
71
71
|
def __init__(self):
|
|
72
72
|
self._internal_lock = asyncio.Lock()
|
|
73
73
|
self.extra_tasks = []
|
|
74
|
-
self.ws_connections = []
|
|
75
74
|
|
|
76
75
|
# endregion
|
|
77
76
|
###########################################################
|
|
@@ -228,14 +227,11 @@ class ExchangeBase(ABC):
|
|
|
228
227
|
await self._internal_lock.acquire()
|
|
229
228
|
await self.httpx_client.aclose()
|
|
230
229
|
|
|
231
|
-
if self.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
logger.warning(f"failed to close ws connection: {ex}")
|
|
237
|
-
continue
|
|
238
|
-
self.ws_connections = []
|
|
230
|
+
if self.price_ws_connection:
|
|
231
|
+
try:
|
|
232
|
+
await self.price_ws_connection.close()
|
|
233
|
+
except Exception as ex:
|
|
234
|
+
logger.warning(f"failed to close ws connection: {ex}")
|
|
239
235
|
|
|
240
236
|
self._internal_lock.release()
|
|
241
237
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from trd_utils.types_helper import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MinimalCandleInfo(BaseModel):
|
|
8
|
+
# The pair in format of BTC/USDT.
|
|
9
|
+
pair: str = None
|
|
10
|
+
|
|
11
|
+
# This candle's open price.
|
|
12
|
+
open_price: Decimal = None
|
|
13
|
+
|
|
14
|
+
# The close price.
|
|
15
|
+
close_price: Decimal = None
|
|
16
|
+
|
|
17
|
+
# volume in the first pair (e.g. BTC).
|
|
18
|
+
volume: Decimal = None
|
|
19
|
+
|
|
20
|
+
# volume in the second part of the pair (e.g. USDT).
|
|
21
|
+
quote_volume: Decimal = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IPriceFetcher:
|
|
25
|
+
"""
|
|
26
|
+
The IPriceFetcher class acts as an interface for classes that support
|
|
27
|
+
fetching last candle of a specific pair, without any specific floodwait or
|
|
28
|
+
ratelimit applied on the method itself (because e.g. they are fetching it
|
|
29
|
+
through a background websocket connection).
|
|
30
|
+
Please do not use this class directly, instead use a class that inherits
|
|
31
|
+
and implements the methods of this class (e.g. one of the exchange classes).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
async def do_price_subscribe(self) -> None:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
async def get_last_candle(self, pair: str) -> MinimalCandleInfo:
|
|
38
|
+
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
trd_utils/__init__.py,sha256=
|
|
1
|
+
trd_utils/__init__.py,sha256=nSoM18hDJ2GE427yOVowkSYX38d1GPSj8rG7RKp-egI,25
|
|
2
2
|
trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
|
|
3
3
|
trd_utils/common_utils/float_utils.py,sha256=aYPwJ005LmrRhXAngojwvdDdtRgeb1FfR6hKeQ5ndMU,470
|
|
4
4
|
trd_utils/common_utils/wallet_utils.py,sha256=OX9q2fymP0VfIWTRIRBP8W33cfyjLXimxMgPOsZe-3g,727
|
|
@@ -11,11 +11,11 @@ trd_utils/exchanges/blofin/__init__.py,sha256=X4r9o4Nyjla4UeOBG8lrgtnGYO2aErFMKa
|
|
|
11
11
|
trd_utils/exchanges/blofin/blofin_client.py,sha256=RJcyET62cjxczEdHqQ-Cy6CT8veJYio_efublNBvEo8,12378
|
|
12
12
|
trd_utils/exchanges/blofin/blofin_types.py,sha256=ZlHX1ClYTd2pDRTQIlZYyBu5ReGpMgxXxKASsPeBQug,4090
|
|
13
13
|
trd_utils/exchanges/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
|
|
14
|
-
trd_utils/exchanges/bx_ultra/bx_types.py,sha256=
|
|
15
|
-
trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=
|
|
14
|
+
trd_utils/exchanges/bx_ultra/bx_types.py,sha256=hRe8DfF3sdXlMdk-knwWx4XpZUp1rITpXPnUDtiy93o,34715
|
|
15
|
+
trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=Wdfg34goiMeJEGd5T-thouXEC_2Gacs8ixiSJ0rcojg,34726
|
|
16
16
|
trd_utils/exchanges/bx_ultra/bx_utils.py,sha256=PwapomwDW33arVmKIDj6cL-aP0ptu4BYy_lOCqSAPOo,1392
|
|
17
17
|
trd_utils/exchanges/errors.py,sha256=P_NTuc389XL7rFegomP59BydWmHv8ckiGyNU-_l5qNQ,167
|
|
18
|
-
trd_utils/exchanges/exchange_base.py,sha256=
|
|
18
|
+
trd_utils/exchanges/exchange_base.py,sha256=wJV966YYw6BQnB7hr9Cgy3a4FHg8_jyWeaidJgzCA7Q,7772
|
|
19
19
|
trd_utils/exchanges/hyperliquid/README.md,sha256=-qaxmDt_9NTus2xRuzyFGkKgYDWgWk7ufHVTSkyn3t4,105
|
|
20
20
|
trd_utils/exchanges/hyperliquid/__init__.py,sha256=QhwGRcneGFHREM-MMdYpbcx-aWdsWsu2WznHzx7LaUM,92
|
|
21
21
|
trd_utils/exchanges/hyperliquid/hyperliquid_client.py,sha256=FOOEDY1wOvyDlUcnSHf0Vp-8KNK2_AkI6Z7h3lls3Co,6832
|
|
@@ -23,6 +23,7 @@ trd_utils/exchanges/hyperliquid/hyperliquid_types.py,sha256=MiGG5fRU7wHqOMtCzQXD
|
|
|
23
23
|
trd_utils/exchanges/okx/__init__.py,sha256=OjVpvcwB9mrCTofLt14JRHV2-fMAzGz9-YkJAMwl6dM,67
|
|
24
24
|
trd_utils/exchanges/okx/okx_client.py,sha256=lUf507ovd4D3fI7Z3Iy1fGvxNJWdm4wTcdzTCkMw13U,7146
|
|
25
25
|
trd_utils/exchanges/okx/okx_types.py,sha256=IkFOfgivcvvIw950jyGHAVfFFGbGqfZcYGfZLWfNLvc,5013
|
|
26
|
+
trd_utils/exchanges/price_fetcher.py,sha256=Jjrlnis-9fFOYR-7T8T0Dkzi63gSN7gSLkehjbbldEg,1074
|
|
26
27
|
trd_utils/html_utils/__init__.py,sha256=1WWs8C7JszRjTkmzIRLHpxWECHur_DrulTPGIeX88oM,426
|
|
27
28
|
trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJXvY54kvU,2299
|
|
28
29
|
trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
|
|
@@ -30,7 +31,7 @@ trd_utils/tradingview/tradingview_client.py,sha256=g_eWYaCRQAL8Kvd-r6AnAdbH7Jha6
|
|
|
30
31
|
trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
|
|
31
32
|
trd_utils/types_helper/__init__.py,sha256=lLbUiW1jUV1gjzTMFLthwkvF0hwauH-F_J2JZq--1U0,67
|
|
32
33
|
trd_utils/types_helper/base_model.py,sha256=Q2KK0r4UXP9PlWeIl6nxdAeCGB5InU5IkTNGAfJasfM,11808
|
|
33
|
-
trd_utils-0.0.
|
|
34
|
-
trd_utils-0.0.
|
|
35
|
-
trd_utils-0.0.
|
|
36
|
-
trd_utils-0.0.
|
|
34
|
+
trd_utils-0.0.33.dist-info/LICENSE,sha256=J1EP2xt87RjjmsTV1jTjHDQMLIM9FjdwEftTpw8hyv4,1067
|
|
35
|
+
trd_utils-0.0.33.dist-info/METADATA,sha256=6L4kyCagWXrwlvxY_4mSpmd2c-8bqpBSVBu72_d7LJI,1179
|
|
36
|
+
trd_utils-0.0.33.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
37
|
+
trd_utils-0.0.33.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|