File size: 4,996 Bytes
092ae8c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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.")