eshan6704 commited on
Commit
2a2ded5
·
verified ·
1 Parent(s): 419762c

Create yahooinfo.py

Browse files
Files changed (1) hide show
  1. app/yahooinfo.py +382 -0
app/yahooinfo.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==============================
2
+ # Imports (kept same style)
3
+ # ==============================
4
+ import yfinance as yf
5
+ import pandas as pd
6
+ import traceback
7
+ import time
8
+
9
+ # persist helpers
10
+ from persist import exists, load, save
11
+
12
+
13
+ # ==============================
14
+ # Yahoo Finance info fetch (RAW)
15
+ # ==============================
16
+ def yfinfo(symbol):
17
+ """
18
+ Low-level Yahoo Finance info fetch.
19
+ Returns raw dict or {"__error__": "..."}
20
+ """
21
+ try:
22
+ t = yf.Ticker(symbol + ".NS")
23
+ info = t.info
24
+ if not info or not isinstance(info, dict):
25
+ return {}
26
+ return info
27
+ except Exception as e:
28
+ return {"__error__": str(e)}
29
+
30
+
31
+ # ==============================
32
+ # Icons
33
+ # ==============================
34
+ SUBGROUP_ICONS = {
35
+ "Live Price": "💹",
36
+ "Volume": "📊",
37
+ "Moving Avg": "📈",
38
+ "Range / Vol": "📉",
39
+ "Bid / Analyst": "📝",
40
+ "Other": "ℹ️"
41
+ }
42
+
43
+ MAIN_ICONS = {
44
+ "Price / Volume": "📈",
45
+ "Fundamentals": "📊",
46
+ "Company Profile": "🏢"
47
+ }
48
+
49
+
50
+ # ==============================
51
+ # Responsive column layout
52
+ # ==============================
53
+ def column_layout(html, min_width=320):
54
+ return f"""
55
+ <div style="
56
+ display:grid;
57
+ grid-template-columns:repeat(auto-fit,minmax({min_width}px,1fr));
58
+ gap:10px;
59
+ align-items:start;
60
+ ">
61
+ {html}
62
+ </div>
63
+ """
64
+
65
+
66
+ # ==============================
67
+ # Card renderer
68
+ # ==============================
69
+ def html_card(title, body, mini=False, shade=0):
70
+ font = "12px" if mini else "14px"
71
+ pad = "6px" if mini else "10px"
72
+
73
+ shades = ["#e6f0fa", "#d7e3f5", "#c8d6f0"]
74
+ grads = [
75
+ "linear-gradient(to right,#1a4f8a,#4a7ac7)",
76
+ "linear-gradient(to right,#1f5595,#5584d6)",
77
+ "linear-gradient(to right,#205ca0,#6192e0)"
78
+ ]
79
+
80
+ return f"""
81
+ <div style="
82
+ background:{shades[shade%3]};
83
+ border:1px solid #a3c0e0;
84
+ border-radius:8px;
85
+ padding:{pad};
86
+ font-size:{font};
87
+ box-shadow:0 2px 6px rgba(0,0,0,.08);
88
+ ">
89
+ <div style="
90
+ background:{grads[shade%3]};
91
+ color:white;
92
+ padding:4px 8px;
93
+ border-radius:6px;
94
+ font-weight:600;
95
+ margin-bottom:6px;
96
+ ">
97
+ {title}
98
+ </div>
99
+ {body}
100
+ </div>
101
+ """
102
+
103
+
104
+ # ==============================
105
+ # Formatting helpers
106
+ # ==============================
107
+ def format_number(x):
108
+ try:
109
+ x = float(x)
110
+ if abs(x) >= 100:
111
+ return f"{x:,.0f}"
112
+ if abs(x) >= 1:
113
+ return f"{x:,.2f}"
114
+ return f"{x:.4f}"
115
+ except:
116
+ return str(x)
117
+
118
+
119
+ # ==============================
120
+ # Compact inline key:value view
121
+ # ==============================
122
+ def make_table(df):
123
+ rows = ""
124
+ for _, r in df.iterrows():
125
+ color = "#0d1f3c"
126
+ if any(x in r[0].lower() for x in ["chg", "%"]):
127
+ try:
128
+ color = "#0a7d32" if float(r[1]) >= 0 else "#b00020"
129
+ except:
130
+ pass
131
+
132
+ rows += f"""
133
+ <div style="
134
+ display:flex;
135
+ justify-content:space-between;
136
+ gap:6px;
137
+ padding:2px 0;
138
+ border-bottom:1px dashed #bcd0ea;
139
+ ">
140
+ <span style="color:#1a4f8a;font-weight:500;">
141
+ {r[0]}
142
+ </span>
143
+ <span style="
144
+ color:{color};
145
+ font-weight:600;
146
+ background:#f1f6ff;
147
+ padding:1px 6px;
148
+ border-radius:4px;
149
+ ">
150
+ {r[1]}
151
+ </span>
152
+ </div>
153
+ """
154
+ return f"<div>{rows}</div>"
155
+
156
+
157
+ # ==============================
158
+ # Noise filtering
159
+ # ==============================
160
+ NOISE_KEYS = {
161
+ "maxAge","priceHint","triggerable",
162
+ "customPriceAlertConfidence",
163
+ "sourceInterval","exchangeDataDelayedBy",
164
+ "esgPopulated"
165
+ }
166
+
167
+ def is_noise(k):
168
+ return k in NOISE_KEYS
169
+
170
+
171
+ # ==============================
172
+ # Duplicate resolver
173
+ # ==============================
174
+ DUPLICATE_PRIORITY = {
175
+ "price": ["regularMarketPrice","currentPrice"],
176
+ "prev": ["regularMarketPreviousClose","previousClose"],
177
+ "open": ["regularMarketOpen","open"],
178
+ "high": ["regularMarketDayHigh","dayHigh"],
179
+ "low": ["regularMarketDayLow","dayLow"],
180
+ "volume": ["regularMarketVolume","volume"]
181
+ }
182
+
183
+ def resolve_duplicates(data):
184
+ resolved, used = {}, set()
185
+ for keys in DUPLICATE_PRIORITY.values():
186
+ for k in keys:
187
+ if k in data:
188
+ resolved[k] = data[k]
189
+ used.update(keys)
190
+ break
191
+ for k,v in data.items():
192
+ if k not in used:
193
+ resolved[k] = v
194
+ return resolved
195
+
196
+
197
+ # ==============================
198
+ # Short key names
199
+ # ==============================
200
+ SHORT_NAMES = {
201
+ "regularMarketPrice":"Price",
202
+ "regularMarketChange":"Chg",
203
+ "regularMarketChangePercent":"Chg%",
204
+ "regularMarketPreviousClose":"Prev",
205
+ "regularMarketOpen":"Open",
206
+ "regularMarketDayHigh":"High",
207
+ "regularMarketDayLow":"Low",
208
+ "regularMarketVolume":"Vol",
209
+ "averageDailyVolume10Day":"AvgV10",
210
+ "averageDailyVolume3Month":"AvgV3M",
211
+ "fiftyDayAverage":"50DMA",
212
+ "twoHundredDayAverage":"200DMA",
213
+ "fiftyTwoWeekLow":"52WL",
214
+ "fiftyTwoWeekHigh":"52WH",
215
+ "beta":"Beta",
216
+ "targetMeanPrice":"Target"
217
+ }
218
+
219
+ def pretty_key(k):
220
+ return SHORT_NAMES.get(k, k[:12])
221
+
222
+
223
+ # ==============================
224
+ # Classifiers
225
+ # ==============================
226
+ def classify_price_volume_subgroup(key):
227
+ k = key.lower()
228
+ if "volume" in k: return "Volume"
229
+ if "average" in k or "dma" in k: return "Moving Avg"
230
+ if "week" in k or "beta" in k: return "Range / Vol"
231
+ if "target" in k or "recommend" in k: return "Bid / Analyst"
232
+ return "Live Price"
233
+
234
+
235
+ def classify_key(key, value):
236
+ k = key.lower()
237
+ if isinstance(value,(int,float)) and any(x in k for x in [
238
+ "price","volume","avg","change","percent","market","week","beta","target"
239
+ ]):
240
+ return "price_volume"
241
+ if any(x in k for x in [
242
+ "revenue","income","profit","margin","pe","pb","roe","roa","debt","equity"
243
+ ]):
244
+ return "fundamental"
245
+ if isinstance(value,str) and len(value) > 80:
246
+ return "long_text"
247
+ return "profile"
248
+
249
+
250
+ # ==============================
251
+ # Group builder
252
+ # ==============================
253
+ def build_grouped_info(info):
254
+ groups = {
255
+ "price_volume":{},
256
+ "fundamental":{},
257
+ "profile":{},
258
+ "long_text":{}
259
+ }
260
+ for k,v in info.items():
261
+ if v in [None,"",[],{}]:
262
+ continue
263
+ groups[classify_key(k,v)][k] = v
264
+ return groups
265
+
266
+
267
+ # ==============================
268
+ # Column splitter
269
+ # ==============================
270
+ def split_df_evenly(df):
271
+ if df is None or df.empty:
272
+ return []
273
+
274
+ n = len(df)
275
+ cols = 1 if n <= 6 else 2 if n <= 14 else 3
276
+ chunk = (n + cols - 1) // cols
277
+ return [df.iloc[i:i+chunk] for i in range(0, n, chunk)]
278
+
279
+
280
+ # ==============================
281
+ # DataFrame builder
282
+ # ==============================
283
+ def build_df_from_dict(data):
284
+ rows = []
285
+ for k,v in data.items():
286
+ if is_noise(k):
287
+ continue
288
+ if isinstance(v,(int,float)):
289
+ v = format_number(v)
290
+ rows.append([pretty_key(k), v])
291
+ return pd.DataFrame(rows, columns=["Field","Value"])
292
+
293
+
294
+ # ==============================
295
+ # MAIN FUNCTION (CACHED)
296
+ # ==============================
297
+ def fetch_info(symbol):
298
+ """
299
+ Cached Yahoo Finance info renderer
300
+ Cache validity: 1 hour
301
+ """
302
+ key = f"info_{symbol}"
303
+
304
+ # ---------- CACHE CHECK ----------
305
+ if exists(key, "html"):
306
+ cached = load(key, "html")
307
+ if cached:
308
+ return cached
309
+
310
+ try:
311
+ info = yfinfo(symbol)
312
+
313
+ # ---------- VALIDATION ----------
314
+ if not info or "__error__" in info:
315
+ return "No data"
316
+
317
+ groups = build_grouped_info(info)
318
+ html = ""
319
+
320
+ # ---------- PRICE / VOLUME ----------
321
+ pv = resolve_duplicates(groups["price_volume"])
322
+ sub = {}
323
+ for k,v in pv.items():
324
+ sg = classify_price_volume_subgroup(k)
325
+ sub.setdefault(sg,{})[k] = v
326
+
327
+ cards = ""
328
+ for i,(t,d) in enumerate(sub.items()):
329
+ df = build_df_from_dict(d)
330
+ if not df.empty:
331
+ cards += html_card(
332
+ f"{SUBGROUP_ICONS.get(t,'ℹ️')} {t}",
333
+ make_table(df),
334
+ mini=True,
335
+ shade=i
336
+ )
337
+
338
+ if cards:
339
+ html += html_card(
340
+ f"{MAIN_ICONS['Price / Volume']} Price / Volume",
341
+ column_layout(cards),
342
+ shade=0
343
+ )
344
+
345
+ # ---------- FUNDAMENTALS ----------
346
+ if groups["fundamental"]:
347
+ chunks = split_df_evenly(build_df_from_dict(groups["fundamental"]))
348
+ cols = "".join(
349
+ html_card("📊 Fundamentals", make_table(c), mini=True, shade=i)
350
+ for i,c in enumerate(chunks)
351
+ )
352
+ html += html_card(
353
+ f"{MAIN_ICONS['Fundamentals']} Fundamentals",
354
+ column_layout(cols),
355
+ shade=1
356
+ )
357
+
358
+ # ---------- COMPANY PROFILE ----------
359
+ if groups["profile"]:
360
+ chunks = split_df_evenly(build_df_from_dict(groups["profile"]))
361
+ cols = "".join(
362
+ html_card("🏢 Profile", make_table(c), mini=True, shade=i)
363
+ for i,c in enumerate(chunks)
364
+ )
365
+ html += html_card(
366
+ f"{MAIN_ICONS['Company Profile']} Company Profile",
367
+ column_layout(cols),
368
+ shade=2
369
+ )
370
+
371
+ # ---------- LONG TEXT ----------
372
+ for k,v in groups["long_text"].items():
373
+ html += html_card(pretty_key(k), v, shade=2)
374
+
375
+ # ---------- SAVE CACHE ----------
376
+ if html.strip():
377
+ save(key, html, "html")
378
+
379
+ return html
380
+
381
+ except Exception:
382
+ return f"<pre>{traceback.format_exc()}</pre>"