import streamlit as st import os # --- SAFETY FIX 1: Map HF_TOKEN to the variable litellm expects --- # This prevents "Auth Error" even if the library ignores the passed key if "HF_TOKEN" in os.environ: os.environ["HUGGINGFACE_API_KEY"] = os.environ["HF_TOKEN"] from smolagents import CodeAgent, LiteLLMModel, tool import yfinance as yf import pandas_ta as ta import pandas as pd import numpy as np from prophet import Prophet from ddgs import DDGS import datetime # --- 1. SETUP PAGE CONFIG --- st.set_page_config(page_title="Indian Stock Analyst AI", layout="wide") # --- 2. TOOLS --- @tool def get_deep_financials(ticker: str) -> dict: """ Fetches deep financial metrics: Valuation, Fundamentals, Shareholding, and Technicals. Args: ticker: The stock symbol (e.g., 'TATAMOTORS.NS'). """ try: stock = yf.Ticker(ticker) info = stock.info hist = stock.history(period="1y") if hist.empty: return {"error": "No data found"} # Technicals cmp = hist['Close'].iloc[-1] high_52 = hist['High'].max() low_52 = hist['Low'].min() drawdown = ((cmp - high_52) / high_52) * 100 hist.ta.rsi(length=14, append=True) rsi = hist['RSI_14'].iloc[-1] if 'RSI_14' in hist else 50 # Returns ret_1y = ((cmp - hist['Close'].iloc[0]) / hist['Close'].iloc[0]) * 100 ret_6m = ((cmp - hist['Close'].iloc[-126]) / hist['Close'].iloc[-126]) * 100 if len(hist) > 126 else 0 # Valuation eps = info.get('trailingEps', 0) book_val = info.get('bookValue', 0) graham_num = np.sqrt(22.5 * eps * book_val) if (eps > 0 and book_val > 0) else 0 # Shareholding promoters = info.get('heldPercentInsiders', 0) * 100 institutions = info.get('heldPercentInstitutions', 0) * 100 public = 100 - (promoters + institutions) def safe(key, default="N/A"): return info.get(key, default) return { "ticker": ticker, "cmp": round(cmp, 2), "high_52": round(high_52, 2), "low_52": round(low_52, 2), "drawdown": round(drawdown, 2), "mkt_cap_cr": round(safe('marketCap', 0) / 10000000, 2), "rsi": round(rsi, 2), "ret_1y": round(ret_1y, 2), "ret_6m": round(ret_6m, 2), "pe": safe('trailingPE'), "peg": safe('pegRatio'), "pb": safe('priceToBook'), "div_yield": safe('dividendYield', 0) * 100 if safe('dividendYield') else 0, "roe": round(safe('returnOnEquity', 0) * 100, 2), "roce_proxy": round(safe('returnOnAssets', 0) * 100, 2), "eps": eps, "book_value": book_val, "graham_number": round(graham_num, 2), "promoters": round(promoters, 2), "institutions": round(institutions, 2), "public": round(public, 2), "sector": safe('sector', 'Unknown'), "desc": safe('longBusinessSummary', '')[:300] } except Exception as e: return {"error": str(e)} @tool def get_market_news(query: str) -> str: """ Searches for broad market news, including results, mergers, deals, and acquisitions. Args: query: The search term (e.g. 'HDFC Bank'). """ try: search_query = f"{query} latest stock news business mergers acquisitions deals" results = DDGS().text(search_query, max_results=6) return "\n".join([f"- {r['title']}: {r['body']}" for r in results]) if results else "No news found." except Exception as e: return f"Error fetching news: {e}" @tool def get_price_prediction(ticker: str) -> dict: """ Runs Prophet to predict trend for the next 30 days. Args: ticker: The stock symbol. """ try: stock = yf.Ticker(ticker) df = stock.history(period="2y").reset_index()[['Date', 'Close']] if df.empty: return {"error": "Insufficient data"} df.columns = ['ds', 'y'] df['ds'] = df['ds'].dt.tz_localize(None) m = Prophet(daily_seasonality=True) m.fit(df) future = m.make_future_dataframe(periods=30) forecast = m.predict(future) future_price = forecast.iloc[-1]['yhat'] curr = df.iloc[-1]['y'] return {"current": round(curr, 2), "pred_30d": round(future_price, 2), "trend": "Bullish" if future_price > curr else "Bearish"} except Exception as e: return {"error": str(e)} # --- 3. DEFINE AGENT --- # Use LiteLLMModel to connect to Hugging Face's free API via their Qwen provider # We use Qwen2.5-Coder because it is free on HF, fast, and excellent at following agent instructions. model = LiteLLMModel( model_id="huggingface/Qwen/Qwen2.5-Coder-32B-Instruct", api_key=os.getenv("HF_TOKEN") # Uses the secret you set earlier ) # Note: For LiteLLM with HF, sometimes just model_id="huggingface/Qwen/..." works too, # but the above is safer for specific endpoints. agent = CodeAgent( tools=[get_deep_financials, get_market_news, get_price_prediction], model=model, add_base_tools=True ) STRICT_TEMPLATE = """ You must generate the output EXACTLY in this format. Do not change the headers. # {TICKER} | Investment Thesis [Insert Date] **Company Description** [Insert 2 sentence description] **Current Stock Performance** ๐Ÿ“ **Current Market Price (CMP)**: {CURRENCY} [CMP] ๐Ÿ“ˆ **52-Week High**: {CURRENCY} [High] ๐Ÿ“‰ **52-Week Low**: {CURRENCY} [Low] ๐Ÿ“‰ **Drawdown from 52-Week High**: [Drawdown]% ๐Ÿ“Š **Market Cap**: [MCap] Cr ๐Ÿ“ˆ **Recent Performance**: [1 sentence summary of recent movement] ๐Ÿ” **1. Financial Performance** * **Revenue/Profit**: [Mention recent Revenue/Profit trends from news if available, else general health] * **Margins**: [Mention if margins are stable/improving based on news] * **EPS (TTM)**: {CURRENCY} [EPS] ๐Ÿ“‰ **2. Stock Performance** * **1-Year Return**: [1Y Ret]% * **6-Month Return**: [6M Ret]% * **Prediction Model (30-Day)**: Predicted {CURRENCY} [Pred Price] ([Trend]) ๐Ÿ“Š **3. Technical Snapshot** * **Trading Range**: {CURRENCY} [Low_52] โ€“ {CURRENCY} [High_52] * **RSI**: [RSI] ([Interpret: Overbought > 70, Neutral 30-70, Oversold < 30]) * **Trend**: [Bullish/Bearish/Consolidating] ๐Ÿงพ **4. Valuation & Fundamentals** * **P/E Ratio**: [PE] | **Industry P/E**: [Estimate or N/A] * **PEG Ratio**: [PEG] * **P/B Ratio**: [PB] * **Dividend Yield**: [Div Yield]% * **ROE**: [ROE]% | **ROCE**: [ROCE_Proxy]% * **Verdict**: [Undervalued/Overvalued/Fairly Priced] ๐Ÿง‘โ€๐Ÿ’ผ **5. Shareholding Pattern** * **Promoters**: [Promoter]% * **Institutions (FII/DII)**: [Inst]% * **Public**: [Public]% ๐Ÿ’ก **Intrinsic Value Analysis** * **Book Value**: {CURRENCY} [BV] * **Graham Number**: {CURRENCY} [Graham Num] * **Valuation Check**: CMP is [Above/Below] Graham Number. ๐Ÿง  **Investment Rationale** * **Technical Setup**: [Comment on RSI/Trend] * **Fundamental View**: [Comment on PE/ROE] * **Growth Catalysts**: [Infer from News] * **Risks**: [Infer from News/Volatility] ๐ŸŽฏ **Final Verdict**: [BUY / SELL / HOLD] [1-2 sentence final conclusion] --- **Recent News** [Bullet points of news] """ # --- 4. STREAMLIT UI --- st.title("AI Stock Analyst Agent") st.markdown("Generates deep investment thesis reports for Indian Stocks.") ticker_input = st.text_input("Enter Stock Symbol", placeholder="e.g. TATAMOTORS, HDFCBANK, INFY") if st.button("Generate Report"): if not ticker_input: st.warning("Please enter a stock symbol.") else: with st.spinner(f"Analyzing {ticker_input}... this may take 30-60 seconds."): try: # Logic to handle tickers clean_ticker = ticker_input.upper().strip() if clean_ticker in ["TATAMOTORS", "RELIANCE", "HDFCBANK", "INFY", "ITC", "SBI", "SBIN"]: ticker = f"{clean_ticker}.NS" currency = "Rs." elif any(x in clean_ticker for x in [".NS", ".BO"]): ticker = clean_ticker currency = "Rs." else: ticker = f"{clean_ticker}.NS" if clean_ticker.isalpha() else clean_ticker currency = "Rs." prompt = f""" You are a professional Financial Analyst. 1. Call 'get_deep_financials' for '{ticker}'. 2. Call 'get_price_prediction' for '{ticker}'. 3. Call 'get_market_news' for '{ticker}'. Using ALL this data, fill out the following template. TEMPLATE TO FILL: {STRICT_TEMPLATE} Notes: - Replace {{TICKER}} with {ticker}. - Replace {{CURRENCY}} with {currency}. """ response = agent.run(prompt) st.markdown(str(response)) except Exception as e: st.error(f"An error occurred: {str(e)}")