QuantumLearner commited on
Commit
1068c53
·
verified ·
1 Parent(s): 165ab5a

Create app.py

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