import streamlit as st
from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
import pandas as pd
import plotly.express as px
import json
import nltk
import datetime
from nltk.sentiment.vader import SentimentIntensityAnalyzer
# Ensure nltk dependencies are downloaded
nltk.download('vader_lexicon')
# Page Config
st.set_page_config(page_title="StockSim News Sentiment Analyzer", layout="wide")
# Custom CSS for Glassmorphism & Styling
st.markdown("""
""", unsafe_allow_html=True)
# Header
st.markdown('
đ StockSim News Sentiment Analyzer đ
', unsafe_allow_html=True)
# Finviz URL
finviz_url = 'https://finviz.com/quote.ashx?t='
def get_news(ticker):
try:
url = finviz_url + ticker
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
response = urlopen(req)
html = BeautifulSoup(response, 'html.parser')
news_table = html.find(id='news-table')
return news_table
except Exception as e:
st.error(f"Error fetching news: {e}")
return None
def parse_news(news_table):
parsed_news = []
today_string = datetime.datetime.today().strftime('%Y-%m-%d')
if news_table:
for x in news_table.findAll('tr'):
try:
text = x.a.get_text() if x.a else ""
date_scrape = x.td.text.split() if x.td else []
date = today_string if len(date_scrape) == 1 else date_scrape[0]
time = date_scrape[-1] if date_scrape else "00:00"
parsed_news.append([date, time, text])
except Exception as e:
st.warning(f"Skipping a row due to error: {e}")
parsed_news_df = pd.DataFrame(parsed_news, columns=['date', 'time', 'headline'])
parsed_news_df['datetime'] = pd.to_datetime(parsed_news_df['date'] + ' ' + parsed_news_df['time'], errors='coerce')
parsed_news_df.dropna(inplace=True)
return parsed_news_df
def score_news(parsed_news_df):
vader = SentimentIntensityAnalyzer()
scores = parsed_news_df['headline'].apply(lambda x: vader.polarity_scores(x) if isinstance(x, str) else {'compound': 0}).tolist()
scores_df = pd.DataFrame(scores)
parsed_and_scored_news = parsed_news_df.join(scores_df).set_index('datetime')
parsed_and_scored_news = parsed_and_scored_news.rename(columns={"compound": "sentiment_score"})
return parsed_and_scored_news
def plot_sentiment(parsed_and_scored_news, ticker, freq):
if not parsed_and_scored_news.empty:
mean_scores = parsed_and_scored_news.resample(freq).mean()
fig = px.bar(mean_scores, x=mean_scores.index, y='sentiment_score',
title=f'{ticker} {freq} Sentiment Scores')
return fig
else:
st.warning("No sentiment data available for plotting.")
return None
# UI Elements
ticker = st.text_input('đ Enter Stock Ticker', '').upper()
if ticker:
try:
st.markdown('', unsafe_allow_html=True)
st.subheader(f"đ Sentiment Analysis for {ticker} Stock")
news_table = get_news(ticker)
if news_table:
parsed_news_df = parse_news(news_table)
parsed_and_scored_news = score_news(parsed_news_df)
fig_hourly = plot_sentiment(parsed_and_scored_news, ticker, 'H')
fig_daily = plot_sentiment(parsed_and_scored_news, ticker, 'D')
if fig_hourly:
st.plotly_chart(fig_hourly)
if fig_daily:
st.plotly_chart(fig_daily)
st.markdown("""
The above charts show **hourly and daily sentiment scores** for the stock.
News headlines are obtained from **FinViz** and analyzed using **NLTK Vader**.
""")
st.table(parsed_and_scored_news[['headline', 'sentiment_score']])
else:
st.warning("No news data available for this ticker.")
st.markdown('
', unsafe_allow_html=True)
except Exception as e:
st.error(f"â ī¸ An unexpected error occurred: {str(e)}")
else:
st.info("âšī¸ Enter a valid stock ticker (e.g., AAPL) and hit Enter.")