eshan6704 commited on
Commit
78e48a1
Β·
verified Β·
1 Parent(s): f361c54

Create yahooinfo.py

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