Spaces:
Sleeping
Sleeping
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(""" | |
<style> | |
body { | |
font-family: 'Arial', sans-serif; | |
} | |
.glass { | |
background: rgba(255, 255, 255, 0.1); | |
border-radius: 16px; | |
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
padding: 20px; | |
} | |
.button { | |
background: linear-gradient(135deg, #ff7eb3, #ff758c); | |
color: black; | |
padding: 10px; | |
border-radius: 10px; | |
text-align: center; | |
font-size: 16px; | |
cursor: pointer; | |
transition: 0.3s; | |
} | |
.button:hover { | |
background: linear-gradient(135deg, #ff758c, #ff7eb3); | |
transform: scale(1.05); | |
} | |
h1 { | |
color: black; | |
text-align: center; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Header | |
st.markdown('<h1>π StockSim News Sentiment Analyzer π</h1>', 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('<div class="glass">', 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('</div>', 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.") |