Spaces:
Sleeping
Sleeping
File size: 15,186 Bytes
18ba6fe 1d8da95 7617bf4 1d8da95 e2d4d5d 1861754 9c40dc7 43fa391 18ba6fe 1d8da95 43fa391 1d8da95 95dda2a 18ba6fe 08e456c e561d26 9c40dc7 e561d26 18ba6fe 1d8da95 18ba6fe 1d8da95 955155d 9c40dc7 18ba6fe 9c40dc7 18ba6fe 1d8da95 18ba6fe 9c40dc7 18ba6fe 1d8da95 9c40dc7 18ba6fe 1d8da95 18ba6fe 9c40dc7 18ba6fe 1d8da95 18ba6fe 9c40dc7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | 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) |