Space3 / app.py
QuantumLearner's picture
Update app.py
955155d verified
import numpy as np
import pandas as pd
import yfinance as yf
import plotly.graph_objects as go
import streamlit as st
# Set up the Streamlit app configuration
st.set_page_config(page_title="Analyzing Future Price Movements with Monte Carlo Simulations", layout="wide")
st.title('Future Price Movements with Monte Carlo Simulations')
# Sidebar instructions
st.sidebar.title('Input Parameters')
with st.sidebar.expander("How to use", expanded=False):
st.markdown("""
1. Enter Ticker and Date: Type the stock or cryptocurrency ticker and select the date range.
2. Set Parameters: Adjust the time horizon, number of simulations, and other parameters to customize the analysis.
- For Cryptocurrencies: The BSM analysis is not applicable. Set the BSM volatility to 0 to exclude the BSM-based analysis.
""")
# Wrapping ticker and date settings in an expander
with st.sidebar.expander("Asset and Date Settings", expanded=True):
ticker = st.text_input('Enter Stock/Crypto Symbol', 'AFX.DE', help="Enter the ticker symbol of the stock or cryptocurrency pair you want to analyze (e.g., 'AAPL' for Apple, 'BTC-USD' for Bitcoin).")
start_date = st.date_input('Start Date', pd.to_datetime('2020-01-01'), help="Select the start date for fetching historical data.")
end_date = st.date_input('End Date', pd.to_datetime('today') + pd.DateOffset(1), help="Select the end date for fetching historical data (default is today plus one day).")
# Wrapping parameter settings in an expander
with st.sidebar.expander("Parameter Settings", expanded=True):
days_to_forecast = st.slider('Time Horizon (Days)', min_value=1, max_value=60, value=30, help="Select the number of days into the future for the simulation.")
num_simulations = st.number_input('Number of Simulations', min_value=100, max_value=100000, value=10000, step=100, help="Set the number of Monte Carlo simulations to run.")
lower_threshold = st.number_input('Lower Threshold', min_value=0, max_value=10000, value=50, help="Set the lower price threshold for probability calculation.")
upper_threshold = st.number_input('Upper Threshold', min_value=0, max_value=10000, value=70, help="Set the upper price threshold for probability calculation.")
volatility_bsm = st.number_input('BSM Volatility (Annualized)', min_value=0.0, max_value=10.0, value=0.29, step=0.01, help="Set the annualized volatility for the Black-Scholes-Merton model. Set to 0 if analyzing a cryptocurrency.")
# Detect if the asset is a cryptocurrency
is_crypto = '-' in ticker
# Automatically set BSM volatility to 0 if it's a cryptocurrency
if is_crypto:
volatility_bsm = 0.0
# Description of the analysis
st.write("""
This tool estimates potential price movements of a selected stock or cryptocurrency over a specified time horizon using Monte Carlo Simulations.
The estimates are based on historical volatility and the implied volatility derived from the Black-Scholes-Merton model.
You can adjust the time horizon, number of simulations, and volatility measures to explore different scenarios of price dynamics.
""")
with st.expander("Click here for more information about the methodology", expanded=False):
st.latex(r'''
P_t = P_0 \times e^{(\mu - \frac{1}{2} \sigma^2) \times t + \sigma \times \sqrt{t} \times Z}
''')
st.markdown("""
**Monte Carlo Simulation Inputs** are
- **(Pt)**: Estimated asset price at time (t).
- **(P0)**: Current asset price.
- **(μ)**: Mean of the log returns.
- **(σ)**: Standard deviation of the log returns, representing historical volatility.
- **(t)**: Time horizon in days.
- **(Z)**: Random variable from the standard normal distribution.
""")
st.write("""To read more about the methodologies, visit [this link](https://entreprenerdly.com/price-movements-with-historical-implied-volatility-monte-carlo/).""")
st.write(f"""
These simulations project multiple potential future price paths for the asset based on the volatility models described.
By running {num_simulations} simulations, the tool generates a distribution of possible future prices. This allows us to calculate confidence intervals and probabilities for various price levels.
""")
# Running the analysis when the button is pressed
if st.sidebar.button('Run Analysis'):
st.write(f"Fetching data for {ticker}...")
try:
stock_data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False)
if not stock_data.empty:
stock_data['Returns'] = stock_data['Close'].pct_change()
log_returns = np.log(stock_data['Close'] / stock_data['Close'].shift(1))
current_price = stock_data.iloc[-1]['Close']
def run_simulation(volatility, dt, annualized=False):
simulated_prices = np.zeros((days_to_forecast, num_simulations))
simulated_prices[0] = current_price
if annualized:
volatility = volatility / np.sqrt(252)
for t in range(1, days_to_forecast):
random_walk = np.random.normal(loc=log_returns.mean() * dt,
scale=volatility * np.sqrt(dt),
size=num_simulations)
simulated_prices[t] = simulated_prices[t - 1] * np.exp(random_walk)
return simulated_prices
# Run simulations based on historical volatility
st.write(f"### Simulation with Historical Volatility for {ticker}")
simulated_prices_historical = run_simulation(log_returns.std(), 1)
fig1 = go.Figure()
mean_price_path = np.mean(simulated_prices_historical, axis=1)
median_price_path = np.median(simulated_prices_historical, axis=1)
lower_bound_68 = np.percentile(simulated_prices_historical, 16, axis=1)
upper_bound_68 = np.percentile(simulated_prices_historical, 84, axis=1)
lower_bound_95 = np.percentile(simulated_prices_historical, 2.5, axis=1)
upper_bound_95 = np.percentile(simulated_prices_historical, 97.5, axis=1)
for i in range(min(num_simulations, 100)):
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=simulated_prices_historical[:, i], mode='lines', line=dict(color='lightgray', width=0.5), opacity=0.3, showlegend=False))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=mean_price_path, mode='lines', name='Mean Price Path', line=dict(color='black')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=median_price_path, mode='lines', name='Median Price Path', line=dict(color='blue')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=lower_bound_68, mode='lines', name='68% confidence interval (Lower)', line=dict(color='green', dash='dash')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=upper_bound_68, mode='lines', name='68% confidence interval (Upper)', line=dict(color='green', dash='dash')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=lower_bound_95, mode='lines', name='95% confidence interval (Lower)', line=dict(color='blue', dash='dash')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=upper_bound_95, mode='lines', name='95% confidence interval (Upper)', line=dict(color='red', dash='dash')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=[upper_threshold] * days_to_forecast, mode='lines', name='Upper Threshold', line=dict(color='purple')))
fig1.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=[lower_threshold] * days_to_forecast, mode='lines', name='Lower Threshold', line=dict(color='orange')))
above_upper_threshold_prob = (simulated_prices_historical[-1] > upper_threshold).sum() / num_simulations
below_lower_threshold_prob = (simulated_prices_historical[-1] < lower_threshold).sum() / num_simulations
between_thresholds_prob = 1 - above_upper_threshold_prob - below_lower_threshold_prob
fig1.update_layout(
title=f'Monte Carlo Confidence Cone for {ticker} using Historical Volatility',
xaxis_title='Days',
yaxis_title='Price',
legend_title='Legend'
)
fig1.add_annotation(text=f'P(>{upper_threshold:.2f}): {above_upper_threshold_prob:.2%}<br>'
f'P(<{lower_threshold:.2f}): {below_lower_threshold_prob:.2%}<br>'
f'P({lower_threshold:.2f} - {upper_threshold:.2f}): {between_thresholds_prob:.2%}',
xref='paper', yref='paper', x=0.02, y=0.95, showarrow=False, bordercolor="black", borderwidth=1)
st.plotly_chart(fig1)
st.markdown(f"""
<h4 style='font-size: 16px;'>Interpretation of Results</h4>
<ul style='font-size: 14px;'>
<li><strong>Mean Price Path:</strong> The average simulated price path over the forecast period.</li>
<li><strong>Median Price Path:</strong> The median simulated price path over the forecast period.</li>
<li><strong>68% Confidence Interval:</strong> The range within which 68% of the simulated prices fall.</li>
<li><strong>95% Confidence Interval:</strong> The range within which 95% of the simulated prices fall.</li>
<li><strong>Probability of Exceeding Upper Threshold ({upper_threshold}):</strong> {above_upper_threshold_prob:.2%}</li>
<li><strong>Probability of Falling Below Lower Threshold ({lower_threshold}):</strong> {below_lower_threshold_prob:.2%}</li>
<li><strong>Probability of Staying Between Thresholds ({lower_threshold} - {upper_threshold}):</strong> {between_thresholds_prob:.2%}</li>
</ul>
""", unsafe_allow_html=True)
# Run BSM-based simulation only if volatility_bsm is greater than 0
if volatility_bsm > 0:
st.write(f"### Simulation with BSM Volatility for {ticker}")
simulated_prices_bsm = run_simulation(volatility_bsm, 1, annualized=True)
fig2 = go.Figure()
mean_price_path = np.mean(simulated_prices_bsm, axis=1)
median_price_path = np.median(simulated_prices_bsm, axis=1)
lower_bound_68 = np.percentile(simulated_prices_bsm, 16, axis=1)
upper_bound_68 = np.percentile(simulated_prices_bsm, 84, axis=1)
lower_bound_95 = np.percentile(simulated_prices_bsm, 2.5, axis=1)
upper_bound_95 = np.percentile(simulated_prices_bsm, 97.5, axis=1)
for i in range(min(num_simulations, 100)):
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=simulated_prices_bsm[:, i], mode='lines', line=dict(color='lightgray', width=0.5), opacity=0.3, showlegend=False))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=mean_price_path, mode='lines', name='Mean Price Path', line=dict(color='black')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=median_price_path, mode='lines', name='Median Price Path', line=dict(color='blue')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=lower_bound_68, mode='lines', name='68% confidence interval (Lower)', line=dict(color='green', dash='dash')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=upper_bound_68, mode='lines', name='68% confidence interval (Upper)', line=dict(color='green', dash='dash')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=lower_bound_95, mode='lines', name='95% confidence interval (Lower)', line=dict(color='blue', dash='dash')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=upper_bound_95, mode='lines', name='95% confidence interval (Upper)', line=dict(color='red', dash='dash')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=[upper_threshold] * days_to_forecast, mode='lines', name='Upper Threshold', line=dict(color='purple')))
fig2.add_trace(go.Scatter(x=np.arange(days_to_forecast), y=[lower_threshold] * days_to_forecast, mode='lines', name='Lower Threshold', line=dict(color='orange')))
above_upper_threshold_prob = (simulated_prices_bsm[-1] > upper_threshold).sum() / num_simulations
below_lower_threshold_prob = (simulated_prices_bsm[-1] < lower_threshold).sum() / num_simulations
between_thresholds_prob = 1 - above_upper_threshold_prob - below_lower_threshold_prob
fig2.update_layout(
title=f'Monte Carlo Confidence Cone for {ticker} using BSM Volatility',
xaxis_title='Days',
yaxis_title='Price',
legend_title='Legend'
)
fig2.add_annotation(text=f'P(>{upper_threshold:.2f}): {above_upper_threshold_prob:.2%}<br>'
f'P(<{lower_threshold:.2f}): {below_lower_threshold_prob:.2%}<br>'
f'P({lower_threshold:.2f} - {upper_threshold:.2f}): {between_thresholds_prob:.2%}',
xref='paper', yref='paper', x=0.02, y=0.95, showarrow=False, bordercolor="black", borderwidth=1)
st.plotly_chart(fig2)
st.markdown(f"""
<h4 style='font-size: 16px;'>Interpretation of Results</h4>
<ul style='font-size: 14px;'>
<li><strong>Mean Price Path:</strong> The average simulated price path over the forecast period.</li>
<li><strong>Median Price Path:</strong> The median simulated price path over the forecast period.</li>
<li><strong>68% Confidence Interval:</strong> The range within which 68% of the simulated prices fall.</li>
<li><strong>95% Confidence Interval:</strong> The range within which 95% of the simulated prices fall.</li>
<li><strong>Probability of Exceeding Upper Threshold ({upper_threshold}):</strong> {above_upper_threshold_prob:.2%}</li>
<li><strong>Probability of Falling Below Lower Threshold ({lower_threshold}):</strong> {below_lower_threshold_prob:.2%}</li>
<li><strong>Probability of Staying Between Thresholds ({lower_threshold} - {upper_threshold}):</strong> {between_thresholds_prob:.2%}</li>
</ul>
""", unsafe_allow_html=True)
else:
st.error("No data found for the given ticker and date range.")
except Exception as e:
st.error(f"Error fetching data: {str(e)}. Please check the ticker symbol or try again later.")
# Hide the Streamlit main menu and footer for a cleaner interface
hide_streamlit_style = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)