Stock3 / app.py
Akshayram1's picture
Update app.py
f3054d4 verified
raw
history blame
11 kB
import streamlit as st
from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
import pandas as pd
import plotly.express as px
from dateutil import parser
import datetime
import requests
from transformers import AutoModelForSequenceClassification, AutoTokenizer, pipeline
import torch
# Page config
st.set_page_config(
page_title="Stock News Sentiment Analyzer",
page_icon="πŸ“ˆ",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for styling
st.markdown("""
<style>
.stAlert {
padding: 10px;
margin-bottom: 20px;
}
.reportview-container {
background: #f0f2f6
}
.main {
padding: 2rem;
}
h1, h2, h3 {
color: #1f77b4;
}
</style>
""", unsafe_allow_html=True)
# Initialize FinBERT-tone model and tokenizer
@st.cache_resource
def load_finbert_model():
try:
model = AutoModelForSequenceClassification.from_pretrained("yiyanghkust/finbert-tone")
tokenizer = AutoTokenizer.from_pretrained("yiyanghkust/finbert-tone")
return pipeline("text-classification", model=model, tokenizer=tokenizer)
except Exception as e:
st.error(f"Error loading model: {str(e)}")
return None
# Load the model
finbert = load_finbert_model()
# Web scraping functions
def verify_link(url, timeout=10, retries=3):
"""Verify if a URL is accessible."""
for _ in range(retries):
try:
response = requests.head(url, timeout=timeout, allow_redirects=True)
return 200 <= response.status_code < 300
except requests.RequestException:
continue
return False
def get_news(ticker):
"""Scrape news from FinViz for a given stock ticker."""
try:
finviz_url = f'https://finviz.com/quote.ashx?t={ticker}'
req = Request(url=finviz_url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})
response = urlopen(req)
html = BeautifulSoup(response, 'html.parser')
news_table = html.find(id='news-table')
if news_table is None:
raise ValueError("No news table found - invalid ticker or website structure changed")
return news_table
except Exception as e:
raise Exception(f"Error fetching news for {ticker}: {str(e)}")
def parse_news(news_table):
"""Parse the news table and return a DataFrame."""
parsed_news = []
try:
for row in news_table.findAll('tr'):
title = row.a.get_text()
link = row.a['href']
date_data = row.td.text.strip().split()
if len(date_data) == 1:
time = date_data[0]
date = datetime.datetime.today().strftime('%Y-%m-%d')
else:
date = date_data[0]
time = date_data[1]
parsed_date = parser.parse(f"{date} {time}")
is_valid = verify_link(link)
parsed_news.append([parsed_date, title, link, is_valid])
return pd.DataFrame(parsed_news, columns=['datetime', 'headline', 'link', 'is_valid'])
except Exception as e:
raise Exception(f"Error parsing news: {str(e)}")
def analyze_sentiment(text):
"""Analyze sentiment of a single piece of text using FinBERT-tone."""
try:
result = finbert(text)[0]
label = result['label']
score = result['score']
sentiment_score = {
'Positive': score,
'Negative': -score,
'Neutral': 0
}.get(label, 0)
return {
'sentiment_score': sentiment_score,
'tone': label,
'confidence': score
}
except Exception as e:
st.error(f"Error analyzing sentiment: {str(e)}")
return {'sentiment_score': 0, 'tone': 'Error', 'confidence': 0}
def process_news_sentiment(parsed_news_df):
"""Process sentiment for all news headlines."""
try:
# Analyze sentiment for each headline
sentiment_data = [analyze_sentiment(headline) for headline in parsed_news_df['headline']]
# Convert to DataFrame
sentiment_df = pd.DataFrame(sentiment_data)
# Join with original news DataFrame
result_df = parsed_news_df.join(sentiment_df)
return result_df.set_index('datetime')
except Exception as e:
raise Exception(f"Error processing sentiments: {str(e)}")
# Visualization functions
def plot_sentiment_timeline(df, ticker):
"""Create an hourly sentiment timeline plot."""
try:
hourly_sentiment = df['sentiment_score'].resample('H').mean()
fig = px.bar(
hourly_sentiment,
title=f"{ticker} Hourly Sentiment Trend",
color=hourly_sentiment.values,
color_continuous_scale=['red', 'yellow', 'green'],
range_color=[-1, 1]
)
fig.update_layout(
xaxis_title="Time",
yaxis_title="Sentiment Score",
coloraxis_colorbar_title="Sentiment",
showlegend=False,
height=400
)
return fig
except Exception as e:
st.error(f"Error creating timeline plot: {str(e)}")
return None
def plot_sentiment_distribution(df, ticker):
"""Create a pie chart of sentiment distribution."""
try:
tone_counts = df['tone'].value_counts()
fig = px.pie(
values=tone_counts.values,
names=tone_counts.index,
title=f"{ticker} Sentiment Distribution",
color=tone_counts.index,
color_discrete_map={
'Positive': 'green',
'Neutral': 'yellow',
'Negative': 'red'
}
)
fig.update_layout(height=400)
return fig
except Exception as e:
st.error(f"Error creating distribution plot: {str(e)}")
return None
def generate_recommendation(df):
"""Generate trading recommendation based on sentiment analysis."""
try:
avg_sentiment = df['sentiment_score'].mean()
tone_counts = df['tone'].value_counts()
total_articles = len(df)
positive_pct = tone_counts.get('Positive', 0) / total_articles * 100
negative_pct = tone_counts.get('Negative', 0) / total_articles * 100
if avg_sentiment >= 0.3 and positive_pct >= 50:
return "🟒 STRONG BUY", f"Strong positive sentiment (Score: {avg_sentiment:.2f}, {positive_pct:.1f}% positive news). The recent news suggests a very favorable outlook."
elif avg_sentiment >= 0.1:
return "🟑 MODERATE BUY", f"Moderately positive sentiment (Score: {avg_sentiment:.2f}, {positive_pct:.1f}% positive news). The recent news leans positive."
elif avg_sentiment <= -0.3 and negative_pct >= 50:
return "πŸ”΄ STRONG SELL", f"Strong negative sentiment (Score: {avg_sentiment:.2f}, {negative_pct:.1f}% negative news). The recent news suggests significant caution."
elif avg_sentiment <= -0.1:
return "🟑 MODERATE SELL", f"Moderately negative sentiment (Score: {avg_sentiment:.2f}, {negative_pct:.1f}% negative news). The recent news leans negative."
else:
return "βšͺ NEUTRAL", f"Neutral sentiment (Score: {avg_sentiment:.2f}). The recent news shows mixed or neutral signals."
except Exception as e:
st.error(f"Error generating recommendation: {str(e)}")
return "⚠️ ERROR", "Unable to generate recommendation due to an error."
# Main application
def main():
st.title("πŸ“ˆ Stock News Sentiment Analyzer")
st.markdown("""
This application analyzes the sentiment of recent news articles for any given stock ticker using the FinBERT-tone model,
which is specifically trained for financial text analysis.
""")
# User input
ticker = st.text_input('Enter Stock Ticker (e.g., AAPL, GOOGL)', '').upper()
if ticker:
try:
with st.spinner('Fetching and analyzing news...'):
# Get and process news
news_table = get_news(ticker)
parsed_news_df = parse_news(news_table)
analyzed_news = process_news_sentiment(parsed_news_df)
# Generate recommendation
signal, explanation = generate_recommendation(analyzed_news)
# Display recommendation
st.header(f"Analysis Results for {ticker}")
st.subheader(f"Signal: {signal}")
st.write(explanation)
# Display charts
col1, col2 = st.columns(2)
with col1:
timeline_fig = plot_sentiment_timeline(analyzed_news, ticker)
if timeline_fig:
st.plotly_chart(timeline_fig, use_container_width=True)
with col2:
distribution_fig = plot_sentiment_distribution(analyzed_news, ticker)
if distribution_fig:
st.plotly_chart(distribution_fig, use_container_width=True)
# Display news table
st.subheader("Recent News Analysis")
# Prepare display DataFrame
display_df = analyzed_news.copy()
display_df['link'] = display_df.apply(
lambda row: f'<a href="{row["link"]}" target="_blank">{"πŸ”—" if row["is_valid"] else "❌"}</a>',
axis=1
)
# Format and display table
display_df = display_df[['headline', 'tone', 'confidence', 'sentiment_score', 'link']]
display_df = display_df.rename(columns={
'headline': 'Headline',
'tone': 'Sentiment',
'confidence': 'Confidence',
'sentiment_score': 'Score',
'link': 'Link'
})
st.write(display_df.to_html(escape=False), unsafe_allow_html=True)
# Disclaimer
st.markdown("""
---
**Disclaimer:** This analysis is based on news sentiment only and should not be considered as financial advice.
Always conduct thorough research and consult with financial professionals before making investment decisions.
""")
except Exception as e:
st.error(f"Error processing {ticker}: {str(e)}")
st.write("Please check the ticker symbol and try again.")
# Run the application
if __name__ == "__main__":
main()