Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,11 +1,3 @@
|
|
| 1 |
-
#import numpy as np
|
| 2 |
-
#np.core.multiarray # Ensure numpy's C API is loaded
|
| 3 |
-
#np._import_array() # Force initialization of numpy's C API
|
| 4 |
-
#import numpy.core.multiarray
|
| 5 |
-
|
| 6 |
-
# app.py
|
| 7 |
-
import init # Ensure numpy's C API is initialized
|
| 8 |
-
|
| 9 |
import numpy as np
|
| 10 |
import streamlit as st
|
| 11 |
import pandas as pd
|
|
@@ -26,93 +18,70 @@ It simulates various market scenarios to analyze the dependencies between differ
|
|
| 26 |
Users can assess potential outcomes in terms of best, worst, and mean case scenarios.
|
| 27 |
""")
|
| 28 |
|
| 29 |
-
|
| 30 |
-
st.sidebar.
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
# Sidebar inputs
|
| 39 |
-
st.sidebar.header("Input Parameters")
|
| 40 |
-
|
| 41 |
-
st.sidebar.subheader("Portfolio Allocation")
|
| 42 |
-
portfolio_tickers = st.sidebar.text_area("Enter Tickers (comma-separated)", "^GSPC,MSFT,AAPL,GOOGL,ASML").split(',')
|
| 43 |
-
portfolio_weights = st.sidebar.text_area("Enter Weights (comma-separated) (Must add to 1)", "0.1,0.3,0.3,0.2,0.1").split(',')
|
| 44 |
-
portfolio_weights = [float(weight) for weight in portfolio_weights]
|
| 45 |
-
|
| 46 |
-
drop_ticker = st.sidebar.text_input("Enter Drop Ticker", "^GSPC")
|
| 47 |
-
market_drop_percentage = st.sidebar.slider("Market Drop Percentage", min_value=-0.1, max_value=0.0, step=0.01, value=-0.05)
|
| 48 |
-
|
| 49 |
-
start_date = st.sidebar.date_input("Start Date", value=pd.to_datetime("2020-01-01"))
|
| 50 |
-
end_date = st.sidebar.date_input("End Date", value=pd.to_datetime("2025-01-01"))
|
| 51 |
-
num_simulations = st.sidebar.number_input("Number of Simulations", min_value=1000, max_value=100000, step=1000, value=50000)
|
| 52 |
-
rolling_window = st.sidebar.number_input("Rolling Window", min_value=7, max_value=1000, step=10, value=250)
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
if len(portfolio_tickers) != len(portfolio_weights):
|
| 56 |
st.sidebar.error("The number of tickers must match the number of weights.")
|
| 57 |
|
| 58 |
-
|
| 59 |
run_button = st.sidebar.button("Run Analysis")
|
| 60 |
|
| 61 |
if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
| 62 |
|
|
|
|
| 63 |
st.markdown("## Simulating Market Drop Scenarios: Best, Worst, and Mean Cases")
|
| 64 |
-
|
| 65 |
-
st.markdown("""
|
| 66 |
-
To prepare the returns for copula modeling, we transform them to a uniform distribution using quantile transformation.
|
| 67 |
-
This step ensures that the data fits within the range required by the copula model.
|
| 68 |
-
""")
|
| 69 |
-
st.latex(r"""
|
| 70 |
-
u_i = \frac{\text{rank}(R_i)}{n + 1}
|
| 71 |
-
""")
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
st.latex(r"""
|
| 79 |
-
C_{\nu}(u_1, u_2, \ldots, u_d) = t_{\nu, \Sigma}(t_{\nu}^{-1}(u_1), t_{\nu}^{-1}(u_2), \ldots, t_{\nu}^{-1}(u_d))
|
| 80 |
-
""")
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
and assess the portfolio's risk under various market conditions.
|
| 86 |
-
""")
|
| 87 |
-
|
| 88 |
-
#st.markdown("## Market Drop Scenarios")
|
| 89 |
-
st.markdown("""
|
| 90 |
-
We identify scenarios where the specified ticker drops by the given percentage. By analyzing these scenarios, we can evaluate how the portfolio performs under stress.
|
| 91 |
-
""")
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
For each identified scenario, we calculate the portfolio returns. The portfolio return is a weighted sum of the individual stock returns.
|
| 96 |
-
""")
|
| 97 |
-
st.latex(r"""
|
| 98 |
-
R_p = \sum_{i=1}^{n} w_i R_i
|
| 99 |
-
""")
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
We visualize the simulated portfolio returns using histograms, cumulative distribution functions (CDF), and kernel density estimates (KDE).
|
| 104 |
-
These visualizations help us understand the distribution and characteristics of potential returns.
|
| 105 |
-
""")
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
This helps in understanding the potential impact of market drops on the portfolio value.
|
| 111 |
-
""")
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
|
| 117 |
# Define the portfolio and allocation
|
| 118 |
portfolio_allocation = dict(zip(portfolio_tickers, portfolio_weights))
|
|
@@ -138,7 +107,6 @@ if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
|
| 138 |
copula.fit(data_uniform.values)
|
| 139 |
|
| 140 |
# Simulate scenarios
|
| 141 |
-
#num_simulations = 50000
|
| 142 |
simulated_uniform = copula.random(num_simulations)
|
| 143 |
simulated_returns = pd.DataFrame(index=range(num_simulations), columns=data_uniform.columns)
|
| 144 |
|
|
@@ -257,25 +225,23 @@ if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
|
| 257 |
|
| 258 |
st.plotly_chart(fig_price)
|
| 259 |
|
| 260 |
-
# Tail Dependence Analysis
|
| 261 |
# Tail Dependence Analysis
|
| 262 |
st.markdown("## Tail Dependence Analysis")
|
| 263 |
st.markdown("""
|
| 264 |
Tail dependence measures the likelihood of extreme returns occurring simultaneously across different stocks in the portfolio.
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
We use the Clayton copula to model the tail dependence. The parameter \\(\theta\\) of the Clayton copula is related to the tail dependence by:
|
| 268 |
-
""")
|
| 269 |
-
st.latex(r"""
|
| 270 |
-
\lambda_{L} = 2^{1/\theta} - 1
|
| 271 |
-
""")
|
| 272 |
-
st.markdown("""
|
| 273 |
-
Where:
|
| 274 |
-
- λ1 is the lower tail dependence.
|
| 275 |
-
- θ is the parameter of the Clayton copula.
|
| 276 |
-
|
| 277 |
-
A higher λ1 value indicates a stronger relationship in the tails, meaning that extreme losses in one stock are more likely to coincide with extreme losses in another.
|
| 278 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
def to_uniform(column):
|
| 281 |
n = len(column)
|
|
@@ -336,11 +302,10 @@ if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
|
| 336 |
""")
|
| 337 |
|
| 338 |
# Rolling window analysis
|
| 339 |
-
window_size = rolling_window #250
|
| 340 |
tail_parameters = []
|
| 341 |
|
| 342 |
-
for start in range(0, len(uniform_data) -
|
| 343 |
-
window_data = uniform_data.iloc[start:start +
|
| 344 |
|
| 345 |
copula = Clayton()
|
| 346 |
copula.fit(window_data.values)
|
|
@@ -350,7 +315,7 @@ if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
|
| 350 |
|
| 351 |
# Tail Dependence Over Time
|
| 352 |
fig1 = go.Figure()
|
| 353 |
-
fig1.add_trace(go.Scatter(x=returns.index[
|
| 354 |
fig1.update_layout(
|
| 355 |
title="Tail Dependence Over Time",
|
| 356 |
xaxis_title="Date",
|
|
@@ -359,11 +324,11 @@ if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
|
| 359 |
|
| 360 |
st.plotly_chart(fig1)
|
| 361 |
|
| 362 |
-
|
| 363 |
hide_streamlit_style = """
|
| 364 |
<style>
|
| 365 |
#MainMenu {visibility: hidden;}
|
| 366 |
footer {visibility: hidden;}
|
| 367 |
</style>
|
| 368 |
"""
|
| 369 |
-
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
|
|
|
| 18 |
Users can assess potential outcomes in terms of best, worst, and mean case scenarios.
|
| 19 |
""")
|
| 20 |
|
| 21 |
+
# Sidebar: How to Use (closed by default)
|
| 22 |
+
with st.sidebar.expander("How to Use", expanded=False):
|
| 23 |
+
st.write("""
|
| 24 |
+
1. Select the date range for the analysis.
|
| 25 |
+
2. Adjust the market drop percentage.
|
| 26 |
+
3. Enter the tickers and their respective weights.
|
| 27 |
+
4. Select the ticker for the drop scenario.
|
| 28 |
+
5. Click on 'Run Analysis' to execute.
|
| 29 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
# Sidebar: Ticker and Dates (open by default)
|
| 32 |
+
with st.sidebar.expander("Ticker and Dates", expanded=True):
|
| 33 |
+
portfolio_tickers = st.text_area("Enter Tickers (comma-separated)", "^GSPC,MSFT,AAPL,GOOGL,ASML",
|
| 34 |
+
help="Enter the tickers for the assets in the portfolio, separated by commas.").split(',')
|
| 35 |
+
portfolio_weights = st.text_area("Enter Weights (comma-separated) (Must add to 1)", "0.1,0.3,0.3,0.2,0.1",
|
| 36 |
+
help="Enter the weights for each ticker, separated by commas. Make sure they sum to 1.").split(',')
|
| 37 |
+
portfolio_weights = [float(weight) for weight in portfolio_weights]
|
| 38 |
+
|
| 39 |
+
start_date = st.date_input("Start Date", value=pd.to_datetime("2020-01-01"),
|
| 40 |
+
help="Select the start date for the analysis period.")
|
| 41 |
+
end_date = st.date_input("End Date", value=pd.to_datetime(pd.Timestamp.now().date() + pd.Timedelta(days=1)),
|
| 42 |
+
help="Select the end date for the analysis period.")
|
| 43 |
+
|
| 44 |
+
# Sidebar: Market Drop Settings (open by default)
|
| 45 |
+
with st.sidebar.expander("Market Drop Settings", expanded=True):
|
| 46 |
+
drop_ticker = st.text_input("Enter Drop Ticker", "^GSPC",
|
| 47 |
+
help="Enter the ticker of the asset that will experience a drop.")
|
| 48 |
+
market_drop_percentage = st.slider("Market Drop Percentage", min_value=-0.1, max_value=0.0, step=0.01, value=-0.05,
|
| 49 |
+
help="Set the percentage drop in the market for the scenario.")
|
| 50 |
+
num_simulations = st.number_input("Number of Simulations", min_value=1000, max_value=100000, step=1000, value=50000,
|
| 51 |
+
help="Set the number of simulations to run.")
|
| 52 |
+
rolling_window = st.number_input("Rolling Window", min_value=7, max_value=1000, step=10, value=250,
|
| 53 |
+
help="Set the rolling window size for tail dependence analysis.")
|
| 54 |
|
| 55 |
if len(portfolio_tickers) != len(portfolio_weights):
|
| 56 |
st.sidebar.error("The number of tickers must match the number of weights.")
|
| 57 |
|
|
|
|
| 58 |
run_button = st.sidebar.button("Run Analysis")
|
| 59 |
|
| 60 |
if run_button and len(portfolio_tickers) == len(portfolio_weights):
|
| 61 |
|
| 62 |
+
# Explanation of the Simulation and Copula Models
|
| 63 |
st.markdown("## Simulating Market Drop Scenarios: Best, Worst, and Mean Cases")
|
| 64 |
+
st.markdown("This section explores how the portfolio performs under simulated market drop scenarios using copula models.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
with st.expander("Methodology", expanded=False):
|
| 67 |
+
st.markdown("""
|
| 68 |
+
**Step 1: Transforming Returns**
|
| 69 |
+
- We transform the returns to a uniform distribution using quantile transformation.
|
| 70 |
+
- This ensures that the data fits within the range required by the copula model.
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
**Step 2: Fitting the Copula Model**
|
| 73 |
+
- A multivariate Student-t copula is fitted to the transformed data.
|
| 74 |
+
- The copula model captures the dependencies between the asset returns.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
+
**Step 3: Simulating Scenarios**
|
| 77 |
+
- We simulate numerous return scenarios using the fitted copula model to assess the portfolio's risk under various market conditions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
**Step 4: Portfolio Returns Calculation**
|
| 80 |
+
- For each identified scenario, we calculate the portfolio returns using a weighted sum of the individual asset returns.
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
+
**Step 5: Visualization**
|
| 83 |
+
- We visualize the simulated portfolio returns using histograms, CDFs, and KDEs to understand potential outcomes.
|
| 84 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
# Define the portfolio and allocation
|
| 87 |
portfolio_allocation = dict(zip(portfolio_tickers, portfolio_weights))
|
|
|
|
| 107 |
copula.fit(data_uniform.values)
|
| 108 |
|
| 109 |
# Simulate scenarios
|
|
|
|
| 110 |
simulated_uniform = copula.random(num_simulations)
|
| 111 |
simulated_returns = pd.DataFrame(index=range(num_simulations), columns=data_uniform.columns)
|
| 112 |
|
|
|
|
| 225 |
|
| 226 |
st.plotly_chart(fig_price)
|
| 227 |
|
|
|
|
| 228 |
# Tail Dependence Analysis
|
| 229 |
st.markdown("## Tail Dependence Analysis")
|
| 230 |
st.markdown("""
|
| 231 |
Tail dependence measures the likelihood of extreme returns occurring simultaneously across different stocks in the portfolio.
|
| 232 |
+
We use the Clayton copula to model the tail dependence. The parameter θ of the Clayton copula is related to the tail dependence by:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
""")
|
| 234 |
+
|
| 235 |
+
with st.expander("Tail Dependence Methodology", expanded=False):
|
| 236 |
+
st.latex(r"""
|
| 237 |
+
\lambda_{L} = 2^{1/\theta} - 1
|
| 238 |
+
""")
|
| 239 |
+
st.markdown("""
|
| 240 |
+
Where:
|
| 241 |
+
- λ1 is the lower tail dependence.
|
| 242 |
+
- θ is the parameter of the Clayton copula.
|
| 243 |
+
A higher λ1 value indicates a stronger relationship in the tails, meaning that extreme losses in one stock are more likely to coincide with extreme losses in another.
|
| 244 |
+
""")
|
| 245 |
|
| 246 |
def to_uniform(column):
|
| 247 |
n = len(column)
|
|
|
|
| 302 |
""")
|
| 303 |
|
| 304 |
# Rolling window analysis
|
|
|
|
| 305 |
tail_parameters = []
|
| 306 |
|
| 307 |
+
for start in range(0, len(uniform_data) - rolling_window):
|
| 308 |
+
window_data = uniform_data.iloc[start:start + rolling_window]
|
| 309 |
|
| 310 |
copula = Clayton()
|
| 311 |
copula.fit(window_data.values)
|
|
|
|
| 315 |
|
| 316 |
# Tail Dependence Over Time
|
| 317 |
fig1 = go.Figure()
|
| 318 |
+
fig1.add_trace(go.Scatter(x=returns.index[rolling_window:], y=tail_parameters, mode='lines', name='Tail Dependence Parameter'))
|
| 319 |
fig1.update_layout(
|
| 320 |
title="Tail Dependence Over Time",
|
| 321 |
xaxis_title="Date",
|
|
|
|
| 324 |
|
| 325 |
st.plotly_chart(fig1)
|
| 326 |
|
| 327 |
+
# Hide the Streamlit style elements
|
| 328 |
hide_streamlit_style = """
|
| 329 |
<style>
|
| 330 |
#MainMenu {visibility: hidden;}
|
| 331 |
footer {visibility: hidden;}
|
| 332 |
</style>
|
| 333 |
"""
|
| 334 |
+
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
|