Spaces:
Sleeping
Sleeping
| 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) |