Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,22 +9,31 @@ from scipy.stats import norm
|
|
| 9 |
# Define functions
|
| 10 |
|
| 11 |
def fetch_earnings_data(ticker, limit=99):
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
def fetch_stock_data(ticker, start_date, end_date, buffer_days):
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
def calculate_metrics(stock_data):
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
return stock_data
|
| 29 |
|
| 30 |
def plot_stock_price_with_earnings(stock_data, earnings_dates, ticker):
|
|
@@ -484,120 +493,130 @@ with st.sidebar.expander("Analysis Parameters", expanded=True):
|
|
| 484 |
|
| 485 |
|
| 486 |
if st.sidebar.button("Run Analysis"):
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
stock_data = calculate_metrics(stock_data)
|
| 497 |
-
|
| 498 |
-
latest_close_price = stock_data['Close'].iloc[-1]
|
| 499 |
-
upper_threshold = 1 + threshold_percentage
|
| 500 |
-
lower_threshold = 1 - threshold_percentage
|
| 501 |
-
|
| 502 |
-
# Normalize price movements around earnings dates
|
| 503 |
-
all_normalized_prices = []
|
| 504 |
-
for earning_date in earnings_dates.index:
|
| 505 |
-
start = earning_date - pd.Timedelta(days=pre_announcement_window)
|
| 506 |
-
end = earning_date + pd.Timedelta(days=post_announcement_window)
|
| 507 |
-
subset = stock_data.loc[start:end]['Close'].copy()
|
| 508 |
-
subset = ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window)
|
| 509 |
-
subset.ffill(inplace=True)
|
| 510 |
-
subset.bfill(inplace=True)
|
| 511 |
-
subset = subset / subset[0]
|
| 512 |
-
all_normalized_prices.append(subset.tolist())
|
| 513 |
-
|
| 514 |
-
# Display earnings data before processing
|
| 515 |
-
st.subheader("Earnings Announcements Data")
|
| 516 |
-
st.dataframe(earnings_dates)
|
| 517 |
-
|
| 518 |
-
# Plot and display charts
|
| 519 |
-
st.subheader("Stock Price with Earnings Surprises")
|
| 520 |
-
st.markdown("This chart shows the stock price movements with markers indicating earnings surprises. "
|
| 521 |
-
"Positive earnings surprises are marked with green upward triangles, while negative surprises "
|
| 522 |
-
"are marked with red downward triangles. The size of the marker indicates the magnitude of the surprise.")
|
| 523 |
-
st.plotly_chart(plot_stock_price_with_earnings(stock_data, earnings_dates, ticker), use_container_width=True)
|
| 524 |
-
|
| 525 |
-
st.subheader("Normalized Price Movements Around Earnings Dates")
|
| 526 |
-
st.markdown("This plot shows the normalized price movements of the stock around earnings dates. "
|
| 527 |
-
"The prices are normalized to the price on the earnings date (Day 0). "
|
| 528 |
-
"We analyze the price behavior before and after the earnings announcement within a specified window. "
|
| 529 |
-
"The plot also calculates the probabilities of price movements exceeding given thresholds.")
|
| 530 |
-
st.latex(r"""
|
| 531 |
-
\text{Normalized Price} = \frac{\text{Stock Price}}{\text{Stock Price on Day 0}}
|
| 532 |
-
""")
|
| 533 |
-
st.markdown("To calculate the probabilities, we count the number of times the normalized prices exceed the upper threshold "
|
| 534 |
-
"or fall below the lower threshold. These counts are then divided by the total number of observations to get the probabilities.")
|
| 535 |
-
st.latex(r"""
|
| 536 |
-
\text{Probability Above} = \frac{\text{Count of Prices Above Upper Threshold}}{\text{Total Observations}}
|
| 537 |
-
""")
|
| 538 |
-
st.latex(r"""
|
| 539 |
-
\text{Probability Below} = \frac{\text{Count of Prices Below Lower Threshold}}{\text{Total Observations}}
|
| 540 |
-
""")
|
| 541 |
-
st.latex(r"""
|
| 542 |
-
\text{Probability Between} = 1 - \text{Probability Above} - \text{Probability Below}
|
| 543 |
-
""")
|
| 544 |
-
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)
|
| 545 |
-
|
| 546 |
-
st.subheader("Volatility Around Earnings Dates")
|
| 547 |
-
st.markdown("This plot shows the 20-day rolling volatility of the stock price around earnings dates. "
|
| 548 |
-
"Volatility is calculated as the standard deviation of daily returns over a 20-day window.")
|
| 549 |
-
st.latex(r"""
|
| 550 |
-
\sigma_{20D} = \sqrt{\frac{1}{19} \sum_{i=1}^{20} (R_i - \bar{R})^2}
|
| 551 |
-
""")
|
| 552 |
-
st.plotly_chart(plot_volatility_around_earnings(stock_data, earnings_dates), use_container_width=True)
|
| 553 |
-
|
| 554 |
-
st.subheader("Volume Around Earnings Dates")
|
| 555 |
-
st.markdown("This plot shows the trading volume changes around earnings dates. "
|
| 556 |
-
"We analyze the volume trends within a specified window around the earnings announcements.")
|
| 557 |
-
st.plotly_chart(plot_volume_around_earnings(stock_data, earnings_dates), use_container_width=True)
|
| 558 |
-
|
| 559 |
-
price_effects = earnings_dates.index.to_series().apply(compute_price_effect, stock_data=stock_data)
|
| 560 |
-
earnings_dates[['Price Before', 'Price On', 'Price After', 'Price Effect (%)']] = pd.DataFrame(price_effects.tolist(), index=earnings_dates.index)
|
| 561 |
-
earnings_dates.dropna(subset=['Price Before', 'Price On', 'Price After'], inplace=True)
|
| 562 |
-
|
| 563 |
-
st.subheader("Price Effects Around Earnings Dates")
|
| 564 |
-
st.markdown("This bar chart compares the stock prices before, on, and after the earnings dates. "
|
| 565 |
-
"It also shows the percentage change in price as the 'Price Effect' due to the earnings announcement.")
|
| 566 |
-
st.plotly_chart(plot_price_effects(earnings_dates), use_container_width=True)
|
| 567 |
-
|
| 568 |
-
st.subheader("Earnings Surprise vs. Price Effect")
|
| 569 |
-
st.markdown("This scatter plot shows the relationship between earnings surprise percentages and the resulting price effects. "
|
| 570 |
-
"A regression line is fitted to show the correlation between these two variables.")
|
| 571 |
-
st.latex(r"""
|
| 572 |
-
\text{Price Effect (\%)} = \beta_0 + \beta_1 \times \text{Surprise (\%)}
|
| 573 |
-
""")
|
| 574 |
-
st.plotly_chart(plot_surprise_vs_price_effect(earnings_dates), use_container_width=True)
|
| 575 |
-
|
| 576 |
-
st.subheader("Monte Carlo Simulation for Normalized Price Movements")
|
| 577 |
-
st.markdown("This plot shows the results of a Monte Carlo simulation for normalized price movements around earnings dates. "
|
| 578 |
-
"We simulate multiple price paths to estimate the probabilities of price movements exceeding given thresholds.")
|
| 579 |
-
window_days = list(range(-pre_announcement_window, post_announcement_window + 1))
|
| 580 |
-
st.plotly_chart(monte_carlo_normalized_prices(all_normalized_prices, upper_threshold, lower_threshold, latest_close_price, window_days, ticker), use_container_width=True)
|
| 581 |
-
|
| 582 |
-
up_target = latest_close_price * upper_threshold
|
| 583 |
-
down_target = latest_close_price * lower_threshold
|
| 584 |
-
|
| 585 |
-
st.subheader("Expected Price Range Based on Implied Volatility")
|
| 586 |
-
st.markdown("This plot shows the expected price range of the stock based on implied volatility over a specified period. "
|
| 587 |
-
"It uses the current stock price and implied volatility to estimate the upper and lower bounds.")
|
| 588 |
-
st.latex(r"""
|
| 589 |
-
\text{Upper Bound} = S_0 \times (1 + \sigma \sqrt{t})
|
| 590 |
-
""")
|
| 591 |
-
st.latex(r"""
|
| 592 |
-
\text{Lower Bound} = S_0 \times (1 - \sigma \sqrt{t})
|
| 593 |
-
""")
|
| 594 |
-
st.plotly_chart(plot_price_ranges(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data), use_container_width=True)
|
| 595 |
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 601 |
|
| 602 |
hide_streamlit_style = """
|
| 603 |
<style>
|
|
|
|
| 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)
|
| 26 |
+
stock_data = yf.download(ticker, start=start_date, end=end_date)
|
| 27 |
+
stock_data.index = stock_data.index.tz_localize(None)
|
| 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):
|
|
|
|
| 493 |
|
| 494 |
|
| 495 |
if st.sidebar.button("Run Analysis"):
|
| 496 |
+
try:
|
| 497 |
+
# Fetch data
|
| 498 |
+
earnings_dates = fetch_earnings_data(ticker)
|
| 499 |
+
current_time = pd.Timestamp.now().tz_localize(None)
|
| 500 |
+
future_eps_estimate = earnings_dates.loc[earnings_dates.index > current_time]
|
| 501 |
+
if not future_eps_estimate.empty:
|
| 502 |
+
future_eps_estimate = future_eps_estimate.iloc[0]['EPS Estimate']
|
| 503 |
+
else:
|
| 504 |
+
future_eps_estimate = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
|
| 506 |
+
stock_data = fetch_stock_data(ticker, earnings_dates.index.min(), earnings_dates.index.max(), buffer_days)
|
| 507 |
+
if stock_data.empty:
|
| 508 |
+
st.error("Failed to fetch stock data. Please try again later.")
|
| 509 |
+
else:
|
| 510 |
+
stock_data = calculate_metrics(stock_data)
|
| 511 |
+
|
| 512 |
+
latest_close_price = stock_data['Close'].iloc[-1]
|
| 513 |
+
upper_threshold = 1 + threshold_percentage
|
| 514 |
+
lower_threshold = 1 - threshold_percentage
|
| 515 |
+
|
| 516 |
+
# Normalize price movements around earnings dates
|
| 517 |
+
all_normalized_prices = []
|
| 518 |
+
for earning_date in earnings_dates.index:
|
| 519 |
+
start = earning_date - pd.Timedelta(days=pre_announcement_window)
|
| 520 |
+
end = earning_date + pd.Timedelta(days=post_announcement_window)
|
| 521 |
+
subset = stock_data.loc[start:end]['Close'].copy()
|
| 522 |
+
subset = ensure_window_size(subset, earning_date, pre_announcement_window, post_announcement_window)
|
| 523 |
+
subset.ffill(inplace=True)
|
| 524 |
+
subset.bfill(inplace=True)
|
| 525 |
+
subset = subset / subset[0]
|
| 526 |
+
all_normalized_prices.append(subset.tolist())
|
| 527 |
+
|
| 528 |
+
# Display earnings data before processing
|
| 529 |
+
st.subheader("Earnings Announcements Data")
|
| 530 |
+
st.dataframe(earnings_dates)
|
| 531 |
+
|
| 532 |
+
# Plot and display charts
|
| 533 |
+
st.subheader("Stock Price with Earnings Surprises")
|
| 534 |
+
st.markdown("This chart shows the stock price movements with markers indicating earnings surprises. "
|
| 535 |
+
"Positive earnings surprises are marked with green upward triangles, while negative surprises "
|
| 536 |
+
"are marked with red downward triangles. The size of the marker indicates the magnitude of the surprise.")
|
| 537 |
+
st.plotly_chart(plot_stock_price_with_earnings(stock_data, earnings_dates, ticker), use_container_width=True)
|
| 538 |
+
|
| 539 |
+
st.subheader("Normalized Price Movements Around Earnings Dates")
|
| 540 |
+
st.markdown("This plot shows the normalized price movements of the stock around earnings dates. "
|
| 541 |
+
"The prices are normalized to the price on the earnings date (Day 0). "
|
| 542 |
+
"We analyze the price behavior before and after the earnings announcement within a specified window. "
|
| 543 |
+
"The plot also calculates the probabilities of price movements exceeding given thresholds.")
|
| 544 |
+
st.latex(r"""
|
| 545 |
+
\text{Normalized Price} = \frac{\text{Stock Price}}{\text{Stock Price on Day 0}}
|
| 546 |
+
""")
|
| 547 |
+
st.markdown("To calculate the probabilities, we count the number of times the normalized prices exceed the upper threshold "
|
| 548 |
+
"or fall below the lower threshold. These counts are then divided by the total number of observations to get the probabilities.")
|
| 549 |
+
st.latex(r"""
|
| 550 |
+
\text{Probability Above} = \frac{\text{Count of Prices Above Upper Threshold}}{\text{Total Observations}}
|
| 551 |
+
""")
|
| 552 |
+
st.latex(r"""
|
| 553 |
+
\text{Probability Below} = \frac{\text{Count of Prices Below Lower Threshold}}{\text{Total Observations}}
|
| 554 |
+
""")
|
| 555 |
+
st.latex(r"""
|
| 556 |
+
\text{Probability Between} = 1 - \text{Probability Above} - \text{Probability Below}
|
| 557 |
+
""")
|
| 558 |
+
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)
|
| 559 |
+
|
| 560 |
+
st.subheader("Volatility Around Earnings Dates")
|
| 561 |
+
st.markdown("This plot shows the 20-day rolling volatility of the stock price around earnings dates. "
|
| 562 |
+
"Volatility is calculated as the standard deviation of daily returns over a 20-day window.")
|
| 563 |
+
st.latex(r"""
|
| 564 |
+
\sigma_{20D} = \sqrt{\frac{1}{19} \sum_{i=1}^{20} (R_i - \bar{R})^2}
|
| 565 |
+
""")
|
| 566 |
+
st.plotly_chart(plot_volatility_around_earnings(stock_data, earnings_dates), use_container_width=True)
|
| 567 |
+
|
| 568 |
+
st.subheader("Volume Around Earnings Dates")
|
| 569 |
+
st.markdown("This plot shows the trading volume changes around earnings dates. "
|
| 570 |
+
"We analyze the volume trends within a specified window around the earnings announcements.")
|
| 571 |
+
st.plotly_chart(plot_volume_around_earnings(stock_data, earnings_dates), use_container_width=True)
|
| 572 |
+
|
| 573 |
+
price_effects = earnings_dates.index.to_series().apply(compute_price_effect, stock_data=stock_data)
|
| 574 |
+
earnings_dates[['Price Before', 'Price On', 'Price After', 'Price Effect (%)']] = pd.DataFrame(price_effects.tolist(), index=earnings_dates.index)
|
| 575 |
+
earnings_dates.dropna(subset=['Price Before', 'Price On', 'Price After'], inplace=True)
|
| 576 |
+
|
| 577 |
+
st.subheader("Price Effects Around Earnings Dates")
|
| 578 |
+
st.markdown("This bar chart compares the stock prices before, on, and after the earnings dates. "
|
| 579 |
+
"It also shows the percentage change in price as the 'Price Effect' due to the earnings announcement.")
|
| 580 |
+
st.plotly_chart(plot_price_effects(earnings_dates), use_container_width=True)
|
| 581 |
+
|
| 582 |
+
st.subheader("Earnings Surprise vs. Price Effect")
|
| 583 |
+
st.markdown("This scatter plot shows the relationship between earnings surprise percentages and the resulting price effects. "
|
| 584 |
+
"A regression line is fitted to show the correlation between these two variables.")
|
| 585 |
+
st.latex(r"""
|
| 586 |
+
\text{Price Effect (\%)} = \beta_0 + \beta_1 \times \text{Surprise (\%)}
|
| 587 |
+
""")
|
| 588 |
+
st.plotly_chart(plot_surprise_vs_price_effect(earnings_dates), use_container_width=True)
|
| 589 |
+
|
| 590 |
+
st.subheader("Monte Carlo Simulation for Normalized Price Movements")
|
| 591 |
+
st.markdown("This plot shows the results of a Monte Carlo simulation for normalized price movements around earnings dates. "
|
| 592 |
+
"We simulate multiple price paths to estimate the probabilities of price movements exceeding given thresholds.")
|
| 593 |
+
window_days = list(range(-pre_announcement_window, post_announcement_window + 1))
|
| 594 |
+
st.plotly_chart(monte_carlo_normalized_prices(all_normalized_prices, upper_threshold, lower_threshold, latest_close_price, window_days, ticker), use_container_width=True)
|
| 595 |
+
|
| 596 |
+
up_target = latest_close_price * upper_threshold
|
| 597 |
+
down_target = latest_close_price * lower_threshold
|
| 598 |
+
|
| 599 |
+
st.subheader("Expected Price Range Based on Implied Volatility")
|
| 600 |
+
st.markdown("This plot shows the expected price range of the stock based on implied volatility over a specified period. "
|
| 601 |
+
"It uses the current stock price and implied volatility to estimate the upper and lower bounds.")
|
| 602 |
+
st.latex(r"""
|
| 603 |
+
\text{Upper Bound} = S_0 \times (1 + \sigma \sqrt{t})
|
| 604 |
+
""")
|
| 605 |
+
st.latex(r"""
|
| 606 |
+
\text{Lower Bound} = S_0 \times (1 - \sigma \sqrt{t})
|
| 607 |
+
""")
|
| 608 |
+
st.plotly_chart(plot_price_ranges(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data), use_container_width=True)
|
| 609 |
+
|
| 610 |
+
st.subheader("Monte Carlo Simulation for Price Movements")
|
| 611 |
+
st.markdown("We simulate multiple price paths using the stock's implied volatility to estimate the probabilities of the stock price reaching given targets.")
|
| 612 |
+
st.markdown("Implied volatility (IV) is used to model the expected volatility of the stock's price. "
|
| 613 |
+
"The simulation generates random price paths based on the IV, the current stock price, and the time remaining until the earnings date.")
|
| 614 |
+
st.plotly_chart(monte_carlo_simulation(ticker, implied_volatility, days_until_earnings, up_target, down_target, stock_data, num_simulations), use_container_width=True)
|
| 615 |
+
|
| 616 |
+
except Exception as e:
|
| 617 |
+
st.error("An error occurred while running the analysis. Please try again.")
|
| 618 |
+
# Optionally log the exception or print it to the console for debugging
|
| 619 |
+
# print(e)
|
| 620 |
|
| 621 |
hide_streamlit_style = """
|
| 622 |
<style>
|