Space16 / app.py
QuantumLearner's picture
Update app.py
45df39a verified
raw
history blame contribute delete
No virus
71 kB
import yfinance as yf
import numpy as np
import pandas as pd
import streamlit as st
import plotly.graph_objects as go
from pypfopt import EfficientFrontier, HRPOpt, EfficientCVaR, risk_models, expected_returns, black_litterman, objective_functions
from pypfopt.risk_models import CovarianceShrinkage
from scipy.optimize import minimize
from scipy.cluster.hierarchy import dendrogram
import matplotlib.pyplot as plt
# Helper functions
def fetch_stock_data(tickers, start_date, end_date):
return yf.download(tickers, start=start_date, end=end_date)['Adj Close']
def plot_efficient_frontier(mean_returns, cov_matrix, title):
ef = EfficientFrontier(mean_returns, cov_matrix)
ef.max_sharpe() # Maximize Sharpe ratio to get the optimal portfolio
cleaned_weights = ef.clean_weights()
# Calculate the portfolio performance for the optimal portfolio
mu, sigma, sharpe = ef.portfolio_performance()
# Re-instantiate to avoid constraints issue for efficient frontier calculation
ef = EfficientFrontier(mean_returns, cov_matrix)
# Efficient frontier data
frontier_y = []
frontier_x = []
for r in np.linspace(0, mu, 100): # Ensure target returns do not exceed maximum return
ef.efficient_return(r)
perf = ef.portfolio_performance()
frontier_y.append(perf[0])
frontier_x.append(perf[1])
# Plotly figure
fig = go.Figure()
# Plot the efficient frontier
fig.add_trace(go.Scatter(x=frontier_x, y=frontier_y, mode='lines', name='Efficient Frontier'))
# Add the optimal portfolio point
fig.add_trace(go.Scatter(x=[sigma], y=[mu], mode='markers', marker=dict(color='red', size=10), name='Optimal Portfolio'))
# Add individual asset points
asset_returns = mean_returns.values
asset_volatility = np.sqrt(np.diag(cov_matrix))
fig.add_trace(go.Scatter(x=asset_volatility, y=asset_returns, mode='markers+text', text=mean_returns.index, name='Assets', textposition='top center'))
fig.update_layout(title=title, xaxis_title='Volatility', yaxis_title='Return')
return fig
def plot_portfolio_weights(weights, tickers, title):
fig = go.Figure(data=[go.Bar(x=tickers, y=weights)])
fig.update_layout(title=title, xaxis_title='Assets', yaxis_title='Weights')
return fig
def plot_cumulative_returns(portfolio_returns, sp500_returns, equal_weighted_returns, title):
portfolio_cumulative = (1 + portfolio_returns).cumprod()
sp500_cumulative = (1 + sp500_returns).cumprod()
equal_weighted_cumulative = (1 + equal_weighted_returns).cumprod()
fig = go.Figure()
fig.add_trace(go.Scatter(x=portfolio_cumulative.index, y=portfolio_cumulative, mode='lines', name='Portfolio'))
fig.add_trace(go.Scatter(x=sp500_cumulative.index, y=sp500_cumulative, mode='lines', name='S&P 500'))
fig.add_trace(go.Scatter(x=equal_weighted_cumulative.index, y=equal_weighted_cumulative, mode='lines', name='Equal Weighted'))
fig.update_layout(title=title, xaxis_title='Date', yaxis_title='Cumulative Returns')
return fig
def plot_rolling_metrics(portfolio_returns, sp500_returns, equal_weighted_returns, risk_free_rate, title_volatility, title_sharpe):
rolling_window = 252 # 1 year
rolling_volatility_portfolio = portfolio_returns.rolling(window=rolling_window).std() * np.sqrt(252)
rolling_sharpe_portfolio = portfolio_returns.rolling(window=rolling_window).apply(lambda x: (x.mean() - risk_free_rate / 252) / x.std()) * np.sqrt(252)
rolling_volatility_sp500 = sp500_returns.rolling(window=rolling_window).std() * np.sqrt(252)
rolling_sharpe_sp500 = sp500_returns.rolling(window=rolling_window).apply(lambda x: (x.mean() - risk_free_rate / 252) / x.std()) * np.sqrt(252)
rolling_volatility_equal = equal_weighted_returns.rolling(window=rolling_window).std() * np.sqrt(252)
rolling_sharpe_equal = equal_weighted_returns.rolling(window=rolling_window).apply(lambda x: (x.mean() - risk_free_rate / 252) / x.std()) * np.sqrt(252)
fig_volatility = go.Figure()
fig_volatility.add_trace(go.Scatter(x=rolling_volatility_portfolio.index, y=rolling_volatility_portfolio, mode='lines', name='Portfolio Volatility'))
fig_volatility.add_trace(go.Scatter(x=rolling_volatility_sp500.index, y=rolling_volatility_sp500, mode='lines', name='S&P 500 Volatility'))
fig_volatility.add_trace(go.Scatter(x=rolling_volatility_equal.index, y=rolling_volatility_equal, mode='lines', name='Equal Weighted Volatility'))
fig_volatility.update_layout(title=title_volatility, xaxis_title='Date', yaxis_title='Volatility')
fig_sharpe = go.Figure()
fig_sharpe.add_trace(go.Scatter(x=rolling_sharpe_portfolio.index, y=rolling_sharpe_portfolio, mode='lines', name='Portfolio Sharpe Ratio'))
fig_sharpe.add_trace(go.Scatter(x=rolling_sharpe_sp500.index, y=rolling_sharpe_sp500, mode='lines', name='S&P 500 Sharpe Ratio'))
fig_sharpe.add_trace(go.Scatter(x=rolling_sharpe_equal.index, y=rolling_sharpe_equal, mode='lines', name='Equal Weighted Sharpe Ratio'))
fig_sharpe.update_layout(title=title_sharpe, xaxis_title='Date', yaxis_title='Sharpe Ratio')
return fig_volatility, fig_sharpe
def plot_max_drawdown(portfolio_returns, sp500_returns, equal_weighted_returns, title):
rolling_window = 252 # 1 year
def max_drawdown(return_series):
cumulative = (1 + return_series).cumprod()
peak = cumulative.cummax()
drawdown = (cumulative - peak) / peak
return drawdown.min()
portfolio_drawdown = portfolio_returns.rolling(window=rolling_window).apply(max_drawdown, raw=False)
sp500_drawdown = sp500_returns.rolling(window=rolling_window).apply(max_drawdown, raw=False)
equal_weighted_drawdown = equal_weighted_returns.rolling(window=rolling_window).apply(max_drawdown, raw=False)
fig = go.Figure()
fig.add_trace(go.Scatter(x=portfolio_drawdown.index, y=portfolio_drawdown, mode='lines', name='Portfolio Drawdown'))
fig.add_trace(go.Scatter(x=sp500_drawdown.index, y=sp500_drawdown, mode='lines', name='S&P 500 Drawdown'))
fig.add_trace(go.Scatter(x=equal_weighted_drawdown.index, y=equal_weighted_drawdown, mode='lines', name='Equal Weighted Drawdown'))
fig.update_layout(title=title, xaxis_title='Date', yaxis_title='Maximum Drawdown')
return fig
def plot_dendrogram(hrp, tickers, title):
plt.figure(figsize=(10, 6))
dendrogram(hrp.clusters, labels=tickers)
plt.title(title)
plt.xlabel('Assets')
plt.ylabel('Distance')
st.pyplot(plt.gcf())
def multi_objective_function(weights, mean_returns, cov_matrix, dividend_yields, maximize_return, minimize_risk, maximize_sharpe, maximize_diversification, minimize_cvar, minimize_transaction_costs, maximize_dividend_yield, risk_free_rate=0.01):
obj_value = 0
if maximize_return:
obj_value -= np.dot(weights, mean_returns) # Maximize return
if minimize_risk:
obj_value += np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) # Minimize risk
if maximize_sharpe:
portfolio_return = np.dot(weights, mean_returns)
portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_volatility
obj_value -= sharpe_ratio # Maximize Sharpe ratio
if maximize_diversification:
w_vol = np.dot(weights.T, np.sqrt(np.diag(cov_matrix)))
p_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
diversification_ratio = w_vol / p_vol
obj_value -= diversification_ratio # Maximize diversification
if minimize_cvar:
cvar = np.percentile(np.dot(returns.values, weights), 5)
obj_value += cvar # Minimize CVaR
if minimize_transaction_costs:
prev_weights = np.ones(len(weights)) / len(weights) # Assume equal weights previously
transaction_costs = np.sum(np.abs(weights - prev_weights)) * 0.001 # Assume 0.1% cost
obj_value += transaction_costs # Minimize transaction costs
if maximize_dividend_yield:
obj_value -= np.dot(weights, dividend_yields) # Maximize dividend yield
return obj_value
def optimize_portfolio(tickers, mean_returns, cov_matrix, dividend_yields, maximize_return, minimize_risk, maximize_sharpe, maximize_diversification, minimize_cvar, minimize_transaction_costs, maximize_dividend_yield):
num_assets = len(tickers)
init_guess = num_assets * [1. / num_assets,]
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, {'type': 'ineq', 'fun': lambda x: x})
opt_result = minimize(multi_objective_function, init_guess, args=(mean_returns, cov_matrix, dividend_yields, maximize_return, minimize_risk, maximize_sharpe, maximize_diversification, minimize_cvar, minimize_transaction_costs, maximize_dividend_yield), method='SLSQP', bounds=[(0, 1) for _ in range(num_assets)], constraints=constraints)
return opt_result.x
# New method: Maximum Diversification Portfolio
def diversification_ratio(weights, cov_matrix, volatilities):
portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
weighted_volatilities = np.dot(weights, volatilities)
return -weighted_volatilities / portfolio_volatility
def optimize_maximum_diversification(tickers, returns, mean_returns, cov_matrix, volatilities):
num_assets = len(tickers)
init_guess = num_assets * [1. / num_assets,]
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, {'type': 'ineq', 'fun': lambda x: x})
opt_result = minimize(diversification_ratio, init_guess, args=(cov_matrix, volatilities), method='SLSQP', bounds=[(0, 1) for _ in range(num_assets)], constraints=constraints)
return opt_result.x
# New method: Maximum Sharpe Ratio Portfolio
def optimize_maximum_sharpe(mean_returns, cov_matrix):
ef = EfficientFrontier(mean_returns, cov_matrix)
optimal_weights = ef.max_sharpe()
return ef.clean_weights()
# New method: Equal Risk Contribution Portfolio
def optimize_equal_risk_contribution(mean_returns, cov_matrix):
ef = EfficientFrontier(mean_returns, cov_matrix)
ef.add_objective(objective_functions.L2_reg)
optimal_weights = ef.min_volatility()
return ef.clean_weights()
# New method: CVaR Minimization Portfolio
def optimize_cvar_minimization(mean_returns, returns):
cvar = EfficientCVaR(mean_returns, returns)
optimal_weights = cvar.min_cvar()
return cvar.clean_weights()
# New method: Maximum Return Portfolio
def optimize_max_return(mean_returns, cov_matrix):
ef = EfficientFrontier(mean_returns, cov_matrix)
target_return = mean_returns.max() * 0.99 # Slightly below the maximum expected return
optimal_weights = ef.efficient_return(target_return=target_return)
return ef.clean_weights()
# New method: Maximum Quadratic Utility Portfolio
def optimize_max_quadratic_utility(mean_returns, cov_matrix, risk_aversion):
ef = EfficientFrontier(mean_returns, cov_matrix)
optimal_weights = ef.max_quadratic_utility(risk_aversion=risk_aversion)
return ef.clean_weights()
# Streamlit app
st.set_page_config(page_title="Portfolio Optimization", layout="wide")
st.title('Portfolio Optimization Methods')
st.markdown("""
This tool allows you to optimize a portfolio of stocks using different optimization methods. You can choose between optimization methods simply by selecting the method, enterING the stock tickers, and setting the parameters. Click the "Run" button to see the results.
""")
with st.sidebar.expander("How to Use", expanded=False):
st.write("""
## Instructions:
1. Select the optimization method from the sidebar.
2. Enter the stock tickers, start date, and end date.
3. For Multi-Objective Optimization, set the optimization objectives.
4. For Robust Optimization, set the L2 regularization parameter (gamma).
5. For Black-Litterman Model, set the market data and your views.
6. Click the "Run" button to perform the optimization and view the results.
""")
st.sidebar.markdown("""
## Input Parameters:
""")
# Expander for Asset Symbols and Dates
with st.sidebar.expander("Asset Symbols and Dates", expanded=True):
tickers_input = st.text_input('Enter Stock Tickers (comma-separated)', 'AAPL, MSFT, GOOGL, AMZN, ASML, META, JPM, V',
help="Input stock tickers separated by commas. Example: 'AAPL, MSFT, GOOGL'")
tickers = [ticker.strip() for ticker in tickers_input.split(',')]
start_date = st.date_input('Start Date', pd.to_datetime('2015-01-01'),
help="Select the start date for the analysis.")
end_date = st.date_input('End Date', pd.to_datetime(pd.Timestamp.now().date() + pd.Timedelta(days=1)),
help="Select the end date for the analysis.")
# Expander for Optimization Method
with st.sidebar.expander("Optimization Method", expanded=True):
selected = st.radio("Optimization Methods", ["Multi-Objective Optimization", "Robust Optimization", "Mean-Variance Optimization", "Black-Litterman Model", "Hierarchical Risk Parity", "Maximum Diversification", "Maximum Sharpe Ratio", "Equal Risk Contribution", "CVaR Minimization", "Maximum Return", "Maximum Quadratic Utility"],
help="Select the optimization method you want to apply.")
if selected == "Multi-Objective Optimization":
st.markdown("""
### Multi-Objective Optimization
Multi-Objective Optimization in portfolio management allows investors to consider multiple objectives simultaneously.
This method aims to find the best portfolio by balancing various goals such as maximizing returns, minimizing risk, maximizing the Sharpe ratio, enhancing diversification, and more.
""")
# Expander for Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Formulas:
**Expected Return**:
""")
st.latex(r"""
\mu = \sum_{i=1}^{n} w_i \mu_i
""")
st.markdown("""
**Portfolio Variance**:
""")
st.latex(r"""
\sigma^2 = \sum_{i=1}^{n} \sum_{j=1}^{n} w_i w_j \sigma_{ij}
""")
st.markdown("""
**Sharpe Ratio**:
""")
st.latex(r"""
S = \frac{\mu_p - r_f}{\sigma_p}
""")
st.markdown("""
**Diversification Ratio**:
""")
st.latex(r"""
DR = \frac{\sum_{i=1}^{n} w_i \sigma_i}{\sigma_p}
""")
st.markdown("""
**Conditional Value at Risk (CVaR)**:
""")
st.latex(r"""
\text{CVaR}_\alpha = \mathbb{E}[X | X \leq \text{VaR}_\alpha]
""")
st.markdown("""
#### Mathematical Optimization Process
The optimization process involves solving a mathematical problem to find the portfolio weights \( wi \) that best meet the selected objectives. The optimization problem can be formulated as follows:
**Objective Function**:
Minimize or maximize a weighted sum of multiple objectives. The overall objective function can be expressed as:
""")
st.latex(r"""
\text{Objective} = \lambda_1 f_1(\mathbf{w}) + \lambda_2 f_2(\mathbf{w}) + \cdots + \lambda_k f_k(\mathbf{w})
""")
st.markdown("""
where:
- \( \lambda_i \) are the weights assigned to each objective.
- \( \mathbf{w} \) is the vector of portfolio weights.
**Constraints**:
1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
""")
st.latex(r"""
\sum_{i=1}^{n} w_i = 1
""")
st.markdown("""
2. No short selling (optional):
""")
st.latex(r"""
w_i \geq 0, \quad \forall i
""")
st.markdown("""
3. Additional constraints based on user preferences or regulatory requirements can also be included.
The optimization is performed using numerical methods, i.e. using the Sequential Least Squares Programming (SLSQP) algorithm. This algorithm iteratively adjusts the portfolio weights to find the optimal solution that satisfies the constraints and minimizes (or maximizes) the objective function.
""")
st.sidebar.header("Optimization Parameters")
maximize_return = st.sidebar.checkbox('Maximize Return', value=True)
minimize_risk = st.sidebar.checkbox('Minimize Risk', value=True)
maximize_sharpe = st.sidebar.checkbox('Maximize Sharpe Ratio', value=False)
maximize_diversification = st.sidebar.checkbox('Maximize Diversification', value=False)
minimize_cvar = st.sidebar.checkbox('Minimize CVaR', value=False)
minimize_transaction_costs = st.sidebar.checkbox('Minimize Transaction Costs', value=False)
maximize_dividend_yield = st.sidebar.checkbox('Maximize Dividend Yield', value=True)
if st.sidebar.button('Run Analysis'):
if 'multi_objective_result' not in st.session_state:
st.session_state.multi_objective_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
dividend_yields = {}
for ticker in tickers:
stock = yf.Ticker(ticker)
info = stock.info
dividend_yield = info.get('dividendYield') or info.get('trailingAnnualDividendYield', 0)
if dividend_yield is None:
dividend_yield = 0
dividend_yields[ticker] = dividend_yield
dividend_yields = pd.Series(dividend_yields)
optimal_weights = optimize_portfolio(tickers, mean_returns, cov_matrix, dividend_yields, maximize_return, minimize_risk, maximize_sharpe, maximize_diversification, minimize_cvar, minimize_transaction_costs, maximize_dividend_yield)
cleaned_weights = dict(zip(tickers, optimal_weights))
st.session_state.multi_objective_result['cleaned_weights'] = cleaned_weights
st.session_state.multi_objective_result['returns'] = returns
st.session_state.multi_objective_result['optimal_weights'] = optimal_weights
if 'multi_objective_result' in st.session_state:
cleaned_weights = st.session_state.multi_objective_result['cleaned_weights']
returns = st.session_state.multi_objective_result['returns']
optimal_weights = st.session_state.multi_objective_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights.values()), list(cleaned_weights.keys()), 'Multi-Objective Optimization Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns = returns.dot(optimal_weights)
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Robust Optimization":
st.markdown("""
### Robust Optimization
Robust Optimization aims to create more stable portfolios by incorporating regularization techniques.
This method adjusts the covariance matrix to reduce the impact of estimation errors, noisy or uncertain data.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The covariance matrix can be unstable due to limited data or market volatility.
Robust Optimization addresses this by applying techniques such as covariance shrinkage to produce a more stable covariance matrix.
#### Regularization Technique:
One common approach in Robust Optimization is the Ledoit-Wolf shrinkage method, which adjusts the sample covariance matrix towards a more structured target, such as the identity matrix.
**Covariance Shrinkage**:
""")
st.latex(r"""
\Sigma_{\text{shrink}} = \lambda T + (1 - \lambda) S
""")
st.markdown("""
Where:
- \( Σ shrink \) is the shrinkage estimator of the covariance matrix.
- \( λ \) is the shrinkage intensity (a value between 0 and 1).
- \( T \) is the target covariance matrix (e.g., identity matrix).
- \( S \) is the sample covariance matrix.
#### Shrinkage Intensity:
The shrinkage intensity \( λ \) determines the balance between the sample covariance matrix and the target.
A higher \( λ \) places more weight on the target matrix, while a lower \( λ \) places more weight on the sample covariance matrix.
#### Mathematical Optimization Process:
The optimization problem can be expressed as:
**Objective Function**:
""")
st.latex(r"""
\text{Minimize } \mathbf{w}^T \Sigma_{\text{shrink}} \mathbf{w}
""")
st.markdown("""
**Constraints**:
1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
""")
st.latex(r"""
\sum_{i=1}^{n} w_i = 1
""")
st.markdown("""
2. No short selling (optional):
""")
st.latex(r"""
w_i \geq 0, \quad \forall i
""")
st.markdown("""
#### Practical Benefits:
- **Stability**: By reducing the impact of outliers and estimation errors, Robust Optimization produces more stable portfolios.
- **Performance**: It can improve portfolio performance by avoiding extreme weights that might result from noisy data.
- **Applicability**: Suitable for environments with high market volatility or limited historical data.
""")
st.sidebar.header("Optimization Parameters")
gamma = st.sidebar.slider('L2 Regularization (Gamma)', min_value=0.0, max_value=1.0, value=0.1, step=0.01)
if st.sidebar.button('Run Analysis'):
if 'robust_result' not in st.session_state:
st.session_state.robust_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix_shrinked = CovarianceShrinkage(data).ledoit_wolf()
ef = EfficientFrontier(mean_returns, cov_matrix_shrinked)
ef.add_objective(objective_functions.L2_reg, gamma=gamma)
optimal_weights_robust = ef.max_sharpe()
cleaned_weights_robust = ef.clean_weights()
st.session_state.robust_result['cleaned_weights'] = cleaned_weights_robust
st.session_state.robust_result['returns'] = returns
st.session_state.robust_result['optimal_weights'] = optimal_weights_robust
if 'robust_result' in st.session_state:
cleaned_weights_robust = st.session_state.robust_result['cleaned_weights']
returns = st.session_state.robust_result['returns']
optimal_weights_robust = st.session_state.robust_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_robust.values()), list(cleaned_weights_robust.keys()), 'Robust Optimization Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_robust = returns.dot(np.array(list(cleaned_weights_robust.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_robust, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_robust, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_robust, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Mean-Variance Optimization":
st.markdown("""
### Mean-Variance Optimization
Mean-Variance Optimization, introduced by Harry Markowitz aims to balance return and risk by maximizing the Sharpe ratio, thereby finding the portfolio with the best risk-adjusted returns.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The Mean-Variance Optimization approach is based on the idea of creating a portfolio that provides the maximum expected return for a given level of risk or, equivalently, the minimum risk for a given level of expected return.
This is achieved by selecting the proportions of various assets in the portfolio.
#### Sharpe Ratio:
The Sharpe ratio is a measure of the risk-adjusted return of a portfolio. It is calculated as:
""")
st.latex(r"""
\text{Sharpe Ratio} = \frac{\mathbb{E}[R_p] - R_f}{\sigma_p}
""")
st.markdown("""
Where:
- \( E[Rp] \) is the expected return of the portfolio.
- \( Rf \) is the risk-free rate.
- \( σp \) is the standard deviation of the portfolio returns.
#### Mathematical Optimization Process:
The optimization problem can be formulated as follows:
**Objective Function**:
""")
st.latex(r"""
\text{Maximize } \frac{\mathbf{w}^T \mathbf{\mu} - R_f}{\sqrt{\mathbf{w}^T \Sigma \mathbf{w}}}
""")
st.markdown("""
Where:
- \( w \) is the vector of portfolio weights.
- \( μ \) is the vector of expected asset returns.
- \( ∑ \) is the covariance matrix of asset returns.
**Constraints**:
1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
""")
st.latex(r"""
\sum_{i=1}^{n} w_i = 1
""")
st.markdown("""
2. No short selling (optional):
""")
st.latex(r"""
w_i \geq 0, \quad \forall i
""")
st.markdown("""
#### Benefits of Mean-Variance Optimization:
- **Risk Management**: It helps in managing the trade-off between risk and return by considering the covariance among asset returns.
- **Diversification**: By incorporating the covariance matrix, it promotes diversification and reduces unsystematic risk.
- **Simplicity**: It provides a straightforward framework for constructing efficient portfolios.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'mean_variance_result' not in st.session_state:
st.session_state.mean_variance_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
ef = EfficientFrontier(mean_returns, cov_matrix)
optimal_weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
st.session_state.mean_variance_result['cleaned_weights'] = cleaned_weights
st.session_state.mean_variance_result['returns'] = returns
st.session_state.mean_variance_result['optimal_weights'] = optimal_weights
st.session_state.mean_variance_result['mean_returns'] = mean_returns # Store mean returns
st.session_state.mean_variance_result['cov_matrix'] = cov_matrix # Store covariance matrix
if 'mean_variance_result' in st.session_state:
cleaned_weights = st.session_state.mean_variance_result['cleaned_weights']
returns = st.session_state.mean_variance_result['returns']
optimal_weights = np.array(list(st.session_state.mean_variance_result['optimal_weights'].values()))
mean_returns = st.session_state.mean_variance_result['mean_returns']
cov_matrix = st.session_state.mean_variance_result['cov_matrix']
fig_weights = plot_portfolio_weights(list(cleaned_weights.values()), list(cleaned_weights.keys()), 'Mean-Variance Optimization Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns = returns.dot(optimal_weights)
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
fig_efficient_frontier = plot_efficient_frontier(mean_returns, cov_matrix, 'Efficient Frontier with Optimal Portfolio')
st.plotly_chart(fig_efficient_frontier)
elif selected == "Black-Litterman Model":
st.markdown("""
### Black-Litterman Model
The Black-Litterman Model combines market equilibrium with investor views to adjust the expected returns and then applies mean-variance optimization. This approach is particularly useful for incorporating subjective views into a theoretically sound framework.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The Black-Litterman Model starts with the market equilibrium returns, which are implied by market capitalizations and a given risk aversion coefficient. Investors can then specify their own views on expected returns for certain assets or combinations of assets.
#### Market Equilibrium Returns:
The market equilibrium returns, \( \Pi \), are given by:
""")
st.latex(r"""
\Pi = \delta \Sigma w_{mkt}
""")
st.markdown("""
Where:
- \( δ \) is the risk aversion coefficient.
- \( Σ \) is the covariance matrix of asset returns.
- \( wmkt \) is the vector of market capitalization weights.
#### Investor Views:
Investor views are expressed in the form of a matrix \( P \) and a vector \( Q \):
""")
st.latex(r"""
P \cdot \mu = Q
""")
st.markdown("""
Where:
- \( P \) is the matrix that identifies the assets involved in each view.
- \( μ \) is the vector of expected returns.
- \( Q \) is the vector of view returns.
#### Adjusted Expected Returns:
The adjusted expected returns, \( μ \), are then calculated as:
""")
st.latex(r"""
\mu = \left( \left( \tau \Sigma \right)^{-1} + P^T \Omega^{-1} P \right)^{-1} \left( \left( \tau \Sigma \right)^{-1} \Pi + P^T \Omega^{-1} Q \right)
""")
st.markdown("""
Where:
- \( τ \) is a scalar representing the uncertainty in the prior estimates.
- \( Ω \) is the diagonal covariance matrix of the error terms in the views.
#### Optimization:
Once the adjusted expected returns, \( \mu \), are obtained, we can apply mean-variance optimization to find the optimal portfolio weights.
**Objective Function**:
""")
st.latex(r"""
\text{Maximize } \frac{\mathbf{w}^T \mathbf{\mu} - R_f}{\sqrt{\mathbf{w}^T \Sigma \mathbf{w}}}
""")
st.markdown("""
**Constraints**:
1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
""")
st.latex(r"""
\sum_{i=1}^{n} w_i = 1
""")
st.markdown("""
2. No short selling (optional):
""")
st.latex(r"""
w_i \geq 0, \quad \forall i
""")
st.markdown("""
#### Benefits of the Black-Litterman Model:
- **Flexibility**: Incorporates investor views into the optimization process.
- **Stability**: Produces more stable and intuitive expected returns than traditional mean-variance optimization.
- **Theoretical Foundation**: Combines the principles of modern portfolio theory with practical investor insights.
The Black-Litterman Model is a tool for integrating subjective views with market data.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'black_litterman_result' not in st.session_state:
st.session_state.black_litterman_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
# Download market data for market capitalization weights (here using S&P 500 as proxy)
market_data = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
# Calculate market-implied risk aversion
delta = black_litterman.market_implied_risk_aversion(market_data)
# Fetch market capitalizations using yfinance
market_caps = {}
for ticker in tickers:
stock = yf.Ticker(ticker)
market_caps[ticker] = stock.info['marketCap']
market_caps_series = pd.Series(market_caps)
# Calculate market-implied prior returns
prior = black_litterman.market_implied_prior_returns(market_caps_series, delta, cov_matrix)
# Create a picking matrix P and a view vector Q (e.g., no views)
P = np.identity(len(tickers))
Q = np.zeros(len(tickers))
# Run Black-Litterman model
bl = black_litterman.BlackLittermanModel(cov_matrix, pi=prior, P=P, Q=Q)
bl_return = bl.bl_returns()
# Perform mean-variance optimization with Black-Litterman returns
ef = EfficientFrontier(bl_return, cov_matrix)
optimal_weights_bl = ef.max_sharpe()
cleaned_weights_bl = ef.clean_weights()
st.session_state.black_litterman_result['cleaned_weights'] = cleaned_weights_bl
st.session_state.black_litterman_result['returns'] = returns
st.session_state.black_litterman_result['optimal_weights'] = optimal_weights_bl
if 'black_litterman_result' in st.session_state:
cleaned_weights_bl = st.session_state.black_litterman_result['cleaned_weights']
returns = st.session_state.black_litterman_result['returns']
optimal_weights_bl = np.array(list(st.session_state.black_litterman_result['optimal_weights'].values()))
fig_weights = plot_portfolio_weights(list(cleaned_weights_bl.values()), list(cleaned_weights_bl.keys()), 'Black-Litterman Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_bl = returns.dot(optimal_weights_bl)
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_bl, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_bl, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_bl, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Hierarchical Risk Parity":
st.markdown("""
### Hierarchical Risk Parity (HRP)
Hierarchical Risk Parity (HRP) is a method that groups assets into clusters based on their correlations and then allocates risk evenly among these clusters.
This approach aims to improve diversification and reduce risk concentration in the portfolio.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
HRP involves the following key steps:
1. **Tree Clustering**:
Assets are grouped into clusters using hierarchical clustering methods. This step forms a tree structure that helps identify similar assets based on their correlations.
2. **Quasi-Diagonalization**:
The covariance matrix is rearranged into a block-diagonal form. This reordering ensures that the most correlated assets are placed next to each other, facilitating more effective risk allocation.
3. **Recursive Bisection**:
Risk is allocated recursively from the top of the tree down to the individual assets. At each level, risk is split evenly between the clusters, ensuring that each cluster receives an equal share of the portfolio risk.
#### Hierarchical Clustering:
Hierarchical clustering is performed using the correlation matrix of asset returns. The distance between assets \( i \) and \( j \) is given by:
""")
st.latex(r"""
d_{i,j} = \sqrt{2 (1 - \rho_{i,j})}
""")
st.markdown("""
Where:
- \( ρ_{i,j} \) is the correlation coefficient between assets \( i \) and \( j \).
#### Quasi-Diagonalization:
The covariance matrix \( \Sigma \) is reordered to a block-diagonal form using the clustering information. This step ensures that the assets with higher correlations are grouped together.
#### Recursive Bisection:
Risk is allocated recursively using the following steps:
1. **Top-Level Allocation**:
The portfolio risk is split equally between the top-level clusters.
""")
st.latex(r"""
w_{\text{parent}} = \frac{\sigma_{\text{left}}}{\sigma_{\text{left}} + \sigma_{\text{right}}}
""")
st.markdown("""
2. **Sub-Level Allocation**:
The risk within each cluster is further split recursively until the individual assets are reached.
This recursive process ensures that risk is allocated evenly across all clusters and individual assets.
""")
st.markdown("""
#### Benefits of HRP:
- **Improved Diversification**: By clustering similar assets and allocating risk evenly, HRP achieves better diversification compared to traditional methods.
- **Reduced Risk Concentration**: HRP avoids concentrating risk in highly correlated assets, resulting in a more balanced portfolio.
- **Robustness**: HRP is less sensitive to estimation errors in the covariance matrix, making it a robust method for portfolio optimization.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'hrp_result' not in st.session_state:
st.session_state.hrp_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
hrp = HRPOpt(returns)
hrp_weights = hrp.optimize()
cleaned_weights_hrp = hrp.clean_weights()
st.session_state.hrp_result['cleaned_weights'] = cleaned_weights_hrp
st.session_state.hrp_result['returns'] = returns
st.session_state.hrp_result['optimal_weights'] = hrp_weights
st.session_state.hrp_result['hrp'] = hrp # Store HRP model
if 'hrp_result' in st.session_state:
cleaned_weights_hrp = st.session_state.hrp_result['cleaned_weights']
returns = st.session_state.hrp_result['returns']
optimal_weights_hrp = np.array(list(st.session_state.hrp_result['optimal_weights'].values()))
hrp = st.session_state.hrp_result['hrp'] # Retrieve HRP model
fig_weights = plot_portfolio_weights(list(cleaned_weights_hrp.values()), list(cleaned_weights_hrp.keys()), 'Hierarchical Risk Parity Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_hrp = returns.dot(optimal_weights_hrp)
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_hrp, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_hrp, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_hrp, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
st.write("### Dendrogram of Asset Clusters")
plot_dendrogram(hrp, tickers, 'Dendrogram of Asset Clusters')
elif selected == "Maximum Diversification Portfolio":
st.markdown("""
### Maximum Diversification Portfolio
The Maximum Diversification Portfolio method aims to create a portfolio that maximizes the diversification ratio. This approach allocates weights to assets to maximize the ratio of the weighted average volatility of individual assets to the overall portfolio volatility.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The diversification ratio (DR) is defined as:
""")
st.latex(r"""
DR = \frac{\sum_{i=1}^{n} w_i \sigma_i}{\sqrt{w^T \Sigma w}}
""")
st.markdown("""
Where:
- \( w_i \) is the weight of asset \( i \) in the portfolio.
- \( \sigma_i \) is the volatility of asset \( i \).
- \( \Sigma \) is the covariance matrix of asset returns.
- \( w \) is the weight vector of the portfolio.
#### Objective:
The objective of the Maximum Diversification Portfolio is to maximize this diversification ratio. By doing so, the portfolio aims to achieve the highest possible diversification benefit, reducing the overall portfolio risk.
#### Mathematical Formulation:
The optimization problem can be formulated as:
""")
st.latex(r"""
\max_{w} \quad \frac{\sum_{i=1}^{n} w_i \sigma_i}{\sqrt{w^T \Sigma w}}
\quad \text{subject to} \quad \sum_{i=1}^{n} w_i = 1, \quad w_i \ge 0
""")
st.markdown("""
This ensures that the sum of the weights is equal to 1 (fully invested portfolio) and that all weights are non-negative (long-only portfolio).
#### Steps Involved:
1. **Calculate Individual Volatilities**:
Compute the volatility (\( \sigma_i \)) for each asset in the portfolio.
2. **Compute the Covariance Matrix**:
Calculate the covariance matrix (\( \Sigma \)) of asset returns.
3. **Optimize the Diversification Ratio**:
Use optimization techniques to find the weight vector (\( w \)) that maximizes the diversification ratio, subject to the constraints.
""")
st.markdown("""
#### Benefits:
- **Enhanced Diversification**: By focusing on maximizing the diversification ratio, this method ensures that the portfolio is well-diversified, reducing the impact of any single asset on the overall portfolio risk.
- **Reduced Portfolio Risk**: By maximizing the diversification benefit, the overall portfolio risk is minimized compared to other traditional optimization methods.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'mdp_result' not in st.session_state:
st.session_state.mdp_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = returns.mean()
cov_matrix = returns.cov()
volatilities = returns.std()
optimal_weights_mdp = optimize_maximum_diversification(tickers, returns, mean_returns, cov_matrix, volatilities)
cleaned_weights_mdp = dict(zip(tickers, optimal_weights_mdp))
st.session_state.mdp_result['cleaned_weights'] = cleaned_weights_mdp
st.session_state.mdp_result['returns'] = returns
st.session_state.mdp_result['optimal_weights'] = optimal_weights_mdp
if 'mdp_result' in st.session_state:
cleaned_weights_mdp = st.session_state.mdp_result['cleaned_weights']
returns = st.session_state.mdp_result['returns']
optimal_weights_mdp = st.session_state.mdp_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_mdp.values()), list(cleaned_weights_mdp.keys()), 'Maximum Diversification Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_mdp = returns.dot(optimal_weights_mdp)
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mdp, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_mdp, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_mdp, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Maximum Sharpe Ratio Portfolio":
st.markdown("""
### Maximum Sharpe Ratio Portfolio
The Maximum Sharpe Ratio Portfolio method aims to create a portfolio that maximizes the Sharpe ratio. The Sharpe ratio is the measure of the portfolio's excess return per unit of risk, providing a risk-adjusted return metric.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The Sharpe ratio (\( S \)) is defined as:
""")
st.latex(r"""
S = \frac{R_p - R_f}{\sigma_p}
""")
st.markdown("""
Where:
- \( R_p \) is the expected portfolio return.
- \( R_f \) is the risk-free rate.
- \( \sigma_p \) is the portfolio's standard deviation (volatility).
#### Objective:
The objective of the Maximum Sharpe Ratio Portfolio is to find the portfolio weights that maximize the Sharpe ratio. This ensures the highest possible return for a given level of risk.
#### Mathematical Formulation:
The optimization problem can be formulated as:
""")
st.latex(r"""
\max_{w} \quad \frac{w^T \mu - R_f}{\sqrt{w^T \Sigma w}}
\quad \text{subject to} \quad \sum_{i=1}^{n} w_i = 1, \quad w_i \ge 0
""")
st.markdown("""
Where:
- \( w \) is the weight vector of the portfolio.
- \( \mu \) is the vector of expected returns for the assets.
- \( \Sigma \) is the covariance matrix of asset returns.
#### Steps Involved:
1. **Estimate Expected Returns**:
Compute the expected returns (\( \mu \)) for each asset in the portfolio.
2. **Compute the Covariance Matrix**:
Calculate the covariance matrix (\( \Sigma \)) of asset returns.
3. **Optimize the Sharpe Ratio**:
Use optimization techniques to find the weight vector (\( w \)) that maximizes the Sharpe ratio, subject to the constraints.
""")
st.markdown("""
#### Benefits:
- **Risk-Adjusted Returns**: By maximizing the Sharpe ratio, this method ensures that the portfolio achieves the highest possible return per unit of risk.
- **Efficiency**: The resulting portfolio is efficient in terms of return and risk, providing a balanced investment strategy.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'msr_result' not in st.session_state:
st.session_state.msr_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
optimal_weights_msr = optimize_maximum_sharpe(mean_returns, cov_matrix)
cleaned_weights_msr = dict(zip(tickers, optimal_weights_msr.values()))
st.session_state.msr_result['cleaned_weights'] = cleaned_weights_msr
st.session_state.msr_result['returns'] = returns
st.session_state.msr_result['optimal_weights'] = optimal_weights_msr
if 'msr_result' in st.session_state:
cleaned_weights_msr = st.session_state.msr_result['cleaned_weights']
returns = st.session_state.msr_result['returns']
optimal_weights_msr = st.session_state.msr_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_msr.values()), list(cleaned_weights_msr.keys()), 'Maximum Sharpe Ratio Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_msr = returns.dot(np.array(list(optimal_weights_msr.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_msr, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_msr, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_msr, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Equal Risk Contribution Portfolio":
st.markdown("""
### Equal Risk Contribution Portfolio
The Equal Risk Contribution (ERC) Portfolio method aims to allocate risk equally among all assets in the portfolio. This approach ensures that each asset contributes the same amount of risk, leading to a well-balanced portfolio.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The key idea is to balance the contribution of each asset to the total portfolio risk, such that the risk contribution from each asset is equal.
#### Objective:
The objective of the ERC Portfolio is to find the weights of the assets that equalize their risk contributions. The risk contribution of an asset \( i \) to the total portfolio risk is given by:
""")
st.latex(r"""
RC_i = w_i \frac{\partial \sigma_p}{\partial w_i}
""")
st.markdown("""
Where:
- \( RC_i \) is the risk contribution of asset \( i \).
- \( w_i \) is the weight of asset \( i \).
- \( \sigma_p \) is the portfolio's standard deviation (volatility).
#### Mathematical Formulation:
The optimization problem can be formulated as:
""")
st.latex(r"""
\min_{w} \quad \sum_{i=1}^{n} \left( w_i \frac{\partial \sigma_p}{\partial w_i} - \frac{\sigma_p}{n} \right)^2
\quad \text{subject to} \quad \sum_{i=1}^{n} w_i = 1, \quad w_i \ge 0
""")
st.markdown("""
Where:
- \( n \) is the number of assets.
- The goal is to minimize the sum of squared deviations of each asset's risk contribution from the average risk contribution \( \frac{\sigma_p}{n} \).
#### Steps Involved:
1. **Estimate Expected Returns and Covariance Matrix**:
Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
2. **Calculate Marginal Risk Contributions**:
Determine the partial derivatives of the portfolio volatility with respect to each asset's weight.
3. **Optimize the Risk Contributions**:
Use optimization techniques to find the weight vector (\( w \)) that equalizes the risk contributions, subject to the constraints.
""")
st.markdown("""
#### Benefits:
- **Balanced Risk Allocation**: Ensures that no single asset dominates the portfolio risk, leading to a more balanced and diversified portfolio.
- **Risk Management**: Helps in managing the overall portfolio risk by spreading it equally across all assets.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'erc_result' not in st.session_state:
st.session_state.erc_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
optimal_weights_erc = optimize_equal_risk_contribution(mean_returns, cov_matrix)
cleaned_weights_erc = dict(zip(tickers, optimal_weights_erc.values()))
st.session_state.erc_result['cleaned_weights'] = cleaned_weights_erc
st.session_state.erc_result['returns'] = returns
st.session_state.erc_result['optimal_weights'] = optimal_weights_erc
if 'erc_result' in st.session_state:
cleaned_weights_erc = st.session_state.erc_result['cleaned_weights']
returns = st.session_state.erc_result['returns']
optimal_weights_erc = st.session_state.erc_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_erc.values()), list(cleaned_weights_erc.keys()), 'Equal Risk Contribution Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_erc = returns.dot(np.array(list(optimal_weights_erc.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_erc, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_erc, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_erc, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "CVaR Minimization Portfolio":
st.markdown("""
### CVaR Minimization Portfolio
The CVaR Minimization Portfolio method focuses on minimizing Conditional Value at Risk (CVaR), which measures the expected loss of the portfolio in the worst-case scenarios. This method is particularly useful for investors who want to limit their downside risk.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
Conditional Value at Risk (CVaR) is a risk assessment measure that quantifies the expected loss in the worst-case scenarios beyond a certain confidence level. It provides a more comprehensive risk measure compared to Value at Risk (VaR).
#### Objective:
The objective of the CVaR Minimization Portfolio is to find the weights of the assets that minimize the CVaR at a given confidence level.
#### Mathematical Formulation:
The optimization problem can be formulated as:
""")
st.latex(r"""
\min_{w, \eta} \quad \eta + \frac{1}{\alpha T} \sum_{t=1}^{T} \max \{0, L_t(w) - \eta \}
\quad \text{subject to} \quad \sum_{i=1}^{n} w_i = 1, \quad w_i \ge 0
""")
st.markdown("""
Where:
- \( w \) is the weight vector of the assets.
- \( \eta \) is a threshold value.
- \( \alpha \) is the confidence level (e.g., 95%).
- \( T \) is the number of observations.
- \( L_t(w) \) is the loss of the portfolio at time \( t \).
#### Steps Involved:
1. **Estimate Expected Returns and Covariance Matrix**:
Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
2. **Calculate Portfolio Losses**:
Determine the losses of the portfolio for each observation period.
3. **Optimize CVaR**:
Use optimization techniques to find the weight vector (\( w \)) that minimizes the CVaR, subject to the constraints.
""")
st.markdown("""
#### Benefits:
- **Downside Risk Management**: Helps in managing the downside risk by focusing on the worst-case scenarios.
- **Risk-Averse Investors**: Suitable for investors with low risk tolerance who want to limit their potential losses.
- **Comprehensive Risk Measure**: Provides a more comprehensive risk measure compared to VaR by considering the tail-end losses.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'cvar_result' not in st.session_state:
st.session_state.cvar_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
optimal_weights_cvar = optimize_cvar_minimization(mean_returns, returns)
cleaned_weights_cvar = dict(zip(tickers, optimal_weights_cvar.values()))
st.session_state.cvar_result['cleaned_weights'] = cleaned_weights_cvar
st.session_state.cvar_result['returns'] = returns
st.session_state.cvar_result['optimal_weights'] = optimal_weights_cvar
if 'cvar_result' in st.session_state:
cleaned_weights_cvar = st.session_state.cvar_result['cleaned_weights']
returns = st.session_state.cvar_result['returns']
optimal_weights_cvar = st.session_state.cvar_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_cvar.values()), list(cleaned_weights_cvar.keys()), 'CVaR Minimization Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_cvar = returns.dot(np.array(list(optimal_weights_cvar.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_cvar, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_cvar, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_cvar, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Maximum Return Portfolio":
st.markdown("""
### Maximum Return Portfolio
The Maximum Return Portfolio method focuses solely on maximizing the expected return of the portfolio, without considering risk. This method allocates the highest weights to the assets with the highest expected returns.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The Maximum Return Portfolio aims to achieve the highest possible return by investing in assets that have the highest expected returns. This approach does not take into account the risk or volatility associated with the assets.
#### Objective:
The objective of the Maximum Return Portfolio is to find the weights of the assets that maximize the expected return.
#### Mathematical Formulation:
The optimization problem can be formulated as:
""")
st.latex(r"""
\max_{w} \quad \mu^T w
\quad \text{subject to} \quad \sum_{i=1}^{n} w_i = 1, \quad w_i \ge 0
""")
st.markdown("""
Where:
- \( w \) is the weight vector of the assets.
- \( \mu \) is the expected return vector of the assets.
- \( n \) is the number of assets.
#### Steps Involved:
1. **Estimate Expected Returns**:
Compute the expected returns (\( \mu \)) for the assets.
2. **Optimize Expected Return**:
Use optimization techniques to find the weight vector (\( w \)) that maximizes the expected return, subject to the constraints.
""")
st.markdown("""
#### Benefits:
- **High Potential Returns**: Focuses on achieving the highest possible returns.
- **Simple Approach**: Easy to understand and implement.
#### Drawbacks:
- **Ignores Risk**: Does not consider the risk or volatility associated with the assets.
- **Potential for High Volatility**: Can result in a highly volatile portfolio.
""")
st.sidebar.header("Optimization Parameters")
if st.sidebar.button('Run Analysis'):
if 'max_return_result' not in st.session_state:
st.session_state.max_return_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
optimal_weights_max_return = optimize_max_return(mean_returns, cov_matrix)
cleaned_weights_max_return = dict(zip(tickers, optimal_weights_max_return.values()))
st.session_state.max_return_result['cleaned_weights'] = cleaned_weights_max_return
st.session_state.max_return_result['returns'] = returns
st.session_state.max_return_result['optimal_weights'] = optimal_weights_max_return
if 'max_return_result' in st.session_state:
cleaned_weights_max_return = st.session_state.max_return_result['cleaned_weights']
returns = st.session_state.max_return_result['returns']
optimal_weights_max_return = st.session_state.max_return_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_max_return.values()), list(cleaned_weights_max_return.keys()), 'Maximum Return Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_max_return = returns.dot(np.array(list(optimal_weights_max_return.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_max_return, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_max_return, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_max_return, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
elif selected == "Maximum Quadratic Utility Portfolio":
st.markdown("""
### Maximum Quadratic Utility Portfolio
The Maximum Quadratic Utility Portfolio method maximizes the quadratic utility function, which balances the trade-off between expected return and risk. It incorporates a risk aversion coefficient to adjust the level of risk tolerance.
""")
# Expander for Detailed Methodology and Formulas
with st.expander("Methodology and Formulas", expanded=False):
st.markdown("""
#### Concept:
The Maximum Quadratic Utility Portfolio aims to find the optimal portfolio that maximizes an investor's utility, which is a measure of satisfaction or preference. This approach takes into account both the expected return and the risk of the portfolio, weighted by a risk aversion coefficient.
#### Objective:
The objective of the Maximum Quadratic Utility Portfolio is to maximize the utility function, which is defined as:
""")
st.latex(r"""
U(w) = \mu^T w - \frac{\lambda}{2} w^T \Sigma w
""")
st.markdown("""
Where:
- \( U(w) \) is the utility function.
- \( w \) is the weight vector of the assets.
- \( \mu \) is the expected return vector of the assets.
- \( \Sigma \) is the covariance matrix of the asset returns.
- \( \lambda \) is the risk aversion coefficient.
#### Steps Involved:
1. **Estimate Expected Returns and Covariance Matrix**:
Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
2. **Set Risk Aversion Coefficient**:
Define the risk aversion coefficient (\( \lambda \)) based on the investor's risk tolerance.
3. **Optimize Utility Function**:
Use optimization techniques to find the weight vector (\( w \)) that maximizes the utility function.
""")
st.markdown("""
#### Benefits:
- **Balances Return and Risk**: Provides a trade-off between maximizing returns and minimizing risk.
- **Customizable Risk Tolerance**: Allows adjustment of the risk aversion coefficient to match the investor's risk preference.
#### Drawbacks:
- **Requires Accurate Estimates**: The effectiveness of the method depends on accurate estimates of expected returns and the covariance matrix.
- **Complexity**: More complex to implement compared to simpler methods like Maximum Return Portfolio.
""")
st.sidebar.header("Optimization Parameters")
risk_aversion = st.sidebar.slider('Risk Aversion Coefficient', min_value=0.0, max_value=10.0, value=1.0, step=0.1)
if st.sidebar.button('Run Analysis'):
if 'mqu_result' not in st.session_state:
st.session_state.mqu_result = {}
data = fetch_stock_data(tickers, start_date, end_date)
returns = data.pct_change().dropna()
mean_returns = expected_returns.mean_historical_return(data)
cov_matrix = risk_models.sample_cov(data)
optimal_weights_mqu = optimize_max_quadratic_utility(mean_returns, cov_matrix, risk_aversion)
cleaned_weights_mqu = dict(zip(tickers, optimal_weights_mqu.values()))
st.session_state.mqu_result['cleaned_weights'] = cleaned_weights_mqu
st.session_state.mqu_result['returns'] = returns
st.session_state.mqu_result['optimal_weights'] = optimal_weights_mqu
if 'mqu_result' in st.session_state:
cleaned_weights_mqu = st.session_state.mqu_result['cleaned_weights']
returns = st.session_state.mqu_result['returns']
optimal_weights_mqu = st.session_state.mqu_result['optimal_weights']
fig_weights = plot_portfolio_weights(list(cleaned_weights_mqu.values()), list(cleaned_weights_mqu.keys()), 'Maximum Quadratic Utility Portfolio Weights')
st.plotly_chart(fig_weights)
portfolio_returns_mqu = returns.dot(np.array(list(optimal_weights_mqu.values())))
sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
sp500_returns = sp500.pct_change().dropna()
equal_weighted_returns = returns.mean(axis=1)
fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mqu, sp500_returns, equal_weighted_returns, 'Cumulative Returns Over Time')
st.plotly_chart(fig_cumulative_returns)
fig_volatility, fig_sharpe = plot_rolling_metrics(portfolio_returns_mqu, sp500_returns, equal_weighted_returns, 0.01, 'Rolling Volatility', 'Rolling Sharpe Ratio')
st.plotly_chart(fig_volatility)
st.plotly_chart(fig_sharpe)
fig_drawdown = plot_max_drawdown(portfolio_returns_mqu, sp500_returns, equal_weighted_returns, 'Maximum Drawdown')
st.plotly_chart(fig_drawdown)
st.markdown(
"""
<style>
/* Adjust the width of the sidebar */
[data-testid="stSidebar"] {
width: 500px; /* Change this value to set the width you want */
}
</style>
""",
unsafe_allow_html=True
)
hide_streamlit_style = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)