Spaces:
Sleeping
Sleeping
File size: 26,116 Bytes
e43c64f 1068c53 78a1597 1068c53 78a1597 1068c53 e43c64f 78a1597 e43c64f 64fbb46 78a1597 e43c64f 78a1597 e43c64f 11b1e6a e43c64f 64fbb46 e43c64f b5c64e6 1068c53 e43c64f 78a1597 e43c64f 64fbb46 b5c64e6 64fbb46 78a1597 e43c64f 1068c53 e43c64f 78a1597 e43c64f 64fbb46 1068c53 11b1e6a 78a1597 1068c53 78a1597 1068c53 78a1597 11b1e6a c9a4e36 b5c64e6 11b1e6a 78a1597 11b1e6a 78a1597 b5c64e6 11b1e6a 78a1597 11b1e6a 5a2287f 11b1e6a 78a1597 9b02f0c 11b1e6a 6a980fe b5c64e6 6a980fe 11b1e6a 78a1597 11b1e6a 6a980fe 11b1e6a 9b02f0c 78a1597 11b1e6a 6a980fe 229a91e 11b1e6a 6a980fe 1068c53 229a91e c9a4e36 229a91e 1068c53 11b1e6a 64fbb46 11b1e6a e43c64f 11b1e6a 78a1597 11b1e6a 229a91e 6a980fe 229a91e 6a980fe 11b1e6a 78a1597 11b1e6a 78a1597 b5c64e6 | 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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 | import os
import yfinance as yf
import pandas as pd
import numpy as np
import requests
import plotly.graph_objects as go
import streamlit as st
from datetime import timedelta
from scipy.stats import norm
# Load API key from environment variables
FMP_API_KEY = os.getenv("FMP_API_KEY")
# Define functions
def fetch_earnings_data(ticker, limit=99):
"""
Fetch earnings data from the Financial Modeling Prep API.
"""
try:
url = f"https://financialmodelingprep.com/api/v3/earnings-surprises/{ticker}?apikey={FMP_API_KEY}"
response = requests.get(url)
response.raise_for_status()
data = response.json()
earnings_data = pd.DataFrame(data)
earnings_data['date'] = pd.to_datetime(earnings_data['date'])
earnings_data.set_index('date', inplace=True)
earnings_data.rename(
columns={
'actualEarningResult': 'Actual EPS',
'estimatedEarning': 'EPS Estimate'
},
inplace=True
)
earnings_data['Surprise(%)'] = (
(earnings_data['Actual EPS'] - earnings_data['EPS Estimate'])
/ earnings_data['EPS Estimate']
) * 100
earnings_data = earnings_data.dropna(subset=['EPS Estimate'])
return earnings_data.head(limit)
except Exception as e:
st.warning(f"There was an issue fetching earnings data: {e}")
return pd.DataFrame()
def fetch_stock_data(ticker, start_date, end_date, buffer_days):
"""
Fetch historical stock data using yfinance.
"""
try:
start_date = start_date - pd.Timedelta(days=buffer_days)
end_date = end_date + pd.Timedelta(days=buffer_days)
stock_data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False)
if isinstance(stock_data.columns, pd.MultiIndex): # Flatten multi-index
stock_data.columns = stock_data.columns.get_level_values(0)
if stock_data.empty:
raise ValueError(f"No data found for {ticker} from {start_date} to {end_date}")
if len(stock_data) < buffer_days:
raise ValueError(f"Insufficient data points for {ticker}")
stock_data.index = stock_data.index.tz_localize(None)
return stock_data
except Exception as e:
st.warning(f"There was an issue fetching stock data: {e}")
return pd.DataFrame()
def calculate_metrics(stock_data):
"""
Add metrics like daily returns and rolling volatility to the stock data.
"""
if not stock_data.empty:
stock_data['Returns'] = stock_data['Close'].pct_change()
stock_data['20D Volatility'] = stock_data['Returns'].rolling(window=20).std()
return stock_data
def ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window):
"""
Ensure subset has the full range of dates around the earnings date.
"""
expected_dates = [earning_date + pd.Timedelta(days=i) for i in range(-pre_announcement_window, post_announcement_window + 1)]
for expected_date in expected_dates:
if expected_date not in subset.index:
subset.loc[expected_date] = np.nan
return subset.sort_index()
def plot_stock_price_with_earnings(stock_data, earnings_dates, ticker):
"""
Plot stock prices with earnings surprises.
"""
fig = go.Figure()
fig.add_trace(go.Scatter(x=stock_data.index, y=stock_data['Close'], mode='lines', name='Stock Price', line=dict(color='blue')))
scaling_factor = 1.2
max_marker_size = 30
added_positive_legend = False
added_negative_legend = False
for index, row in earnings_dates.iterrows():
date = index
if date not in stock_data.index:
date = stock_data.index[stock_data.index.get_indexer([date], method='nearest')[0]]
surprise = row['Surprise(%)']
marker_size = abs(surprise) * scaling_factor if not np.isnan(surprise) else 10
marker_size = min(marker_size, max_marker_size)
color = 'green' if surprise > 0 else 'red'
marker = '^' if surprise > 0 else 'v'
if surprise > 0:
name = 'Positive EPS Surprise' if not added_positive_legend else None
added_positive_legend = True
else:
name = 'Negative EPS Surprise' if not added_negative_legend else None
added_negative_legend = True
fig.add_trace(go.Scatter(x=[date], y=[stock_data.loc[date, 'Close']], mode='markers',
marker=dict(symbol='triangle-up' if marker == '^' else 'triangle-down', size=10 if name is not None else marker_size, color=color),
name=name, showlegend=name is not None))
fig.update_layout(title=f'{ticker} Stock Price with Earnings Surprise',
xaxis_title='Date', yaxis_title='Stock Price',
legend_title='Legend', template='plotly_white',
height=600, width=1200)
return fig
def plot_normalized_price_movements(stock_data, earnings_dates, ticker, pre_announcement_window, post_announcement_window, upper_threshold, lower_threshold):
"""
Plot normalized price movements around earnings dates.
"""
all_normalized_prices = []
for earning_date in earnings_dates.index:
start = earning_date - pd.Timedelta(days=pre_announcement_window)
end = earning_date + pd.Timedelta(days=post_announcement_window)
subset = stock_data.loc[start:end]['Close'].copy()
subset = ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window)
subset.ffill(inplace=True)
subset.bfill(inplace=True)
subset = subset / subset[0]
all_normalized_prices.append(subset.tolist())
above_count = 0
below_count = 0
between_count = 0
for prices in all_normalized_prices:
if max(prices) > upper_threshold:
above_count += 1
elif min(prices) < lower_threshold:
below_count += 1
else:
between_count += 1
total_periods = len(all_normalized_prices)
prob_above = above_count / total_periods
prob_below = below_count / total_periods
prob_between = between_count / total_periods
latest_close_price = stock_data['Close'].iloc[-1]
actual_upper_threshold = latest_close_price * upper_threshold
actual_lower_threshold = latest_close_price * lower_threshold
window_days = list(range(-pre_announcement_window, post_announcement_window + 1))
fig = go.Figure()
for prices in all_normalized_prices:
if len(prices) == len(window_days):
fig.add_trace(go.Scatter(x=window_days, y=prices, mode='lines', line=dict(width=1), opacity=0.5, showlegend=False))
fig.add_hline(y=upper_threshold, line_dash="dash", line_color="green", annotation_text=f"+{(upper_threshold-1)*100:.2f}% Threshold (Price: {round(actual_upper_threshold, 2)})", annotation_position="top left")
fig.add_hline(y=lower_threshold, line_dash="dash", line_color="orange", annotation_text=f"-{(1-lower_threshold)*100:.2f}% Threshold (Price: {round(actual_lower_threshold, 2)})", annotation_position="bottom left")
fig.add_vline(x=0, line_dash="dash", line_color="red")
fig.update_layout(title=f"Normalized Price Movements Around Earnings Dates for {ticker}", xaxis_title="Days Relative to Earnings Date", yaxis_title="Normalized Price", legend_title="Legend", template='plotly_white', height=600, width=1200)
fig.add_trace(go.Scatter(x=[None], y=[None], mode='markers', marker=dict(size=10, color='white'), showlegend=True, name=f"Prob. Above +{(upper_threshold-1)*100:.2f}%: {prob_above:.2%}"))
fig.add_trace(go.Scatter(x=[None], y=[None], mode='markers', marker=dict(size=10, color='white'), showlegend=True, name=f"Prob. Below -{(1-lower_threshold)*100:.2f}%: {prob_below:.2%}"))
fig.add_trace(go.Scatter(x=[None], y=[None], mode='markers', marker=dict(size=10, color='white'), showlegend=True, name=f"Prob. Between: {prob_between:.2%}"))
return fig
def plot_volatility_around_earnings(stock_data, earnings_dates, window=5):
"""
Plot 20-day rolling volatility around earnings dates.
"""
volatilities = []
for earnings_date in earnings_dates.index:
start_date = earnings_date - timedelta(days=window)
end_date = earnings_date + timedelta(days=window)
subset = stock_data.loc[start_date:end_date, '20D Volatility']
date_range = pd.date_range(start=start_date, end=end_date)
subset = subset.reindex(date_range, fill_value=np.nan).fillna(method='ffill').fillna(method='bfill')
normalized_volatility = subset - subset.iloc[0]
volatilities.append(normalized_volatility.values)
volatility_data = pd.DataFrame(volatilities, index=earnings_dates.index)
fig = go.Figure()
for i in range(volatility_data.shape[0]):
fig.add_trace(go.Scatter(x=np.arange(-window, window + 1), y=volatility_data.iloc[i], mode='lines', showlegend=False, line=dict(width=1)))
fig.add_shape(dict(type="line", x0=0, y0=volatility_data.min().min(), x1=0, y1=volatility_data.max().max(), line=dict(color="red", width=2, dash="dash")))
fig.update_layout(title='20-Day Rolling Volatility Around Earnings Announcements', xaxis_title='Days Relative to Earnings', yaxis_title='20-Day Volatility', xaxis=dict(tickmode='array', tickvals=np.arange(-window, window + 1, 1)), template='plotly_white')
return fig
def plot_volume_around_earnings(stock_data, earnings_dates, window=5):
"""
Plot reindexed volume around earnings dates.
"""
volumes = []
for earnings_date in earnings_dates.index:
start_date = earnings_date - timedelta(days=window)
end_date = earnings_date + timedelta(days=window)
subset = stock_data.loc[start_date:end_date, 'Volume']
date_range = pd.date_range(start=start_date, end=end_date)
subset = subset.reindex(date_range, fill_value=np.nan).fillna(method='ffill').fillna(method='bfill')
normalized_volume = subset - subset.iloc[0]
volumes.append(normalized_volume.values)
volume_data = pd.DataFrame(volumes, index=earnings_dates.index)
fig = go.Figure()
for i in range(volume_data.shape[0]):
fig.add_trace(go.Scatter(x=np.arange(-window, window + 1), y=volume_data.iloc[i], mode='lines', showlegend=False, line=dict(width=1)))
fig.add_shape(dict(type="line", x0=0, y0=volume_data.min().min(), x1=0, y1=volume_data.max().max(), line=dict(color="red", width=2, dash="dash")))
fig.update_layout(title='Reindexed Volume Around Earnings Announcements', xaxis_title='Days Relative to Earnings', yaxis_title='Reindexed Volume', xaxis=dict(tickmode='array', tickvals=np.arange(-window, window + 1, 1)), template='plotly_white')
return fig
def compute_price_effect(earnings_date, stock_data):
"""
Compute price effects around earnings dates.
"""
try:
closest_date = stock_data.index[np.argmin(np.abs(stock_data.index - earnings_date))]
price_before_date = closest_date - pd.Timedelta(days=1)
price_on_date = closest_date
price_after_date = closest_date + pd.Timedelta(days=1)
price_before = stock_data.loc[:price_before_date, 'Close'].ffill().iloc[-1]
price_on = stock_data.loc[price_on_date, 'Close']
price_after = stock_data.loc[price_after_date:, 'Close'].bfill().iloc[0]
price_effect = ((price_after - price_before) / price_before) * 100
return price_before, price_on, price_after, price_effect
except (KeyError, IndexError) as e:
print(f"Missing data for date: {earnings_date} with error: {e}")
return None, None, None, None
def plot_price_effects(earnings_dates):
"""
Plot price effects around earnings dates.
"""
latest_earnings_data = earnings_dates.sort_index(ascending=False).head(14).sort_index()
fig = go.Figure()
positions = list(range(len(latest_earnings_data)))
width = 0.25
fig.add_trace(go.Bar(x=[pos - width for pos in positions], y=latest_earnings_data['Price Before'], width=width, name='Price Before', marker_color='blue'))
fig.add_trace(go.Bar(x=positions, y=latest_earnings_data['Price On'], width=width, name='Price On', marker_color='cyan'))
fig.add_trace(go.Bar(x=[pos + width for pos in positions], y=latest_earnings_data['Price After'], width=width, name='Price After', marker_color='lightblue'))
fig.add_trace(go.Scatter(x=positions, y=latest_earnings_data['Surprise(%)'], mode='lines+markers+text', name='Surprise(%)', marker=dict(color='red', size=8), text=[f"{round(val, 2)}%" for val in latest_earnings_data['Surprise(%)']], textposition="top center", yaxis='y2'))
fig.add_trace(go.Scatter(x=positions, y=latest_earnings_data['Price Effect (%)'], mode='lines+markers+text', name='Price Effect (%)', marker=dict(color='green', size=8), text=[f"{round(val, 2)}%" for val in latest_earnings_data['Price Effect (%)']], textposition="top center", yaxis='y2'))
fig.update_layout(title='Earnings Data with Surprise and Price Effect', xaxis=dict(tickmode='array', tickvals=positions, ticktext=latest_earnings_data.index.strftime('%Y-%m-%d'), tickangle=45), barmode='group', yaxis=dict(title='Price', side='left'), yaxis2=dict(title='Percentage (%)', overlaying='y', side='right', tickmode='auto', nticks=10, range=[min(latest_earnings_data['Surprise(%)'].min(), latest_earnings_data['Price Effect (%)'].min()) - 5, max(latest_earnings_data['Surprise(%)'].max(), latest_earnings_data['Price Effect (%)'].max()) + 5]), legend=dict(x=0.01, y=0.99, bordercolor="Black", borderwidth=1), template='plotly_white')
return fig
def plot_surprise_vs_price_effect(earnings_dates):
"""
Plot earnings surprise vs. price effect.
"""
filtered_earnings_data = earnings_dates.dropna(subset=['Surprise(%)', 'Price Effect (%)'])
if filtered_earnings_data.empty:
st.warning("Not enough data to plot Surprise vs. Price Effect.")
return go.Figure()
slope, intercept = np.polyfit(filtered_earnings_data['Surprise(%)'], filtered_earnings_data['Price Effect (%)'], 1)
x = np.array(filtered_earnings_data['Surprise(%)'])
y_pred = slope * x + intercept
correlation_matrix = np.corrcoef(filtered_earnings_data['Surprise(%)'], filtered_earnings_data['Price Effect (%)'])
correlation_xy = correlation_matrix[0, 1]
r_squared = correlation_xy**2
fig = go.Figure()
fig.add_trace(go.Scatter(x=filtered_earnings_data['Surprise(%)'], y=filtered_earnings_data['Price Effect (%)'], mode='markers', marker=dict(color='blue', size=8), name='Data Points'))
fig.add_trace(go.Scatter(x=x, y=y_pred, mode='lines', line=dict(color='red'), name=f'y={slope:.3f}x + {intercept:.3f}'))
fig.update_layout(title='Earnings Surprise vs. Price Effect', xaxis_title='Earnings Surprise(%)', yaxis_title='Price Effect(%)', template='plotly_white', height=600, width=1200, showlegend=True)
fig.add_annotation(x=0.05, y=0.95, xref='paper', yref='paper', text=f'R-squared = {r_squared:.3f}', showarrow=False, font=dict(size=15, color='green'))
return fig
def monte_carlo_simulation(ticker, annual_iv, days_to_earnings, upper_target, lower_target, stock_data, num_simulations=10000):
"""
Perform Monte Carlo simulation for stock price movements.
"""
current_price = stock_data['Close'].iloc[-1]
daily_iv = annual_iv / np.sqrt(252)
daily_returns = np.random.normal(0, daily_iv, (days_to_earnings, num_simulations))
price_paths = np.zeros_like(daily_returns)
price_paths[0] = current_price
for t in range(1, days_to_earnings):
price_paths[t] = price_paths[t-1] * (1 + daily_returns[t])
final_prices = price_paths[-1]
above_target = np.sum(final_prices > upper_target)
below_target = np.sum(final_prices < lower_target)
between_targets = num_simulations - above_target - below_target
prob_above = above_target / num_simulations
prob_below = below_target / num_simulations
prob_between = between_targets / num_simulations
fig = go.Figure()
for i in range(num_simulations):
fig.add_trace(go.Scatter(x=np.arange(days_to_earnings), y=price_paths[:, i], mode='lines', line=dict(color='lightblue', width=1), opacity=0.1, showlegend=False))
fig.add_trace(go.Scatter(x=[0, days_to_earnings-1], y=[upper_target, upper_target], mode='lines', line=dict(color='red', dash='dash'), name=f'Upper Target: {round(upper_target, 2)}'))
fig.add_trace(go.Scatter(x=[0, days_to_earnings-1], y=[lower_target, lower_target], mode='lines', line=dict(color='green', dash='dash'), name=f'Lower Target: {round(lower_target, 2)}'))
fig.add_trace(go.Scatter(x=[0, 0], y=[price_paths.min(), price_paths.max()], mode='lines', line=dict(color='red', dash='dash'), showlegend=False))
fig.add_annotation(x=0.05, y=0.95, xref='paper', yref='paper', text=f'P(>{round(upper_target, 2)}): {prob_above:.2%}<br>P(<{round(lower_target, 2)}): {prob_below:.2%}<br>P({round(lower_target, 2)}-{round(upper_target, 2)}): {prob_between:.2%}', showarrow=False, font=dict(size=12), bordercolor='black', borderwidth=1)
fig.update_layout(title=f"Monte Carlo Simulation of {ticker}'s Stock Price Over {days_to_earnings} Days", xaxis_title='Days', yaxis_title='Stock Price', template='plotly_white', height=600, width=1200, showlegend=True)
return fig
# Streamlit app
st.set_page_config(layout="wide")
st.title("Earnings Announcements Analysis")
st.write(
"""
This tool helps you analyze the impact of earnings announcements
on a company's stock price. By providing a ticker symbol and configuring the analysis parameters in the sidebar,
you can explore various aspects of stock price behavior around earnings dates and the likelihood of future movements.
"""
)
with st.expander("Key Features", expanded=False):
st.write(
"""
- **Stock Price with Earnings Surprises**: Visualize the stock price movement with indicators for positive and negative earnings surprises.
- **Normalized Price Movements**: Examine how the stock price changes relative to its price on the earnings announcement date.
- **Volatility Analysis**: Assess the stock's volatility around earnings dates to understand the market's reaction.
- **Volume Trends**: Analyze the trading volume before and after earnings announcements.
- **Price Effects**: Compare stock prices before, during, and after earnings to quantify the impact.
- **Earnings Surprise vs. Price Effect**: Investigate the correlation between earnings surprises and subsequent price changes.
- **Monte Carlo Simulations**: Use advanced statistical techniques to predict future price movements and estimate the probabilities of reaching specific price targets.
"""
)
st.sidebar.title("Input Parameters")
with st.sidebar.expander("How to Use", expanded=False):
st.write("""
**How to use this app:**
1. Enter the ticker symbol for the stock you want to analyze.
2. Adjust the pre and post-announcement windows to define the period around earnings dates.
3. Set the threshold percentage for price movement analysis.
4. Configure buffer days for fetching stock data.
5. Enter the implied volatility and days until earnings for Monte Carlo simulation.
6. Set the number of simulations for more precise results.
7. Check the box if you wish to run the Monte Carlo simulations (may slow down the app).
8. Click the "Run Analysis" button to start the analysis.
""")
with st.sidebar.expander("Ticker and Date Selection", expanded=True):
ticker = st.text_input("Enter Ticker Symbol", "MSFT", help="Enter the ticker symbol of the stock you want to analyze.")
pre_announcement_window = st.number_input("Pre-announcement Window (days)", value=5, min_value=1, help="Set the number of days before the earnings announcement to include in the analysis.")
post_announcement_window = st.number_input("Post-announcement Window (days)", value=10, min_value=1, help="Set the number of days after the earnings announcement to include in the analysis.")
with st.sidebar.expander("Analysis Parameters", expanded=True):
threshold_percentage = st.number_input("Threshold Percentage", value=0.10, min_value=0.01, max_value=1.0, step=0.01, help="Set the threshold for percentage changes in price analysis.")
buffer_days = st.number_input("Buffer Days", value=10, min_value=1, help="Set the number of buffer days around the earnings dates when fetching stock data.")
days_until_earnings = st.number_input("Days Until Earnings", value=10, min_value=1, help="Enter the number of days until the earnings announcement for the Monte Carlo simulation.")
with st.sidebar.expander("Monte Carlo Simulation", expanded=False):
run_simulation = st.checkbox("Run Monte Carlo Simulations (will take a few seconds)", value=False, help="Whether to run Monte Carlo Simulation Analysis. May slow down the app.")
implied_volatility = st.number_input("Implied Volatility", value=0.30, min_value=0.01, max_value=1.0, step=0.01, help="Enter the implied volatility for the Monte Carlo simulation.")
num_simulations = st.number_input("Number of Simulations for Monte Carlo", value=10000, min_value=100, help="Set the number of simulations for the Monte Carlo analysis.")
if st.sidebar.button("Run Analysis"):
try:
if not FMP_API_KEY:
st.error("Please set your FMP_API_KEY in the environment variables.")
else:
earnings_dates = fetch_earnings_data(ticker)
if earnings_dates.empty:
st.error("Failed to fetch earnings data. Please check the ticker or API key.")
else:
current_time = pd.Timestamp.now().tz_localize(None)
future_eps_estimate = earnings_dates.loc[earnings_dates.index > current_time]
if not future_eps_estimate.empty:
future_eps_estimate = future_eps_estimate.iloc[0]['EPS Estimate']
else:
future_eps_estimate = None
stock_data = fetch_stock_data(ticker, earnings_dates.index.min(), earnings_dates.index.max(), buffer_days)
if stock_data.empty:
st.error("Failed to fetch stock data. Please try again later.")
else:
stock_data = calculate_metrics(stock_data)
latest_close_price = stock_data['Close'].iloc[-1]
upper_threshold = 1 + threshold_percentage
lower_threshold = 1 - threshold_percentage
st.subheader("Earnings Announcements Data")
st.dataframe(earnings_dates)
st.subheader("Stock Price with Earnings Surprises")
st.markdown("This chart shows the stock price movements with markers indicating earnings surprises.")
st.plotly_chart(plot_stock_price_with_earnings(stock_data, earnings_dates, ticker), use_container_width=True)
st.subheader("Normalized Price Movements Around Earnings Dates")
st.markdown("This plot shows the normalized price movements of the stock around earnings dates.")
st.plotly_chart(plot_normalized_price_movements(stock_data, earnings_dates, ticker, pre_announcement_window, post_announcement_window, upper_threshold, lower_threshold), use_container_width=True)
st.subheader("Volatility Around Earnings Dates")
st.markdown("This plot shows the 20-day rolling volatility of the stock price around earnings dates.")
st.plotly_chart(plot_volatility_around_earnings(stock_data, earnings_dates), use_container_width=True)
st.subheader("Volume Around Earnings Dates")
st.markdown("This plot shows the trading volume changes around earnings dates.")
st.plotly_chart(plot_volume_around_earnings(stock_data, earnings_dates), use_container_width=True)
price_effects = earnings_dates.index.to_series().apply(compute_price_effect, stock_data=stock_data)
earnings_dates[['Price Before', 'Price On', 'Price After', 'Price Effect (%)']] = pd.DataFrame(price_effects.tolist(), index=earnings_dates.index)
earnings_dates.dropna(subset=['Price Before', 'Price On', 'Price After'], inplace=True)
st.subheader("Price Effects Around Earnings Dates")
st.markdown("This bar chart compares the stock prices before, on, and after the earnings dates.")
st.plotly_chart(plot_price_effects(earnings_dates), use_container_width=True)
st.subheader("Earnings Surprise vs. Price Effect")
st.markdown("This scatter plot shows the relationship between earnings surprise percentages and the resulting price effects.")
st.plotly_chart(plot_surprise_vs_price_effect(earnings_dates), use_container_width=True)
if run_simulation:
up_target = latest_close_price * upper_threshold
down_target = latest_close_price * lower_threshold
st.subheader("Monte Carlo Simulation for Price Movements")
st.markdown("We simulate multiple price paths using the stock's implied volatility to estimate the probabilities of the stock price reaching given targets.")
st.plotly_chart(monte_carlo_simulation(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data, num_simulations), use_container_width=True)
except Exception as e:
st.error(f"An error occurred while running the analysis: {e}")
hide_streamlit_style = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True) |