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%}
' f'P(<{lower_threshold:.2f}): {below_lower_threshold_prob:.2%}
' 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"""

Interpretation of Results

""", 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%}
' f'P(<{lower_threshold:.2f}): {below_lower_threshold_prob:.2%}
' 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"""

Interpretation of Results

""", 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 = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)