trd-utils 0.0.36__tar.gz → 0.0.37__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.36 → trd_utils-0.0.37}/PKG-INFO +1 -1
  2. {trd_utils-0.0.36 → trd_utils-0.0.37}/pyproject.toml +1 -1
  3. trd_utils-0.0.37/trd_utils/__init__.py +3 -0
  4. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/bx_ultra/bx_types.py +69 -0
  5. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/bx_ultra/bx_ultra_client.py +156 -30
  6. trd_utils-0.0.36/trd_utils/__init__.py +0 -3
  7. {trd_utils-0.0.36 → trd_utils-0.0.37}/LICENSE +0 -0
  8. {trd_utils-0.0.36 → trd_utils-0.0.37}/README.md +0 -0
  9. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/cipher/__init__.py +0 -0
  10. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/common_utils/float_utils.py +0 -0
  11. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/common_utils/wallet_utils.py +0 -0
  12. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/date_utils/__init__.py +0 -0
  13. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/date_utils/datetime_helpers.py +0 -0
  14. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/README.md +0 -0
  15. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/__init__.py +0 -0
  16. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/base_types.py +0 -0
  17. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/blofin/__init__.py +0 -0
  18. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/blofin/blofin_client.py +0 -0
  19. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/blofin/blofin_types.py +0 -0
  20. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/bx_ultra/__init__.py +0 -0
  21. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/bx_ultra/bx_utils.py +0 -0
  22. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/errors.py +0 -0
  23. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/exchange_base.py +0 -0
  24. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/hyperliquid/README.md +0 -0
  25. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/hyperliquid/__init__.py +0 -0
  26. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/hyperliquid/hyperliquid_client.py +0 -0
  27. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/hyperliquid/hyperliquid_types.py +0 -0
  28. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/okx/__init__.py +0 -0
  29. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/okx/okx_client.py +0 -0
  30. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/okx/okx_types.py +0 -0
  31. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/exchanges/price_fetcher.py +0 -0
  32. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/html_utils/__init__.py +0 -0
  33. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/html_utils/html_formats.py +0 -0
  34. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/tradingview/__init__.py +0 -0
  35. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/tradingview/tradingview_client.py +0 -0
  36. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/tradingview/tradingview_types.py +0 -0
  37. {trd_utils-0.0.36 → trd_utils-0.0.37}/trd_utils/types_helper/__init__.py +0 -0
  38. {trd_utils-0.0.36 → trd_utils-0.0.37}/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.36
3
+ Version: 0.0.37
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.36"
3
+ version = "0.0.37"
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.37"
3
+
@@ -1217,6 +1217,75 @@ class ContractConfigResponse(BxApiResponse):
1217
1217
  data: ContractConfigData = None
1218
1218
 
1219
1219
 
1220
+ # endregion
1221
+
1222
+ ###########################################################
1223
+
1224
+ # region std futures types
1225
+
1226
+
1227
+ class CopyTraderStdFuturesPositionInfo(BaseModel):
1228
+ order_no: str = None
1229
+ quotation_coin_vo: QuotationCoinVOInfo = None
1230
+ margin: Decimal = None
1231
+ margin_coin_name: str = None
1232
+ lever_times: Decimal = None
1233
+ display_lever_times: Decimal = None
1234
+ amount: Decimal = None
1235
+ display_price: Decimal = None
1236
+ display_close_price: Decimal = None
1237
+ order_type: int = None
1238
+ close_type: int = None
1239
+ status: Any = None
1240
+ open_date: datetime = None
1241
+ fees: Decimal = None
1242
+ lever_fee: Decimal = None
1243
+ name: str = None
1244
+ order_create_type: int = None
1245
+ hide_price: bool = None
1246
+ fee_rate: Decimal = None
1247
+ hide: bool = None
1248
+ liquidation_desc: str = None
1249
+ contract_account_mode: Any = None
1250
+ current_price: Decimal = None
1251
+ sys_force_price: Decimal = None
1252
+ fund_type: int = None
1253
+ interest: Any = None
1254
+ order_open_trade: OrderOpenTradeInfo = None
1255
+ order_debit: OrderDebitInfo = None
1256
+ open_rate: Decimal = None
1257
+ close_rate: Decimal = None
1258
+ market_status: int = None
1259
+ create_time: datetime = None
1260
+ coupon_amount: Decimal = None
1261
+ stop_profit_rate: Decimal = None
1262
+ stop_loss_rate: Decimal = None
1263
+ stop_profit_modify_time: datetime = None
1264
+ stop_loss_modify_time: datetime = None
1265
+ show_adjust_margin: int = None
1266
+ trailing_stop: Decimal = None
1267
+ trailing_close_price: Decimal = None
1268
+ stop_rate: Decimal = None
1269
+ profit_loss_info: ProfitLossInfoContainer = None
1270
+ stop_offset_rate: None
1271
+
1272
+ def __str__(self):
1273
+ return super().__str__()
1274
+
1275
+ def __repr__(self):
1276
+ return self.__str__()
1277
+
1278
+
1279
+ class CopyTraderStdFuturesPositionsResult(BaseModel):
1280
+ page_id: int = None
1281
+ total_str: str = None
1282
+ positions: list[CopyTraderStdFuturesPositionInfo] = None
1283
+
1284
+
1285
+ class CopyTraderStdFuturesPositionsResponse(BxApiResponse):
1286
+ data: CopyTraderStdFuturesPositionsResult = None
1287
+
1288
+
1220
1289
  # endregion
1221
1290
 
1222
1291
  ###########################################################
@@ -16,10 +16,8 @@ import httpx
16
16
  import time
17
17
  from pathlib import Path
18
18
 
19
+ import pytz
19
20
  import websockets
20
- import websockets.asyncio
21
- import websockets.asyncio.client
22
- import websockets.asyncio.connection
23
21
 
24
22
  from trd_utils.exchanges.base_types import (
25
23
  UnifiedPositionInfo,
@@ -34,6 +32,7 @@ from trd_utils.exchanges.bx_ultra.bx_types import (
34
32
  ContractsListResponse,
35
33
  CopyTraderFuturesStatsResponse,
36
34
  CopyTraderResumeResponse,
35
+ CopyTraderStdFuturesPositionsResponse,
37
36
  CopyTraderTradePositionsResponse,
38
37
  CreateOrderDelegationResponse,
39
38
  HintListResponse,
@@ -67,7 +66,7 @@ WEB_APP_VERSION = "4.78.12"
67
66
  TG_APP_VERSION = "5.0.15"
68
67
 
69
68
  ACCEPT_ENCODING_HEADER = "gzip, deflate, br, zstd"
70
- BASE_PROFILE_URL = "https://bingx.com/en/CopyTrading/"
69
+ BASE_PROFILE_URL = "https://\u0062ing\u0078.co\u006d/en/CopyTr\u0061ding/"
71
70
 
72
71
  logger = logging.getLogger(__name__)
73
72
 
@@ -102,6 +101,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
102
101
  # a dict that maps "BTC/USDT" to it single candle info.
103
102
  __last_candle_storage: dict = None
104
103
  __last_candle_lock: asyncio.Lock = None
104
+
105
105
  # endregion
106
106
  ###########################################################
107
107
  # region client constructor
@@ -323,11 +323,15 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
323
323
  self.price_ws_connection = ws
324
324
  self._internal_lock.release()
325
325
 
326
- await ws.send(json.dumps({
327
- "dataType": "swap.market.v2.contracts",
328
- "id": uuid.uuid4().hex,
329
- "reqType": "sub",
330
- }))
326
+ await ws.send(
327
+ json.dumps(
328
+ {
329
+ "dataType": "swap.market.v2.contracts",
330
+ "id": uuid.uuid4().hex,
331
+ "reqType": "sub",
332
+ }
333
+ )
334
+ )
331
335
  async for msg in ws:
332
336
  try:
333
337
  decompressed_message = gzip.decompress(msg)
@@ -336,7 +340,9 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
336
340
  str_msg=str_msg,
337
341
  )
338
342
  except Exception as ex:
339
- logger.info(f"failed to handle ws message from exchange: {msg}; {ex}")
343
+ logger.info(
344
+ f"failed to handle ws message from exchange: {msg}; {ex}"
345
+ )
340
346
 
341
347
  async def _handle_price_ws_msg(self, str_msg: str):
342
348
  if str_msg.lower() == "ping":
@@ -356,14 +362,18 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
356
362
  target_id = data["ping"]
357
363
  target_time = data.get(
358
364
  "time",
359
- datetime.now(
360
- timezone(timedelta(hours=8))
361
- ).isoformat(timespec="seconds")
365
+ datetime.now(timezone(timedelta(hours=8))).isoformat(
366
+ timespec="seconds"
367
+ ),
368
+ )
369
+ await self.price_ws_connection.send(
370
+ json.dumps(
371
+ {
372
+ "pong": target_id,
373
+ "time": target_time,
374
+ }
375
+ )
362
376
  )
363
- await self.price_ws_connection.send(json.dumps({
364
- "pong": target_id,
365
- "time": target_time,
366
- }))
367
377
  return
368
378
 
369
379
  inner_data = data.get("data", None)
@@ -379,7 +389,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
379
389
  return
380
390
 
381
391
  logger.info(f"we got some unknown data: {data}")
382
-
392
+
383
393
  async def get_last_candle(self, pair: str) -> SingleCandleInfo:
384
394
  """
385
395
  Returns the last candle's info in this exchange.
@@ -390,16 +400,16 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
390
400
  info = self.__last_candle_storage.get(pair.lower())
391
401
  self.__last_candle_lock.release()
392
402
  return info
393
-
403
+
394
404
  # endregion
395
405
  ###########################################################
396
406
  # region contract
397
407
  async def get_contract_config(
398
408
  self,
399
- fund_type: int, # e.g. 1
400
- coin_name: str, # e.g. "SOL"
401
- valuation_name: str, # e.g. "USDT"
402
- margin_coin_name: str, # e.g. "USDT"
409
+ fund_type: int, # e.g. 1
410
+ coin_name: str, # e.g. "SOL"
411
+ valuation_name: str, # e.g. "USDT"
412
+ margin_coin_name: str, # e.g. "USDT"
403
413
  ) -> ContractConfigResponse:
404
414
  params = {
405
415
  "fundType": f"{fund_type}",
@@ -417,7 +427,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
417
427
  params=params,
418
428
  model_type=CopyTraderTradePositionsResponse,
419
429
  )
420
-
430
+
421
431
  async def get_contract_list(
422
432
  self,
423
433
  quotation_coin_id: int = -1,
@@ -605,7 +615,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
605
615
  "marginCoinName": margin_coin_name or "USDT",
606
616
  "marketFactor": market_factor or 1,
607
617
  "orderType": f"{order_type or 0}",
608
- "price": float(price), # e.g. 107161.27
618
+ "price": float(price), # e.g. 107161.27
609
619
  "profitLossRateDto": {
610
620
  "stopProfitRate": stop_profit_rate or -1,
611
621
  "stopLossRate": stop_loss_rate or -1,
@@ -626,6 +636,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
626
636
  content=payload,
627
637
  model_type=CreateOrderDelegationResponse,
628
638
  )
639
+
629
640
  # endregion
630
641
  ###########################################################
631
642
  # region copy-trade-facade
@@ -652,6 +663,27 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
652
663
  model_type=CopyTraderTradePositionsResponse,
653
664
  )
654
665
 
666
+ async def get_copy_trader_std_futures_positions(
667
+ self,
668
+ uid: int | str,
669
+ page_size: int = 20,
670
+ page_id: int = 0,
671
+ ) -> CopyTraderStdFuturesPositionsResponse:
672
+ params = {
673
+ "trader": f"{uid}",
674
+ # it seems like this method doesn't really need api identity param...
675
+ # "apiIdentity": f"{api_identity}",
676
+ "pageSize": f"{page_size}",
677
+ "pageId": f"{page_id}",
678
+ }
679
+ headers = self.get_headers(params)
680
+ return await self.invoke_get(
681
+ f"{self.we_api_base_url}/v1/copy-trade/traderContractHold",
682
+ headers=headers,
683
+ params=params,
684
+ model_type=CopyTraderStdFuturesPositionsResponse,
685
+ )
686
+
655
687
  async def search_copy_traders(
656
688
  self,
657
689
  exchange_id: int = 2,
@@ -740,7 +772,9 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
740
772
  api_identity = resume.data.api_identity
741
773
  if not api_identity:
742
774
  # second try: try to use one of the sub-accounts' identity
743
- api_identity = resume.data.get_account_identity_by_filter(sub_account_filter)
775
+ api_identity = resume.data.get_account_identity_by_filter(
776
+ filter_text=sub_account_filter,
777
+ )
744
778
 
745
779
  # maybe also try to fetch it in other ways later?
746
780
  # ...
@@ -881,6 +915,37 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
881
915
  # region unified methods
882
916
 
883
917
  async def get_unified_trader_positions(
918
+ self,
919
+ uid: int | str,
920
+ api_identity: int | str | None = None,
921
+ no_warn: bool = False,
922
+ ) -> UnifiedTraderPositions:
923
+ perp_positions = []
924
+ std_positions = []
925
+ try:
926
+ result = await self.get_unified_trader_positions_perp(
927
+ uid=uid,
928
+ api_identity=api_identity,
929
+ )
930
+ perp_positions = result.positions
931
+ except Exception as ex:
932
+ if not no_warn:
933
+ logger.warning(f"Failed to fetch perp positions of {uid}: {ex}")
934
+
935
+ try:
936
+ result = await self.get_unified_trader_positions_std(
937
+ uid=uid,
938
+ )
939
+ std_positions = result.positions
940
+ except Exception as ex:
941
+ if not no_warn:
942
+ logger.warning(f"Failed to fetch std positions of {uid}: {ex}")
943
+
944
+ unified_result = UnifiedTraderPositions()
945
+ unified_result.positions = perp_positions + std_positions
946
+ return unified_result
947
+
948
+ async def get_unified_trader_positions_perp(
884
949
  self,
885
950
  uid: int | str,
886
951
  api_identity: int | str | None = None,
@@ -891,7 +956,7 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
891
956
  uid=uid,
892
957
  sub_account_filter=sub_account_filter,
893
958
  )
894
-
959
+
895
960
  if not api_identity:
896
961
  raise ValueError(f"Failed to fetch api_identity for user {uid}")
897
962
 
@@ -920,7 +985,9 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
920
985
  unified_pos.margin_mode = "isolated" # TODO: fix this
921
986
  unified_pos.position_leverage = position.leverage
922
987
  unified_pos.position_pair = position.symbol.replace("-", "/")
923
- unified_pos.open_time = None # TODO: do something for this?
988
+ unified_pos.open_time = datetime.now(
989
+ pytz.UTC
990
+ ) # TODO: do something for this?
924
991
  unified_pos.open_price = position.avg_price
925
992
  unified_pos.initial_margin = position.margin
926
993
  unified_pos.open_price_unit = (
@@ -936,6 +1003,65 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
936
1003
 
937
1004
  return unified_result
938
1005
 
1006
+ async def get_unified_trader_positions_std(
1007
+ self,
1008
+ uid: int | str,
1009
+ page_offset: int = 0,
1010
+ page_size: int = 50,
1011
+ ) -> UnifiedTraderPositions:
1012
+ unified_result = UnifiedTraderPositions()
1013
+ unified_result.positions = []
1014
+ current_page_id = page_offset - 1
1015
+
1016
+ while True:
1017
+ current_page_id += 1
1018
+ try:
1019
+ result = await self.get_copy_trader_std_futures_positions(
1020
+ uid=uid,
1021
+ page_size=page_size,
1022
+ page_id=current_page_id,
1023
+ )
1024
+
1025
+ if result.code != 0 and not result.data:
1026
+ if result.msg:
1027
+ raise ExchangeError(f"got error from API: {result.msg}")
1028
+ raise ExchangeError(
1029
+ f"got unknown error from bx API while fetching std positions for {uid}"
1030
+ )
1031
+
1032
+ for position in result.data.positions:
1033
+ unified_pos = UnifiedPositionInfo()
1034
+ unified_pos.position_id = position.order_no
1035
+ unified_pos.position_pnl = (
1036
+ position.current_price - position.display_price
1037
+ ) * position.amount
1038
+ unified_pos.position_side = (
1039
+ "LONG" if position.amount > 0 else "SHORT"
1040
+ )
1041
+ unified_pos.margin_mode = "isolated" # TODO: fix this
1042
+ unified_pos.position_leverage = position.lever_times
1043
+ unified_pos.position_pair = f"{position.quotation_coin_vo.coin.name}/{position.margin_coin_name}"
1044
+ unified_pos.open_time = position.create_time
1045
+ unified_pos.open_price = position.display_price
1046
+ unified_pos.initial_margin = position.margin
1047
+ unified_pos.open_price_unit = position.margin_coin_name
1048
+
1049
+ last_candle = await self.get_last_candle(unified_pos.position_pair)
1050
+ if last_candle:
1051
+ unified_pos.last_price = last_candle.close_price
1052
+ unified_pos.last_volume = last_candle.quote_volume
1053
+
1054
+ unified_result.positions.append(unified_pos)
1055
+
1056
+ if int(result.data.total_str) <= len(unified_result.positions):
1057
+ # all is done
1058
+ return unified_result
1059
+ except Exception as ex:
1060
+ logger.warning(
1061
+ f"Failed to fetch std positions from exchange for {uid}: {ex}"
1062
+ )
1063
+ return unified_result
1064
+
939
1065
  async def get_unified_trader_info(
940
1066
  self,
941
1067
  uid: int | str,
@@ -945,8 +1071,8 @@ class BXUltraClient(ExchangeBase, IPriceFetcher):
945
1071
  )
946
1072
  if resume_resp.code != 0 and not resume_resp.data:
947
1073
  if resume_resp.msg:
948
- raise ValueError(f"got error from API: {resume_resp.msg}")
949
- raise ValueError(
1074
+ raise ExchangeError(f"got error from API: {resume_resp.msg}")
1075
+ raise ExchangeError(
950
1076
  f"got unknown error from bx API while fetching resume for {uid}"
951
1077
  )
952
1078
 
@@ -1,3 +0,0 @@
1
-
2
- __version__ = "0.0.36"
3
-
File without changes
File without changes