Spaces:
Sleeping
Sleeping
| 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) |