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 nltk nltk.downloader.download('vader_lexicon') from nltk.sentiment.vader import SentimentIntensityAnalyzer import datetime import requests st.set_page_config(page_title="Stock News Sentiment Analyzer", layout="wide") def verify_link(url): try: response = requests.head(url, timeout=5) return response.status_code == 200 except requests.RequestException: return False def get_news(ticker): finviz_url = 'https://finviz.com/quote.ashx?t=' url = finviz_url + ticker req = Request(url=url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0'}) response = urlopen(req) html = BeautifulSoup(response, 'html.parser') news_table = html.find(id='news-table') return news_table def parse_news(news_table): parsed_news = [] for x in news_table.findAll('tr'): try: text = x.a.get_text() link = x.a['href'] date_scrape = x.td.text.strip().split() if len(date_scrape) == 1: date = datetime.datetime.today().strftime('%Y-%m-%d') time = date_scrape[0] else: date = date_scrape[0] time = date_scrape[1] datetime_str = f"{date} {time}" datetime_parsed = parser.parse(datetime_str) is_valid = verify_link(link) parsed_news.append([datetime_parsed, text, link, is_valid]) except Exception as e: print("Error parsing news:", e) continue columns = ['datetime', 'headline', 'link', 'is_valid'] parsed_news_df = pd.DataFrame(parsed_news, columns=columns) return parsed_news_df def score_news(parsed_news_df): vader = SentimentIntensityAnalyzer() scores = parsed_news_df['headline'].apply(vader.polarity_scores).tolist() scores_df = pd.DataFrame(scores) parsed_and_scored_news = parsed_news_df.join(scores_df, rsuffix='_right') parsed_and_scored_news = parsed_and_scored_news.set_index('datetime') parsed_and_scored_news = parsed_and_scored_news.rename(columns={"compound": "sentiment_score"}) return parsed_and_scored_news def plot_hourly_sentiment(parsed_and_scored_news, ticker): numeric_cols = parsed_and_scored_news.select_dtypes(include=['float64', 'int64']) mean_scores = numeric_cols.resample('h').mean() fig = px.bar(mean_scores, x=mean_scores.index, y='sentiment_score', title=f'{ticker} Hourly Sentiment Scores') return fig def plot_daily_sentiment(parsed_and_scored_news, ticker): numeric_cols = parsed_and_scored_news.select_dtypes(include=['float64', 'int64']) mean_scores = numeric_cols.resample('D').mean() fig = px.bar(mean_scores, x=mean_scores.index, y='sentiment_score', title=f'{ticker} Daily Sentiment Scores') return fig st.header("Stock News Sentiment Analyzer") ticker = st.text_input('Enter Stock Ticker', '').upper() try: st.subheader(f"Hourly and Daily Sentiment of {ticker} Stock") news_table = get_news(ticker) parsed_news_df = parse_news(news_table) parsed_and_scored_news = score_news(parsed_news_df) fig_hourly = plot_hourly_sentiment(parsed_and_scored_news, ticker) fig_daily = plot_daily_sentiment(parsed_and_scored_news, ticker) st.plotly_chart(fig_hourly) st.plotly_chart(fig_daily) description = f""" The above chart averages the sentiment scores of {ticker} stock hourly and daily. The table below gives each of the most recent headlines of the stock and the negative, neutral, positive and an aggregated sentiment score. The news headlines are obtained from the FinViz website. Sentiments are given by the nltk.sentiment.vader Python library. Links have been verified for validity. """ st.write(description) parsed_and_scored_news['link'] = parsed_and_scored_news.apply( lambda row: f'{"Valid" if row["is_valid"] else "Invalid"} Link', axis=1 ) st.write(parsed_and_scored_news.drop(columns=['is_valid']).to_html(escape=False), unsafe_allow_html=True) except Exception as e: print(str(e)) st.write("Enter a correct stock ticker, e.g. 'AAPL' above and hit Enter.") hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)