trd-utils 0.0.14__py3-none-any.whl → 0.0.16__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/date_utils/__init__.py +8 -0
- trd_utils/date_utils/datetime_helpers.py +16 -0
- trd_utils/exchanges/README.md +201 -0
- trd_utils/exchanges/__init__.py +13 -3
- trd_utils/exchanges/base_types.py +106 -0
- trd_utils/exchanges/blofin/__init__.py +1 -1
- trd_utils/exchanges/blofin/blofin_client.py +78 -69
- trd_utils/exchanges/blofin/blofin_types.py +35 -35
- trd_utils/exchanges/bx_ultra/bx_ultra_client.py +124 -70
- trd_utils/exchanges/exchange_base.py +70 -12
- trd_utils/exchanges/hyperliquid/README.md +3 -0
- trd_utils/exchanges/hyperliquid/__init__.py +7 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_client.py +138 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_types.py +109 -0
- trd_utils/types_helper/__init__.py +1 -1
- trd_utils/types_helper/base_model.py +1 -1
- {trd_utils-0.0.14.dist-info → trd_utils-0.0.16.dist-info}/METADATA +1 -1
- trd_utils-0.0.16.dist-info/RECORD +31 -0
- trd_utils-0.0.14.dist-info/RECORD +0 -23
- {trd_utils-0.0.14.dist-info → trd_utils-0.0.16.dist-info}/LICENSE +0 -0
- {trd_utils-0.0.14.dist-info → trd_utils-0.0.16.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Type
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from trd_utils.cipher import AESCipher
|
|
11
|
+
from trd_utils.exchanges.exchange_base import ExchangeBase
|
|
12
|
+
from trd_utils.exchanges.hyperliquid.hyperliquid_types import HyperLiquidApiResponse, TraderPositionsInfoResponse
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HyperLiquidClient(ExchangeBase):
|
|
18
|
+
###########################################################
|
|
19
|
+
# region client parameters
|
|
20
|
+
hyperliquid_api_base_host: str = "https://api.hyperliquid.xyz"
|
|
21
|
+
hyperliquid_api_base_url: str = "https://api.hyperliquid.xyz"
|
|
22
|
+
origin_header: str = "app.hyperliquid.xy"
|
|
23
|
+
|
|
24
|
+
# endregion
|
|
25
|
+
###########################################################
|
|
26
|
+
# region client constructor
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
account_name: str = "default",
|
|
30
|
+
http_verify: bool = True,
|
|
31
|
+
fav_letter: str = "^",
|
|
32
|
+
read_session_file: bool = False,
|
|
33
|
+
sessions_dir: str = "sessions",
|
|
34
|
+
use_http1: bool = True,
|
|
35
|
+
use_http2: bool = False,
|
|
36
|
+
):
|
|
37
|
+
# it looks like hyperliquid's api endpoints don't support http2 :(
|
|
38
|
+
self.httpx_client = httpx.AsyncClient(
|
|
39
|
+
verify=http_verify,
|
|
40
|
+
http1=use_http1,
|
|
41
|
+
http2=use_http2,
|
|
42
|
+
)
|
|
43
|
+
self.account_name = account_name
|
|
44
|
+
self._fav_letter = fav_letter
|
|
45
|
+
self.sessions_dir = sessions_dir
|
|
46
|
+
|
|
47
|
+
if read_session_file:
|
|
48
|
+
self.read_from_session_file(f"{sessions_dir}/{self.account_name}.hl")
|
|
49
|
+
|
|
50
|
+
# endregion
|
|
51
|
+
###########################################################
|
|
52
|
+
# region info endpoints
|
|
53
|
+
async def get_trader_positions_info(
|
|
54
|
+
self,
|
|
55
|
+
uid: int | str,
|
|
56
|
+
) -> TraderPositionsInfoResponse:
|
|
57
|
+
payload = {
|
|
58
|
+
"type": "clearinghouseState",
|
|
59
|
+
"user": f"{uid}",
|
|
60
|
+
}
|
|
61
|
+
headers = self.get_headers()
|
|
62
|
+
return await self.invoke_post(
|
|
63
|
+
f"{self.hyperliquid_api_base_host}/info",
|
|
64
|
+
headers=headers,
|
|
65
|
+
content=payload,
|
|
66
|
+
model=TraderPositionsInfoResponse,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
#endregion
|
|
70
|
+
###########################################################
|
|
71
|
+
# region another-thing
|
|
72
|
+
# async def get_another_thing_info(self, uid: int) -> AnotherThingInfoResponse:
|
|
73
|
+
# payload = {
|
|
74
|
+
# "uid": uid,
|
|
75
|
+
# }
|
|
76
|
+
# headers = self.get_headers()
|
|
77
|
+
# return await self.invoke_post(
|
|
78
|
+
# f"{self.hyperliquid_api_base_url}/another-thing/info",
|
|
79
|
+
# headers=headers,
|
|
80
|
+
# content=payload,
|
|
81
|
+
# model=CopyTraderInfoResponse,
|
|
82
|
+
# )
|
|
83
|
+
|
|
84
|
+
# endregion
|
|
85
|
+
###########################################################
|
|
86
|
+
# region client helper methods
|
|
87
|
+
def get_headers(self, payload=None, needs_auth: bool = False) -> dict:
|
|
88
|
+
the_headers = {
|
|
89
|
+
# "Host": self.hyperliquid_api_base_host,
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"Accept": "application/json",
|
|
92
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
93
|
+
"User-Agent": self.user_agent,
|
|
94
|
+
"Connection": "close",
|
|
95
|
+
"appsiteid": "0",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if self.x_requested_with:
|
|
99
|
+
the_headers["X-Requested-With"] = self.x_requested_with
|
|
100
|
+
|
|
101
|
+
if needs_auth:
|
|
102
|
+
the_headers["Authorization"] = f"Bearer {self.authorization_token}"
|
|
103
|
+
return the_headers
|
|
104
|
+
|
|
105
|
+
def read_from_session_file(self, file_path: str) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Reads from session file; if it doesn't exist, creates it.
|
|
108
|
+
"""
|
|
109
|
+
# check if path exists
|
|
110
|
+
target_path = Path(file_path)
|
|
111
|
+
if not target_path.exists():
|
|
112
|
+
return self._save_session_file(file_path=file_path)
|
|
113
|
+
|
|
114
|
+
aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
|
|
115
|
+
content = aes.decrypt(target_path.read_text()).decode("utf-8")
|
|
116
|
+
json_data: dict = json.loads(content)
|
|
117
|
+
|
|
118
|
+
self.authorization_token = json_data.get(
|
|
119
|
+
"authorization_token",
|
|
120
|
+
self.authorization_token,
|
|
121
|
+
)
|
|
122
|
+
self.user_agent = json_data.get("user_agent", self.user_agent)
|
|
123
|
+
|
|
124
|
+
def _save_session_file(self, file_path: str) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Saves current information to the session file.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
json_data = {
|
|
130
|
+
"authorization_token": self.authorization_token,
|
|
131
|
+
"user_agent": self.user_agent,
|
|
132
|
+
}
|
|
133
|
+
aes = AESCipher(key=f"bf_{self.account_name}_bf", fav_letter=self._fav_letter)
|
|
134
|
+
target_path = Path(file_path)
|
|
135
|
+
target_path.write_text(aes.encrypt(json.dumps(json_data)))
|
|
136
|
+
|
|
137
|
+
# endregion
|
|
138
|
+
###########################################################
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from trd_utils.types_helper import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
###########################################################
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# region Common types
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HyperLiquidApiResponse(BaseModel):
|
|
12
|
+
time: int = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# endregion
|
|
16
|
+
|
|
17
|
+
###########################################################
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# region info types
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CumFundingInfo(BaseModel):
|
|
24
|
+
all_time: str = None
|
|
25
|
+
since_open: str = None
|
|
26
|
+
since_change: str = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LeverageInfo(BaseModel):
|
|
30
|
+
type: str = None
|
|
31
|
+
value: int = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PositionInfo(BaseModel):
|
|
35
|
+
coin: str = None
|
|
36
|
+
szi: Decimal = None
|
|
37
|
+
leverage: LeverageInfo = None
|
|
38
|
+
entry_px: Decimal = None
|
|
39
|
+
position_value: Decimal = None
|
|
40
|
+
unrealized_pnl: Decimal = None
|
|
41
|
+
return_on_equity: Decimal = None
|
|
42
|
+
liquidation_px: Decimal = None
|
|
43
|
+
margin_used: Decimal = None
|
|
44
|
+
max_leverage: int = None
|
|
45
|
+
cum_funding: CumFundingInfo = None
|
|
46
|
+
|
|
47
|
+
def get_side(self) -> str:
|
|
48
|
+
if self.szi > 0:
|
|
49
|
+
return "LONG"
|
|
50
|
+
elif self.szi < 0:
|
|
51
|
+
return "SHORT"
|
|
52
|
+
return "UNKNOWN_SIDE"
|
|
53
|
+
|
|
54
|
+
def get_position_id(self) -> str:
|
|
55
|
+
"""
|
|
56
|
+
As far as I know, the API endpoint does not return the position id,
|
|
57
|
+
maybe it only returns it to the account owner?
|
|
58
|
+
In any case, we will have to somehow fake it in order to be able to compare
|
|
59
|
+
it with other positions...
|
|
60
|
+
"""
|
|
61
|
+
return (
|
|
62
|
+
f"{self.coin}-{self.leverage.value}{self.entry_px}"
|
|
63
|
+
).encode("utf-8").hex()
|
|
64
|
+
|
|
65
|
+
def get_leverage(self) -> str:
|
|
66
|
+
return f"{self.leverage.value}x ({self.leverage.type})"
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return (
|
|
70
|
+
f"{self.get_side()} {self.get_leverage()} {self.coin} "
|
|
71
|
+
f"Margin: {self.margin_used}, PNL: {self.unrealized_pnl}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
return self.__repr__()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class AssetPosition(BaseModel):
|
|
79
|
+
type: str = None
|
|
80
|
+
position: PositionInfo = None
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return f"{self.position}; {self.type}"
|
|
84
|
+
|
|
85
|
+
def __str__(self):
|
|
86
|
+
return self.__str__()
|
|
87
|
+
|
|
88
|
+
def get_position_id(self) -> str:
|
|
89
|
+
return self.position.get_position_id()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class MarginSummaryInfo(BaseModel):
|
|
93
|
+
account_value: Decimal = None
|
|
94
|
+
total_ntl_pos: Decimal = None
|
|
95
|
+
total_raw_usd: Decimal = None
|
|
96
|
+
total_margin_used: Decimal = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TraderPositionsInfoResponse(BaseModel):
|
|
100
|
+
margin_summary: MarginSummaryInfo = None
|
|
101
|
+
cross_margin_summary: MarginSummaryInfo = None
|
|
102
|
+
cross_maintenance_margin_used: Decimal = None
|
|
103
|
+
withdrawable: Decimal = None
|
|
104
|
+
asset_positions: list[AssetPosition] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# endregion
|
|
108
|
+
|
|
109
|
+
###########################################################
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
trd_utils/__init__.py,sha256=TpbNgwUz9GdaWu47KkuekhUKxSBLfBCjqfgHJ3CAzUc,25
|
|
2
|
+
trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
|
|
3
|
+
trd_utils/common_utils/float_utils.py,sha256=W-jv7nzjl88xwGB6gsEXmDDhF6DseOrrVT2qx7OvyCo,266
|
|
4
|
+
trd_utils/date_utils/__init__.py,sha256=Erg_E1TfKWNpiuZFm_NXRjCwoRMfxpPS2-mJK6V4lFM,77
|
|
5
|
+
trd_utils/date_utils/datetime_helpers.py,sha256=Ai9rDCLoMg2dRH8NIBTJQgUSfNQp15Vtp6a1IQwHEbw,366
|
|
6
|
+
trd_utils/exchanges/README.md,sha256=UwkpsfcoLCJaMvJe4yBsFkDpf8P6DOLYhtybb6xWMLc,6738
|
|
7
|
+
trd_utils/exchanges/__init__.py,sha256=x74_8-P7ktZyL-1AuAU_SDQD8eKZMPh1gfkLzPuS1xY,440
|
|
8
|
+
trd_utils/exchanges/base_types.py,sha256=bPsRUetQIHdW7ESMAQcjvX-Z2uixtCC93Vb-XnX6FFs,3229
|
|
9
|
+
trd_utils/exchanges/blofin/__init__.py,sha256=X4r9o4Nyjla4UeOBG8lrgtnGYO2aErFMKaJ7yQrFasE,76
|
|
10
|
+
trd_utils/exchanges/blofin/blofin_client.py,sha256=q9NrAalg8KtyBkDrFKA0Vkq0iw2aIK7SQugzVpPJHds,12261
|
|
11
|
+
trd_utils/exchanges/blofin/blofin_types.py,sha256=LnK3LEVlqU3Vg0Cg1jNmb5GGHQImwI6LCKaDw-aQa6A,4059
|
|
12
|
+
trd_utils/exchanges/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
|
|
13
|
+
trd_utils/exchanges/bx_ultra/bx_types.py,sha256=muHLa9lEjIOIDLQCb6xdyxSoQiIMSHErVe0NCoAS-RI,31017
|
|
14
|
+
trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=VcHBhyckn99T8bI_se9u2vyfOHl3w8VPuLrjE16-sQU,24978
|
|
15
|
+
trd_utils/exchanges/bx_ultra/bx_utils.py,sha256=PwapomwDW33arVmKIDj6cL-aP0ptu4BYy_lOCqSAPOo,1392
|
|
16
|
+
trd_utils/exchanges/exchange_base.py,sha256=ZY_9vL9LXtAbAmlWygdVyy0wC-QDla-JGWZ8HMRlkk8,4266
|
|
17
|
+
trd_utils/exchanges/hyperliquid/README.md,sha256=-qaxmDt_9NTus2xRuzyFGkKgYDWgWk7ufHVTSkyn3t4,105
|
|
18
|
+
trd_utils/exchanges/hyperliquid/__init__.py,sha256=QhwGRcneGFHREM-MMdYpbcx-aWdsWsu2WznHzx7LaUM,92
|
|
19
|
+
trd_utils/exchanges/hyperliquid/hyperliquid_client.py,sha256=tEYys_w0sGzxzGdLAUIfl14YTh-zapYwxH0K3B5jBxM,4683
|
|
20
|
+
trd_utils/exchanges/hyperliquid/hyperliquid_types.py,sha256=ueL7Q4yOAK4orlUqeLVNRk6u1AG83pDeGJasTeT3774,2666
|
|
21
|
+
trd_utils/html_utils/__init__.py,sha256=1WWs8C7JszRjTkmzIRLHpxWECHur_DrulTPGIeX88oM,426
|
|
22
|
+
trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJXvY54kvU,2299
|
|
23
|
+
trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
|
|
24
|
+
trd_utils/tradingview/tradingview_client.py,sha256=g_eWYaCRQAL8Kvd-r6AnAdbH7Jha6C_GAyCuxh-RQUU,3917
|
|
25
|
+
trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
|
|
26
|
+
trd_utils/types_helper/__init__.py,sha256=lLbUiW1jUV1gjzTMFLthwkvF0hwauH-F_J2JZq--1U0,67
|
|
27
|
+
trd_utils/types_helper/base_model.py,sha256=cdkvzkjZ1RE__GY98YvhtALHKMux9M4uzcdd8ofQNYU,10617
|
|
28
|
+
trd_utils-0.0.16.dist-info/LICENSE,sha256=J1EP2xt87RjjmsTV1jTjHDQMLIM9FjdwEftTpw8hyv4,1067
|
|
29
|
+
trd_utils-0.0.16.dist-info/METADATA,sha256=vUC58vdFOVmQuZeOra-BglRiwTjzItdZBtEYTCzvOvE,1095
|
|
30
|
+
trd_utils-0.0.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
31
|
+
trd_utils-0.0.16.dist-info/RECORD,,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
trd_utils/__init__.py,sha256=Q91tBCajUFOuF7TB9Y-VDxL4Kg7YxglI35Ypj_Aww5A,25
|
|
2
|
-
trd_utils/cipher/__init__.py,sha256=V05KNuzQwCic-ihMVHlC8sENaJGc3I8MCb4pg4849X8,1765
|
|
3
|
-
trd_utils/common_utils/float_utils.py,sha256=W-jv7nzjl88xwGB6gsEXmDDhF6DseOrrVT2qx7OvyCo,266
|
|
4
|
-
trd_utils/exchanges/__init__.py,sha256=SQJt5cIXh305miWuDumkOLZHzqDUyOqSmlhTT9Xc9RY,180
|
|
5
|
-
trd_utils/exchanges/blofin/__init__.py,sha256=dQkY9aSbI5fZJDOSbkrbrbpHSbWbJjLEmjpkXxDMDD4,74
|
|
6
|
-
trd_utils/exchanges/blofin/blofin_client.py,sha256=SoA272BtqvG8FpxZYpwWx-flJxHEm1-qVMzklmDYZkQ,11611
|
|
7
|
-
trd_utils/exchanges/blofin/blofin_types.py,sha256=8uTJEMYL3Tq3Q_-yAZ4naGKQdQCgyjVn7bE1GRIr8kA,3845
|
|
8
|
-
trd_utils/exchanges/bx_ultra/__init__.py,sha256=8Ssy-eOemQR32Nv1-FoPHm87nRqRO4Fm2PU5GHEFKfQ,80
|
|
9
|
-
trd_utils/exchanges/bx_ultra/bx_types.py,sha256=muHLa9lEjIOIDLQCb6xdyxSoQiIMSHErVe0NCoAS-RI,31017
|
|
10
|
-
trd_utils/exchanges/bx_ultra/bx_ultra_client.py,sha256=UtWbS__FYyYbYgGvxIrpIkuXLaD4nppP_AyEZWOe5ec,22591
|
|
11
|
-
trd_utils/exchanges/bx_ultra/bx_utils.py,sha256=PwapomwDW33arVmKIDj6cL-aP0ptu4BYy_lOCqSAPOo,1392
|
|
12
|
-
trd_utils/exchanges/exchange_base.py,sha256=SQERi-z9nvDAzuM4oZ3_v-wYWiw_eZV75jmAL2E4MG4,1976
|
|
13
|
-
trd_utils/html_utils/__init__.py,sha256=1WWs8C7JszRjTkmzIRLHpxWECHur_DrulTPGIeX88oM,426
|
|
14
|
-
trd_utils/html_utils/html_formats.py,sha256=unKsvOiiDmYTTaM0DYZEUNLEUzWQKKrqASJXvY54kvU,2299
|
|
15
|
-
trd_utils/tradingview/__init__.py,sha256=H0QYb-O5qvy7qC3yswtlcSWLmeBnaS6oJ3JtjvmaV_Y,154
|
|
16
|
-
trd_utils/tradingview/tradingview_client.py,sha256=g_eWYaCRQAL8Kvd-r6AnAdbH7Jha6C_GAyCuxh-RQUU,3917
|
|
17
|
-
trd_utils/tradingview/tradingview_types.py,sha256=z21MXPVdWHAduEl3gSeMIRhxtBN9yK-jPYHfZSMIbSA,6144
|
|
18
|
-
trd_utils/types_helper/__init__.py,sha256=VlEXDzOyn6fYH-dE86EGJ6u_el08QvdyOtJkj-0EAVA,65
|
|
19
|
-
trd_utils/types_helper/base_model.py,sha256=uy3s_AzMBxCVQEIMQYZZw9a5go327oOEGIl1iIZP-gs,10600
|
|
20
|
-
trd_utils-0.0.14.dist-info/LICENSE,sha256=J1EP2xt87RjjmsTV1jTjHDQMLIM9FjdwEftTpw8hyv4,1067
|
|
21
|
-
trd_utils-0.0.14.dist-info/METADATA,sha256=h6G9UoCSP4YXQfiguu3LwASCqAPx69JOJMncTkYxG44,1095
|
|
22
|
-
trd_utils-0.0.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
23
|
-
trd_utils-0.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|