eshan6704 commited on
Commit
bd45e82
·
verified ·
1 Parent(s): 1b80aa1

Create yahooinfo.py

Browse files
Files changed (1) hide show
  1. app/yahooinfo.py +298 -0
app/yahooinfo.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ======================================================
2
+ # FINAL YAHOO FINANCE INFO PROCESSOR + RENDERER
3
+ # ======================================================
4
+
5
+ import yfinance as yf
6
+ import pandas as pd
7
+ import traceback
8
+ import re
9
+ from datetime import datetime, UTC
10
+ from math import floor
11
+
12
+ from .persist import exists, load, save
13
+
14
+
15
+ # ======================================================
16
+ # CONSTANTS
17
+ # ======================================================
18
+ CR = 10_000_000
19
+ KCR = 10_000_000_000
20
+ LCR = 10_000_000_000_000
21
+
22
+ DATE_KEYS = ("date", "timestamp", "time", "yearend")
23
+
24
+
25
+ # ======================================================
26
+ # RAW YAHOO FETCH
27
+ # ======================================================
28
+ def yfinfo(symbol):
29
+ try:
30
+ t = yf.Ticker(symbol + ".NS")
31
+ info = t.info
32
+ return info if isinstance(info, dict) else {}
33
+ except Exception as e:
34
+ return {"__error__": str(e)}
35
+
36
+
37
+ # ======================================================
38
+ # HELPERS
39
+ # ======================================================
40
+ def truncate(num, decimals=4):
41
+ f = 10 ** decimals
42
+ return floor(num * f) / f
43
+
44
+
45
+ def compress_key(key, used):
46
+ if len(key) <= 10:
47
+ used.add(key)
48
+ return key
49
+
50
+ parts = re.findall(r"[A-Z][a-z]*|[a-z]+", key)
51
+ base = "".join(p[:3] for p in parts)
52
+
53
+ if base not in used:
54
+ used.add(base)
55
+ return base
56
+
57
+ i = 1
58
+ while f"{base}_{i}" in used:
59
+ i += 1
60
+
61
+ final = f"{base}_{i}"
62
+ used.add(final)
63
+ return final
64
+
65
+
66
+ # ======================================================
67
+ # CORE PROCESSOR
68
+ # ======================================================
69
+ def process_info(info):
70
+ main = {}
71
+ long_text = {}
72
+
73
+ new_to_old = {}
74
+ old_to_new = {}
75
+
76
+ used_keys = set()
77
+
78
+ for old_k, v in info.items():
79
+ lk = old_k.lower()
80
+ new_k = compress_key(old_k, used_keys)
81
+
82
+ new_to_old[new_k] = old_k
83
+ old_to_new[old_k] = new_k
84
+
85
+ # ---- Long text ----
86
+ if isinstance(v, str) and len(v) > 200:
87
+ long_text[new_k] = v
88
+ continue
89
+
90
+ # ---- Date conversion ----
91
+ if (
92
+ isinstance(v, int)
93
+ and v >= 1_000_000_000
94
+ and any(x in lk for x in DATE_KEYS)
95
+ ):
96
+ if v > 1_000_000_000_000:
97
+ v //= 1000
98
+ try:
99
+ main[new_k] = datetime.fromtimestamp(v, UTC).strftime("%d-%m-%Y")
100
+ except:
101
+ main[new_k] = v
102
+ continue
103
+
104
+ # ---- Large numbers (Cr/KCr/LCr) ----
105
+ if isinstance(v, (int, float)) and abs(v) >= CR:
106
+ av = abs(v)
107
+ sign = -1 if v < 0 else 1
108
+
109
+ if av >= LCR:
110
+ main[new_k] = f"{sign * av / LCR:.2f} LCr"
111
+ elif av >= KCR:
112
+ main[new_k] = f"{sign * av / KCR:.2f} KCr"
113
+ else:
114
+ main[new_k] = f"{sign * av / CR:.2f} Cr"
115
+ continue
116
+
117
+ # ---- Float trimming ----
118
+ if isinstance(v, float):
119
+ main[new_k] = 0.0 if v == 0.0 else truncate(v)
120
+ continue
121
+
122
+ main[new_k] = v
123
+
124
+ return main, long_text, new_to_old, old_to_new
125
+
126
+
127
+ # ======================================================
128
+ # NOISE
129
+ # ======================================================
130
+ NOISE_KEYS = {
131
+ "maxAge", "priceHint", "triggerable",
132
+ "customPriceAlertConfidence",
133
+ "sourceInterval", "exchangeDataDelayedBy",
134
+ "esgPopulated"
135
+ }
136
+
137
+ def is_noise(k):
138
+ return k in NOISE_KEYS
139
+
140
+
141
+ # ======================================================
142
+ # DUPLICATE RESOLUTION (OLD KEYS)
143
+ # ======================================================
144
+ DUPLICATE_PRIORITY = {
145
+ "price": ["regularMarketPrice", "currentPrice"],
146
+ "prev": ["regularMarketPreviousClose", "previousClose"],
147
+ "open": ["regularMarketOpen", "open"],
148
+ "high": ["regularMarketDayHigh", "dayHigh"],
149
+ "low": ["regularMarketDayLow", "dayLow"],
150
+ "volume": ["regularMarketVolume", "volume"],
151
+ }
152
+
153
+ def resolve_duplicates(data, new_to_old):
154
+ resolved = {}
155
+ used_old = set()
156
+
157
+ for keys in DUPLICATE_PRIORITY.values():
158
+ for old_k in keys:
159
+ for new_k, mapped_old in new_to_old.items():
160
+ if mapped_old == old_k and new_k in data:
161
+ resolved[new_k] = data[new_k]
162
+ used_old.update(keys)
163
+ break
164
+ else:
165
+ continue
166
+ break
167
+
168
+ for new_k, v in data.items():
169
+ if new_to_old[new_k] not in used_old:
170
+ resolved[new_k] = v
171
+
172
+ return resolved
173
+
174
+
175
+ # ======================================================
176
+ # CLASSIFIERS (UNCHANGED – OLD KEYS ONLY)
177
+ # ======================================================
178
+ def classify_price_volume_subgroup(old_key):
179
+ k = old_key.lower()
180
+ if "volume" in k: return "Volume"
181
+ if "average" in k or "dma" in k: return "Moving Avg"
182
+ if "week" in k or "beta" in k: return "Range / Vol"
183
+ if "target" in k or "recommend" in k: return "Bid / Analyst"
184
+ return "Live Price"
185
+
186
+
187
+ def classify_key(old_key, value):
188
+ k = old_key.lower()
189
+
190
+ if isinstance(value, (int, float)) and any(x in k for x in [
191
+ "price", "volume", "avg", "change", "percent",
192
+ "market", "week", "beta", "target"
193
+ ]):
194
+ return "price_volume"
195
+
196
+ if any(x in k for x in [
197
+ "revenue", "income", "profit", "margin",
198
+ "pe", "pb", "roe", "roa", "debt", "equity"
199
+ ]):
200
+ return "fundamental"
201
+
202
+ if isinstance(value, str) and len(value) > 80:
203
+ return "long_text"
204
+
205
+ return "profile"
206
+
207
+
208
+ # ======================================================
209
+ # GROUP BUILDER (KEY POINT)
210
+ # ======================================================
211
+ def build_grouped_info(info, new_to_old):
212
+ groups = {
213
+ "price_volume": {},
214
+ "fundamental": {},
215
+ "profile": {},
216
+ "long_text": {},
217
+ }
218
+
219
+ for new_k, v in info.items():
220
+ if v in [None, "", [], {}]:
221
+ continue
222
+
223
+ old_k = new_to_old.get(new_k, new_k)
224
+ g = classify_key(old_k, v)
225
+ groups[g][new_k] = v
226
+
227
+ return groups
228
+
229
+
230
+ # ======================================================
231
+ # FORMATTERS
232
+ # ======================================================
233
+ def format_number(x):
234
+ try:
235
+ x = float(x)
236
+ if abs(x) >= 100:
237
+ return f"{x:,.0f}"
238
+ if abs(x) >= 1:
239
+ return f"{x:,.2f}"
240
+ return f"{x:.4f}"
241
+ except:
242
+ return str(x)
243
+
244
+
245
+ def build_df_from_dict(data):
246
+ rows = []
247
+ for k, v in data.items():
248
+ if is_noise(k):
249
+ continue
250
+ if isinstance(v, (int, float)):
251
+ v = format_number(v)
252
+ rows.append([k, v])
253
+ return pd.DataFrame(rows, columns=["Field", "Value"])
254
+
255
+
256
+ # ======================================================
257
+ # MAIN ENTRY (CACHED)
258
+ # ======================================================
259
+ def fetch_info(symbol):
260
+ cache_key = f"info_{symbol}"
261
+
262
+ if exists(cache_key, "html"):
263
+ cached = load(cache_key, "html")
264
+ if cached:
265
+ return cached
266
+
267
+ try:
268
+ raw = yfinfo(symbol)
269
+ if not raw or "__error__" in raw:
270
+ return "No data"
271
+
272
+ info, long_text, new_to_old, _ = process_info(raw)
273
+ groups = build_grouped_info(info, new_to_old)
274
+
275
+ html = ""
276
+
277
+ # ---- PRICE / VOLUME ----
278
+ pv = resolve_duplicates(groups["price_volume"], new_to_old)
279
+ for new_k, v in pv.items():
280
+ html += f"<div><b>{new_k}</b> : {v}</div>"
281
+
282
+ # ---- FUNDAMENTALS ----
283
+ for new_k, v in groups["fundamental"].items():
284
+ html += f"<div><b>{new_k}</b> : {v}</div>"
285
+
286
+ # ---- PROFILE ----
287
+ for new_k, v in groups["profile"].items():
288
+ html += f"<div><b>{new_k}</b> : {v}</div>"
289
+
290
+ # ---- LONG TEXT ----
291
+ for v in long_text.values():
292
+ html += f"<div style='margin-top:8px'>{v}</div>"
293
+
294
+ save(cache_key, html, "html")
295
+ return html
296
+
297
+ except Exception:
298
+ return f"<pre>{traceback.format_exc()}</pre>"