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