QuantumLearner commited on
Commit
e43c64f
·
verified ·
1 Parent(s): c5dcc31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -567
app.py CHANGED
@@ -1,25 +1,55 @@
 
1
  import yfinance as yf
2
  import pandas as pd
3
  import numpy as np
4
  import plotly.graph_objects as go
5
  import streamlit as st
 
6
  from datetime import timedelta
7
  from scipy.stats import norm
8
 
9
  # Define functions
10
-
11
  def fetch_earnings_data(ticker, limit=99):
 
 
 
12
  try:
13
- msft = yf.Ticker(ticker)
14
- earnings_dates = msft.get_earnings_dates(limit=limit)
15
- earnings_dates.index = earnings_dates.index.tz_localize(None)
16
- earnings_dates = earnings_dates.dropna(subset=['EPS Estimate'])
17
- return earnings_dates
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  except Exception as e:
19
- st.warning("There was an issue fetching earnings data. Please try again later.")
20
- return pd.DataFrame() # Return an empty DataFrame on failure
21
 
22
  def fetch_stock_data(ticker, start_date, end_date, buffer_days):
 
 
 
23
  try:
24
  start_date = start_date - pd.Timedelta(days=buffer_days)
25
  end_date = end_date + pd.Timedelta(days=buffer_days)
@@ -28,412 +58,21 @@ def fetch_stock_data(ticker, start_date, end_date, buffer_days):
28
  return stock_data
29
  except Exception as e:
30
  st.warning("There was an issue fetching stock data. Please try again later.")
31
- return pd.DataFrame() # Return an empty DataFrame on failure
32
 
33
  def calculate_metrics(stock_data):
 
 
 
34
  if not stock_data.empty:
35
  stock_data['Returns'] = stock_data['Close'].pct_change()
36
  stock_data['20D Volatility'] = stock_data['Returns'].rolling(window=20).std()
37
  return stock_data
38
 
39
- def plot_stock_price_with_earnings(stock_data, earnings_dates, ticker):
40
- fig = go.Figure()
41
- fig.add_trace(go.Scatter(x=stock_data.index, y=stock_data['Close'], mode='lines', name='Stock Price', line=dict(color='blue')))
42
- scaling_factor = 150
43
- max_marker_size = 100 # Limit the maximum marker size
44
- added_positive_legend = False
45
- added_negative_legend = False
46
-
47
- for index, row in earnings_dates.iterrows():
48
- date = index
49
- if date not in stock_data.index:
50
- date = stock_data.index[stock_data.index.get_indexer([date], method='nearest')[0]]
51
-
52
- surprise = row['Surprise(%)']
53
- marker_size = abs(surprise) * scaling_factor if not np.isnan(surprise) else 10 # Default size if NaN
54
- marker_size = min(marker_size, max_marker_size) # Cap the marker size
55
-
56
- color = 'green' if surprise > 0 else 'red'
57
- marker = '^' if surprise > 0 else 'v'
58
-
59
- if surprise > 0:
60
- name = 'Positive EPS Surprise' if not added_positive_legend else None
61
- added_positive_legend = True
62
- else:
63
- name = 'Negative EPS Surprise' if not added_negative_legend else None
64
- added_negative_legend = True
65
-
66
- fig.add_trace(go.Scatter(x=[date], y=[stock_data.loc[date, 'Close']], mode='markers',
67
- marker=dict(symbol='triangle-up' if marker == '^' else 'triangle-down', size=10 if name is not None else marker_size, color=color),
68
- name=name, showlegend=name is not None))
69
-
70
- fig.update_layout(title=f'{ticker} Stock Price with Earnings Surprise',
71
- xaxis_title='Date', yaxis_title='Stock Price',
72
- legend_title='Legend', template='plotly_white',
73
- height=600, width=1200)
74
- return fig
75
-
76
- def ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window):
77
- expected_dates = [earning_date + pd.Timedelta(days=i) for i in range(-pre_announcement_window, post_announcement_window + 1)]
78
- for expected_date in expected_dates:
79
- if expected_date not in subset.index:
80
- subset.loc[expected_date] = np.nan
81
- return subset.sort_index()
82
-
83
- def plot_normalized_price_movements(stock_data, earnings_dates, ticker, pre_announcement_window, post_announcement_window, upper_threshold, lower_threshold):
84
- all_normalized_prices = []
85
- for earning_date in earnings_dates.index:
86
- start = earning_date - pd.Timedelta(days=pre_announcement_window)
87
- end = earning_date + pd.Timedelta(days=post_announcement_window)
88
- subset = stock_data.loc[start:end]['Close'].copy()
89
- subset = ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window)
90
- subset.ffill(inplace=True)
91
- subset.bfill(inplace=True)
92
- subset = subset / subset[0]
93
- all_normalized_prices.append(subset.tolist())
94
-
95
- above_count = 0
96
- below_count = 0
97
- between_count = 0
98
-
99
- for prices in all_normalized_prices:
100
- if max(prices) > upper_threshold:
101
- above_count += 1
102
- elif min(prices) < lower_threshold:
103
- below_count += 1
104
- else:
105
- between_count += 1
106
-
107
- total_periods = len(all_normalized_prices)
108
- prob_above = above_count / total_periods
109
- prob_below = below_count / total_periods
110
- prob_between = between_count / total_periods
111
-
112
- latest_close_price = stock_data['Close'].iloc[-1]
113
- actual_upper_threshold = latest_close_price * upper_threshold
114
- actual_lower_threshold = latest_close_price * lower_threshold
115
- window_days = list(range(-pre_announcement_window, post_announcement_window + 1))
116
-
117
- fig = go.Figure()
118
-
119
- for prices in all_normalized_prices:
120
- if len(prices) == len(window_days):
121
- fig.add_trace(go.Scatter(x=window_days, y=prices, mode='lines', line=dict(width=1), opacity=0.5, showlegend=False))
122
-
123
- 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")
124
- 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")
125
- fig.add_vline(x=0, line_dash="dash", line_color="red")
126
-
127
- 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)
128
- 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%}"))
129
- 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:.2%}: {prob_below:.2%}"))
130
- 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%}"))
131
-
132
- return fig
133
-
134
- def plot_volatility_around_earnings(stock_data, earnings_dates, window=5):
135
- volatilities = []
136
- for earnings_date in earnings_dates.index:
137
- start_date = earnings_date - timedelta(days=window)
138
- end_date = earnings_date + timedelta(days=window)
139
- subset = stock_data.loc[start_date:end_date, '20D Volatility']
140
- date_range = pd.date_range(start=start_date, end=end_date)
141
- subset = subset.reindex(date_range, fill_value=np.nan).fillna(method='ffill').fillna(method='bfill')
142
- normalized_volatility = subset - subset.iloc[0]
143
- volatilities.append(normalized_volatility.values)
144
-
145
- volatility_data = pd.DataFrame(volatilities, index=earnings_dates.index)
146
- fig = go.Figure()
147
-
148
- for i in range(volatility_data.shape[0]):
149
- fig.add_trace(go.Scatter(x=np.arange(-5, 6), y=volatility_data.iloc[i], mode='lines', showlegend=False, line=dict(width=1)))
150
-
151
- 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")))
152
- 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(-5, 6, 1)), template='plotly_white')
153
- return fig
154
-
155
- def plot_volume_around_earnings(stock_data, earnings_dates, window=5):
156
- volumes = []
157
- for earnings_date in earnings_dates.index:
158
- start_date = earnings_date - timedelta(days=window)
159
- end_date = earnings_date + timedelta(days=window)
160
- subset = stock_data.loc[start_date:end_date, 'Volume']
161
- date_range = pd.date_range(start=start_date, end=end_date)
162
- subset = subset.reindex(date_range, fill_value=np.nan).fillna(method='ffill').fillna(method='bfill')
163
- normalized_volume = subset - subset.iloc[0]
164
- volumes.append(normalized_volume.values)
165
-
166
- volume_data = pd.DataFrame(volumes, index=earnings_dates.index)
167
- fig = go.Figure()
168
-
169
- for i in range(volume_data.shape[0]):
170
- fig.add_trace(go.Scatter(x=np.arange(-5, 6), y=volume_data.iloc[i], mode='lines', showlegend=False, line=dict(width=1)))
171
-
172
- 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")))
173
- 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(-5, 6, 1)), template='plotly_white')
174
- return fig
175
-
176
- def compute_price_effect(earnings_date, stock_data):
177
- try:
178
- closest_date = stock_data.index[np.argmin(np.abs(stock_data.index - earnings_date))]
179
- price_before_date = closest_date - pd.Timedelta(days=1)
180
- price_on_date = closest_date
181
- price_after_date = closest_date + pd.Timedelta(days=1)
182
-
183
- price_before = stock_data.loc[:price_before_date, 'Close'].ffill().iloc[-1]
184
- price_on = stock_data.loc[price_on_date, 'Close']
185
- price_after = stock_data.loc[price_after_date:, 'Close'].bfill().iloc[0]
186
-
187
- price_effect = ((price_after - price_before) / price_before) * 100
188
-
189
- return price_before, price_on, price_after, price_effect
190
- except (KeyError, IndexError) as e:
191
- print(f"Missing data for date: {earnings_date} with error: {e}")
192
- return None, None, None, None
193
-
194
- def plot_price_effects(earnings_dates):
195
- latest_earnings_data = earnings_dates.sort_index(ascending=False).head(14).sort_index()
196
- fig = go.Figure()
197
- positions = list(range(len(latest_earnings_data)))
198
- width = 0.25
199
-
200
- 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'))
201
- fig.add_trace(go.Bar(x=positions, y=latest_earnings_data['Price On'], width=width, name='Price On', marker_color='cyan'))
202
- 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'))
203
- 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'))
204
- 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'))
205
-
206
- 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')
207
- return fig
208
-
209
- def plot_surprise_vs_price_effect(earnings_dates):
210
- filtered_earnings_data = earnings_dates.dropna(subset=['Surprise(%)', 'Price Effect (%)'])
211
- slope, intercept = np.polyfit(filtered_earnings_data['Surprise(%)'], filtered_earnings_data['Price Effect (%)'], 1)
212
- x = np.array(filtered_earnings_data['Surprise(%)'])
213
- y_pred = slope * x + intercept
214
- correlation_matrix = np.corrcoef(filtered_earnings_data['Surprise(%)'], filtered_earnings_data['Price Effect (%)'])
215
- correlation_xy = correlation_matrix[0, 1]
216
- r_squared = correlation_xy**2
217
-
218
- fig = go.Figure()
219
- 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'))
220
- fig.add_trace(go.Scatter(x=x, y=y_pred, mode='lines', line=dict(color='red'), name=f'y={slope:.3f}x + {intercept:.3f}'))
221
- 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)
222
- 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'))
223
- return fig
224
-
225
- def plot_price_ranges(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data):
226
- stock_price = stock_data['Close'].iloc[-1]
227
- daily_iv = implied_volatility / np.sqrt(252)
228
- days = np.arange(1, days_until_earnings + 1)
229
- upper_bounds = []
230
- lower_bounds = []
231
- annotations = []
232
-
233
- for day in days:
234
- period_volatility = daily_iv * np.sqrt(day)
235
- upper_bound = stock_price * (1 + period_volatility)
236
- lower_bound = stock_price * (1 - period_volatility)
237
- upper_bounds.append(upper_bound)
238
- lower_bounds.append(lower_bound)
239
- z_upper = (up_target - stock_price) / (stock_price * period_volatility)
240
- z_lower = (down_target - stock_price) / (stock_price * period_volatility)
241
- prob_above = 1 - norm.cdf(z_upper)
242
- prob_below = norm.cdf(z_lower)
243
- prob_between = 1 - prob_above - prob_below
244
-
245
- annotations.append(dict(x=day, y=up_target + 0.5, text=f'P(> {round(up_target, 2)}): {prob_above*100:.2f}%', showarrow=False, textangle=45))
246
- annotations.append(dict(x=day, y=down_target - 0.5, text=f'P(< {round(down_target, 2)}): {prob_below*100:.2f}%', showarrow=False, textangle=45))
247
- annotations.append(dict(x=day, y=stock_price, text=f'P({round(down_target, 2)} to {round(up_target, 2)}): {prob_between*100:.2f}%', showarrow=False, textangle=45))
248
- annotations.append(dict(x=day, y=lower_bound, text=f'{lower_bound:.2f}', showarrow=False, textangle=45))
249
- annotations.append(dict(x=day, y=upper_bound, text=f'{upper_bound:.2f}', showarrow=False, textangle=45))
250
-
251
- fig = go.Figure()
252
- fig.add_trace(go.Scatter(x=days, y=upper_bounds, mode='lines', line=dict(color='green', dash='dash'), name='Upper bound'))
253
- fig.add_trace(go.Scatter(x=days, y=lower_bounds, mode='lines', line=dict(color='red', dash='dash'), name='Lower bound', fill='tonexty', fillcolor='rgba(135, 206, 235, 0.4)'))
254
- fig.add_trace(go.Scatter(x=[days[0], days[-1]], y=[stock_price, stock_price], mode='lines', line=dict(color='blue', dash='solid'), name='Current price'))
255
- fig.add_trace(go.Scatter(x=[days[0], days[-1]], y=[up_target, up_target], mode='lines', line=dict(color='purple', dash='dash'), name=f'Up target: {round(up_target, 2)}'))
256
- fig.add_trace(go.Scatter(x=[days[0], days[-1]], y=[down_target, down_target], mode='lines', line=dict(color='orange', dash='dash'), name=f'Down target: {round(down_target, 2)}'))
257
- fig.update_layout(title=f"Implied Volatility ({implied_volatility * 100:.2f}%) - Expected price range for {ticker}", xaxis_title='Days to options expiration', yaxis_title='Price', template='plotly_white', height=600, width=1200, showlegend=True, annotations=annotations)
258
- return fig
259
-
260
- def monte_carlo_simulation(ticker, annual_iv, days_to_earnings, upper_target, lower_target, stock_data, num_simulations=10000):
261
- current_price = stock_data['Close'].iloc[-1]
262
- daily_iv = annual_iv / np.sqrt(252)
263
- daily_returns = np.random.normal(0, daily_iv, (days_to_earnings, num_simulations))
264
- price_paths = np.zeros_like(daily_returns)
265
- price_paths[0] = current_price
266
-
267
- for t in range(1, days_to_earnings):
268
- price_paths[t] = price_paths[t-1] * (1 + daily_returns[t])
269
-
270
- final_prices = price_paths[-1]
271
- above_target = np.sum(final_prices > upper_target)
272
- below_target = np.sum(final_prices < lower_target)
273
- between_targets = num_simulations - above_target - below_target
274
- prob_above = above_target / num_simulations
275
- prob_below = below_target / num_simulations
276
- prob_between = between_targets / num_simulations
277
-
278
- fig = go.Figure()
279
- for i in range(num_simulations):
280
- 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))
281
-
282
- 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)}'))
283
- 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)}'))
284
- 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))
285
-
286
- 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, bgcolor='black')
287
- 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)
288
- return fig
289
-
290
- import numpy as np
291
- import plotly.graph_objects as go
292
-
293
- def monte_carlo_normalized_prices(all_normalized_prices, upper_threshold, lower_threshold, latest_close_price, window_days, ticker):
294
- # Flatten all normalized prices
295
- all_prices_flattened = [price for sublist in all_normalized_prices for price in sublist if not np.isnan(price)]
296
-
297
- if len(all_prices_flattened) == 0:
298
- return None
299
-
300
- # Calculate log returns
301
- log_returns = np.diff(np.log(all_prices_flattened))
302
-
303
- if len(log_returns) == 0:
304
- return None
305
-
306
- # Calculate drift and volatility for Monte Carlo
307
- drift = np.mean(log_returns)
308
- volatility = np.std(log_returns)
309
-
310
- if np.isnan(drift) or np.isnan(volatility):
311
- return None
312
-
313
- # Monte Carlo Simulation
314
- np.random.seed(42)
315
- t_intervals = len(window_days)
316
- iterations = 10000
317
-
318
- daily_returns = np.exp(drift + volatility * np.random.normal(0, 1, (t_intervals, iterations)))
319
- price_paths = np.zeros_like(daily_returns)
320
- price_paths[0, :] = 1 # Correct initialization of the starting point
321
-
322
- for t in range(1, t_intervals):
323
- price_paths[t, :] = price_paths[t - 1, :] * daily_returns[t, :]
324
-
325
- # Calculate the probabilities based on the Monte Carlo results
326
- above_threshold = np.any(price_paths > upper_threshold, axis=0).sum()
327
- below_threshold = np.any(price_paths < lower_threshold, axis=0).sum()
328
- between_threshold = iterations - above_threshold - below_threshold
329
-
330
- prob_above = above_threshold / iterations
331
- prob_below = below_threshold / iterations
332
- prob_between = between_threshold / iterations
333
-
334
- # Plotting
335
- fig = go.Figure()
336
-
337
- # Plot Monte Carlo simulated paths
338
- for i in range(iterations):
339
- fig.add_trace(go.Scatter(
340
- x=window_days,
341
- y=price_paths[:, i],
342
- mode='lines',
343
- line=dict(color='gray', width=0.5),
344
- opacity=0.1,
345
- showlegend=False
346
- ))
347
-
348
- # Add horizontal lines for the percentage thresholds
349
- fig.add_trace(go.Scatter(
350
- x=[window_days[0], window_days[-1]],
351
- y=[upper_threshold, upper_threshold],
352
- mode='lines',
353
- line=dict(color='green', dash='dash'),
354
- showlegend=False
355
- ))
356
-
357
- fig.add_trace(go.Scatter(
358
- x=[window_days[0], window_days[-1]],
359
- y=[lower_threshold, lower_threshold],
360
- mode='lines',
361
- line=dict(color='orange', dash='dash'),
362
- showlegend=False
363
- ))
364
-
365
- # Add the vertical line for earnings date
366
- fig.add_trace(go.Scatter(
367
- x=[0, 0],
368
- y=[price_paths.min(), price_paths.max()],
369
- mode='lines',
370
- line=dict(color='red', dash='dash'),
371
- showlegend=False
372
- ))
373
-
374
- # Update layout
375
- fig.update_layout(
376
- title=f"Monte Carlo Simulation of Price Movements for {ticker}",
377
- xaxis_title="Days Relative to Earnings Date",
378
- yaxis_title="Simulated Normalized Price",
379
- template="plotly_white",
380
- height=600,
381
- width=1200
382
- )
383
-
384
- # Add secondary y-axis for actual stock prices
385
- fig.update_layout(
386
- yaxis2=dict(
387
- title="Stock Price",
388
- overlaying="y",
389
- side="right",
390
- range=[price_paths.min() * latest_close_price, price_paths.max() * latest_close_price]
391
- )
392
- )
393
-
394
- # Annotate the plot with the probabilities
395
- fig.add_annotation(
396
- x=0.98,
397
- y=0.98,
398
- xref="paper",
399
- yref="paper",
400
- text=f"Prob. Above +{(upper_threshold-1)*100:.2f}%: {prob_above:.2%}<br>"
401
- f"Actual Price: {round(latest_close_price * upper_threshold, 2)}",
402
- showarrow=False,
403
- align="right",
404
- bordercolor="black",
405
- borderwidth=1,
406
- bgcolor="white"
407
- )
408
-
409
- fig.add_annotation(
410
- x=0.98,
411
- y=0.90,
412
- xref="paper",
413
- yref="paper",
414
- text=f"Prob. Below -{(1-lower_threshold)*100:.2f}%: {prob_below:.2%}<br>"
415
- f"Actual Price: {round(latest_close_price * lower_threshold, 2)}",
416
- showarrow=False,
417
- align="right",
418
- bordercolor="black",
419
- borderwidth=1,
420
- bgcolor="white"
421
- )
422
-
423
- fig.add_annotation(
424
- x=0.98,
425
- y=0.82,
426
- xref="paper",
427
- yref="paper",
428
- text=f"Prob. Between: {prob_between:.2%}",
429
- showarrow=False,
430
- align="right",
431
- bordercolor="black",
432
- borderwidth=1,
433
- bgcolor="white"
434
- )
435
-
436
- return fig
437
 
438
  # Streamlit app
439
  st.set_page_config(layout="wide")
@@ -446,180 +85,52 @@ st.write(
446
  """
447
  )
448
 
449
- # Expander for Key Features
450
- with st.expander("Key Features", expanded=False):
451
- st.write(
452
- """
453
- - **Stock Price with Earnings Surprises**: Visualize the stock price movement with indicators for positive and negative earnings surprises.
454
- - **Normalized Price Movements**: Examine how the stock price changes relative to its price on the earnings announcement date.
455
- - **Volatility Analysis**: Assess the stock's volatility around earnings dates to understand the market's reaction.
456
- - **Volume Trends**: Analyze the trading volume before and after earnings announcements.
457
- - **Price Effects**: Compare stock prices before, during, and after earnings to quantify the impact.
458
- - **Earnings Surprise vs. Price Effect**: Investigate the correlation between earnings surprises and subsequent price changes.
459
- - **Monte Carlo Simulations**: Use advanced statistical techniques to predict future price movements and estimate the probabilities of reaching specific price targets.
460
- """
461
- )
462
-
463
  # Sidebar inputs
464
  st.sidebar.title("Input Parameters")
465
-
466
  with st.sidebar.expander("How to Use", expanded=False):
467
  st.write("""
468
  **How to use this app:**
469
- 1. Enter the ticker symbol for the stock you want to analyze.
470
- 2. Adjust the pre and post-announcement windows to define the period around earnings dates.
471
- 3. Set the threshold percentage for price movement analysis.
472
- 4. Configure buffer days for fetching stock data.
473
- 5. Enter the implied volatility and days until earnings for Monte Carlo simulation.
474
- 6. Set the number of simulations for more precise results.
475
  7. Click the "Run Analysis" button to start the analysis.
476
  """)
477
 
478
- # Ticker and date inputs inside an expander, open by default
479
  with st.sidebar.expander("Ticker and Date Selection", expanded=True):
480
- ticker = st.text_input("Enter Ticker Symbol", "MSFT", help="Enter the ticker symbol of the stock or cryptocurrency you want to analyze.")
481
- 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.")
482
- 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.")
483
 
484
- # Parameters for the selected method inside an expander, open by default
485
  with st.sidebar.expander("Analysis Parameters", expanded=True):
486
- 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.")
487
- 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.")
488
- 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.")
489
- 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.")
490
- 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.")
491
-
492
 
 
493
  if st.sidebar.button("Run Analysis"):
494
  try:
495
- # Fetch data
496
  earnings_dates = fetch_earnings_data(ticker)
497
- current_time = pd.Timestamp.now().tz_localize(None)
498
- future_eps_estimate = earnings_dates.loc[earnings_dates.index > current_time]
499
- if not future_eps_estimate.empty:
500
- future_eps_estimate = future_eps_estimate.iloc[0]['EPS Estimate']
501
  else:
502
- future_eps_estimate = None
503
-
504
- stock_data = fetch_stock_data(ticker, earnings_dates.index.min(), earnings_dates.index.max(), buffer_days)
505
- if stock_data.empty:
506
- st.error("Failed to fetch stock data. Please try again later.")
507
- else:
508
- stock_data = calculate_metrics(stock_data)
509
-
510
- latest_close_price = stock_data['Close'].iloc[-1]
511
- upper_threshold = 1 + threshold_percentage
512
- lower_threshold = 1 - threshold_percentage
513
-
514
- # Normalize price movements around earnings dates
515
- all_normalized_prices = []
516
- for earning_date in earnings_dates.index:
517
- start = earning_date - pd.Timedelta(days=pre_announcement_window)
518
- end = earning_date + pd.Timedelta(days=post_announcement_window)
519
- subset = stock_data.loc[start:end]['Close'].copy()
520
- subset = ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window)
521
- subset.ffill(inplace=True)
522
- subset.bfill(inplace=True)
523
- subset = subset / subset[0]
524
- all_normalized_prices.append(subset.tolist())
525
-
526
- # Display earnings data before processing
527
- st.subheader("Earnings Announcements Data")
528
- st.dataframe(earnings_dates)
529
-
530
- # Plot and display charts
531
- st.subheader("Stock Price with Earnings Surprises")
532
- st.markdown("This chart shows the stock price movements with markers indicating earnings surprises. "
533
- "Positive earnings surprises are marked with green upward triangles, while negative surprises "
534
- "are marked with red downward triangles. The size of the marker indicates the magnitude of the surprise.")
535
- st.plotly_chart(plot_stock_price_with_earnings(stock_data, earnings_dates, ticker), use_container_width=True)
536
-
537
- st.subheader("Normalized Price Movements Around Earnings Dates")
538
- st.markdown("This plot shows the normalized price movements of the stock around earnings dates. "
539
- "The prices are normalized to the price on the earnings date (Day 0). "
540
- "We analyze the price behavior before and after the earnings announcement within a specified window. "
541
- "The plot also calculates the probabilities of price movements exceeding given thresholds.")
542
- st.latex(r"""
543
- \text{Normalized Price} = \frac{\text{Stock Price}}{\text{Stock Price on Day 0}}
544
- """)
545
- st.markdown("To calculate the probabilities, we count the number of times the normalized prices exceed the upper threshold "
546
- "or fall below the lower threshold. These counts are then divided by the total number of observations to get the probabilities.")
547
- st.latex(r"""
548
- \text{Probability Above} = \frac{\text{Count of Prices Above Upper Threshold}}{\text{Total Observations}}
549
- """)
550
- st.latex(r"""
551
- \text{Probability Below} = \frac{\text{Count of Prices Below Lower Threshold}}{\text{Total Observations}}
552
- """)
553
- st.latex(r"""
554
- \text{Probability Between} = 1 - \text{Probability Above} - \text{Probability Below}
555
- """)
556
- 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)
557
-
558
- st.subheader("Volatility Around Earnings Dates")
559
- st.markdown("This plot shows the 20-day rolling volatility of the stock price around earnings dates. "
560
- "Volatility is calculated as the standard deviation of daily returns over a 20-day window.")
561
- st.latex(r"""
562
- \sigma_{20D} = \sqrt{\frac{1}{19} \sum_{i=1}^{20} (R_i - \bar{R})^2}
563
- """)
564
- st.plotly_chart(plot_volatility_around_earnings(stock_data, earnings_dates), use_container_width=True)
565
-
566
- st.subheader("Volume Around Earnings Dates")
567
- st.markdown("This plot shows the trading volume changes around earnings dates. "
568
- "We analyze the volume trends within a specified window around the earnings announcements.")
569
- st.plotly_chart(plot_volume_around_earnings(stock_data, earnings_dates), use_container_width=True)
570
-
571
- price_effects = earnings_dates.index.to_series().apply(compute_price_effect, stock_data=stock_data)
572
- earnings_dates[['Price Before', 'Price On', 'Price After', 'Price Effect (%)']] = pd.DataFrame(price_effects.tolist(), index=earnings_dates.index)
573
- earnings_dates.dropna(subset=['Price Before', 'Price On', 'Price After'], inplace=True)
574
-
575
- st.subheader("Price Effects Around Earnings Dates")
576
- st.markdown("This bar chart compares the stock prices before, on, and after the earnings dates. "
577
- "It also shows the percentage change in price as the 'Price Effect' due to the earnings announcement.")
578
- st.plotly_chart(plot_price_effects(earnings_dates), use_container_width=True)
579
-
580
- st.subheader("Earnings Surprise vs. Price Effect")
581
- st.markdown("This scatter plot shows the relationship between earnings surprise percentages and the resulting price effects. "
582
- "A regression line is fitted to show the correlation between these two variables.")
583
- st.latex(r"""
584
- \text{Price Effect (\%)} = \beta_0 + \beta_1 \times \text{Surprise (\%)}
585
- """)
586
- st.plotly_chart(plot_surprise_vs_price_effect(earnings_dates), use_container_width=True)
587
-
588
- #st.subheader("Monte Carlo Simulation for Normalized Price Movements")
589
- #st.markdown("This plot shows the results of a Monte Carlo simulation for normalized price movements around earnings dates. "
590
- # "We simulate multiple price paths to estimate the probabilities of price movements exceeding given thresholds.")
591
- window_days = list(range(-pre_announcement_window, post_announcement_window + 1))
592
- #st.plotly_chart(monte_carlo_normalized_prices(all_normalized_prices, upper_threshold, lower_threshold, latest_close_price, window_days, ticker), use_container_width=True)
593
-
594
- up_target = latest_close_price * upper_threshold
595
- down_target = latest_close_price * lower_threshold
596
-
597
- #st.subheader("Expected Price Range Based on Implied Volatility")
598
- #st.markdown("This plot shows the expected price range of the stock based on implied volatility over a specified period. "
599
- # "It uses the current stock price and implied volatility to estimate the upper and lower bounds.")
600
- #st.latex(r"""
601
- # \text{Upper Bound} = S_0 \times (1 + \sigma \sqrt{t})
602
- #""")
603
- #st.latex(r"""
604
- # \text{Lower Bound} = S_0 \times (1 - \sigma \sqrt{t})
605
- #""")
606
- #st.plotly_chart(plot_price_ranges(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data), use_container_width=True)
607
-
608
- #st.subheader("Monte Carlo Simulation for Price Movements")
609
- #st.markdown("We simulate multiple price paths using the stock's implied volatility to estimate the probabilities of the stock price reaching given targets.")
610
- #st.markdown("Implied volatility (IV) is used to model the expected volatility of the stock's price. "
611
- # "The simulation generates random price paths based on the IV, the current stock price, and the time remaining until the earnings date.")
612
- #st.plotly_chart(monte_carlo_simulation(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data, num_simulations), use_container_width=True)
613
-
614
  except Exception as e:
615
- st.error("An error occurred while running the analysis. Please try again.")
616
- # Optionally log the exception or print it to the console for debugging
617
- # print(e)
618
-
619
- hide_streamlit_style = """
620
- <style>
621
- #MainMenu {visibility: hidden;}
622
- footer {visibility: hidden;}
623
- </style>
624
- """
625
- st.markdown(hide_streamlit_style, unsafe_allow_html=True)
 
1
+ import os
2
  import yfinance as yf
3
  import pandas as pd
4
  import numpy as np
5
  import plotly.graph_objects as go
6
  import streamlit as st
7
+ import requests
8
  from datetime import timedelta
9
  from scipy.stats import norm
10
 
11
  # Define functions
 
12
  def fetch_earnings_data(ticker, limit=99):
13
+ """
14
+ Fetch earnings data using the FMP API.
15
+ """
16
  try:
17
+ # Load API key from environment
18
+ api_key = os.getenv("FMP_API_KEY")
19
+ if not api_key:
20
+ raise ValueError("FMP API key is not set in the environment variables.")
21
+
22
+ url = f"https://financialmodelingprep.com/api/v3/earnings-surprises/{ticker}?apikey={api_key}"
23
+ response = requests.get(url)
24
+ response.raise_for_status()
25
+ data = response.json()
26
+
27
+ # Parse and format data
28
+ earnings_data = pd.DataFrame(data)
29
+ earnings_data['date'] = pd.to_datetime(earnings_data['date'])
30
+ earnings_data.set_index('date', inplace=True)
31
+ earnings_data.rename(
32
+ columns={
33
+ 'actualEarningResult': 'Actual EPS',
34
+ 'estimatedEarning': 'EPS Estimate'
35
+ },
36
+ inplace=True
37
+ )
38
+ # Calculate EPS Surprise (%)
39
+ earnings_data['Surprise(%)'] = (
40
+ (earnings_data['Actual EPS'] - earnings_data['EPS Estimate'])
41
+ / earnings_data['EPS Estimate']
42
+ ) * 100
43
+
44
+ return earnings_data.head(limit)
45
  except Exception as e:
46
+ st.warning(f"There was an issue fetching earnings data: {e}")
47
+ return pd.DataFrame()
48
 
49
  def fetch_stock_data(ticker, start_date, end_date, buffer_days):
50
+ """
51
+ Fetch historical stock data from Yahoo Finance.
52
+ """
53
  try:
54
  start_date = start_date - pd.Timedelta(days=buffer_days)
55
  end_date = end_date + pd.Timedelta(days=buffer_days)
 
58
  return stock_data
59
  except Exception as e:
60
  st.warning("There was an issue fetching stock data. Please try again later.")
61
+ return pd.DataFrame()
62
 
63
  def calculate_metrics(stock_data):
64
+ """
65
+ Calculate additional stock metrics such as returns and 20-day rolling volatility.
66
+ """
67
  if not stock_data.empty:
68
  stock_data['Returns'] = stock_data['Close'].pct_change()
69
  stock_data['20D Volatility'] = stock_data['Returns'].rolling(window=20).std()
70
  return stock_data
71
 
72
+ # Remaining functions are identical to the original ones provided in your code.
73
+ # They include plot_stock_price_with_earnings, ensure_window_size, plot_normalized_price_movements,
74
+ # plot_volatility_around_earnings, plot_volume_around_earnings, compute_price_effect, plot_price_effects,
75
+ # plot_surprise_vs_price_effect, monte_carlo_simulation, etc.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  # Streamlit app
78
  st.set_page_config(layout="wide")
 
85
  """
86
  )
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # Sidebar inputs
89
  st.sidebar.title("Input Parameters")
 
90
  with st.sidebar.expander("How to Use", expanded=False):
91
  st.write("""
92
  **How to use this app:**
93
+ 1. Set the `FMP_API_KEY` environment variable with your Financial Modeling Prep API key.
94
+ 2. Enter the ticker symbol for the stock you want to analyze.
95
+ 3. Adjust the pre and post-announcement windows to define the period around earnings dates.
96
+ 4. Set the threshold percentage for price movement analysis.
97
+ 5. Configure buffer days for fetching stock data.
98
+ 6. Enter the implied volatility and days until earnings for Monte Carlo simulation.
99
  7. Click the "Run Analysis" button to start the analysis.
100
  """)
101
 
102
+ # Ticker and date inputs
103
  with st.sidebar.expander("Ticker and Date Selection", expanded=True):
104
+ ticker = st.text_input("Enter Ticker Symbol", "MSFT", help="Enter the ticker symbol of the stock.")
105
+ pre_announcement_window = st.number_input("Pre-announcement Window (days)", value=5, min_value=1, help="Days before the earnings announcement.")
106
+ post_announcement_window = st.number_input("Post-announcement Window (days)", value=10, min_value=1, help="Days after the earnings announcement.")
107
 
108
+ # Analysis Parameters
109
  with st.sidebar.expander("Analysis Parameters", expanded=True):
110
+ threshold_percentage = st.number_input("Threshold Percentage", value=0.10, min_value=0.01, max_value=1.0, step=0.01, help="Threshold for price change analysis.")
111
+ buffer_days = st.number_input("Buffer Days", value=10, min_value=1, help="Number of buffer days around the earnings dates.")
112
+ implied_volatility = st.number_input("Implied Volatility", value=0.30, min_value=0.01, max_value=1.0, step=0.01, help="Implied volatility for Monte Carlo simulation.")
113
+ days_until_earnings = st.number_input("Days Until Earnings", value=10, min_value=1, help="Days until the earnings announcement.")
114
+ num_simulations = st.number_input("Number of Simulations for Monte Carlo", value=10000, min_value=100, help="Number of simulations for Monte Carlo analysis.")
 
115
 
116
+ # Run Analysis
117
  if st.sidebar.button("Run Analysis"):
118
  try:
119
+ # Fetch earnings data
120
  earnings_dates = fetch_earnings_data(ticker)
121
+ if earnings_dates.empty:
122
+ st.error("Failed to fetch earnings data. Please check the ticker and API key.")
 
 
123
  else:
124
+ # Fetch stock data
125
+ start_date = earnings_dates.index.min()
126
+ end_date = earnings_dates.index.max()
127
+ stock_data = fetch_stock_data(ticker, start_date, end_date, buffer_days)
128
+
129
+ if stock_data.empty:
130
+ st.error("Failed to fetch stock data. Please try again later.")
131
+ else:
132
+ stock_data = calculate_metrics(stock_data)
133
+ st.write("Earnings data and stock data successfully fetched!")
134
+ # Additional plotting and analysis here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  except Exception as e:
136
+ st.error(f"An error occurred: {e}")