eshan6704 commited on
Commit
ad02125
·
verified ·
1 Parent(s): 7dece44

Create yahooinfo.py

Browse files
Files changed (1) hide show
  1. yahooinfo.py +351 -0
yahooinfo.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==============================
2
+ # Imports
3
+ # ==============================
4
+ import yfinance as yf
5
+ import pandas as pd
6
+ import traceback
7
+
8
+ # ==============================
9
+ # Yahoo Finance info fetch
10
+ # ==============================
11
+ def yfinfo(symbol):
12
+ try:
13
+ t = yf.Ticker(symbol+".NS")
14
+ info = t.info
15
+ if not info or not isinstance(info, dict):
16
+ return {}
17
+ return info
18
+ except Exception as e:
19
+ return {"__error__": str(e)}
20
+
21
+ # ==============================
22
+ # Subgroup icons
23
+ # ==============================
24
+ SUBGROUP_ICONS = {
25
+ "Live Price": "💹",
26
+ "Volume": "📊",
27
+ "Moving Avg": "📈",
28
+ "Range / Vol": "📉",
29
+ "Bid / Analyst": "📝",
30
+ "Other": "ℹ️"
31
+ }
32
+
33
+ # Main section icons
34
+ MAIN_ICONS = {
35
+ "Price / Volume": "📈",
36
+ "Fundamentals": "📊",
37
+ "Company Profile": "🏢"
38
+ }
39
+
40
+ # ==============================
41
+ # HTML card renderer
42
+ # ==============================
43
+ def html_card(title, body, mini=False, shade=0):
44
+ font = "12px" if mini else "14px"
45
+ pad = "6px" if mini else "10px"
46
+
47
+ # Card background shades
48
+ shades = ["#e6f0fa", "#d7e3f5", "#c8d6f0", "#b9c9eb"]
49
+ bg = shades[shade % len(shades)]
50
+
51
+ # Gradient headers for background
52
+ header_gradients = [
53
+ "linear-gradient(to right, #1a4f8a, #4a7ac7)",
54
+ "linear-gradient(to right, #1f5595, #5584d6)",
55
+ "linear-gradient(to right, #205ca0, #6192e0)",
56
+ "linear-gradient(to right, #2360ab, #6ba0e5)"
57
+ ]
58
+ header_bg = header_gradients[shade % len(header_gradients)]
59
+
60
+ return f"""
61
+ <div style="
62
+ background:{bg};
63
+ border:1px solid #a3c0e0;
64
+ border-radius:8px;
65
+ padding:{pad};
66
+ margin:6px 0;
67
+ color:#0d1f3c;
68
+ font-size:{font};
69
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1);
70
+ transition: transform 0.1s ease, box-shadow 0.2s ease;
71
+ " onmouseover="this.style.transform='scale(1.02)';this.style.boxShadow='0 4px 12px rgba(0,0,0,0.15)';"
72
+ onmouseout="this.style.transform='scale(1)';this.style.boxShadow='0 2px 6px rgba(0,0,0,0.1)';">
73
+ <div style="
74
+ font-weight:600;
75
+ background:{header_bg};
76
+ color:white;
77
+ padding:4px 8px;
78
+ border-radius:6px 6px 0 0;
79
+ margin:-{pad}px -{pad}px {pad}px -{pad}px;
80
+ ">
81
+ {title}
82
+ </div>
83
+ <div>{body}</div>
84
+ </div>
85
+ """
86
+
87
+ # ==============================
88
+ # DataFrame → HTML table
89
+ # ==============================
90
+ def make_table(df, compact=False):
91
+ if df is None or df.empty:
92
+ return "<i>No data</i>"
93
+
94
+ font = "11px" if compact else "13px"
95
+ pad = "4px 8px" if compact else "6px 10px"
96
+
97
+ th = "".join(
98
+ f"<th style='padding:{pad};border-bottom:2px solid #a3c0e0;text-align:left;color:#1a4f8a;'>"
99
+ f"{c}</th>"
100
+ for c in df.columns
101
+ )
102
+
103
+ rows = ""
104
+ for i, r in df.iterrows():
105
+ bg = "#f5f9ff" if i%2==0 else "#e6f0fa" # alternate row colors
106
+ tds = "".join(
107
+ f"<td style='padding:{pad};border-bottom:1px solid #c0d4ee;background:{bg}'>{v}</td>"
108
+ for v in r
109
+ )
110
+ rows += f"<tr>{tds}</tr>"
111
+
112
+ return f"""
113
+ <table style="
114
+ width:100%;
115
+ border-collapse:collapse;
116
+ font-size:{font};
117
+ color:#0d1f3c;
118
+ ">
119
+ <thead style='background:#c0d4ee'>{th}</thead>
120
+ <tbody>{rows}</tbody>
121
+ </table>
122
+ """
123
+
124
+ # ==============================
125
+ # Number formatting
126
+ # ==============================
127
+ def format_number(x):
128
+ try:
129
+ if x is None:
130
+ return "-"
131
+ x = float(x)
132
+ if abs(x) >= 100:
133
+ return f"{x:,.0f}"
134
+ if abs(x) >= 1:
135
+ return f"{x:,.2f}"
136
+ return f"{x:.4f}"
137
+ except Exception:
138
+ return str(x)
139
+
140
+ def format_large_number(x):
141
+ try:
142
+ x = float(x)
143
+ for u in ["", "K", "M", "B", "T"]:
144
+ if abs(x) < 1000:
145
+ return f"{x:.2f}{u}"
146
+ x /= 1000
147
+ return f"{x:.2f}P"
148
+ except Exception:
149
+ return str(x)
150
+
151
+ # ==============================
152
+ # HTML error block
153
+ # ==============================
154
+ def html_error(msg):
155
+ return f"""
156
+ <div style="
157
+ background:#fdecea;
158
+ color:#a00;
159
+ border:1px solid #f5c0c0;
160
+ border-radius:8px;
161
+ padding:10px;
162
+ font-weight:600;
163
+ box-shadow: 0 1px 4px rgba(0,0,0,0.05);
164
+ ">
165
+ ❌ {msg}
166
+ </div>
167
+ """
168
+
169
+ # ==============================
170
+ # Noise keys
171
+ # ==============================
172
+ NOISE_KEYS = {
173
+ "maxAge", "priceHint", "triggerable",
174
+ "customPriceAlertConfidence",
175
+ "sourceInterval", "exchangeDataDelayedBy",
176
+ "esgPopulated"
177
+ }
178
+ def is_noise(k):
179
+ return k in NOISE_KEYS
180
+
181
+ # ==============================
182
+ # Duplicate resolution priority
183
+ # ==============================
184
+ DUPLICATE_PRIORITY = {
185
+ "price": ["regularMarketPrice", "currentPrice"],
186
+ "prev": ["regularMarketPreviousClose", "previousClose"],
187
+ "open": ["regularMarketOpen", "open"],
188
+ "high": ["regularMarketDayHigh", "dayHigh"],
189
+ "low": ["regularMarketDayLow", "dayLow"],
190
+ "volume": ["regularMarketVolume", "volume"],
191
+ }
192
+ def resolve_duplicates(data):
193
+ resolved = {}
194
+ used = set()
195
+ for _, keys in DUPLICATE_PRIORITY.items():
196
+ for k in keys:
197
+ if k in data:
198
+ resolved[k] = data[k]
199
+ used.update(keys)
200
+ break
201
+ for k, v in data.items():
202
+ if k not in used:
203
+ resolved[k] = v
204
+ return resolved
205
+
206
+ # ==============================
207
+ # Short display names
208
+ # ==============================
209
+ SHORT_NAMES = {
210
+ "regularMarketPrice": "Price",
211
+ "regularMarketChange": "Chg",
212
+ "regularMarketChangePercent": "Chg%",
213
+ "regularMarketPreviousClose": "Prev",
214
+ "regularMarketOpen": "Open",
215
+ "regularMarketDayHigh": "High",
216
+ "regularMarketDayLow": "Low",
217
+ "regularMarketVolume": "Vol",
218
+ "averageDailyVolume10Day": "AvgV10",
219
+ "averageDailyVolume3Month": "AvgV3M",
220
+ "fiftyDayAverage": "50DMA",
221
+ "fiftyDayAverageChangePercent": "50DMA%",
222
+ "twoHundredDayAverage": "200DMA",
223
+ "twoHundredDayAverageChangePercent": "200DMA%",
224
+ "fiftyTwoWeekLow": "52WL",
225
+ "fiftyTwoWeekHigh": "52WH",
226
+ "fiftyTwoWeekRange": "52WR",
227
+ "beta": "Beta",
228
+ "targetHighPrice": "TgtH",
229
+ "targetLowPrice": "TgtL",
230
+ "targetMeanPrice": "Tgt",
231
+ "recommendationMean": "Reco",
232
+ }
233
+ def pretty_key(k):
234
+ return SHORT_NAMES.get(k, k[:12])
235
+
236
+ # ==============================
237
+ # Subgroup classifier
238
+ # ==============================
239
+ def classify_price_volume_subgroup(key):
240
+ k = key.lower()
241
+ if any(x in k for x in ["price", "open", "close", "change", "day"]):
242
+ return "Live Price"
243
+ if "volume" in k:
244
+ return "Volume"
245
+ if "average" in k or "fiftyday" in k or "twohundredday" in k:
246
+ return "Moving Avg"
247
+ if any(x in k for x in ["week", "range", "high", "low", "alltime", "beta"]):
248
+ return "Range / Vol"
249
+ if any(x in k for x in ["bid", "ask", "target", "recommendation", "analyst"]):
250
+ return "Bid / Analyst"
251
+ return "Other"
252
+
253
+ def build_price_volume_subgroups(data):
254
+ sub = {}
255
+ for k, v in data.items():
256
+ sg = classify_price_volume_subgroup(k)
257
+ sub.setdefault(sg, {})[k] = v
258
+ return sub
259
+
260
+ # ==============================
261
+ # Main key classifier
262
+ # ==============================
263
+ def classify_key(key, value):
264
+ k = key.lower()
265
+ if isinstance(value, str) and len(value) > 80:
266
+ return "long_text"
267
+ if isinstance(value, (int, float)) and any(x in k for x in [
268
+ "price", "volume", "avg", "average", "change",
269
+ "percent", "market", "day", "week", "bid",
270
+ "ask", "beta", "target", "recommendation"
271
+ ]):
272
+ return "price_volume"
273
+ if any(x in k for x in [
274
+ "revenue", "income", "earnings", "profit",
275
+ "margin", "pe", "pb", "roe", "roa",
276
+ "cash", "debt", "equity", "dividend",
277
+ "ebitda", "growth", "ratio", "shares"
278
+ ]):
279
+ return "fundamental"
280
+ return "profile"
281
+
282
+ # ==============================
283
+ # Group builder
284
+ # ==============================
285
+ def build_grouped_info(info):
286
+ groups = {"price_volume": {}, "fundamental": {}, "profile": {}, "long_text": {}}
287
+ for k, v in info.items():
288
+ if v in [None, "", [], {}]:
289
+ continue
290
+ grp = classify_key(k, v)
291
+ groups[grp][k] = v
292
+ return groups
293
+
294
+ # ==============================
295
+ # Build DataFrame
296
+ # ==============================
297
+ def build_df_from_dict(data):
298
+ rows = []
299
+ for k, v in data.items():
300
+ if is_noise(k):
301
+ continue
302
+ if isinstance(v, (int, float)):
303
+ v = format_number(v)
304
+ elif isinstance(v, list):
305
+ v = ", ".join(map(str, v[:5]))
306
+ rows.append([pretty_key(k), v])
307
+ return pd.DataFrame(rows, columns=["Field", "Value"])
308
+
309
+ # ==============================
310
+ # MAIN FUNCTION
311
+ # ==============================
312
+ def fetch_info(symbol):
313
+ try:
314
+ info = yfinfo(symbol)
315
+ if not info:
316
+ return html_error(f"No information found for {symbol}")
317
+
318
+ groups = build_grouped_info(info)
319
+ final_html = ""
320
+
321
+ # ---------------- PRICE / VOLUME ----------------
322
+ price_data = groups["price_volume"]
323
+ price_data = resolve_duplicates(price_data)
324
+ price_subgroups = build_price_volume_subgroups(price_data)
325
+ price_html = ""
326
+ for i, (title, data) in enumerate(price_subgroups.items()):
327
+ df = build_df_from_dict(data)
328
+ if not df.empty:
329
+ icon = SUBGROUP_ICONS.get(title, "ℹ️")
330
+ price_html += html_card(f"{icon} {title}", make_table(df, compact=True), mini=True, shade=i)
331
+ if price_html:
332
+ final_html += html_card(f"{MAIN_ICONS['Price / Volume']} Price / Volume", price_html, shade=0)
333
+
334
+ # ---------------- FUNDAMENTALS ----------------
335
+ if groups["fundamental"]:
336
+ df = build_df_from_dict(groups["fundamental"])
337
+ final_html += html_card(f"{MAIN_ICONS['Fundamentals']} Fundamentals", make_table(df, compact=True), shade=1)
338
+
339
+ # ---------------- PROFILE ----------------
340
+ if groups["profile"]:
341
+ df = build_df_from_dict(groups["profile"])
342
+ final_html += html_card(f"{MAIN_ICONS['Company Profile']} Company Profile", make_table(df, compact=True), shade=2)
343
+
344
+ # ---------------- LONG TEXT ----------------
345
+ for i, (k, v) in enumerate(groups["long_text"].items()):
346
+ final_html += html_card(pretty_key(k), f"<div class='long-text'>{v}</div>", shade=3)
347
+
348
+ return final_html
349
+
350
+ except Exception as e:
351
+ return html_error(f"INFO ERROR: {e}<br><pre>{traceback.format_exc()}</pre>")