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): data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=False)['Adj Close'] if isinstance(data.columns, pd.MultiIndex): data.columns = data.columns.get_level_values(0) if data.empty: raise ValueError(f"No data fetched for {tickers} from {start_date} to {end_date}.") return data 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() mu, sigma, sharpe = ef.portfolio_performance() ef = EfficientFrontier(mean_returns, cov_matrix) frontier_y = [] frontier_x = [] for r in np.linspace(0, mu, 100): ef.efficient_return(r) perf = ef.portfolio_performance() frontier_y.append(perf[0]) frontier_x.append(perf[1]) fig = go.Figure() fig.add_trace(go.Scatter(x=frontier_x, y=frontier_y, mode='lines', name='Efficient Frontier')) fig.add_trace(go.Scatter(x=[sigma], y=[mu], mode='markers', marker=dict(color='red', size=10), name='Optimal Portfolio')) 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 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 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) if minimize_risk: obj_value += np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) 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 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 if minimize_cvar: cvar = np.percentile(np.dot(returns.values, weights), 5) obj_value += cvar if minimize_transaction_costs: prev_weights = np.ones(len(weights)) / len(weights) transaction_costs = np.sum(np.abs(weights - prev_weights)) * 0.001 obj_value += transaction_costs if maximize_dividend_yield: obj_value -= np.dot(weights, dividend_yields) 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 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 def optimize_maximum_sharpe(mean_returns, cov_matrix): ef = EfficientFrontier(mean_returns, cov_matrix) optimal_weights = ef.max_sharpe() return ef.clean_weights() 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() def optimize_cvar_minimization(mean_returns, returns): cvar = EfficientCVaR(mean_returns, returns) optimal_weights = cvar.min_cvar() return cvar.clean_weights() def optimize_max_return(mean_returns, cov_matrix): ef = EfficientFrontier(mean_returns, cov_matrix) target_return = mean_returns.max() * 0.99 optimal_weights = ef.efficient_return(target_return=target_return) return ef.clean_weights() 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.header("Input Parameters") # Sidebar: 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.") # Sidebar: 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.") # Method-specific parameters 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. """) 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) 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. """) 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) 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. """) st.sidebar.header("Optimization Parameters") # No additional parameters typically required, but could add risk-free rate or constraints if desired 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. """) st.sidebar.header("Optimization Parameters") # Views could be added here, but keeping simple as per original 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. """) st.sidebar.header("Optimization Parameters") # HRP typically doesn't need additional parameters elif selected == "Maximum Diversification": 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. """) st.sidebar.header("Optimization Parameters") # No additional parameters typically needed elif selected == "Maximum Sharpe Ratio": 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. """) st.sidebar.header("Optimization Parameters") # Could add risk-free rate, but keeping as is elif selected == "Equal Risk Contribution": 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. """) st.sidebar.header("Optimization Parameters") # No additional parameters typically needed elif selected == "CVaR Minimization": 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. """) st.sidebar.header("Optimization Parameters") # Could add confidence level, but keeping simple elif selected == "Maximum Return": 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. """) st.sidebar.header("Optimization Parameters") # No additional parameters typically needed elif selected == "Maximum Quadratic Utility": 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. """) 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) 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] """) if st.sidebar.button('Run'): if selected == "Multi-Objective Optimization": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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 st.session_state.mean_variance_result['cov_matrix'] = cov_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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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) market_data = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False) if isinstance(market_data.columns, pd.MultiIndex): market_data.columns = market_data.columns.get_level_values(0) if market_data.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") market_data = market_data['Adj Close'] delta = black_litterman.market_implied_risk_aversion(market_data) market_caps = {} for ticker in tickers: stock = yf.Ticker(ticker) market_caps[ticker] = stock.info['marketCap'] market_caps_series = pd.Series(market_caps) prior = black_litterman.market_implied_prior_returns(market_caps_series, delta, cov_matrix) P = np.identity(len(tickers)) Q = np.zeros(len(tickers)) bl = black_litterman.BlackLittermanModel(cov_matrix, pi=prior, P=P, Q=Q) bl_return = bl.bl_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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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 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'] 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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": 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, auto_adjust=False) if isinstance(sp500.columns, pd.MultiIndex): sp500.columns = sp500.columns.get_level_values(0) if sp500.empty: raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.") sp500_returns = sp500['Adj Close'].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( """ """, unsafe_allow_html=True ) hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)