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)