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)