|
|
import gradio as gr |
|
|
from urllib.request import urlopen |
|
|
import certifi |
|
|
import json |
|
|
import pandas as pd |
|
|
import os |
|
|
import warnings |
|
|
import re |
|
|
warnings.filterwarnings("ignore") |
|
|
|
|
|
from langchain_community.document_loaders import CSVLoader |
|
|
from langchain_community.vectorstores import Chroma |
|
|
from langchain.text_splitter import RecursiveCharacterTextSplitter |
|
|
from langchain_community.embeddings import HuggingFaceEmbeddings |
|
|
from langchain.chains import RetrievalQA |
|
|
from langchain.prompts import PromptTemplate |
|
|
from langchain_community.llms import HuggingFacePipeline |
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline |
|
|
|
|
|
|
|
|
os.environ["HUGGINGFACEHUB_API_TOKEN"] = os.environ.get("HUGGINGFACEHUB_API_TOKEN") |
|
|
|
|
|
|
|
|
model_id = "tiiuae/Falcon3-3B-Instruct" |
|
|
tokenizer = AutoTokenizer.from_pretrained(model_id) |
|
|
model = AutoModelForCausalLM.from_pretrained(model_id) |
|
|
|
|
|
pipe = pipeline( |
|
|
"text-generation", |
|
|
model=model, |
|
|
tokenizer=tokenizer, |
|
|
max_new_tokens=512, |
|
|
do_sample=True, |
|
|
temperature=0.1, |
|
|
top_k=50, |
|
|
top_p=0.95, |
|
|
eos_token_id=tokenizer.eos_token_id, |
|
|
) |
|
|
|
|
|
llm = HuggingFacePipeline(pipeline=pipe) |
|
|
|
|
|
|
|
|
template = """ |
|
|
You are a Financial Market Expert. Analyze the provided market data and create a comprehensive financial report. |
|
|
Market Data: {context} |
|
|
Company: {question} |
|
|
Please provide a detailed analysis including: |
|
|
1. Current stock performance metrics |
|
|
2. Price movements and trends |
|
|
3. Market capitalization details |
|
|
4. Trading volume analysis |
|
|
5. Key financial indicators |
|
|
Format the response as a clear, professional financial report with proper sections and bullet points. |
|
|
""" |
|
|
PROMPT = PromptTemplate(input_variables=["context", "question"], template=template) |
|
|
|
|
|
def format_currency(value): |
|
|
"""Format currency values""" |
|
|
try: |
|
|
if isinstance(value, (int, float)): |
|
|
return f"${value:,.2f}" |
|
|
return str(value) |
|
|
except: |
|
|
return str(value) |
|
|
|
|
|
def format_percentage(value): |
|
|
"""Format percentage values""" |
|
|
try: |
|
|
if isinstance(value, (int, float)): |
|
|
return f"{value:.2f}%" |
|
|
return str(value) |
|
|
except: |
|
|
return str(value) |
|
|
|
|
|
def create_html_report(data, llm_analysis): |
|
|
"""Create a formatted HTML report""" |
|
|
|
|
|
if not data or len(data) == 0: |
|
|
return "<div class='error'>No data available for this ticker.</div>" |
|
|
|
|
|
|
|
|
stock_data = data[0] if isinstance(data, list) else data |
|
|
|
|
|
|
|
|
symbol = stock_data.get('symbol', 'N/A') |
|
|
name = stock_data.get('name', 'N/A') |
|
|
price = stock_data.get('price', 0) |
|
|
change = stock_data.get('change', 0) |
|
|
change_percent = stock_data.get('changesPercentage', 0) |
|
|
market_cap = stock_data.get('marketCap', 0) |
|
|
volume = stock_data.get('volume', 0) |
|
|
avg_volume = stock_data.get('avgVolume', 0) |
|
|
pe_ratio = stock_data.get('pe', 'N/A') |
|
|
eps = stock_data.get('eps', 'N/A') |
|
|
|
|
|
|
|
|
trend_color = "#22c55e" if change >= 0 else "#ef4444" |
|
|
trend_icon = "π" if change >= 0 else "π" |
|
|
|
|
|
html = f""" |
|
|
<div style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 800px; margin: 0 auto;"> |
|
|
<!-- Header Section --> |
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> |
|
|
<h1 style="margin: 0 0 10px 0; font-size: 28px; font-weight: 600;">{symbol} - Financial Report</h1> |
|
|
<h2 style="margin: 0; font-size: 18px; font-weight: 400; opacity: 0.9;">{name}</h2> |
|
|
<div style="margin-top: 15px; font-size: 14px; opacity: 0.8;"> |
|
|
π
Generated on {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')} |
|
|
</div> |
|
|
</div> |
|
|
<!-- Key Metrics Grid --> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 25px;"> |
|
|
|
|
|
<!-- Current Price Card --> |
|
|
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-left: 4px solid #3b82f6;"> |
|
|
<div style="color: #6b7280; font-size: 14px; margin-bottom: 5px;">Current Price</div> |
|
|
<div style="font-size: 24px; font-weight: 700; color: #1f2937;">{format_currency(price)}</div> |
|
|
</div> |
|
|
<!-- Price Change Card --> |
|
|
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-left: 4px solid {trend_color};"> |
|
|
<div style="color: #6b7280; font-size: 14px; margin-bottom: 5px;">Price Change {trend_icon}</div> |
|
|
<div style="font-size: 18px; font-weight: 600; color: {trend_color};"> |
|
|
{format_currency(change)} ({format_percentage(change_percent)}) |
|
|
</div> |
|
|
</div> |
|
|
<!-- Market Cap Card --> |
|
|
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-left: 4px solid #10b981;"> |
|
|
<div style="color: #6b7280; font-size: 14px; margin-bottom: 5px;">Market Cap</div> |
|
|
<div style="font-size: 18px; font-weight: 600; color: #1f2937;"> |
|
|
{format_currency(market_cap) if market_cap else 'N/A'} |
|
|
</div> |
|
|
</div> |
|
|
<!-- Volume Card --> |
|
|
<div style="background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); border-left: 4px solid #f59e0b;"> |
|
|
<div style="color: #6b7280; font-size: 14px; margin-bottom: 5px;">Volume</div> |
|
|
<div style="font-size: 18px; font-weight: 600; color: #1f2937;"> |
|
|
{f"{volume:,}" if volume else 'N/A'} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<!-- Additional Metrics Table --> |
|
|
<div style="background: white; border-radius: 10px; padding: 25px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 25px;"> |
|
|
<h3 style="margin: 0 0 20px 0; color: #1f2937; font-size: 20px; font-weight: 600; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;"> |
|
|
π Detailed Metrics |
|
|
</h3> |
|
|
<table style="width: 100%; border-collapse: collapse;"> |
|
|
<tr style="border-bottom: 1px solid #e5e7eb;"> |
|
|
<td style="padding: 12px 0; font-weight: 600; color: #374151; width: 40%;">P/E Ratio</td> |
|
|
<td style="padding: 12px 0; color: #6b7280;">{pe_ratio}</td> |
|
|
</tr> |
|
|
<tr style="border-bottom: 1px solid #e5e7eb;"> |
|
|
<td style="padding: 12px 0; font-weight: 600; color: #374151;">Earnings Per Share (EPS)</td> |
|
|
<td style="padding: 12px 0; color: #6b7280;">{format_currency(eps) if eps != 'N/A' else 'N/A'}</td> |
|
|
</tr> |
|
|
<tr style="border-bottom: 1px solid #e5e7eb;"> |
|
|
<td style="padding: 12px 0; font-weight: 600; color: #374151;">Average Volume</td> |
|
|
<td style="padding: 12px 0; color: #6b7280;">{f"{avg_volume:,}" if avg_volume else 'N/A'}</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td style="padding: 12px 0; font-weight: 600; color: #374151;">Exchange</td> |
|
|
<td style="padding: 12px 0; color: #6b7280;">{stock_data.get('exchange', 'N/A')}</td> |
|
|
</tr> |
|
|
</table> |
|
|
</div> |
|
|
<!-- AI Analysis Section --> |
|
|
<div style="background: white; border-radius: 10px; padding: 25px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"> |
|
|
<h3 style="margin: 0 0 20px 0; color: #1f2937; font-size: 20px; font-weight: 600; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;"> |
|
|
π€ AI Analysis |
|
|
</h3> |
|
|
<div style="background: #f8fafc; border-radius: 8px; padding: 20px; border-left: 4px solid #6366f1;"> |
|
|
<pre style="white-space: pre-wrap; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; line-height: 1.6; color: #374151;">{llm_analysis}</pre> |
|
|
</div> |
|
|
</div> |
|
|
<!-- Footer --> |
|
|
<div style="text-align: center; margin-top: 25px; padding: 20px; color: #6b7280; font-size: 14px; border-top: 1px solid #e5e7eb;"> |
|
|
<p style="margin: 0;">β οΈ This report is for informational purposes only and should not be considered as investment advice.</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return html |
|
|
|
|
|
def generate_report(ticker: str, exchange: str): |
|
|
"""Generate enhanced financial report""" |
|
|
api_key = "C1HRSweTniWdBuLmTTse9w8KpkoiouM5" |
|
|
|
|
|
if not ticker.strip(): |
|
|
return "<div style='color: red; padding: 20px; text-align: center;'>β Please enter a valid ticker symbol.</div>" |
|
|
|
|
|
try: |
|
|
|
|
|
loading_html = """ |
|
|
<div style="text-align: center; padding: 40px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;"> |
|
|
<div style="display: inline-block; padding: 20px; background: #f0f9ff; border-radius: 10px; border: 1px solid #0ea5e9;"> |
|
|
<div style="font-size: 18px; color: #0369a1; margin-bottom: 10px;">π Generating Report...</div> |
|
|
<div style="color: #64748b;">Fetching data and running AI analysis</div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
ticker = ticker.upper().strip() |
|
|
if exchange == "NSE": |
|
|
url = f"https://financialmodelingprep.com/api/v3/search?query={ticker}&exchange=NSE&apikey={api_key}" |
|
|
else: |
|
|
url = f"https://financialmodelingprep.com/api/v3/quote/{ticker}?apikey={api_key}" |
|
|
|
|
|
response = urlopen(url, cafile=certifi.where()) |
|
|
data = json.loads(response.read().decode("utf-8")) |
|
|
|
|
|
if not data: |
|
|
return "<div style='color: red; padding: 20px; text-align: center;'>β No data found for ticker: " + ticker + "</div>" |
|
|
|
|
|
|
|
|
df = pd.DataFrame(data) |
|
|
if 'timestamp' in df.columns: |
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce') |
|
|
if 'earningsAnnouncement' in df.columns: |
|
|
df['earningsAnnouncement'] = pd.to_datetime(df['earningsAnnouncement'], errors='coerce') |
|
|
df.to_csv("eco_ind.csv", index=False) |
|
|
|
|
|
|
|
|
loader = CSVLoader("eco_ind.csv") |
|
|
documents = loader.load() |
|
|
splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20) |
|
|
texts = splitter.split_documents(documents) |
|
|
embeddings = HuggingFaceEmbeddings() |
|
|
|
|
|
chroma = Chroma.from_documents( |
|
|
documents=texts, |
|
|
collection_name="economic_data", |
|
|
embedding=embeddings, |
|
|
persist_directory="docs/chroma_rag" |
|
|
) |
|
|
|
|
|
retriever = chroma.as_retriever(search_kwargs={"k": 3}) |
|
|
qa = RetrievalQA.from_chain_type( |
|
|
llm=llm, |
|
|
chain_type="stuff", |
|
|
chain_type_kwargs={"prompt": PROMPT}, |
|
|
retriever=retriever, |
|
|
return_source_documents=False |
|
|
) |
|
|
|
|
|
|
|
|
query = f"Provide a comprehensive financial analysis for {ticker}" |
|
|
result = qa({"query": query}) |
|
|
llm_analysis = result["result"] |
|
|
|
|
|
|
|
|
return create_html_report(data, llm_analysis) |
|
|
|
|
|
except Exception as e: |
|
|
error_html = f""" |
|
|
<div style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 600px; margin: 20px auto; padding: 25px; background: #fef2f2; border-radius: 10px; border: 1px solid #fecaca;"> |
|
|
<h3 style="color: #dc2626; margin: 0 0 15px 0; display: flex; align-items: center;"> |
|
|
β Error Occurred |
|
|
</h3> |
|
|
<div style="background: white; padding: 15px; border-radius: 6px; border: 1px solid #f3f4f6;"> |
|
|
<strong>Details:</strong> {str(e)} |
|
|
</div> |
|
|
<div style="margin-top: 15px; color: #6b7280; font-size: 14px;"> |
|
|
Please check your ticker symbol and try again. Make sure you have a stable internet connection. |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
return error_html |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
#component-0 { |
|
|
max-width: 1200px !important; |
|
|
margin: 0 auto !important; |
|
|
} |
|
|
.gradio-container { |
|
|
background-color: black !important; |
|
|
min-height: 100vh !important; |
|
|
} |
|
|
#title { |
|
|
text-align: center !important; |
|
|
color: white !important; |
|
|
font-size: 2.5rem !important; |
|
|
font-weight: 700 !important; |
|
|
margin-bottom: 1rem !important; |
|
|
text-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; |
|
|
} |
|
|
#description { |
|
|
text-align: center !important; |
|
|
color: white !important; |
|
|
font-size: 1.1rem !important; |
|
|
margin-bottom: 2rem !important; |
|
|
} |
|
|
.input-container { |
|
|
background: white !important; |
|
|
border-radius: 5px !important; |
|
|
padding: 3px !important; |
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1) !important; |
|
|
margin-bottom: 20px !important; |
|
|
} |
|
|
.output-container { |
|
|
background: transparent !important; |
|
|
border-radius: 15px !important; |
|
|
padding: 10px; |
|
|
border: 2px white solid ; |
|
|
overflow: hidden !important; |
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2) !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks(css=custom_css, title="π Financial Report Generator") as iface: |
|
|
gr.HTML('<div id="title">π AI-Powered Financial Report Generator</div>') |
|
|
gr.HTML('<div id="description">Enter a stock ticker and exchange to get a comprehensive AI-generated financial analysis</div>') |
|
|
|
|
|
|
|
|
with gr.Sidebar(): |
|
|
gr.Markdown("## π₯ Input Parameters") |
|
|
|
|
|
ticker_input = gr.Textbox( |
|
|
label="π― Stock Ticker Symbol", |
|
|
placeholder="e.g., MSFT, AAPL, GOOGL", |
|
|
info="Enter the stock symbol you want to analyze" |
|
|
) |
|
|
|
|
|
exchange_input = gr.Radio( |
|
|
choices=["US", "NSE"], |
|
|
label="π’ Exchange", |
|
|
value="US", |
|
|
info="Select the stock exchange" |
|
|
) |
|
|
|
|
|
generate_btn = gr.Button( |
|
|
"π Generate Report", |
|
|
variant="primary" |
|
|
) |
|
|
|
|
|
clear_btn = gr.Button( |
|
|
"π Clear", |
|
|
variant="secondary" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(elem_classes=["output-container"]): |
|
|
output = gr.HTML(label="π Financial Report") |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=generate_report, |
|
|
inputs=[ticker_input, exchange_input], |
|
|
outputs=output |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
lambda: ["", "US", ""], |
|
|
outputs=[ticker_input, exchange_input, output] |
|
|
) |
|
|
|
|
|
ticker_input.submit( |
|
|
fn=generate_report, |
|
|
inputs=[ticker_input, exchange_input], |
|
|
outputs=output |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
iface.launch( |
|
|
share=True, |
|
|
show_error=True |
|
|
) |