|
import yfinance as yf |
|
import pandas as pd |
|
from langchain_google_genai import ChatGoogleGenerativeAI |
|
from langchain.prompts import PromptTemplate |
|
from langchain.chains import LLMChain |
|
from typing import Dict, Any |
|
import time |
|
|
|
def get_llm_analysis(ticker: str, company_name: str, intelligence_briefing: Dict[str, Any]) -> Dict[str, Any]: |
|
""" |
|
Uses Gemini 1.5 Flash to analyze historical data and news to generate |
|
a forecast and a complete investment thesis. |
|
""" |
|
print(f"Starting LLM-powered analysis for {ticker} with Gemini 1.5 Flash...") |
|
|
|
|
|
original_ticker = ticker |
|
if not ticker.endswith(('.NS', '.BO', '.L', '.TO')): |
|
|
|
ticker = f"{ticker}.NS" |
|
print(f"Formatted ticker from '{original_ticker}' to '{ticker}' for Yahoo Finance") |
|
|
|
historical_data_text = "Could not fetch historical price data due to repeated errors." |
|
|
|
for attempt in range(3): |
|
try: |
|
print(f"Attempt {attempt + 1}/3 to download historical data for {ticker}...") |
|
stock_data = yf.download(ticker, period="100d", interval="1d", progress=False) |
|
|
|
if not stock_data.empty: |
|
|
|
stock_data = stock_data.round(2) |
|
|
|
recent_data = stock_data.tail(20) |
|
historical_data_text = f"Recent 20 days of data for {ticker}:\n{recent_data.to_string()}" |
|
print("-> Successfully downloaded historical data.") |
|
break |
|
else: |
|
raise ValueError("Downloaded data is empty.") |
|
|
|
except Exception as e: |
|
print(f"-> Attempt {attempt + 1} failed: {e}") |
|
|
|
if attempt == 0 and ticker.endswith('.NS'): |
|
ticker = f"{original_ticker}.BO" |
|
print(f"Retrying with BSE ticker: {ticker}") |
|
elif attempt < 2: |
|
print(" Waiting 2 seconds before retrying...") |
|
time.sleep(2) |
|
|
|
|
|
if "Could not fetch historical price data" in historical_data_text: |
|
try: |
|
print("Attempting to get basic stock info as fallback...") |
|
stock = yf.Ticker(ticker) |
|
info = stock.info |
|
if info and info.get('regularMarketPrice'): |
|
current_price = info.get('regularMarketPrice') |
|
previous_close = info.get('previousClose', current_price) |
|
historical_data_text = f"Limited data available for {ticker}:\nCurrent Price: ₹{current_price}\nPrevious Close: ₹{previous_close}" |
|
print("-> Got basic stock info as fallback.") |
|
else: |
|
print("-> No data available from any source.") |
|
except Exception as e: |
|
print(f"-> Fallback also failed: {e}") |
|
|
|
|
|
articles = intelligence_briefing.get('articles', []) |
|
if articles: |
|
news_summary = "\n".join([f"- {article['title']} (Source: {article['source']}, Sentiment: {article['sentiment']})" for article in articles[:10]]) |
|
else: |
|
news_summary = "No recent news or social media mentions found." |
|
|
|
|
|
try: |
|
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash-latest", temperature=0.3) |
|
except Exception as e: |
|
print(f"Error initializing Gemini: {e}") |
|
return {"error": f"Failed to initialize Gemini API: {str(e)}"} |
|
|
|
|
|
prompt = PromptTemplate( |
|
input_variables=["ticker", "company_name", "historical_data", "news_summary"], |
|
template=""" |
|
You are a Senior Financial Analyst for an Indian market-focused investment fund. |
|
Your task is to provide a comprehensive analysis and a 30-day forecast for the stock: {ticker} ({company_name}). |
|
|
|
**Important Instructions:** |
|
- If historical data is limited or unavailable, focus your analysis on news sentiment and general market conditions |
|
- Always provide a forecast range even with limited data, but adjust confidence accordingly |
|
- Base your analysis ONLY on the provided data |
|
|
|
**Data Provided:** |
|
|
|
1. **Historical Price Data:** |
|
``` |
|
{historical_data} |
|
``` |
|
|
|
2. **Recent News & Social Media Headlines:** |
|
{news_summary} |
|
|
|
**Your Analysis Report (in Markdown format):** |
|
|
|
## 30-Day Price Forecast |
|
|
|
**Analysis:** Analyze available data (price trends if available, news sentiment, market conditions). If price data is limited, focus on sentiment analysis and sector trends. |
|
|
|
**Predicted Range:** Provide a realistic price range for 30 days (e.g., ₹1500 - ₹1650). If no current price available, state "Unable to provide specific range due to data limitations." |
|
|
|
**Justification:** Explain your forecast based on available information. |
|
|
|
**Confidence:** High/Moderate/Low based on data quality and availability. |
|
|
|
## Investment Thesis |
|
|
|
**Bull Case:** |
|
- Point 1 based on positive signals from available data |
|
- Point 2 based on news sentiment or market conditions |
|
- Point 3 if sufficient data available |
|
|
|
**Bear Case:** |
|
- Point 1 based on negative signals or risks |
|
- Point 2 based on market concerns |
|
- Point 3 if sufficient data available |
|
|
|
## Actionable Strategy |
|
|
|
**Signal:** Buy/Sell/Hold (choose one) |
|
|
|
**Strategy:** Provide 1-2 sentences with specific actionable advice based on the analysis above. |
|
|
|
**Risk Management:** Brief note on stop-loss or position sizing if relevant. |
|
""" |
|
) |
|
|
|
|
|
chain = LLMChain(llm=llm, prompt=prompt) |
|
try: |
|
response = chain.run( |
|
ticker=ticker, |
|
company_name=company_name, |
|
historical_data=historical_data_text, |
|
news_summary=news_summary |
|
) |
|
print("Successfully generated analysis from Gemini.") |
|
return {"llm_report": response} |
|
except Exception as e: |
|
error_msg = f"Failed to generate analysis from Gemini: {str(e)}" |
|
print(f"Error calling Gemini API: {e}") |
|
return {"error": error_msg} |