|
|
|
|
|
""" |
|
|
Yahoo Finance Sentiment Analysis with Gemma LLM |
|
|
Hugging Face Space Application |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
import pandas as pd |
|
|
from datetime import datetime |
|
|
from utils import YahooFinanceScraper, SentimentAnalyzer, LLMAnalyzer |
|
|
from config import POPULAR_STOCKS |
|
|
import plotly.graph_objects as go |
|
|
|
|
|
|
|
|
print("Initializing application...") |
|
|
scraper = YahooFinanceScraper() |
|
|
sentiment_analyzer = SentimentAnalyzer() |
|
|
llm_analyzer = LLMAnalyzer() |
|
|
print("Application ready!") |
|
|
|
|
|
|
|
|
def analyze_stock_news(symbol: str, num_articles: int = 10): |
|
|
""" |
|
|
Main function to analyze stock news |
|
|
|
|
|
Args: |
|
|
symbol: Stock ticker symbol |
|
|
num_articles: Number of articles to analyze |
|
|
|
|
|
Returns: |
|
|
Tuple of (summary, dataframe, chart, llm_insights) |
|
|
""" |
|
|
try: |
|
|
|
|
|
articles = scraper.get_stock_news(symbol, num_articles) |
|
|
|
|
|
if not articles: |
|
|
return "No news found for this symbol.", None, None, "No articles to analyze." |
|
|
|
|
|
|
|
|
sentiments = [] |
|
|
for article in articles: |
|
|
text = f"{article['title']}. {article.get('summary', '')}" |
|
|
sentiment = sentiment_analyzer.analyze_comprehensive(text) |
|
|
sentiments.append(sentiment) |
|
|
|
|
|
|
|
|
market_summary = llm_analyzer.summarize_news(articles) |
|
|
investment_insight = llm_analyzer.generate_investment_insight(symbol, articles, sentiments) |
|
|
|
|
|
|
|
|
df_data = [] |
|
|
for article, sentiment in zip(articles, sentiments): |
|
|
df_data.append({ |
|
|
'Title': article['title'], |
|
|
'Publisher': article['publisher'], |
|
|
'Sentiment': sentiment['sentiment_label'], |
|
|
'Score': f"{sentiment['combined_score']:.3f}", |
|
|
'Confidence': f"{sentiment['confidence']:.2%}", |
|
|
'VADER': f"{sentiment['vader']['compound']:.3f}", |
|
|
'FinBERT +': f"{sentiment['finbert']['positive']:.3f}", |
|
|
'FinBERT -': f"{sentiment['finbert']['negative']:.3f}", |
|
|
}) |
|
|
|
|
|
df = pd.DataFrame(df_data) |
|
|
|
|
|
|
|
|
sentiment_counts = df['Sentiment'].value_counts() |
|
|
fig = go.Figure(data=[ |
|
|
go.Bar( |
|
|
x=sentiment_counts.index, |
|
|
y=sentiment_counts.values, |
|
|
marker_color=['#00cc66' if x=='Positive' else '#ff6666' if x=='Negative' else '#999999' |
|
|
for x in sentiment_counts.index] |
|
|
) |
|
|
]) |
|
|
fig.update_layout( |
|
|
title=f"Sentiment Distribution for {symbol}", |
|
|
xaxis_title="Sentiment", |
|
|
yaxis_title="Number of Articles", |
|
|
height=400 |
|
|
) |
|
|
|
|
|
|
|
|
avg_score = sum(s['combined_score'] for s in sentiments) / len(sentiments) |
|
|
positive_pct = (sentiment_counts.get('Positive', 0) / len(sentiments)) * 100 |
|
|
negative_pct = (sentiment_counts.get('Negative', 0) / len(sentiments)) * 100 |
|
|
|
|
|
summary = f""" |
|
|
## π Analysis Summary for {symbol} |
|
|
|
|
|
**Total Articles Analyzed:** {len(articles)} |
|
|
|
|
|
**Sentiment Distribution:** |
|
|
- π’ Positive: {sentiment_counts.get('Positive', 0)} ({positive_pct:.1f}%) |
|
|
- π΄ Negative: {sentiment_counts.get('Negative', 0)} ({negative_pct:.1f}%) |
|
|
- βͺ Neutral: {sentiment_counts.get('Neutral', 0)} ({100-positive_pct-negative_pct:.1f}%) |
|
|
|
|
|
**Average Sentiment Score:** {avg_score:.3f} |
|
|
|
|
|
**Overall Sentiment:** {"π’ Positive" if avg_score > 0.05 else "π΄ Negative" if avg_score < -0.05 else "βͺ Neutral"} |
|
|
""" |
|
|
|
|
|
llm_insights = f""" |
|
|
## π€ AI-Generated Insights (Powered by Gemma) |
|
|
|
|
|
### Market Summary: |
|
|
{market_summary} |
|
|
|
|
|
### Investment Perspective: |
|
|
{investment_insight} |
|
|
|
|
|
--- |
|
|
*Note: These insights are generated by AI and should not be considered as financial advice.* |
|
|
""" |
|
|
|
|
|
return summary, df, fig, llm_insights |
|
|
|
|
|
except Exception as e: |
|
|
return f"Error: {str(e)}", None, None, "Error generating insights." |
|
|
|
|
|
|
|
|
def analyze_single_headline(headline: str): |
|
|
""" |
|
|
Analyze a single headline |
|
|
|
|
|
Args: |
|
|
headline: News headline text |
|
|
|
|
|
Returns: |
|
|
Analysis results |
|
|
""" |
|
|
try: |
|
|
sentiment = sentiment_analyzer.analyze_comprehensive(headline) |
|
|
|
|
|
|
|
|
article = {'title': headline, 'summary': ''} |
|
|
explanation = llm_analyzer.analyze_sentiment_context(article, sentiment) |
|
|
|
|
|
result = f""" |
|
|
## Sentiment Analysis Results |
|
|
|
|
|
**Headline:** {headline} |
|
|
|
|
|
**Overall Sentiment:** {sentiment['sentiment_label']} (Score: {sentiment['combined_score']:.3f}) |
|
|
**Confidence:** {sentiment['confidence']:.2%} |
|
|
|
|
|
### Detailed Scores: |
|
|
- **VADER Compound:** {sentiment['vader']['compound']:.3f} |
|
|
- **FinBERT Positive:** {sentiment['finbert']['positive']:.3%} |
|
|
- **FinBERT Negative:** {sentiment['finbert']['negative']:.3%} |
|
|
- **FinBERT Neutral:** {sentiment['finbert']['neutral']:.3%} |
|
|
|
|
|
### AI Explanation: |
|
|
{explanation} |
|
|
""" |
|
|
|
|
|
return result |
|
|
|
|
|
except Exception as e: |
|
|
return f"Error analyzing headline: {str(e)}" |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Yahoo Finance Sentiment Analyzer", theme=gr.themes.Soft()) as demo: |
|
|
gr.Markdown(""" |
|
|
# π Yahoo Finance Sentiment Analyzer |
|
|
### Powered by FinBERT + Gemma LLM |
|
|
|
|
|
Analyze market sentiment from Yahoo Finance news using advanced NLP and AI. |
|
|
""") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.Tab("π Stock Sentiment Analysis"): |
|
|
gr.Markdown("### Analyze sentiment of news for any stock symbol") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
stock_input = gr.Textbox( |
|
|
label="Stock Symbol", |
|
|
placeholder="e.g., AAPL, GOOGL, TSLA", |
|
|
value="AAPL" |
|
|
) |
|
|
with gr.Column(scale=1): |
|
|
num_articles = gr.Slider( |
|
|
minimum=5, |
|
|
maximum=20, |
|
|
value=10, |
|
|
step=1, |
|
|
label="Number of Articles" |
|
|
) |
|
|
|
|
|
gr.Markdown("**Quick Select:**") |
|
|
quick_buttons = [] |
|
|
with gr.Row(): |
|
|
for stock in POPULAR_STOCKS[:5]: |
|
|
btn = gr.Button(stock, size="sm") |
|
|
quick_buttons.append(btn) |
|
|
with gr.Row(): |
|
|
for stock in POPULAR_STOCKS[5:10]: |
|
|
btn = gr.Button(stock, size="sm") |
|
|
quick_buttons.append(btn) |
|
|
|
|
|
analyze_btn = gr.Button("π Analyze News", variant="primary", size="lg") |
|
|
|
|
|
summary_output = gr.Markdown(label="Summary") |
|
|
insights_output = gr.Markdown(label="AI Insights") |
|
|
chart_output = gr.Plot(label="Sentiment Distribution") |
|
|
table_output = gr.Dataframe(label="Detailed Results") |
|
|
|
|
|
|
|
|
analyze_btn.click( |
|
|
fn=analyze_stock_news, |
|
|
inputs=[stock_input, num_articles], |
|
|
outputs=[summary_output, table_output, chart_output, insights_output] |
|
|
) |
|
|
|
|
|
|
|
|
for btn in quick_buttons: |
|
|
btn.click( |
|
|
fn=lambda x: x, |
|
|
inputs=[btn], |
|
|
outputs=[stock_input] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("π° Single Headline Analyzer"): |
|
|
gr.Markdown("### Analyze sentiment of a single news headline") |
|
|
|
|
|
headline_input = gr.Textbox( |
|
|
label="News Headline", |
|
|
placeholder="Enter a financial news headline...", |
|
|
lines=3 |
|
|
) |
|
|
|
|
|
gr.Markdown("**Example Headlines:**") |
|
|
example_headlines = [ |
|
|
"Apple reaches all-time high as iPhone sales surge", |
|
|
"Tesla stock plummets amid production concerns", |
|
|
"Fed maintains interest rates, markets remain stable" |
|
|
] |
|
|
|
|
|
with gr.Row(): |
|
|
for example in example_headlines: |
|
|
gr.Button(example[:50] + "...", size="sm").click( |
|
|
fn=lambda x: x, |
|
|
inputs=[gr.Textbox(value=example, visible=False)], |
|
|
outputs=[headline_input] |
|
|
) |
|
|
|
|
|
analyze_headline_btn = gr.Button("π Analyze Headline", variant="primary") |
|
|
headline_output = gr.Markdown(label="Analysis Results") |
|
|
|
|
|
analyze_headline_btn.click( |
|
|
fn=analyze_single_headline, |
|
|
inputs=[headline_input], |
|
|
outputs=[headline_output] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("βΉοΈ About"): |
|
|
gr.Markdown(""" |
|
|
## About This Application |
|
|
|
|
|
This application analyzes sentiment from Yahoo Finance news using multiple advanced techniques: |
|
|
|
|
|
### π οΈ Technologies Used: |
|
|
|
|
|
1. **VADER Sentiment Analysis** |
|
|
- Rule-based sentiment analysis |
|
|
- Good for general text sentiment |
|
|
|
|
|
2. **FinBERT** |
|
|
- BERT model fine-tuned for financial text |
|
|
- Specialized in financial sentiment analysis |
|
|
- Model: `ProsusAI/finbert` |
|
|
|
|
|
3. **Gemma LLM** |
|
|
- Google's Gemma language model |
|
|
- Generates human-like insights and summaries |
|
|
- Model: `google/gemma-2-2b-it` |
|
|
|
|
|
### π Features: |
|
|
|
|
|
- Real-time news scraping from Yahoo Finance |
|
|
- Multi-model sentiment analysis |
|
|
- AI-generated market insights |
|
|
- Interactive visualizations |
|
|
- Batch and single headline analysis |
|
|
|
|
|
### π Sentiment Scores: |
|
|
|
|
|
- **Positive**: Score > 0.05 |
|
|
- **Negative**: Score < -0.05 |
|
|
- **Neutral**: -0.05 β€ Score β€ 0.05 |
|
|
|
|
|
### β οΈ Disclaimer: |
|
|
|
|
|
This tool is for educational and research purposes only. |
|
|
The sentiment analysis and AI-generated insights should NOT be used as financial advice. |
|
|
Always do your own research and consult with financial professionals before making investment decisions. |
|
|
|
|
|
--- |
|
|
|
|
|
**Created with β€οΈ using Hugging Face Spaces** |
|
|
""") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|
|
|
|