trd-utils 0.0.57__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.
- trd_utils/__init__.py +3 -0
- trd_utils/cipher/__init__.py +44 -0
- trd_utils/common_utils/float_utils.py +21 -0
- trd_utils/common_utils/wallet_utils.py +26 -0
- trd_utils/date_utils/__init__.py +8 -0
- trd_utils/date_utils/datetime_helpers.py +25 -0
- trd_utils/exchanges/README.md +203 -0
- trd_utils/exchanges/__init__.py +28 -0
- trd_utils/exchanges/base_types.py +229 -0
- trd_utils/exchanges/binance/__init__.py +13 -0
- trd_utils/exchanges/binance/binance_client.py +389 -0
- trd_utils/exchanges/binance/binance_types.py +116 -0
- trd_utils/exchanges/blofin/__init__.py +6 -0
- trd_utils/exchanges/blofin/blofin_client.py +375 -0
- trd_utils/exchanges/blofin/blofin_types.py +173 -0
- trd_utils/exchanges/bx_ultra/__init__.py +6 -0
- trd_utils/exchanges/bx_ultra/bx_types.py +1338 -0
- trd_utils/exchanges/bx_ultra/bx_ultra_client.py +1123 -0
- trd_utils/exchanges/bx_ultra/bx_utils.py +51 -0
- trd_utils/exchanges/errors.py +10 -0
- trd_utils/exchanges/exchange_base.py +301 -0
- trd_utils/exchanges/hyperliquid/README.md +3 -0
- trd_utils/exchanges/hyperliquid/__init__.py +7 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_client.py +292 -0
- trd_utils/exchanges/hyperliquid/hyperliquid_types.py +183 -0
- trd_utils/exchanges/okx/__init__.py +6 -0
- trd_utils/exchanges/okx/okx_client.py +219 -0
- trd_utils/exchanges/okx/okx_types.py +197 -0
- trd_utils/exchanges/price_fetcher.py +48 -0
- trd_utils/html_utils/__init__.py +26 -0
- trd_utils/html_utils/html_formats.py +72 -0
- trd_utils/tradingview/__init__.py +8 -0
- trd_utils/tradingview/tradingview_client.py +128 -0
- trd_utils/tradingview/tradingview_types.py +185 -0
- trd_utils/types_helper/__init__.py +12 -0
- trd_utils/types_helper/base_model.py +350 -0
- trd_utils/types_helper/decorators.py +20 -0
- trd_utils/types_helper/model_config.py +6 -0
- trd_utils/types_helper/ultra_list.py +39 -0
- trd_utils/types_helper/utils.py +40 -0
- trd_utils-0.0.57.dist-info/METADATA +42 -0
- trd_utils-0.0.57.dist-info/RECORD +44 -0
- trd_utils-0.0.57.dist-info/WHEEL +4 -0
- trd_utils-0.0.57.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import html
|
|
2
|
+
|
|
3
|
+
def camel_to_snake(str_value: str) -> str:
|
|
4
|
+
"""
|
|
5
|
+
Convert CamelCase to snake_case.
|
|
6
|
+
https://stackoverflow.com/a/44969381/16518789
|
|
7
|
+
"""
|
|
8
|
+
return ''.join(['_'+c.lower() if c.isupper() else c for c in str_value]).lstrip('_')
|
|
9
|
+
|
|
10
|
+
def to_camel_case(snake_str: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Convert snake_case to CamelCase.
|
|
13
|
+
https://stackoverflow.com/a/19053800/16518789
|
|
14
|
+
"""
|
|
15
|
+
return "".join(x.capitalize() for x in snake_str.lower().split("_"))
|
|
16
|
+
|
|
17
|
+
def to_lower_camel_case(snake_str: str) -> str:
|
|
18
|
+
# We capitalize the first letter of each component except the first one
|
|
19
|
+
# with the 'capitalize' method and join them together.
|
|
20
|
+
camel_string = to_camel_case(snake_str)
|
|
21
|
+
return snake_str[0].lower() + camel_string[1:]
|
|
22
|
+
|
|
23
|
+
def get_html_normal(*argv) -> str:
|
|
24
|
+
if argv is None or len(argv) == 0:
|
|
25
|
+
return ""
|
|
26
|
+
|
|
27
|
+
my_str = ""
|
|
28
|
+
for value in argv:
|
|
29
|
+
if not value:
|
|
30
|
+
continue
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
my_str += value
|
|
33
|
+
else:
|
|
34
|
+
my_str += str(value)
|
|
35
|
+
|
|
36
|
+
return my_str
|
|
37
|
+
|
|
38
|
+
def html_normal(value, *argv) -> str:
|
|
39
|
+
my_str = html.escape(str(value))
|
|
40
|
+
for value in argv:
|
|
41
|
+
if isinstance(value, str):
|
|
42
|
+
my_str += value
|
|
43
|
+
return my_str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def html_mono(value, *argv) -> str:
|
|
47
|
+
return f"<code>{html.escape(str(value))}</code>" + get_html_normal(*argv)
|
|
48
|
+
|
|
49
|
+
def html_in_parenthesis(value) -> str:
|
|
50
|
+
if not value:
|
|
51
|
+
return ": "
|
|
52
|
+
return f" ({html.escape(str(value))}): "
|
|
53
|
+
|
|
54
|
+
def html_bold(value, *argv) -> str:
|
|
55
|
+
return f"<b>{html.escape(str(value))}</b>" + get_html_normal(*argv)
|
|
56
|
+
|
|
57
|
+
def html_italic(value, *argv) -> str:
|
|
58
|
+
return f"<i>{html.escape(str(value))}</i>" + get_html_normal(*argv)
|
|
59
|
+
|
|
60
|
+
def html_link(value, link: str, *argv) -> str:
|
|
61
|
+
if not isinstance(link, str) or len(link) == 0:
|
|
62
|
+
return html_mono(value, *argv)
|
|
63
|
+
return f"<a href={html.escape(link)}>{html.escape(str(value))}</a>" + get_html_normal(*argv)
|
|
64
|
+
|
|
65
|
+
def html_code_snippets(value, language: str, *argv):
|
|
66
|
+
return html_pre(value, language, *argv)
|
|
67
|
+
|
|
68
|
+
def html_pre(value, language: str, *argv):
|
|
69
|
+
return f"<pre language={html.escape(language)}>{html.escape(str(value))}</pre>" + get_html_normal(*argv)
|
|
70
|
+
|
|
71
|
+
def html_spoiler(value, *argv):
|
|
72
|
+
return f"<spoiler>{html.escape(str(value))}</spoiler>" + get_html_normal(*argv)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from trd_utils.types_helper import new_list
|
|
5
|
+
|
|
6
|
+
from .tradingview_types import CoinScanInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TradingViewClient:
|
|
10
|
+
"""TradingViewClient class to interact with TradingView API."""
|
|
11
|
+
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
async def get_coin_scan(
|
|
16
|
+
self,
|
|
17
|
+
coin_filter: Optional[str] = None,
|
|
18
|
+
limit: int = 200,
|
|
19
|
+
offset: int = 0,
|
|
20
|
+
) -> list["CoinScanInfo"]:
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
23
|
+
cookies = {
|
|
24
|
+
"cookiesSettings": '{"analytics":true,"advertising":true}',
|
|
25
|
+
"cookiePrivacyPreferenceBannerProduction": "accepted",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
headers = {
|
|
29
|
+
"accept": "application/json",
|
|
30
|
+
"accept-language": "en-US,en;q=0.9",
|
|
31
|
+
"cache-control": "no-cache",
|
|
32
|
+
"content-type": "text/plain;charset=UTF-8",
|
|
33
|
+
"origin": "https://www.tradingview.com",
|
|
34
|
+
"pragma": "no-cache",
|
|
35
|
+
"priority": "u=1, i",
|
|
36
|
+
"referer": "https://www.tradingview.com/",
|
|
37
|
+
"sec-ch-ua": '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
|
|
38
|
+
"sec-ch-ua-mobile": "?0",
|
|
39
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
40
|
+
"sec-fetch-dest": "empty",
|
|
41
|
+
"sec-fetch-mode": "cors",
|
|
42
|
+
"sec-fetch-site": "same-site",
|
|
43
|
+
"user-agent": (
|
|
44
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
45
|
+
+ "(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
params = {
|
|
50
|
+
"label-product": "screener-coin",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
data = {
|
|
54
|
+
"columns": [
|
|
55
|
+
"base_currency",
|
|
56
|
+
"base_currency_desc",
|
|
57
|
+
"base_currency_logoid",
|
|
58
|
+
"update_mode",
|
|
59
|
+
"type",
|
|
60
|
+
"typespecs",
|
|
61
|
+
"exchange",
|
|
62
|
+
"crypto_total_rank",
|
|
63
|
+
"Recommend.All",
|
|
64
|
+
"Recommend.MA",
|
|
65
|
+
"Recommend.Other",
|
|
66
|
+
"RSI",
|
|
67
|
+
"Mom",
|
|
68
|
+
"pricescale",
|
|
69
|
+
"minmov",
|
|
70
|
+
"fractional",
|
|
71
|
+
"minmove2",
|
|
72
|
+
"AO",
|
|
73
|
+
"CCI20",
|
|
74
|
+
"Stoch.K",
|
|
75
|
+
"Stoch.D",
|
|
76
|
+
"profit_addresses_percentage",
|
|
77
|
+
"sentiment",
|
|
78
|
+
"socialdominance",
|
|
79
|
+
"galaxyscore",
|
|
80
|
+
"social_volume_24h",
|
|
81
|
+
"altrank",
|
|
82
|
+
"large_tx_count",
|
|
83
|
+
"close",
|
|
84
|
+
"currency",
|
|
85
|
+
"change_abs",
|
|
86
|
+
"Volatility.D",
|
|
87
|
+
],
|
|
88
|
+
"ignore_unknown_fields": False,
|
|
89
|
+
"options": {"lang": "en"},
|
|
90
|
+
"range": [
|
|
91
|
+
offset,
|
|
92
|
+
offset + limit,
|
|
93
|
+
],
|
|
94
|
+
"sort": {"sortBy": "crypto_total_rank", "sortOrder": "asc"},
|
|
95
|
+
"symbols": {},
|
|
96
|
+
"markets": ["coin"],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if coin_filter:
|
|
100
|
+
data["filter"] = [
|
|
101
|
+
{
|
|
102
|
+
"left": "base_currency,base_currency_desc",
|
|
103
|
+
"operation": "match",
|
|
104
|
+
"right": f"{coin_filter}",
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
data = json.dumps(data)
|
|
108
|
+
async with httpx.AsyncClient() as client:
|
|
109
|
+
response = await client.post(
|
|
110
|
+
"https://scanner.tradingview.com/coin/scan",
|
|
111
|
+
params=params,
|
|
112
|
+
cookies=cookies,
|
|
113
|
+
headers=headers,
|
|
114
|
+
data=data,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
j_result = response.json()
|
|
118
|
+
if not isinstance(j_result, dict) or not j_result.get("data", None):
|
|
119
|
+
raise Exception("No data found")
|
|
120
|
+
|
|
121
|
+
j_data = j_result["data"]
|
|
122
|
+
all_infos = new_list()
|
|
123
|
+
for current_data in j_data:
|
|
124
|
+
if not isinstance(current_data, dict):
|
|
125
|
+
continue
|
|
126
|
+
all_infos.append(CoinScanInfo._parse(current_data.get("d", [])))
|
|
127
|
+
|
|
128
|
+
return all_infos
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CoinScanInfo:
|
|
6
|
+
base_currency: str = ""
|
|
7
|
+
base_currency_desc: str = ""
|
|
8
|
+
base_currency_logoid: str = ""
|
|
9
|
+
update_mode: str = ""
|
|
10
|
+
type: str = ""
|
|
11
|
+
typespecs: str = ""
|
|
12
|
+
exchange: str = ""
|
|
13
|
+
crypto_total_rank: Optional[int] = None
|
|
14
|
+
recommend_all: Optional[float] = None
|
|
15
|
+
recommend_ma: Optional[float] = None
|
|
16
|
+
recommend_other: Optional[float] = None
|
|
17
|
+
RSI: Optional[float] = None
|
|
18
|
+
Mom: Optional[float] = None
|
|
19
|
+
pricescale: Optional[int] = None
|
|
20
|
+
minmov: Optional[int] = None
|
|
21
|
+
fractional: Optional[bool] = None
|
|
22
|
+
minmove2: Optional[int] = None
|
|
23
|
+
AO: Optional[float] = None
|
|
24
|
+
CCI20: Optional[float] = None
|
|
25
|
+
stoch_k: Optional[float] = None
|
|
26
|
+
stoch_d: Optional[float] = None
|
|
27
|
+
profit_addresses_percentage: Optional[float] = None
|
|
28
|
+
sentiment: Optional[float] = None
|
|
29
|
+
socialdominance: Optional[float] = None
|
|
30
|
+
galaxyscore: Optional[int] = None
|
|
31
|
+
social_volume_24h: Optional[int] = None
|
|
32
|
+
altrank: Optional[int] = None
|
|
33
|
+
large_tx_count: Optional[int] = None
|
|
34
|
+
close_price: Optional[float] = None
|
|
35
|
+
currency: Optional[str] = None
|
|
36
|
+
change_abs: Optional[float] = None
|
|
37
|
+
volatility_d: Optional[float] = None
|
|
38
|
+
|
|
39
|
+
def get_tech_rating_str(self) -> str:
|
|
40
|
+
if self.recommend_all is None:
|
|
41
|
+
return "N/A"
|
|
42
|
+
|
|
43
|
+
is_negative = False
|
|
44
|
+
emoji = "🟢"
|
|
45
|
+
if self.recommend_all < 0:
|
|
46
|
+
is_negative = True
|
|
47
|
+
self.recommend_all = abs(self.recommend_all)
|
|
48
|
+
emoji = "🔴"
|
|
49
|
+
|
|
50
|
+
if self.recommend_all > 0.5:
|
|
51
|
+
return f"{emoji} Strong {'Sell' if is_negative else 'Buy'}"
|
|
52
|
+
|
|
53
|
+
if self.recommend_all > 0.1:
|
|
54
|
+
return f"{emoji} {'Sell' if is_negative else 'Buy'}"
|
|
55
|
+
|
|
56
|
+
return "Neutral"
|
|
57
|
+
|
|
58
|
+
def get_ma_rating_str(self) -> str:
|
|
59
|
+
if self.recommend_ma is None:
|
|
60
|
+
return "N/A"
|
|
61
|
+
|
|
62
|
+
is_negative = False
|
|
63
|
+
emoji = "🟢"
|
|
64
|
+
if self.recommend_ma < 0:
|
|
65
|
+
is_negative = True
|
|
66
|
+
self.recommend_ma = abs(self.recommend_ma)
|
|
67
|
+
emoji = "🔴"
|
|
68
|
+
|
|
69
|
+
if self.recommend_ma > 0.5:
|
|
70
|
+
return f"{emoji} Strong {'Sell' if is_negative else 'Buy'}"
|
|
71
|
+
|
|
72
|
+
if self.recommend_ma > 0.1:
|
|
73
|
+
return f"{emoji} {'Sell' if is_negative else 'Buy'}"
|
|
74
|
+
|
|
75
|
+
return "Neutral"
|
|
76
|
+
|
|
77
|
+
def get_os_rating_str(self) -> str:
|
|
78
|
+
if self.recommend_other is None:
|
|
79
|
+
return "N/A"
|
|
80
|
+
|
|
81
|
+
is_negative = False
|
|
82
|
+
emoji = "🟢"
|
|
83
|
+
if self.recommend_other < 0:
|
|
84
|
+
is_negative = True
|
|
85
|
+
self.recommend_other = abs(self.recommend_other)
|
|
86
|
+
emoji = "🔴"
|
|
87
|
+
|
|
88
|
+
if self.recommend_other > 0.5:
|
|
89
|
+
return f"{emoji} Strong {'Sell' if is_negative else 'Buy'}"
|
|
90
|
+
|
|
91
|
+
if self.recommend_other > 0.1:
|
|
92
|
+
return f"{emoji} {'Sell' if is_negative else 'Buy'}"
|
|
93
|
+
|
|
94
|
+
return "Neutral"
|
|
95
|
+
|
|
96
|
+
def get_addresses_in_profit_str(self) -> str:
|
|
97
|
+
if self.profit_addresses_percentage is None:
|
|
98
|
+
return "N/A"
|
|
99
|
+
return f"{self.profit_addresses_percentage:.2f}%"
|
|
100
|
+
|
|
101
|
+
def get_sentiment_str(self) -> str:
|
|
102
|
+
if self.sentiment is None:
|
|
103
|
+
return "N/A"
|
|
104
|
+
return f"{self.sentiment:.2f}%"
|
|
105
|
+
|
|
106
|
+
def get_socialdominance_str(self) -> str:
|
|
107
|
+
if self.socialdominance is None:
|
|
108
|
+
return "N/A"
|
|
109
|
+
return f"{self.socialdominance:.2f}%"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_as_markdown(self) -> str:
|
|
113
|
+
text = f"*{self.crypto_total_rank}. {self.base_currency_desc} ({self.base_currency})*:\n"
|
|
114
|
+
text += f" *• Tech Rating:* {self.get_tech_rating_str()}\n"
|
|
115
|
+
text += f" *• MA Rating:* {self.get_ma_rating_str()}\n"
|
|
116
|
+
text += f" *• Os Rating:* {self.get_os_rating_str()}\n"
|
|
117
|
+
|
|
118
|
+
if self.profit_addresses_percentage:
|
|
119
|
+
text += f" *• In Profit:* {self.get_addresses_in_profit_str()}\n"
|
|
120
|
+
|
|
121
|
+
if self.sentiment:
|
|
122
|
+
text += f" *• Sentiment:* {self.get_sentiment_str()}\n"
|
|
123
|
+
|
|
124
|
+
if self.socialdominance:
|
|
125
|
+
text += f" *• Social Dominance:* {self.get_socialdominance_str()}\n"
|
|
126
|
+
|
|
127
|
+
if self.galaxyscore:
|
|
128
|
+
text += f" *• Galaxy Score:* {self.galaxyscore}\n"
|
|
129
|
+
|
|
130
|
+
if self.social_volume_24h:
|
|
131
|
+
text += f" *• Social Volume 24h:* {self.social_volume_24h}\n"
|
|
132
|
+
|
|
133
|
+
if self.altrank:
|
|
134
|
+
text += f" *• AltRank:* {self.altrank}\n"
|
|
135
|
+
|
|
136
|
+
if self.large_tx_count:
|
|
137
|
+
text += f" *• Large TX Count:* {self.large_tx_count}\n"
|
|
138
|
+
|
|
139
|
+
if self.close_price and self.currency:
|
|
140
|
+
text += f" *• Price:* `{self.close_price} {self.currency}`"
|
|
141
|
+
text += f" (`{self.change_abs:+.2f}`)\n"
|
|
142
|
+
|
|
143
|
+
if self.volatility_d:
|
|
144
|
+
text += f" *• Volatility:* {self.volatility_d:.2f}%\n"
|
|
145
|
+
|
|
146
|
+
return text
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _parse(data: list) -> "CoinScanInfo":
|
|
151
|
+
instance = CoinScanInfo()
|
|
152
|
+
instance.base_currency = data[0]
|
|
153
|
+
instance.base_currency_desc = data[1]
|
|
154
|
+
instance.base_currency_logoid = data[2]
|
|
155
|
+
instance.update_mode = data[3]
|
|
156
|
+
instance.type = data[4]
|
|
157
|
+
instance.typespecs = data[5]
|
|
158
|
+
instance.exchange = data[6]
|
|
159
|
+
instance.crypto_total_rank = data[7]
|
|
160
|
+
instance.recommend_all = data[8]
|
|
161
|
+
instance.recommend_ma = data[9]
|
|
162
|
+
instance.recommend_other = data[10]
|
|
163
|
+
instance.RSI = data[11]
|
|
164
|
+
instance.Mom = data[12]
|
|
165
|
+
instance.pricescale = data[13]
|
|
166
|
+
instance.minmov = data[14]
|
|
167
|
+
instance.fractional = data[15]
|
|
168
|
+
instance.minmove2 = data[16]
|
|
169
|
+
instance.AO = data[17]
|
|
170
|
+
instance.CCI20 = data[18]
|
|
171
|
+
instance.stoch_k = data[19]
|
|
172
|
+
instance.stoch_d = data[20]
|
|
173
|
+
instance.profit_addresses_percentage = data[21]
|
|
174
|
+
instance.sentiment = data[22]
|
|
175
|
+
instance.socialdominance = data[23]
|
|
176
|
+
instance.galaxyscore = int(data[24] or 0)
|
|
177
|
+
instance.social_volume_24h = int(data[25] or 0)
|
|
178
|
+
instance.altrank = int(data[26] or 0)
|
|
179
|
+
instance.large_tx_count = int(data[27] or 0)
|
|
180
|
+
instance.close_price = data[28]
|
|
181
|
+
instance.currency = data[29]
|
|
182
|
+
instance.change_abs = data[30]
|
|
183
|
+
instance.volatility_d = data[31]
|
|
184
|
+
|
|
185
|
+
return instance
|