Spaces:
Running
Running
import yfinance as yf | |
import numpy as np | |
import pandas as pd | |
import streamlit as st | |
import plotly.graph_objects as go | |
# Fetch stock data | |
def get_stock_data(ticker, start_date, end_date): | |
stock_data = yf.download(ticker, start=start_date, end=end_date) | |
return stock_data['Close'] | |
# Bootstrapping simulation function | |
def bootstrap_simulation(data, days, n_iterations=10000): | |
daily_returns = data.pct_change().dropna() | |
simulations = np.zeros((n_iterations, days)) | |
for i in range(n_iterations): | |
sample = np.random.choice(daily_returns, size=days, replace=True) | |
simulations[i] = np.cumprod(1 + sample) * data.iloc[-1] | |
return simulations | |
# Calculate probabilities | |
def calculate_probabilities(simulations, thresholds): | |
final_prices = simulations[:, -1] | |
below = np.mean(final_prices < thresholds[0]) | |
above = np.mean(final_prices > thresholds[1]) | |
between = np.mean((final_prices >= thresholds[0]) & (final_prices <= thresholds[1])) | |
return {'below': below, 'between': between, 'above': above} | |
# Calculate percentiles | |
def calculate_percentiles(simulations): | |
percentiles = np.percentile(simulations, [2.5, 16, 50, 84, 97.5], axis=0) | |
return percentiles | |
# Plot distributions | |
def plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities): | |
final_bootstrap_prices = bootstrap_simulations[:, -1] | |
mean_bootstrap_price = np.mean(final_bootstrap_prices) | |
median_bootstrap_price = np.median(final_bootstrap_prices) | |
ci_68_bootstrap = np.percentile(final_bootstrap_prices, [16, 84]) | |
ci_95_bootstrap = np.percentile(final_bootstrap_prices, [2.5, 97.5]) | |
latest_price = data.iloc[-1] | |
fig = go.Figure() | |
# Plot for Bootstrapping | |
fig.add_trace(go.Histogram(x=final_bootstrap_prices, nbinsx=50, name='Simulated Final Prices', | |
marker_color='blue', opacity=0.7)) | |
fig.add_vline(x=mean_bootstrap_price, line=dict(color='red', dash='dash'), name=f'Mean: {mean_bootstrap_price:.2f}') | |
fig.add_vline(x=median_bootstrap_price, line=dict(color='orange', dash='dash'), name=f'Median: {median_bootstrap_price:.2f}') | |
fig.add_vline(x=latest_price, line=dict(color='green', dash='dash'), name=f'Latest Price: {latest_price:.2f}') | |
fig.add_vrect(x0=ci_68_bootstrap[0], x1=ci_68_bootstrap[1], fillcolor='yellow', opacity=0.2, layer="below", line_width=0, annotation_text="68% CI", annotation_position="top left") | |
fig.add_vrect(x0=ci_95_bootstrap[0], x1=ci_95_bootstrap[1], fillcolor='grey', opacity=0.2, layer="below", line_width=0, annotation_text="95% CI", annotation_position="top left") | |
max_freq = np.histogram(final_bootstrap_prices, bins=50)[0].max() | |
# Calculate positions based on a fraction of the max frequency | |
mean_y_pos = max_freq * 0.9 | |
median_y_pos = max_freq * 0.7 | |
latest_y_pos = max_freq * 0.5 | |
# Annotations for the vertical lines | |
fig.add_annotation(x=mean_bootstrap_price, y=mean_y_pos, text=f'Mean: {mean_bootstrap_price:.2f}', showarrow=False) | |
fig.add_annotation(x=median_bootstrap_price, y=median_y_pos, text=f'Median: {median_bootstrap_price:.2f}', showarrow=False) | |
fig.add_annotation(x=latest_price, y=latest_y_pos, text=f'Latest: {latest_price:.2f}', showarrow=False) | |
fig.add_vline(x=mean_bootstrap_price, line=dict(color='red', dash='dash'), name='Mean', showlegend=True) | |
fig.add_vline(x=median_bootstrap_price, line=dict(color='orange', dash='dash'), name='Median', showlegend=True) | |
fig.add_vline(x=latest_price, line=dict(color='green', dash='dash'), name='Latest Price', showlegend=True) | |
textstr = f'P(>{thresholds[1]:.2f}): {bootstrap_probabilities["above"]:.2%}<br>' + \ | |
f'P(<{thresholds[0]:.2f}): {bootstrap_probabilities["below"]:.2%}<br>' + \ | |
f'P({thresholds[0]:.2f} - {thresholds[1]:.2f}): {bootstrap_probabilities["between"]:.2%}' | |
fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr, showarrow=False, | |
bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4, font=dict(color="black")) | |
fig.update_layout(title='Bootstrapping Simulation', xaxis_title='Final Price', yaxis_title='Frequency', showlegend=True) | |
return fig, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price | |
# Plot price data with simulation cones | |
def plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities): | |
last_date = data.index[-1] | |
future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=days, freq='D') | |
fig = go.Figure() | |
# Plot historical prices | |
fig.add_trace(go.Scatter(x=data.index, y=data, mode='lines', name='Historical Prices', line=dict(color='white'))) | |
# Plot bootstrapping simulation cone | |
fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[2], mode='lines', name='Bootstrap Median', line=dict(color='red', dash='dash'))) | |
fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[0], fill=None, mode='lines', line=dict(color='lightgrey'), showlegend=False)) | |
fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[4], fill='tonexty', mode='lines', line=dict(color='lightgrey'), name='Bootstrap 95% CI')) | |
fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[1], fill=None, mode='lines', line=dict(color='lightyellow'), showlegend=False)) | |
fig.add_trace(go.Scatter(x=future_dates, y=bootstrap_percentiles[3], fill='tonexty', mode='lines', line=dict(color='lightyellow'), name='Bootstrap 68% CI')) | |
# Annotate the thresholds | |
fig.add_hline(y=thresholds[0], line=dict(color='blue', dash='dash'), annotation_text=f'Threshold 1: {thresholds[0]}', annotation_position="top left") | |
fig.add_hline(y=thresholds[1], line=dict(color='green', dash='dash'), annotation_text=f'Threshold 2: {thresholds[1]}', annotation_position="top left") | |
# Add probability annotations | |
textstr_bootstrap = f'Bootstrap Probabilities:<br>Below {thresholds[0]}: {bootstrap_probabilities["below"]:.2%}<br>' + \ | |
f'Between {thresholds[0]} and {thresholds[1]}: {bootstrap_probabilities["between"]:.2%}<br>' + \ | |
f'Above {thresholds[1]}: {bootstrap_probabilities["above"]:.2%}' | |
fig.add_annotation(xref='paper', yref='paper', x=0.98, y=0.02, text=textstr_bootstrap, showarrow=False, | |
bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white", opacity=0.4, font=dict(color="black")) | |
fig.update_layout(title='Bootstrapping Simulation Cone', xaxis_title='Date', yaxis_title='Price', showlegend=True) | |
fig.update_xaxes(type='date') | |
return fig | |
# Streamlit app | |
st.set_page_config(layout="wide") | |
st.title('Future Stock Price Bootstrap Simulation') | |
st.sidebar.header('Input Parameters') | |
st.write(""" | |
### Description | |
This application simulates future stock prices using bootstrapping simulation methods. | |
You can specify the stock ticker, the date range, the number of simulation days, the number of simulations, and price thresholds. | |
The simulation results will show the probability of the stock price falling below, between, or above the specified thresholds. | |
**Background and Concept** | |
The concept of bootstrapping was introduced by Bradley Efron in 1979. The primary goal of bootstrapping is to understand the variability of a statistic by generating multiple samples from the observed data. This approach assumes that the sample data represents the population, allowing us to draw inferences about the population from the sample. | |
**Steps in Bootstrapping:** | |
Given a dataset \( X = \{x_1, x_2, ..., x_n\} \), we aim to estimate the statistic \( \theta \) (e.g., the mean return of a stock). | |
1. **Resampling**: Create a resample \( X^* \) by drawing \( n \) observations from \( X \) with replacement. This means that each data point can be selected multiple times in a single resample: | |
""") | |
st.latex(r'X^* = \{x_1^*, x_2^*, ..., x_n^*\}') | |
st.write(""" | |
2. **Statistic Calculation**: Calculate the statistic \( \theta^* \) for the resample \( X^* \): | |
""") | |
st.latex(r'\theta^* = f(X^*)') | |
st.write(""" | |
3. **Repeat**: Repeat the above steps \( B \) times to generate \( B \) bootstrap statistics: | |
""") | |
st.latex(r'\{\theta_1^*, \theta_2^*, ..., \theta_B^*\}') | |
st.write(""" | |
4. **Estimate**: Use the bootstrap statistics to estimate the mean, standard error, and confidence intervals of \( \theta \). | |
**How to use:** | |
1. Enter the stock ticker, start date, and end date. | |
2. Set the number of days for the simulation and the number of iterations. | |
3. Enter the price thresholds. | |
4. Click 'Run Simulation' to start the bootstrapping simulation. | |
**Results:** | |
The app will display two charts: | |
1. The distribution of the final simulated prices with key statistical measures. | |
2. The historical stock prices with simulated future price cones and the specified thresholds. | |
""") | |
ticker = st.sidebar.text_input('Enter Stock Ticker', 'ASML.AS') | |
start_date = st.sidebar.date_input('Start Date', pd.to_datetime('2020-01-01')) | |
end_date = st.sidebar.date_input('End Date', pd.to_datetime('2025-01-01')) | |
days = st.sidebar.number_input('Number of Days for Simulation', min_value=1, max_value=365, value=30) | |
n_iterations = st.sidebar.number_input('Number of Simulations', min_value=100, max_value=100000, value=10000) | |
threshold1 = st.sidebar.number_input('Threshold 1', min_value=0, value=850) | |
threshold2 = st.sidebar.number_input('Threshold 2', min_value=0, value=1050) | |
thresholds = [threshold1, threshold2] | |
if st.sidebar.button('Run Simulation'): | |
data = get_stock_data(ticker, start_date, end_date) | |
bootstrap_simulations = bootstrap_simulation(data, days, n_iterations) | |
bootstrap_probabilities = calculate_probabilities(bootstrap_simulations, thresholds) | |
bootstrap_percentiles = calculate_percentiles(bootstrap_simulations) | |
fig1, mean_bootstrap_price, median_bootstrap_price, ci_68_bootstrap, ci_95_bootstrap, latest_price = plot_distributions(bootstrap_simulations, data, thresholds, bootstrap_probabilities) | |
fig2 = plot_price_with_cones(data, bootstrap_percentiles, days, thresholds, bootstrap_probabilities) | |
st.plotly_chart(fig1) | |
st.plotly_chart(fig2) | |
st.write(f""" | |
### Interpretation of Results | |
**Distribution of Final Simulated Prices:** | |
- **Mean Final Price:** {mean_bootstrap_price:.2f} | |
- **Median Final Price:** {median_bootstrap_price:.2f} | |
- **68% Confidence Interval (CI):** [{ci_68_bootstrap[0]:.2f}, {ci_68_bootstrap[1]:.2f}] | |
- **95% Confidence Interval (CI):** [{ci_95_bootstrap[0]:.2f}, {ci_95_bootstrap[1]:.2f}] | |
- **Latest Price:** {latest_price:.2f} | |
**Bootstrapping Simulation Cone:** | |
- **Bootstrap Median:** The median of the simulated future prices for each day. | |
- **Bootstrap 68% CI:** The 68% confidence interval for the simulated future prices. | |
- **Bootstrap 95% CI:** The 95% confidence interval for the simulated future prices. | |
- **Threshold 1 and Threshold 2:** {threshold1:.2f}, {threshold2:.2f} | |
- **Probability Annotations:** | |
- The probability of the stock price being below Threshold 1: {bootstrap_probabilities["below"]:.2%} | |
- The probability of the stock price being between Threshold 1 and Threshold 2: {bootstrap_probabilities["between"]:.2%} | |
- The probability of the stock price being above Threshold 2: {bootstrap_probabilities["above"]:.2%} | |
These results help in understanding the potential future movements of the stock price based on historical data and bootstrapping simulation. | |
""") | |
hide_streamlit_style = """ | |
<style> | |
#MainMenu {visibility: hidden;} | |
footer {visibility: hidden;} | |
</style> | |
""" | |
st.markdown(hide_streamlit_style, unsafe_allow_html=True) | |