Spaces:
Sleeping
Sleeping
File size: 7,194 Bytes
fe3d5c9 afc2587 fe3d5c9 ac01bd6 fe3d5c9 e6a496c 74265b0 e6a496c fe3d5c9 ac01bd6 5f04129 ac01bd6 5f04129 37276fb ac01bd6 37276fb fe3d5c9 e6a496c fe3d5c9 e6a496c fe3d5c9 e6a496c fe3d5c9 afc2587 fe3d5c9 afc2587 7ddc5f5 afc2587 fe3d5c9 afc2587 fe3d5c9 afc2587 e6a496c fe3d5c9 afc2587 e6a496c afc2587 fe3d5c9 e6a496c fe3d5c9 eb055f8 580a5c5 e6a496c fe3d5c9 eb055f8 580a5c5 e6a496c fe3d5c9 37276fb b7c7c89 fe3d5c9 37276fb 7ddc5f5 37276fb 7ddc5f5 e6a496c 37276fb 7ddc5f5 e6a496c 7ddc5f5 e6a496c 7ed461b e6a496c 7ddc5f5 e6a496c 7ddc5f5 fe3d5c9 7ddc5f5 fe3d5c9 5f04129 | 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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | 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.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")
# Function to set up headers
def set_headers():
# Adding meta tags for CORS and Content-Security-Policy
st.markdown("""
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: gap: https://ssl.gstatic.com 'https://*'; connect-src * ws://localhost:* wss://localhost:*">
<meta http-equiv="Access-Control-Allow-Origin" content="*">
""", unsafe_allow_html=True)
set_headers()
def verify_link(url, timeout=10, retries=3):
for _ in range(retries): # Fixed the range usage
try:
response = requests.head(url, timeout=timeout, allow_redirects=True)
if 200 <= response.status_code < 300:
return True
except requests.RequestException:
continue
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',
color='sentiment_score',
color_continuous_scale=['red', 'yellow', 'green'],
range_color=[-1, 1])
fig.update_layout(coloraxis_colorbar=dict(
title="Sentiment",
tickvals=[-1, 0, 1],
ticktext=["Negative", "Neutral", "Positive"],
))
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',
color='sentiment_score',
color_continuous_scale=['red', 'yellow', 'green'],
range_color=[-1, 1])
fig.update_layout(coloraxis_colorbar=dict(
title="Sentiment",
tickvals=[-1, 0, 1],
ticktext=["Negative", "Neutral", "Positive"],
))
return fig
def get_recommendation(sentiment_scores):
avg_sentiment = sentiment_scores['sentiment_score'].mean()
if avg_sentiment >= 0.05:
return f"Positive sentiment (Score: {avg_sentiment:.2f}). The recent news suggests a favorable outlook for this stock. Consider buying or holding if you already own it."
elif avg_sentiment <= -0.05:
return f"Negative sentiment (Score: {avg_sentiment:.2f}). The recent news suggests caution. Consider selling or avoiding this stock for now."
else:
return f"Neutral sentiment (Score: {avg_sentiment:.2f}). The recent news doesn't show a strong bias. Consider holding if you own the stock, or watch for more definitive trends before making a decision."
st.header("Stock News Sentiment Analyzer")
ticker = st.text_input('Enter Stock Ticker', '').upper()
try:
st.subheader(f"Sentiment Analysis and Recommendation for {ticker} Stock")
news_table = get_news(ticker)
parsed_news_df = parse_news(news_table)
parsed_and_scored_news = score_news(parsed_news_df)
# Generate and display recommendation
recommendation = get_recommendation(parsed_and_scored_news)
st.write(recommendation)
# Display a disclaimer
st.warning("Disclaimer: This recommendation is based solely on recent news sentiment and should not be considered as financial advice. Always do your own research and consult with a qualified financial advisor before making investment decisions.")
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 charts average 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'<a href="{row["link"]}" target="_blank">{"Valid✅" if row["is_valid"] else "Invalid❌"} Link</a>',
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 = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|