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.")