trd-utils 0.0.31__tar.gz → 0.0.33__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.

Potentially problematic release.


This version of trd-utils might be problematic. Click here for more details.

Files changed (38) hide show
  1. {trd_utils-0.0.31 → trd_utils-0.0.33}/PKG-INFO +1 -1
  2. {trd_utils-0.0.31 → trd_utils-0.0.33}/pyproject.toml +1 -1
  3. trd_utils-0.0.33/trd_utils/__init__.py +3 -0
  4. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/bx_ultra/bx_types.py +2 -16
  5. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/bx_ultra/bx_ultra_client.py +63 -42
  6. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/exchange_base.py +7 -11
  7. trd_utils-0.0.33/trd_utils/exchanges/price_fetcher.py +38 -0
  8. trd_utils-0.0.31/trd_utils/__init__.py +0 -3
  9. {trd_utils-0.0.31 → trd_utils-0.0.33}/LICENSE +0 -0
  10. {trd_utils-0.0.31 → trd_utils-0.0.33}/README.md +0 -0
  11. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/cipher/__init__.py +0 -0
  12. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/common_utils/float_utils.py +0 -0
  13. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/common_utils/wallet_utils.py +0 -0
  14. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/date_utils/__init__.py +0 -0
  15. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/date_utils/datetime_helpers.py +0 -0
  16. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/README.md +0 -0
  17. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/__init__.py +0 -0
  18. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/base_types.py +0 -0
  19. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/blofin/__init__.py +0 -0
  20. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/blofin/blofin_client.py +0 -0
  21. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/blofin/blofin_types.py +0 -0
  22. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/bx_ultra/__init__.py +0 -0
  23. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/bx_ultra/bx_utils.py +0 -0
  24. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/errors.py +0 -0
  25. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/hyperliquid/README.md +0 -0
  26. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/hyperliquid/__init__.py +0 -0
  27. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/hyperliquid/hyperliquid_client.py +0 -0
  28. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/hyperliquid/hyperliquid_types.py +0 -0
  29. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/okx/__init__.py +0 -0
  30. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/okx/okx_client.py +0 -0
  31. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/exchanges/okx/okx_types.py +0 -0
  32. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/html_utils/__init__.py +0 -0
  33. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/html_utils/html_formats.py +0 -0
  34. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/tradingview/__init__.py +0 -0
  35. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/tradingview/tradingview_client.py +0 -0
  36. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/tradingview/tradingview_types.py +0 -0
  37. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/types_helper/__init__.py +0 -0
  38. {trd_utils-0.0.31 → trd_utils-0.0.33}/trd_utils/types_helper/base_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: trd_utils
3
- Version: 0.0.31
3
+ Version: 0.0.33
4
4
  Summary: Common Basic Utils for Python3. By ALiwoto.
5
5
  Keywords: utils,trd_utils,basic-utils,common-utils
6
6
  Author: ALiwoto
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "trd_utils"
3
- version = "0.0.31"
3
+ version = "0.0.33"
4
4
  description = "Common Basic Utils for Python3. By ALiwoto."
5
5
  authors = ["ALiwoto <aminnimaj@gmail.com>"]
6
6
  packages = [
@@ -0,0 +1,3 @@
1
+
2
+ __version__ = "0.0.33"
3
+
@@ -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(BaseModel):
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.ws_connections.append(ws)
315
+ self.price_ws_connection = ws
301
316
  self._internal_lock.release()
302
317
 
303
318
  await ws.send(json.dumps({
@@ -308,48 +323,54 @@ class BXUltraClient(ExchangeBase):
308
323
  async for msg in ws:
309
324
  try:
310
325
  decompressed_message = gzip.decompress(msg)
311
- if decompressed_message.lower() == "ping":
312
- await ws.send("Pong")
313
- continue
314
-
315
- data: dict = json.loads(decompressed_message, parse_float=Decimal)
316
- if not isinstance(data, dict):
317
- logger.warning(f"invalid data instance: {type(data)}")
318
- continue
319
-
320
- if data.get("code", 0) == 0 and data.get("data", None) is None:
321
- # it's all fine
322
- continue
323
-
324
- if data.get("ping", None):
325
- target_id = data["ping"]
326
- target_time = data.get(
327
- "time",
328
- datetime.now(
329
- timezone(timedelta(hours=8))
330
- ).isoformat(timespec="seconds")
331
- )
332
- await ws.send(json.dumps({
333
- "pong": target_id,
334
- "time": target_time,
335
- }))
336
- continue
337
-
338
- inner_data = data.get("data", None)
339
- if isinstance(inner_data, dict):
340
- if data.get("dataType", None) == "swap.market.v2.contracts":
341
- list_data = inner_data.get("l", None)
342
- await self.__last_candle_lock.acquire()
343
- for current in list_data:
344
- info = SingleCandleInfo.deserialize_short(current)
345
- if info:
346
- self.__last_candle_storage[info.pair.lower()] = info
347
- self.__last_candle_lock.release()
348
- continue
349
-
350
- logger.info(f"we got some unknown data: {data}")
326
+ str_msg = decompressed_message.decode("utf-8")
327
+ await self._handle_price_ws_msg(
328
+ str_msg=str_msg,
329
+ )
351
330
  except Exception as ex:
352
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}")
353
374
 
354
375
  async def get_last_candle(self, pair: str) -> SingleCandleInfo:
355
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 connections to be closed when this client is closed.
66
- ws_connections: list[WSConnection] = None
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.ws_connections:
232
- for current in self.ws_connections:
233
- try:
234
- await current.close()
235
- except Exception as ex:
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,3 +0,0 @@
1
-
2
- __version__ = "0.0.31"
3
-
File without changes
File without changes