'''# ============================== # Imports # ============================== import yfinance as yf import pandas as pd import traceback from datetime import datetime, timezone from .persist import exists, load, save # ============================== # Icons # ============================== MAIN_ICONS = { "Price / Volume": "📈", "Fundamentals": "📊", "Trend": "📈", "Signals": "🧠", "Company Profile": "🏢", "Management": "👔", "VWAP": "📌", } # ============================== # Short names # ============================== SHORT_NAMES = { "regularMarketPrice": "Price", "regularMarketChange": "Chg", "regularMarketChangePercent": "Chg%", "regularMarketPreviousClose": "Prev", "regularMarketOpen": "Open", "regularMarketDayHigh": "High", "regularMarketDayLow": "Low", "regularMarketVolume": "Vol", "averageDailyVolume10Day": "Avg Vol 10D", "averageDailyVolume3Month": "Avg Vol 3M", "fiftyDayAverage": "50DMA", "twoHundredDayAverage": "200DMA", "fiftyTwoWeekLow": "52W Low", "fiftyTwoWeekHigh": "52W High", "mostRecentQuarter":"Recent Q", "lastFiscalYearEnd":"FY End", "vwap":"VWAP", "dailyGapPercent":"Gap%", "dailyRangePercent":"Range%" } # ============================== # Price / Volume Sub-Groups # ============================== PRICE_VOLUME_GROUPS = { "Market Price": ["Price","Chg","Chg%","Prev","Open"], "Intraday Range": ["High","Low","dailyRangePercent"], "Volume & Liquidity": ["Vol","Avg Vol 10D","Avg Vol 3M"], "Moving Averages": ["50DMA","200DMA","vs 50DMA","vs 200DMA"], "52W Range": ["52W Low","52W High","52W Pos"], "VWAP & Gap": ["VWAP","dailyGapPercent"] } # ============================== # Noise keys # ============================== NOISE_KEYS = { "maxAge","priceHint","triggerable", "customPriceAlertConfidence", "sourceInterval","exchangeDataDelayedBy", "esgPopulated" } # ============================== # Low-level Yahoo fetch # ============================== def yfinfo(symbol): try: t = yf.Ticker(symbol + ".NS") info = t.info return info if isinstance(info, dict) else {} except Exception as e: return {"__error__": str(e)} # ============================== # Formatting # ============================== def human_number(n): try: n = float(n) if abs(n) >= 1e7: return f"{n/1e7:.2f}Cr" if abs(n) >= 1e5: return f"{n/1e5:.2f}L" if abs(n) >= 1e3: return f"{n/1e3:.2f}K" return f"{n:,.2f}" except: return str(n) DATE_KEYWORDS = ("date", "time", "timestamp", "fiscal", "quarter","earnings","dividend") def looks_like_unix_ts(v): try: v = int(v) return (946684800 <= v <= 4102444800 or 946684800000 <= v <= 4102444800000) except: return False def unix_to_dt(v): v = int(v) if v > 10**12: v //= 1000 return datetime.fromtimestamp(v, tz=timezone.utc) def fy_quarter_label(dt): y, m = dt.year, dt.month if m >= 4: fy = y + 1 q = (m - 1)//3 + 1 else: fy = y q = (m + 8)//3 return f"Q{q} FY{str(fy)[-2:]}" def format_value(k, v): lk = k.lower() # Date / Quarter if isinstance(v,(int,float)) and looks_like_unix_ts(v): if any(x in lk for x in DATE_KEYWORDS): dt = unix_to_dt(v) if "quarter" in lk: return fy_quarter_label(dt) return dt.strftime("%d %b %Y") # Numbers if isinstance(v,(int,float)): cls = "pos" if v>0 else "neg" if v<0 else "" if "percent" in lk: return f'{v:.2f}%' if any(x in lk for x in ["marketcap","revenue","income","value","cap","enterprise"]): return f'₹{human_number(v)}' return f'{human_number(v)}' return v # ============================== # HTML Helpers # ============================== def column_layout(html): return f"""
{html}
""" def html_card(title,body,mini=False): font = "12px" if mini else "14px" pad = "6px" if mini else "10px" return f"""
{title}
{body}
""" def make_table(df): return "".join( f"""
{r.Field}{r.Value}
""" for r in df.itertuples() ) def collapsible(title,body): return f"""
{title} {body}
""" # ============================== # Data Helpers # ============================== def build_df_from_dict(data): rows = [(SHORT_NAMES.get(k,k[:16]), format_value(k,v)) for k,v in data.items() if k not in NOISE_KEYS] return pd.DataFrame(rows,columns=["Field","Value"]) def resolve_duplicates(data): DUP = { "price":["regularMarketPrice","currentPrice"], "prev":["regularMarketPreviousClose","previousClose"], "open":["regularMarketOpen","open"], "high":["regularMarketDayHigh","dayHigh"], "low":["regularMarketDayLow","dayLow"], "volume":["regularMarketVolume","volume"] } resolved, used = {}, set() for keys in DUP.values(): for k in keys: if k in data: resolved[k] = data[k] used.update(keys) break for k,v in data.items(): if k not in used: resolved[k] = v return resolved def classify(k,v): lk = k.lower() if k=="companyOfficers": return "management" if any(x in lk for x in ["pe","pb","roe","roa","margin","debt","revenue","profit","eps","cap"]): return "fundamental" if isinstance(v,(int,float)): return "price_volume" if isinstance(v,str) and len(v)>80: return "long_text" return "profile" def group_info(info): g = {"price_volume":{}, "fundamental":{}, "profile":{}, "management":{}, "long_text":{}} for k,v in info.items(): if k in NOISE_KEYS or v in [None,"",[],{}]: continue g[classify(k,v)][k] = v return g def split_df(df): n = len(df) cols = 1 if n<=6 else 2 if n<=14 else 3 size = (n+cols-1)//cols return [df.iloc[i:i+size] for i in range(0,n,size)] # ============================== # Derived Metrics & Signals # ============================== def build_price_volume_derived(info): out={} price=info.get("regularMarketPrice") dma50=info.get("fiftyDayAverage") dma200=info.get("twoHundredDayAverage") low52=info.get("fiftyTwoWeekLow") high52=info.get("fiftyTwoWeekHigh") if price and dma50: out["vs 50DMA"]="Above ↑" if price>dma50 else "Below ↓" if price and dma200: out["vs 200DMA"]="Above ↑" if price>dma200 else "Below ↓" if price and low52 and high52 and high52!=low52: out["52W Pos"]=f"{(price-low52)/(high52-low52)*100:.1f}%" # Gap % and daily range % prev=info.get("regularMarketPreviousClose") high=info.get("regularMarketDayHigh") low=info.get("regularMarketDayLow") if price and prev: out["dailyGapPercent"]=(price-prev)/prev*100 if high and low and price: out["dailyRangePercent"]=(high-low)/price*100 # VWAP (approximation) vol=info.get("regularMarketVolume") if vol and price: out["VWAP"]=price return out def build_smart_signals(info): rows=[] pe=info.get("trailingPE") roe=info.get("returnOnEquity") debt=info.get("debtToEquity") price=info.get("regularMarketPrice") dma50=info.get("fiftyDayAverage") dma200=info.get("twoHundredDayAverage") if pe: rows.append(("Valuation","Expensive" if pe>35 else "Cheap" if pe<15 else "Fair")) if roe: rows.append(("Quality","High" if roe>0.18 else "Average")) if debt: rows.append(("Balance Sheet","Weak" if debt>1 else "Healthy")) if price and dma50 and dma200: trend = "Bullish" if price>dma50>dma200 else "Bearish" if price{traceback.format_exc()}" ''' # ============================== # Imports # ============================== import yfinance as yf import pandas as pd import traceback from datetime import datetime, timezone from .persist import exists, load, save # ============================== # Icons # ============================== MAIN_ICONS = { "Price / Volume": "📈", "Fundamentals": "📊", "Trend": "📈", "Signals": "🧠", "Company Profile": "🏢", "Management": "👔", "VWAP": "📌", "IPO": "🚀" } # ============================== # Short names # ============================== SHORT_NAMES = { "regularMarketPrice": "Price", "regularMarketChange": "Chg", "regularMarketChangePercent": "Chg%", "regularMarketPreviousClose": "Prev", "regularMarketOpen": "Open", "regularMarketDayHigh": "High", "regularMarketDayLow": "Low", "regularMarketVolume": "Vol", "averageDailyVolume10Day": "Avg Vol 10D", "averageDailyVolume3Month": "Avg Vol 3M", "fiftyDayAverage": "50DMA", "twoHundredDayAverage": "200DMA", "fiftyTwoWeekLow": "52W Low", "fiftyTwoWeekHigh": "52W High", "mostRecentQuarter":"Recent Q", "lastFiscalYearEnd":"FY End", "vwap":"VWAP", "dailyGapPercent":"Gap%", "dailyRangePercent":"Range%", "firstTradeDate":"IPO Date" } # ============================== # Price / Volume Sub-Groups # ============================== PRICE_VOLUME_GROUPS = { "Market Price": ["Price","Chg","Chg%","Prev","Open","firstTradeDate"], "Intraday Range": ["High","Low","dailyRangePercent"], "Volume & Liquidity": ["Vol","Avg Vol 10D","Avg Vol 3M"], "Moving Averages": ["50DMA","200DMA","vs 50DMA","vs 200DMA"], "52W Range": ["52W Low","52W High","52W Pos"], "VWAP & Gap": ["VWAP","dailyGapPercent"] } # ============================== # Noise keys # ============================== NOISE_KEYS = { "maxAge","priceHint","triggerable", "customPriceAlertConfidence", "sourceInterval","exchangeDataDelayedBy", "esgPopulated" } # ============================== # Low-level Yahoo fetch # ============================== def yfinfo(symbol): try: t = yf.Ticker(symbol + ".NS") info = t.info return info if isinstance(info, dict) else {} except Exception as e: return {"__error__": str(e)} # ============================== # Formatting # ============================== def human_number(n): try: n = float(n) if abs(n) >= 1e7: return f"{n/1e7:.2f}Cr" if abs(n) >= 1e5: return f"{n/1e5:.2f}L" if abs(n) >= 1e3: return f"{n/1e3:.2f}K" return f"{n:,.2f}" except: return str(n) DATE_KEYWORDS = ("date", "time", "timestamp", "fiscal", "quarter","earnings","dividend","firstTradeDate") def looks_like_unix_ts(v): try: v = int(v) return (946684800 <= v <= 4102444800 or 946684800000 <= v <= 4102444800000) except: return False def unix_to_dt(v): v = int(v) if v > 10**12: v //= 1000 return datetime.fromtimestamp(v, tz=timezone.utc) def fy_quarter_label(dt): y, m = dt.year, dt.month if m >= 4: fy = y + 1 q = (m - 1)//3 + 1 else: fy = y q = (m + 8)//3 return f"Q{q} FY{str(fy)[-2:]}" def format_value(k, v): lk = k.lower() # Date / Quarter if isinstance(v,(int,float)) and looks_like_unix_ts(v): if any(x in lk for x in DATE_KEYWORDS): dt = unix_to_dt(v) if "quarter" in lk: return fy_quarter_label(dt) return dt.strftime("%d %b %Y") # Numbers if isinstance(v,(int,float)): cls = "pos" if v>0 else "neg" if v<0 else "" if "percent" in lk: return f'{v:.2f}%' if any(x in lk for x in ["marketcap","revenue","income","value","cap","enterprise"]): return f'₹{human_number(v)}' return f'{human_number(v)}' # Strings if isinstance(v,str) and len(v)>80: return f'
{v}
' return v # ============================== # HTML Helpers # ============================== def column_layout(html): return f"""
{html}
""" def html_card(title,body,mini=False): font = "12px" if mini else "14px" pad = "6px" if mini else "10px" return f"""
{title}
{body}
""" def make_table(df): return "".join( f"""
{r.Field}{r.Value}
""" for r in df.itertuples() ) # ============================== # Data Helpers # ============================== def build_df_from_dict(data): rows = [(SHORT_NAMES.get(k,k[:16]), format_value(k,v)) for k,v in data.items() if k not in NOISE_KEYS] return pd.DataFrame(rows,columns=["Field","Value"]) def resolve_duplicates(data): DUP = { "price":["regularMarketPrice","currentPrice"], "prev":["regularMarketPreviousClose","previousClose"], "open":["regularMarketOpen","open"], "high":["regularMarketDayHigh","dayHigh"], "low":["regularMarketDayLow","dayLow"], "volume":["regularMarketVolume","volume"] } resolved, used = {}, set() for keys in DUP.values(): for k in keys: if k in data: resolved[k] = data[k] used.update(keys) break for k,v in data.items(): if k not in used: resolved[k] = v return resolved def classify(k,v): lk = k.lower() if k=="companyOfficers": return "management" if any(x in lk for x in ["pe","pb","roe","roa","margin","debt","revenue","profit","eps","cap"]): return "fundamental" if isinstance(v,(int,float)): return "price_volume" if isinstance(v,str) and len(v)>80: return "long_text" return "profile" def group_info(info): g = {"price_volume":{}, "fundamental":{}, "profile":{}, "management":{}, "long_text":{}} for k,v in info.items(): if k in NOISE_KEYS or v in [None,"",[],{}]: continue g[classify(k,v)][k] = v return g def split_df(df): n = len(df) cols = 1 if n<=6 else 2 if n<=14 else 3 size = (n+cols-1)//cols return [df.iloc[i:i+size] for i in range(0,n,size)] # ============================== # Derived Metrics & Signals # ============================== def build_price_volume_derived(info): out={} price=info.get("regularMarketPrice") dma50=info.get("fiftyDayAverage") dma200=info.get("twoHundredDayAverage") low52=info.get("fiftyTwoWeekLow") high52=info.get("fiftyTwoWeekHigh") if price and dma50: out["vs 50DMA"]="Above ↑" if price>dma50 else "Below ↓" if price and dma200: out["vs 200DMA"]="Above ↑" if price>dma200 else "Below ↓" if price and low52 and high52 and high52!=low52: out["52W Pos"]=f"{(price-low52)/(high52-low52)*100:.1f}%" prev=info.get("regularMarketPreviousClose") high=info.get("regularMarketDayHigh") low=info.get("regularMarketDayLow") if price and prev: out["dailyGapPercent"]=(price-prev)/prev*100 if high and low and price: out["dailyRangePercent"]=(high-low)/price*100 vol=info.get("regularMarketVolume") if vol and price: out["VWAP"]=price if info.get("firstTradeDate"): out["firstTradeDate"]=info.get("firstTradeDate") return out def build_smart_signals(info): rows=[] pe=info.get("trailingPE") roe=info.get("returnOnEquity") debt=info.get("debtToEquity") price=info.get("regularMarketPrice") dma50=info.get("fiftyDayAverage") dma200=info.get("twoHundredDayAverage") # Alerts if pe: rows.append(("Valuation",f'{"Expensive" if pe>35 else "Cheap" if pe<15 else "Fair"}')) if roe: rows.append(("Quality",f'{"High" if roe>0.18 else "Average"}')) if debt: rows.append(("Balance Sheet",f'{"Weak" if debt>1 else "Healthy"}')) if price and dma50 and dma200: trend = "Bullish" if price>dma50>dma200 else "Bearish" if price{trend}')) return pd.DataFrame(rows,columns=["Field","Value"]) # ============================== # Build Price/Volume Section # ============================== def build_price_volume_section(info,pv_data): df=build_df_from_dict(pv_data) derived=build_price_volume_derived(info) if derived: df=pd.concat([df,pd.DataFrame(derived.items(),columns=["Field","Value"])],ignore_index=True) cards="" for title,fields in PRICE_VOLUME_GROUPS.items(): sub=df[df["Field"].isin(fields)] if not sub.empty: cards+=html_card(title,make_table(sub),mini=True) trend_df=df[df["Field"].isin(["vs 50DMA","vs 200DMA","52W Pos"])] if not trend_df.empty: cards+=html_card("Trend & Momentum",make_table(trend_df),mini=True) signals=build_smart_signals(info) if not signals.empty: cards+=html_card("Smart Signals",make_table(signals),mini=True) return column_layout(cards) # ============================== # Main Function # ============================== def fetch_info(symbol): key=f"info_{symbol}" if exists(key,"html"): cached=load(key,"html") if cached: return cached try: info=yfinfo(symbol) if "__error__" in info: return "No data" groups=group_info(info) html="" # Price / Volume pv=resolve_duplicates(groups["price_volume"]) if pv: html+=html_card(f"{MAIN_ICONS['Price / Volume']} Price / Volume",build_price_volume_section(info,pv)) # Fundamentals if groups["fundamental"]: df=build_df_from_dict(groups["fundamental"]) html+=html_card(f"{MAIN_ICONS['Fundamentals']} Fundamentals", column_layout("".join(html_card("Fundamentals",make_table(c),mini=True) for c in split_df(df)))) # Company Profile if groups["profile"]: df=build_df_from_dict(groups["profile"]) html+=html_card(f"{MAIN_ICONS['Company Profile']} Company Profile", column_layout("".join(html_card("Profile",make_table(c),mini=True) for c in split_df(df)))) # Management if groups["management"].get("companyOfficers"): cards="" for o in groups["management"]["companyOfficers"]: cards+=html_card(o.get("name",""),o.get("title",""),mini=True) html+=html_card(f"{MAIN_ICONS['Management']} Management",column_layout(cards)) # Long Text for k,v in groups["long_text"].items(): html+=html_card(SHORT_NAMES.get(k,k[:16]),v) save(key,html,"html") return html except Exception: return f"
{traceback.format_exc()}
"