QuantumLearner commited on
Commit
feabf7f
·
verified ·
1 Parent(s): cdc403e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -74
app.py CHANGED
@@ -11,7 +11,12 @@ import matplotlib.pyplot as plt
11
 
12
  # Helper functions
13
  def fetch_stock_data(tickers, start_date, end_date):
14
- return yf.download(tickers, start=start_date, end=end_date)['Adj Close']
 
 
 
 
 
15
 
16
  def plot_efficient_frontier(mean_returns, cov_matrix, title):
17
  ef = EfficientFrontier(mean_returns, cov_matrix)
@@ -259,7 +264,6 @@ def optimize_max_quadratic_utility(mean_returns, cov_matrix, risk_aversion):
259
  return ef.clean_weights()
260
 
261
  # Streamlit app
262
- st.set_page_config(page_title="Portfolio Optimization", layout="wide")
263
  st.title('Portfolio Optimization Methods')
264
  st.markdown("""
265
  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.
@@ -319,7 +323,6 @@ if selected == "Multi-Objective Optimization":
319
  with st.expander("Methodology and Formulas", expanded=False):
320
  st.markdown("""
321
  #### Formulas:
322
-
323
  **Expected Return**:
324
  """)
325
  st.latex(r"""
@@ -356,9 +359,7 @@ with st.expander("Methodology and Formulas", expanded=False):
356
 
357
  st.markdown("""
358
  #### Mathematical Optimization Process
359
-
360
  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:
361
-
362
  **Objective Function**:
363
  Minimize or maximize a weighted sum of multiple objectives. The overall objective function can be expressed as:
364
  """)
@@ -370,7 +371,6 @@ with st.expander("Methodology and Formulas", expanded=False):
370
  where:
371
  - \( \lambda_i \) are the weights assigned to each objective.
372
  - \( \mathbf{w} \) is the vector of portfolio weights.
373
-
374
  **Constraints**:
375
  1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
376
  """)
@@ -387,9 +387,7 @@ with st.expander("Methodology and Formulas", expanded=False):
387
 
388
  st.markdown("""
389
  3. Additional constraints based on user preferences or regulatory requirements can also be included.
390
-
391
  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.
392
-
393
  """)
394
 
395
  st.sidebar.header("Optimization Parameters")
@@ -441,8 +439,12 @@ if 'multi_objective_result' in st.session_state:
441
  st.plotly_chart(fig_weights)
442
 
443
  portfolio_returns = returns.dot(optimal_weights)
444
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
445
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
446
  equal_weighted_returns = returns.mean(axis=1)
447
 
448
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns,
@@ -479,7 +481,6 @@ elif selected == "Robust Optimization":
479
  #### Concept:
480
  The covariance matrix can be unstable due to limited data or market volatility.
481
  Robust Optimization addresses this by applying techniques such as covariance shrinkage to produce a more stable covariance matrix.
482
-
483
  #### Regularization Technique:
484
  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.
485
  **Covariance Shrinkage**:
@@ -518,7 +519,6 @@ elif selected == "Robust Optimization":
518
  """)
519
  st.markdown("""
520
  #### Practical Benefits:
521
-
522
  - **Stability**: By reducing the impact of outliers and estimation errors, Robust Optimization produces more stable portfolios.
523
  - **Performance**: It can improve portfolio performance by avoiding extreme weights that might result from noisy data.
524
  - **Applicability**: Suitable for environments with high market volatility or limited historical data.
@@ -556,8 +556,12 @@ elif selected == "Robust Optimization":
556
  st.plotly_chart(fig_weights)
557
 
558
  portfolio_returns_robust = returns.dot(np.array(list(cleaned_weights_robust.values())))
559
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
560
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
561
  equal_weighted_returns = returns.mean(axis=1)
562
 
563
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_robust,
@@ -593,7 +597,6 @@ elif selected == "Mean-Variance Optimization":
593
  #### Concept:
594
  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.
595
  This is achieved by selecting the proportions of various assets in the portfolio.
596
-
597
  #### Sharpe Ratio:
598
  The Sharpe ratio is a measure of the risk-adjusted return of a portfolio. It is calculated as:
599
  """)
@@ -605,10 +608,8 @@ elif selected == "Mean-Variance Optimization":
605
  - \( \mathbb{E}[R_p] \) is the expected return of the portfolio.
606
  - \( R_f \) is the risk-free rate.
607
  - \( \sigma_p \) is the standard deviation of the portfolio returns.
608
-
609
  #### Mathematical Optimization Process:
610
  The optimization problem can be formulated as follows:
611
-
612
  **Objective Function**:
613
  """)
614
  st.latex(r"""
@@ -619,7 +620,6 @@ elif selected == "Mean-Variance Optimization":
619
  - \( w \) is the vector of portfolio weights.
620
  - \( \mu \) is the vector of expected asset returns.
621
  - \( \Sigma \) is the covariance matrix of asset returns.
622
-
623
  **Constraints**:
624
  1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
625
  """)
@@ -634,7 +634,6 @@ elif selected == "Mean-Variance Optimization":
634
  """)
635
  st.markdown("""
636
  #### Benefits of Mean-Variance Optimization:
637
-
638
  - **Risk Management**: It helps in managing the trade-off between risk and return by considering the covariance among asset returns.
639
  - **Diversification**: By incorporating the covariance matrix, it promotes diversification and reduces unsystematic risk.
640
  - **Simplicity**: It provides a straightforward framework for constructing efficient portfolios.
@@ -674,8 +673,12 @@ elif selected == "Mean-Variance Optimization":
674
  st.plotly_chart(fig_weights)
675
 
676
  portfolio_returns = returns.dot(optimal_weights)
677
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
678
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
679
  equal_weighted_returns = returns.mean(axis=1)
680
 
681
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns,
@@ -715,7 +718,6 @@ elif selected == "Black-Litterman Model":
715
  st.markdown("""
716
  #### Concept:
717
  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.
718
-
719
  #### Market Equilibrium Returns:
720
  The market equilibrium returns, \( \Pi \), are given by:
721
  """)
@@ -727,7 +729,6 @@ elif selected == "Black-Litterman Model":
727
  - \( \delta \) is the risk aversion coefficient.
728
  - \( \Sigma \) is the covariance matrix of asset returns.
729
  - \( w_{mkt} \) is the vector of market capitalization weights.
730
-
731
  #### Investor Views:
732
  Investor views are expressed in the form of a matrix \( P \) and a vector \( Q \):
733
  """)
@@ -739,7 +740,6 @@ elif selected == "Black-Litterman Model":
739
  - \( P \) is the matrix that identifies the assets involved in each view.
740
  - \( \mu \) is the vector of expected returns.
741
  - \( Q \) is the vector of view returns.
742
-
743
  #### Adjusted Expected Returns:
744
  The adjusted expected returns, \( \mu \), are then calculated as:
745
  """)
@@ -750,10 +750,8 @@ elif selected == "Black-Litterman Model":
750
  Where:
751
  - \( \tau \) is a scalar representing the uncertainty in the prior estimates.
752
  - \( \Omega \) is the diagonal covariance matrix of the error terms in the views.
753
-
754
  #### Optimization:
755
  Once the adjusted expected returns, \( \mu \), are obtained, we can apply mean-variance optimization to find the optimal portfolio weights.
756
-
757
  **Objective Function**:
758
  """)
759
  st.latex(r"""
@@ -774,7 +772,6 @@ elif selected == "Black-Litterman Model":
774
  """)
775
  st.markdown("""
776
  #### Benefits of the Black-Litterman Model:
777
-
778
  - **Flexibility**: Incorporates investor views into the optimization process.
779
  - **Stability**: Produces more stable and intuitive expected returns than traditional mean-variance optimization.
780
  - **Theoretical Foundation**: Combines the principles of modern portfolio theory with practical investor insights.
@@ -792,7 +789,12 @@ elif selected == "Black-Litterman Model":
792
  cov_matrix = risk_models.sample_cov(data)
793
 
794
  # Download market data for market capitalization weights (here using S&P 500 as proxy)
795
- market_data = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
 
 
 
 
 
796
 
797
  # Calculate market-implied risk aversion
798
  delta = black_litterman.market_implied_risk_aversion(market_data)
@@ -838,8 +840,12 @@ elif selected == "Black-Litterman Model":
838
  st.plotly_chart(fig_weights)
839
 
840
  portfolio_returns_bl = returns.dot(optimal_weights_bl)
841
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
842
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
843
  equal_weighted_returns = returns.mean(axis=1)
844
 
845
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_bl,
@@ -890,10 +896,8 @@ elif selected == "Hierarchical Risk Parity":
890
  st.markdown("""
891
  Where:
892
  - \( \rho_{i,j} \) is the correlation coefficient between assets \( i \) and \( j \).
893
-
894
  #### Quasi-Diagonalization:
895
  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.
896
-
897
  #### Recursive Bisection:
898
  Risk is allocated recursively using the following steps:
899
  1. **Top-Level Allocation**:
@@ -905,13 +909,11 @@ elif selected == "Hierarchical Risk Parity":
905
  st.markdown("""
906
  2. **Sub-Level Allocation**:
907
  The risk within each cluster is further split recursively until the individual assets are reached.
908
-
909
  This recursive process ensures that risk is allocated evenly across all clusters and individual assets.
910
  """)
911
 
912
  st.markdown("""
913
  #### Benefits of HRP:
914
-
915
  - **Improved Diversification**: By clustering similar assets and allocating risk evenly, HRP achieves better diversification compared to traditional methods.
916
  - **Reduced Risk Concentration**: HRP avoids concentrating risk in highly correlated assets, resulting in a more balanced portfolio.
917
  - **Robustness**: HRP is less sensitive to estimation errors in the covariance matrix, making it a robust method for portfolio optimization.
@@ -949,8 +951,12 @@ elif selected == "Hierarchical Risk Parity":
949
  st.plotly_chart(fig_weights)
950
 
951
  portfolio_returns_hrp = returns.dot(optimal_weights_hrp)
952
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
953
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
954
  equal_weighted_returns = returns.mean(axis=1)
955
 
956
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_hrp,
@@ -998,10 +1004,8 @@ elif selected == "Maximum Diversification":
998
  - \( \sigma_i \) is the volatility of asset \( i \).
999
  - \( \Sigma \) is the covariance matrix of asset returns.
1000
  - \( w \) is the weight vector of the portfolio.
1001
-
1002
  #### Objective:
1003
  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.
1004
-
1005
  #### Mathematical Formulation:
1006
  The optimization problem can be formulated as:
1007
  """)
@@ -1011,21 +1015,17 @@ elif selected == "Maximum Diversification":
1011
  """)
1012
  st.markdown("""
1013
  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).
1014
-
1015
  #### Steps Involved:
1016
  1. **Calculate Individual Volatilities**:
1017
  Compute the volatility (\( \sigma_i \)) for each asset in the portfolio.
1018
-
1019
  2. **Compute the Covariance Matrix**:
1020
  Calculate the covariance matrix (\( \Sigma \)) of asset returns.
1021
-
1022
  3. **Optimize the Diversification Ratio**:
1023
  Use optimization techniques to find the weight vector (\( w \)) that maximizes the diversification ratio, subject to the constraints.
1024
  """)
1025
 
1026
  st.markdown("""
1027
  #### Benefits:
1028
-
1029
  - **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.
1030
  - **Reduced Portfolio Risk**: By maximizing the diversification benefit, the overall portfolio risk is minimized compared to other traditional optimization methods.
1031
  """)
@@ -1064,8 +1064,12 @@ elif selected == "Maximum Diversification":
1064
  st.plotly_chart(fig_weights)
1065
 
1066
  portfolio_returns_mdp = returns.dot(optimal_weights_mdp)
1067
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1068
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1069
  equal_weighted_returns = returns.mean(axis=1)
1070
 
1071
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mdp,
@@ -1109,7 +1113,6 @@ elif selected == "Maximum Sharpe Ratio":
1109
  - \( R_p \) is the expected portfolio return.
1110
  - \( R_f \) is the risk-free rate.
1111
  - \( \sigma_p \) is the portfolio's standard deviation (volatility).
1112
-
1113
  #### Objective:
1114
  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.
1115
  """)
@@ -1149,8 +1152,12 @@ elif selected == "Maximum Sharpe Ratio":
1149
  st.plotly_chart(fig_weights)
1150
 
1151
  portfolio_returns_msr = returns.dot(np.array(list(optimal_weights_msr.values())))
1152
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1153
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1154
  equal_weighted_returns = returns.mean(axis=1)
1155
 
1156
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_msr,
@@ -1185,7 +1192,6 @@ elif selected == "Equal Risk Contribution":
1185
  st.markdown("""
1186
  #### Concept:
1187
  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.
1188
-
1189
  #### Objective:
1190
  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:
1191
  """)
@@ -1197,7 +1203,6 @@ elif selected == "Equal Risk Contribution":
1197
  - \( RC_i \) is the risk contribution of asset \( i \).
1198
  - \( w_i \) is the weight of asset \( i \).
1199
  - \( \sigma_p \) is the portfolio's standard deviation (volatility).
1200
-
1201
  #### Mathematical Formulation:
1202
  The optimization problem can be formulated as:
1203
  """)
@@ -1209,7 +1214,6 @@ elif selected == "Equal Risk Contribution":
1209
  Where:
1210
  - \( n \) is the number of assets.
1211
  - 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} \).
1212
-
1213
  #### Steps Involved:
1214
  1. **Estimate Expected Returns and Covariance Matrix**:
1215
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
@@ -1221,7 +1225,6 @@ elif selected == "Equal Risk Contribution":
1221
 
1222
  st.markdown("""
1223
  #### Benefits:
1224
-
1225
  - **Balanced Risk Allocation**: Ensures that no single asset dominates the portfolio risk, leading to a more balanced and diversified portfolio.
1226
  - **Risk Management**: Helps in managing the overall portfolio risk by spreading it equally across all assets.
1227
  """)
@@ -1255,8 +1258,12 @@ elif selected == "Equal Risk Contribution":
1255
  st.plotly_chart(fig_weights)
1256
 
1257
  portfolio_returns_erc = returns.dot(np.array(list(optimal_weights_erc.values())))
1258
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1259
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1260
  equal_weighted_returns = returns.mean(axis=1)
1261
 
1262
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_erc,
@@ -1291,10 +1298,8 @@ elif selected == "CVaR Minimization":
1291
  st.markdown("""
1292
  #### Concept:
1293
  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).
1294
-
1295
  #### Objective:
1296
  The objective of the CVaR Minimization Portfolio is to find the weights of the assets that minimize the CVaR at a given confidence level.
1297
-
1298
  #### Mathematical Formulation:
1299
  The optimization problem can be formulated as:
1300
  """)
@@ -1309,7 +1314,6 @@ elif selected == "CVaR Minimization":
1309
  - \( \alpha \) is the confidence level (e.g., 95%).
1310
  - \( T \) is the number of observations.
1311
  - \( L_t(w) \) is the loss of the portfolio at time \( t \).
1312
-
1313
  #### Steps Involved:
1314
  1. **Estimate Expected Returns and Covariance Matrix**:
1315
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
@@ -1354,8 +1358,12 @@ elif selected == "CVaR Minimization":
1354
  st.plotly_chart(fig_weights)
1355
 
1356
  portfolio_returns_cvar = returns.dot(np.array(list(optimal_weights_cvar.values())))
1357
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1358
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1359
  equal_weighted_returns = returns.mean(axis=1)
1360
 
1361
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_cvar,
@@ -1390,10 +1398,8 @@ elif selected == "Maximum Return":
1390
  st.markdown("""
1391
  #### Concept:
1392
  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.
1393
-
1394
  #### Objective:
1395
  The objective of the Maximum Return Portfolio is to find the weights of the assets that maximize the expected return.
1396
-
1397
  #### Mathematical Formulation:
1398
  The optimization problem can be formulated as:
1399
  """)
@@ -1406,7 +1412,6 @@ elif selected == "Maximum Return":
1406
  - \( w \) is the weight vector of the assets.
1407
  - \( \mu \) is the expected return vector of the assets.
1408
  - \( n \) is the number of assets.
1409
-
1410
  #### Steps Involved:
1411
  1. **Estimate Expected Returns**:
1412
  Compute the expected returns (\( \mu \)) for each asset.
@@ -1416,12 +1421,9 @@ elif selected == "Maximum Return":
1416
 
1417
  st.markdown("""
1418
  #### Benefits:
1419
-
1420
  - **High Potential Returns**: Focuses on achieving the highest possible returns.
1421
  - **Simple Approach**: Easy to understand and implement.
1422
-
1423
  #### Drawbacks:
1424
-
1425
  - **Ignores Risk**: Does not consider the risk or volatility associated with the assets.
1426
  - **Potential for High Volatility**: Can result in a highly volatile portfolio.
1427
  """)
@@ -1455,8 +1457,12 @@ elif selected == "Maximum Return":
1455
  st.plotly_chart(fig_weights)
1456
 
1457
  portfolio_returns_max_return = returns.dot(np.array(list(optimal_weights_max_return.values())))
1458
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1459
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1460
  equal_weighted_returns = returns.mean(axis=1)
1461
 
1462
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_max_return,
@@ -1491,7 +1497,6 @@ elif selected == "Maximum Quadratic Utility":
1491
  st.markdown("""
1492
  #### Concept:
1493
  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.
1494
-
1495
  #### Objective:
1496
  The objective of the Maximum Quadratic Utility Portfolio is to maximize the utility function, which is defined as:
1497
  """)
@@ -1505,7 +1510,6 @@ elif selected == "Maximum Quadratic Utility":
1505
  - \( \mu \) is the expected return vector of the assets.
1506
  - \( \Sigma \) is the covariance matrix of the asset returns.
1507
  - \( \lambda \) is the risk aversion coefficient.
1508
-
1509
  #### Steps Involved:
1510
  1. **Estimate Expected Returns and Covariance Matrix**:
1511
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
@@ -1517,12 +1521,9 @@ elif selected == "Maximum Quadratic Utility":
1517
 
1518
  st.markdown("""
1519
  #### Benefits:
1520
-
1521
  - **Balances Return and Risk**: Provides a trade-off between maximizing returns and minimizing risk.
1522
  - **Customizable Risk Tolerance**: Allows adjustment of the risk aversion coefficient to match the investor's risk preference.
1523
-
1524
  #### Drawbacks:
1525
-
1526
  - **Requires Accurate Estimates**: The effectiveness of the method depends on accurate estimates of expected returns and the covariance matrix.
1527
  - **Complexity**: More complex to implement compared to simpler methods like Maximum Return Portfolio.
1528
  """)
@@ -1557,8 +1558,12 @@ elif selected == "Maximum Quadratic Utility":
1557
  st.plotly_chart(fig_weights)
1558
 
1559
  portfolio_returns_mqu = returns.dot(np.array(list(optimal_weights_mqu.values())))
1560
- sp500 = yf.download('^GSPC', start=start_date, end=end_date)['Adj Close']
1561
- sp500_returns = sp500.pct_change().dropna()
 
 
 
 
1562
  equal_weighted_returns = returns.mean(axis=1)
1563
 
1564
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mqu,
@@ -1600,4 +1605,4 @@ hide_streamlit_style = """
1600
  footer {visibility: hidden;}
1601
  </style>
1602
  """
1603
- st.markdown(hide_streamlit_style, unsafe_allow_html=True)
 
11
 
12
  # Helper functions
13
  def fetch_stock_data(tickers, start_date, end_date):
14
+ data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=False)['Adj Close']
15
+ if isinstance(data.columns, pd.MultiIndex):
16
+ data.columns = data.columns.get_level_values(0)
17
+ if data.empty:
18
+ raise ValueError(f"No data fetched for {tickers} from {start_date} to {end_date}.")
19
+ return data
20
 
21
  def plot_efficient_frontier(mean_returns, cov_matrix, title):
22
  ef = EfficientFrontier(mean_returns, cov_matrix)
 
264
  return ef.clean_weights()
265
 
266
  # Streamlit app
 
267
  st.title('Portfolio Optimization Methods')
268
  st.markdown("""
269
  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.
 
323
  with st.expander("Methodology and Formulas", expanded=False):
324
  st.markdown("""
325
  #### Formulas:
 
326
  **Expected Return**:
327
  """)
328
  st.latex(r"""
 
359
 
360
  st.markdown("""
361
  #### Mathematical Optimization Process
 
362
  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:
 
363
  **Objective Function**:
364
  Minimize or maximize a weighted sum of multiple objectives. The overall objective function can be expressed as:
365
  """)
 
371
  where:
372
  - \( \lambda_i \) are the weights assigned to each objective.
373
  - \( \mathbf{w} \) is the vector of portfolio weights.
 
374
  **Constraints**:
375
  1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
376
  """)
 
387
 
388
  st.markdown("""
389
  3. Additional constraints based on user preferences or regulatory requirements can also be included.
 
390
  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.
 
391
  """)
392
 
393
  st.sidebar.header("Optimization Parameters")
 
439
  st.plotly_chart(fig_weights)
440
 
441
  portfolio_returns = returns.dot(optimal_weights)
442
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
443
+ if isinstance(sp500.columns, pd.MultiIndex):
444
+ sp500.columns = sp500.columns.get_level_values(0)
445
+ if sp500.empty:
446
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
447
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
448
  equal_weighted_returns = returns.mean(axis=1)
449
 
450
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns,
 
481
  #### Concept:
482
  The covariance matrix can be unstable due to limited data or market volatility.
483
  Robust Optimization addresses this by applying techniques such as covariance shrinkage to produce a more stable covariance matrix.
 
484
  #### Regularization Technique:
485
  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.
486
  **Covariance Shrinkage**:
 
519
  """)
520
  st.markdown("""
521
  #### Practical Benefits:
 
522
  - **Stability**: By reducing the impact of outliers and estimation errors, Robust Optimization produces more stable portfolios.
523
  - **Performance**: It can improve portfolio performance by avoiding extreme weights that might result from noisy data.
524
  - **Applicability**: Suitable for environments with high market volatility or limited historical data.
 
556
  st.plotly_chart(fig_weights)
557
 
558
  portfolio_returns_robust = returns.dot(np.array(list(cleaned_weights_robust.values())))
559
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
560
+ if isinstance(sp500.columns, pd.MultiIndex):
561
+ sp500.columns = sp500.columns.get_level_values(0)
562
+ if sp500.empty:
563
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
564
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
565
  equal_weighted_returns = returns.mean(axis=1)
566
 
567
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_robust,
 
597
  #### Concept:
598
  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.
599
  This is achieved by selecting the proportions of various assets in the portfolio.
 
600
  #### Sharpe Ratio:
601
  The Sharpe ratio is a measure of the risk-adjusted return of a portfolio. It is calculated as:
602
  """)
 
608
  - \( \mathbb{E}[R_p] \) is the expected return of the portfolio.
609
  - \( R_f \) is the risk-free rate.
610
  - \( \sigma_p \) is the standard deviation of the portfolio returns.
 
611
  #### Mathematical Optimization Process:
612
  The optimization problem can be formulated as follows:
 
613
  **Objective Function**:
614
  """)
615
  st.latex(r"""
 
620
  - \( w \) is the vector of portfolio weights.
621
  - \( \mu \) is the vector of expected asset returns.
622
  - \( \Sigma \) is the covariance matrix of asset returns.
 
623
  **Constraints**:
624
  1. The sum of the portfolio weights must equal 1 (fully invested portfolio):
625
  """)
 
634
  """)
635
  st.markdown("""
636
  #### Benefits of Mean-Variance Optimization:
 
637
  - **Risk Management**: It helps in managing the trade-off between risk and return by considering the covariance among asset returns.
638
  - **Diversification**: By incorporating the covariance matrix, it promotes diversification and reduces unsystematic risk.
639
  - **Simplicity**: It provides a straightforward framework for constructing efficient portfolios.
 
673
  st.plotly_chart(fig_weights)
674
 
675
  portfolio_returns = returns.dot(optimal_weights)
676
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
677
+ if isinstance(sp500.columns, pd.MultiIndex):
678
+ sp500.columns = sp500.columns.get_level_values(0)
679
+ if sp500.empty:
680
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
681
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
682
  equal_weighted_returns = returns.mean(axis=1)
683
 
684
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns,
 
718
  st.markdown("""
719
  #### Concept:
720
  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.
 
721
  #### Market Equilibrium Returns:
722
  The market equilibrium returns, \( \Pi \), are given by:
723
  """)
 
729
  - \( \delta \) is the risk aversion coefficient.
730
  - \( \Sigma \) is the covariance matrix of asset returns.
731
  - \( w_{mkt} \) is the vector of market capitalization weights.
 
732
  #### Investor Views:
733
  Investor views are expressed in the form of a matrix \( P \) and a vector \( Q \):
734
  """)
 
740
  - \( P \) is the matrix that identifies the assets involved in each view.
741
  - \( \mu \) is the vector of expected returns.
742
  - \( Q \) is the vector of view returns.
 
743
  #### Adjusted Expected Returns:
744
  The adjusted expected returns, \( \mu \), are then calculated as:
745
  """)
 
750
  Where:
751
  - \( \tau \) is a scalar representing the uncertainty in the prior estimates.
752
  - \( \Omega \) is the diagonal covariance matrix of the error terms in the views.
 
753
  #### Optimization:
754
  Once the adjusted expected returns, \( \mu \), are obtained, we can apply mean-variance optimization to find the optimal portfolio weights.
 
755
  **Objective Function**:
756
  """)
757
  st.latex(r"""
 
772
  """)
773
  st.markdown("""
774
  #### Benefits of the Black-Litterman Model:
 
775
  - **Flexibility**: Incorporates investor views into the optimization process.
776
  - **Stability**: Produces more stable and intuitive expected returns than traditional mean-variance optimization.
777
  - **Theoretical Foundation**: Combines the principles of modern portfolio theory with practical investor insights.
 
789
  cov_matrix = risk_models.sample_cov(data)
790
 
791
  # Download market data for market capitalization weights (here using S&P 500 as proxy)
792
+ market_data = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
793
+ if isinstance(market_data.columns, pd.MultiIndex):
794
+ market_data.columns = market_data.columns.get_level_values(0)
795
+ if market_data.empty:
796
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
797
+ market_data = market_data['Adj Close']
798
 
799
  # Calculate market-implied risk aversion
800
  delta = black_litterman.market_implied_risk_aversion(market_data)
 
840
  st.plotly_chart(fig_weights)
841
 
842
  portfolio_returns_bl = returns.dot(optimal_weights_bl)
843
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
844
+ if isinstance(sp500.columns, pd.MultiIndex):
845
+ sp500.columns = sp500.columns.get_level_values(0)
846
+ if sp500.empty:
847
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
848
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
849
  equal_weighted_returns = returns.mean(axis=1)
850
 
851
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_bl,
 
896
  st.markdown("""
897
  Where:
898
  - \( \rho_{i,j} \) is the correlation coefficient between assets \( i \) and \( j \).
 
899
  #### Quasi-Diagonalization:
900
  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.
 
901
  #### Recursive Bisection:
902
  Risk is allocated recursively using the following steps:
903
  1. **Top-Level Allocation**:
 
909
  st.markdown("""
910
  2. **Sub-Level Allocation**:
911
  The risk within each cluster is further split recursively until the individual assets are reached.
 
912
  This recursive process ensures that risk is allocated evenly across all clusters and individual assets.
913
  """)
914
 
915
  st.markdown("""
916
  #### Benefits of HRP:
 
917
  - **Improved Diversification**: By clustering similar assets and allocating risk evenly, HRP achieves better diversification compared to traditional methods.
918
  - **Reduced Risk Concentration**: HRP avoids concentrating risk in highly correlated assets, resulting in a more balanced portfolio.
919
  - **Robustness**: HRP is less sensitive to estimation errors in the covariance matrix, making it a robust method for portfolio optimization.
 
951
  st.plotly_chart(fig_weights)
952
 
953
  portfolio_returns_hrp = returns.dot(optimal_weights_hrp)
954
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
955
+ if isinstance(sp500.columns, pd.MultiIndex):
956
+ sp500.columns = sp500.columns.get_level_values(0)
957
+ if sp500.empty:
958
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
959
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
960
  equal_weighted_returns = returns.mean(axis=1)
961
 
962
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_hrp,
 
1004
  - \( \sigma_i \) is the volatility of asset \( i \).
1005
  - \( \Sigma \) is the covariance matrix of asset returns.
1006
  - \( w \) is the weight vector of the portfolio.
 
1007
  #### Objective:
1008
  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.
 
1009
  #### Mathematical Formulation:
1010
  The optimization problem can be formulated as:
1011
  """)
 
1015
  """)
1016
  st.markdown("""
1017
  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).
 
1018
  #### Steps Involved:
1019
  1. **Calculate Individual Volatilities**:
1020
  Compute the volatility (\( \sigma_i \)) for each asset in the portfolio.
 
1021
  2. **Compute the Covariance Matrix**:
1022
  Calculate the covariance matrix (\( \Sigma \)) of asset returns.
 
1023
  3. **Optimize the Diversification Ratio**:
1024
  Use optimization techniques to find the weight vector (\( w \)) that maximizes the diversification ratio, subject to the constraints.
1025
  """)
1026
 
1027
  st.markdown("""
1028
  #### Benefits:
 
1029
  - **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.
1030
  - **Reduced Portfolio Risk**: By maximizing the diversification benefit, the overall portfolio risk is minimized compared to other traditional optimization methods.
1031
  """)
 
1064
  st.plotly_chart(fig_weights)
1065
 
1066
  portfolio_returns_mdp = returns.dot(optimal_weights_mdp)
1067
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1068
+ if isinstance(sp500.columns, pd.MultiIndex):
1069
+ sp500.columns = sp500.columns.get_level_values(0)
1070
+ if sp500.empty:
1071
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1072
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1073
  equal_weighted_returns = returns.mean(axis=1)
1074
 
1075
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mdp,
 
1113
  - \( R_p \) is the expected portfolio return.
1114
  - \( R_f \) is the risk-free rate.
1115
  - \( \sigma_p \) is the portfolio's standard deviation (volatility).
 
1116
  #### Objective:
1117
  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.
1118
  """)
 
1152
  st.plotly_chart(fig_weights)
1153
 
1154
  portfolio_returns_msr = returns.dot(np.array(list(optimal_weights_msr.values())))
1155
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1156
+ if isinstance(sp500.columns, pd.MultiIndex):
1157
+ sp500.columns = sp500.columns.get_level_values(0)
1158
+ if sp500.empty:
1159
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1160
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1161
  equal_weighted_returns = returns.mean(axis=1)
1162
 
1163
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_msr,
 
1192
  st.markdown("""
1193
  #### Concept:
1194
  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.
 
1195
  #### Objective:
1196
  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:
1197
  """)
 
1203
  - \( RC_i \) is the risk contribution of asset \( i \).
1204
  - \( w_i \) is the weight of asset \( i \).
1205
  - \( \sigma_p \) is the portfolio's standard deviation (volatility).
 
1206
  #### Mathematical Formulation:
1207
  The optimization problem can be formulated as:
1208
  """)
 
1214
  Where:
1215
  - \( n \) is the number of assets.
1216
  - 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} \).
 
1217
  #### Steps Involved:
1218
  1. **Estimate Expected Returns and Covariance Matrix**:
1219
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
 
1225
 
1226
  st.markdown("""
1227
  #### Benefits:
 
1228
  - **Balanced Risk Allocation**: Ensures that no single asset dominates the portfolio risk, leading to a more balanced and diversified portfolio.
1229
  - **Risk Management**: Helps in managing the overall portfolio risk by spreading it equally across all assets.
1230
  """)
 
1258
  st.plotly_chart(fig_weights)
1259
 
1260
  portfolio_returns_erc = returns.dot(np.array(list(optimal_weights_erc.values())))
1261
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1262
+ if isinstance(sp500.columns, pd.MultiIndex):
1263
+ sp500.columns = sp500.columns.get_level_values(0)
1264
+ if sp500.empty:
1265
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1266
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1267
  equal_weighted_returns = returns.mean(axis=1)
1268
 
1269
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_erc,
 
1298
  st.markdown("""
1299
  #### Concept:
1300
  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).
 
1301
  #### Objective:
1302
  The objective of the CVaR Minimization Portfolio is to find the weights of the assets that minimize the CVaR at a given confidence level.
 
1303
  #### Mathematical Formulation:
1304
  The optimization problem can be formulated as:
1305
  """)
 
1314
  - \( \alpha \) is the confidence level (e.g., 95%).
1315
  - \( T \) is the number of observations.
1316
  - \( L_t(w) \) is the loss of the portfolio at time \( t \).
 
1317
  #### Steps Involved:
1318
  1. **Estimate Expected Returns and Covariance Matrix**:
1319
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
 
1358
  st.plotly_chart(fig_weights)
1359
 
1360
  portfolio_returns_cvar = returns.dot(np.array(list(optimal_weights_cvar.values())))
1361
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1362
+ if isinstance(sp500.columns, pd.MultiIndex):
1363
+ sp500.columns = sp500.columns.get_level_values(0)
1364
+ if sp500.empty:
1365
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1366
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1367
  equal_weighted_returns = returns.mean(axis=1)
1368
 
1369
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_cvar,
 
1398
  st.markdown("""
1399
  #### Concept:
1400
  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.
 
1401
  #### Objective:
1402
  The objective of the Maximum Return Portfolio is to find the weights of the assets that maximize the expected return.
 
1403
  #### Mathematical Formulation:
1404
  The optimization problem can be formulated as:
1405
  """)
 
1412
  - \( w \) is the weight vector of the assets.
1413
  - \( \mu \) is the expected return vector of the assets.
1414
  - \( n \) is the number of assets.
 
1415
  #### Steps Involved:
1416
  1. **Estimate Expected Returns**:
1417
  Compute the expected returns (\( \mu \)) for each asset.
 
1421
 
1422
  st.markdown("""
1423
  #### Benefits:
 
1424
  - **High Potential Returns**: Focuses on achieving the highest possible returns.
1425
  - **Simple Approach**: Easy to understand and implement.
 
1426
  #### Drawbacks:
 
1427
  - **Ignores Risk**: Does not consider the risk or volatility associated with the assets.
1428
  - **Potential for High Volatility**: Can result in a highly volatile portfolio.
1429
  """)
 
1457
  st.plotly_chart(fig_weights)
1458
 
1459
  portfolio_returns_max_return = returns.dot(np.array(list(optimal_weights_max_return.values())))
1460
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1461
+ if isinstance(sp500.columns, pd.MultiIndex):
1462
+ sp500.columns = sp500.columns.get_level_values(0)
1463
+ if sp500.empty:
1464
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1465
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1466
  equal_weighted_returns = returns.mean(axis=1)
1467
 
1468
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_max_return,
 
1497
  st.markdown("""
1498
  #### Concept:
1499
  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.
 
1500
  #### Objective:
1501
  The objective of the Maximum Quadratic Utility Portfolio is to maximize the utility function, which is defined as:
1502
  """)
 
1510
  - \( \mu \) is the expected return vector of the assets.
1511
  - \( \Sigma \) is the covariance matrix of the asset returns.
1512
  - \( \lambda \) is the risk aversion coefficient.
 
1513
  #### Steps Involved:
1514
  1. **Estimate Expected Returns and Covariance Matrix**:
1515
  Compute the expected returns (\( \mu \)) and the covariance matrix (\( \Sigma \)) for the assets.
 
1521
 
1522
  st.markdown("""
1523
  #### Benefits:
 
1524
  - **Balances Return and Risk**: Provides a trade-off between maximizing returns and minimizing risk.
1525
  - **Customizable Risk Tolerance**: Allows adjustment of the risk aversion coefficient to match the investor's risk preference.
 
1526
  #### Drawbacks:
 
1527
  - **Requires Accurate Estimates**: The effectiveness of the method depends on accurate estimates of expected returns and the covariance matrix.
1528
  - **Complexity**: More complex to implement compared to simpler methods like Maximum Return Portfolio.
1529
  """)
 
1558
  st.plotly_chart(fig_weights)
1559
 
1560
  portfolio_returns_mqu = returns.dot(np.array(list(optimal_weights_mqu.values())))
1561
+ sp500 = yf.download('^GSPC', start=start_date, end=end_date, auto_adjust=False)
1562
+ if isinstance(sp500.columns, pd.MultiIndex):
1563
+ sp500.columns = sp500.columns.get_level_values(0)
1564
+ if sp500.empty:
1565
+ raise ValueError(f"No S&P 500 data fetched from {start_date} to {end_date}.")
1566
+ sp500_returns = sp500['Adj Close'].pct_change().dropna()
1567
  equal_weighted_returns = returns.mean(axis=1)
1568
 
1569
  fig_cumulative_returns = plot_cumulative_returns(portfolio_returns_mqu,
 
1605
  footer {visibility: hidden;}
1606
  </style>
1607
  """
1608
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)