-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
210 lines (179 loc) · 8.88 KB
/
app.py
File metadata and controls
210 lines (179 loc) · 8.88 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import re
import uuid
import requests
import pandas as pd
import streamlit as st
import plotly.express as px
from bs4 import BeautifulSoup
from textblob import TextBlob
import yfinance as yf
from datetime import datetime, timedelta
from fake_useragent import UserAgent
# ---- FUNCTION TO GENERATE SAFE KEYS ----
def safe_ticker_key(ticker):
"""Removes special characters from tickers and generates a unique key."""
clean_ticker = re.sub(r'\W+', '_', ticker) # Replace non-alphanumeric characters with underscores
unique_suffix = str(uuid.uuid4())[:8] # Generate a short unique suffix
return f"{clean_ticker}_{unique_suffix}" # Append suffix to ensure uniqueness
# Function to convert "43 minutes ago" to a timestamp (in local time)
def convert_relative_time(time_str):
"""Converts relative time (e.g., '43 minutes ago') to an approximate timestamp in local time."""
now = datetime.now() # Get current time in local timezone
if time_str == 'yesterday':
return now - timedelta(days=1)
elif "minute" in time_str:
minutes = int(time_str.split()[0])
return now - timedelta(minutes=minutes)
elif "hour" in time_str:
hours = int(time_str.split()[0])
return now - timedelta(hours=hours)
elif "day" in time_str:
days = int(time_str.split()[0])
return now - timedelta(days=days)
else:
return None # If the format is unrecognized, return None
# Function to scrape financial news
def get_financial_news():
ua = UserAgent()
headers = {'User-Agent': ua.random}
# Fetch the website
url = "https://finance.yahoo.com/topic/stock-market-news/"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
articles = soup.find_all("div", class_="content") # Adjust selector
news_list = []
for article in articles:
title = article.text
link = article.a["href"]
para = article.p.text
press = article.find("div", class_="publishing").text.split(' • ')[0] if article.find("div", class_="publishing") else None
time_str = article.find("div", class_="publishing").text.split(' • ')[1] if article.find("div", class_="publishing") else None
timestamp = convert_relative_time(time_str) if time_str else None
a_ticker = article.find("a", class_="ticker")
ticker = a_ticker.find("span", class_="symbol").get_text(strip=True) if a_ticker else None
news_list.append({"title": title, "link": link, "paragraph": para, "Press": press,
"Publishing Time": timestamp, "Ticker": ticker})
return pd.DataFrame(news_list)
# Function to analyze sentiment
def analyze_sentiment(text):
analysis = TextBlob(text)
return "Positive" if analysis.sentiment.polarity > 0 else "Negative" if analysis.sentiment.polarity < 0 else "Neutral"
# Function to analyze subjectivity
def analyze_subjectivity(text):
return TextBlob(text).sentiment.subjectivity
# Function to fetch intraday stock price flows for multiple tickers
def get_intraday_stock_prices(tickers):
all_data = []
for ticker in tickers:
try:
stock = yf.Ticker(ticker)
hist = stock.history(period="1d", interval="15m") # Fetch intraday data (30-minute interval)
hist["Percentage Change"] = hist["Close"].pct_change() * 100 # Convert to percentage change
hist = hist.dropna() # Remove NaN values
hist.reset_index(inplace=True) # Reset index for plotting
# Ensure stock timestamps remain in local time
hist["Datetime"] = hist["Datetime"].dt.tz_localize(None)
hist["Ticker"] = ticker # Add ticker column
all_data.append(hist)
except:
pass # If an error occurs, skip the ticker
if all_data:
return pd.concat(all_data, ignore_index=True)
else:
return None
# Function to get real-time stock price trends (percentage change)
def get_stock_price_trend(tickers):
stock_data = {}
for ticker in tickers:
try:
stock = yf.Ticker(ticker)
hist = stock.history(period="2d") # Get last 2 days of data
if len(hist) >= 2:
prev_close = hist["Close"].iloc[-2] # Previous day's close
latest_close = hist["Close"].iloc[-1] # Latest close
percent_change = ((latest_close - prev_close) / prev_close) * 100
stock_data[ticker] = round(percent_change, 2) # Round to 2 decimals
else:
stock_data[ticker] = None # Handle missing data
except:
stock_data[ticker] = None # Handle missing data
return stock_data
# Streamlit UI
st.title("📊 Financial News Sentiment & Stock Price Trend Analysis")
st.write("Latest financial news with real-time stock price change comparison")
# Get and display news
news_df = get_financial_news()
st.dataframe(news_df)
news_df["Sentiment"] = news_df["title"].apply(analyze_sentiment)
news_df["Subjectivity"] = news_df["title"].apply(analyze_subjectivity)
# Fetch real-time stock price trends
unique_tickers = news_df["Ticker"].dropna().unique()
stock_trends = get_stock_price_trend(unique_tickers)
news_df["Stock Price Change (%)"] = news_df["Ticker"].map(stock_trends)
# Replace NaN or None with 0 for valid plotting
news_df["Stock Price Change (%)"] = news_df["Stock Price Change (%)"].fillna(0)
# ---- INTRADAY STOCK PRICE FLOW CHART (MULTI-TICKER COMPARISON) ----
st.subheader("📈 Intraday Stock Price & Sentiment Timeline")
# Multi-select filter for multiple stock tickers
selected_stocks = st.multiselect("Select Stocks for Timeline", unique_tickers, default=unique_tickers[:2])
if selected_stocks:
intraday_df = get_intraday_stock_prices(selected_stocks)
news_timeline_df = news_df[news_df["Ticker"].isin(selected_stocks)][["Publishing Time", "Sentiment", "Subjectivity", "Ticker", "title"]]
news_timeline_df = news_timeline_df.dropna() # Remove missing timestamps
# Ensure news timestamps remain in local time
news_timeline_df["Publishing Time"] = pd.to_datetime(news_timeline_df["Publishing Time"])
# Determine min and max timestamps for shared x-axis range
all_timestamps = pd.concat([intraday_df["Datetime"], news_timeline_df["Publishing Time"]])
x_min, x_max = all_timestamps.min() - timedelta(minutes=30), all_timestamps.max() + timedelta(minutes=30) # 30-min buffer
if intraday_df is not None and not news_timeline_df.empty:
# Stock Price Timeline
fig_stock = px.line(
intraday_df,
x="Datetime",
y="Percentage Change",
color="Ticker",
title="Intraday Stock Price Flow",
labels={"Percentage Change": "Stock Price % Change"},
)
fig_stock.update_layout(xaxis_range=[x_min, x_max]) # Set common x-axis span
st.plotly_chart(fig_stock, use_container_width=True, key=f"chart_{safe_ticker_key('_'.join(selected_stocks))}")
# Sentiment Timeline
fig_sentiment = px.scatter(
news_timeline_df,
x="Publishing Time",
y="Sentiment",
color="Ticker",
hover_data=["title"],
title="News Sentiment Timeline",
category_orders={"Sentiment": ["Negative", "Neutral", "Positive"]} # Order sentiment categories
)
fig_sentiment.update_layout(xaxis_range=[x_min, x_max]) # Set common x-axis span
st.plotly_chart(fig_sentiment, use_container_width=True, key=f"sentiment_chart_{safe_ticker_key('_'.join(selected_stocks))}")
# Subjectivity Timeline
fig_subjectivity = px.scatter(
news_timeline_df,
x="Publishing Time",
y="Subjectivity",
color="Ticker",
hover_data=["title"],
title="News Subjectivity Timeline",
labels={"Subjectivity": "Subjectivity Score (0 = Objective, 1 = Subjective)"}
)
fig_subjectivity.update_layout(xaxis_range=[x_min, x_max]) # Set common x-axis span
st.plotly_chart(fig_subjectivity, use_container_width=True, key=f"subjectivity_chart_{safe_ticker_key('_'.join(selected_stocks))}")
else:
st.write("No intraday or news sentiment data available for the selected stocks.")
if "Stock Price Change (%)" in news_df.columns:
filtered_df = news_df.dropna(subset=["Stock Price Change (%)"]) # Remove NaNs before plotting
filtered_df["Abs Stock Price Change (%)"] = filtered_df["Stock Price Change (%)"].abs() # Ensure all sizes are positive
st.subheader("📉 In-time Stock Price Change vs. Sentiment")
fig = px.scatter(
filtered_df,
x="Stock Price Change (%)",
y="Sentiment",
color="Sentiment",
size="Abs Stock Price Change (%)", # Use absolute value to ensure positive sizes
hover_data=["Ticker"],
title="Stock Price Movement vs Sentiments"
)
st.plotly_chart(fig, use_container_width=True)