We've already seen that given a set of expected returns and a covariance matrix, we can plot the efficient frontier. In this section, we'll extend the code to locate the point on the efficient frontier that we are most interested in, which is the tangency portfolio or the Max Sharpe Ratio portfolio. Let's start by the usual imports, and load in the data.
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import PortfolioAnalytics as PortfolioAnalytics
import PortfolioLoadData as PortfolioLoadData
import PortfolioRisk as PortfolioRisk
import PortfolioStatistics as PortfolioStatistics
import PortfolioBacktest as PortfolioBacktest
%load_ext autoreload
%autoreload 2
ind = PortfolioLoadData.get_ind_returns()
er = PortfolioStatistics.annualize_rets(ind["1996":"2000"], 12)
cov = ind["1996":"2000"].cov()
PortfolioAnalytics.plot_ef(20, er, cov, style='-', show_cml=True, riskfree_rate=0.1)
Although the promise of the Markowitz procedure is exciting, in tends to fall apart in practice. The problem is that we rarely know Expected Returns and Expected Covariance in advance. Our estimates almost certainly contain some estimation error, and we'll see that the procedure is highly sensitive to these errors, which tend to get exaggerated in the portfolio.
One way to avoid this estimation game is to skip the entire process and just rely on naive diversification, which means hold all stocks with equal weight.
PortfolioAnalytics.plot_ef(20, er, cov, show_cml=True, riskfree_rate=0.1, show_ew=True)
Researchers have shown that the EW portfolio is a remarkably good portfolio to hold. In fact, there is overwhelming siupport for the idea that it is a far better portfolio to hold than a cap-weighted equivalent. As you can see, the EW portfolio is far inside the efficient frontier, but it requires no estimation whatsoever.
However, there is another point on the efficient frontier that is very interesting. This is the nose of the hull, which is the portfolio of lowest volatility across all possible portfolios. This is called the Minimum Volatility or the Global Minimum Volatility or GMV portfolio.
The interesting thing about it is that if you assume that all returns are the same, the optimizer cannot improve the sharpe ratio through raising returns, and so it must do so my lowering volatility. This means that if we just skip any returns estimation and assume all returns have the return, we'd get the weights of the GMV portfolio!
PortfolioAnalytics.plot_ef(20, er, cov, show_cml=True, riskfree_rate=0.1, show_ew=True, show_gmv=True)
We'll start by implementing the basic Constant Proportion Portfolio Insurance dynamic risk budgeting algorithm, and test it against different portfolios. The CPPI concept is also known as the Constant Risk of Ruin Principles
# Load the industry returns and the total market index
ind_return = PortfolioLoadData.get_ind_returns()
tmi_return = PortfolioLoadData.get_total_market_index_returns()
ind_return.head()
The CPPI algorithm is surprisingly simple to implement. This takes as input, the returns of a risky asset and a safe asset, along with the initial wealth to invest at the start, along with a floor that should not be violated.
btr = PortfolioBacktest.run_cppi(ind_return["2000":][["Steel", "Fin", "Beer"]])
ax = btr["Wealth"].plot(figsize=(12,5))
btr["Risky Wealth"].plot(ax=ax, style="--")
# with CPPI
PortfolioBacktest.summary_stats(btr["Wealth"].pct_change().dropna())
# without CPPI
PortfolioBacktest.summary_stats(btr["Risky Wealth"].pct_change().dropna())
Insurance strategies usually help with drawdowns, but they can also be adapted to explictly limit the drawdown.You can now call run_cppi witk a parameter drawdown. For instance, to run CPPI and limit the drawdown to 20%
btr = PortfolioBacktest.run_cppi(ind_return["2000":][["Steel", "Fin", "Beer"]], drawdown=0.20)
ax = btr["Wealth"].plot(figsize=(12,5))
btr["Risky Wealth"].plot(ax=ax, style="--")
# with drawdown limit of 20%
PortfolioBacktest.summary_stats(btr["Wealth"].pct_change().dropna())[["Annualized Return", "Annualized Vol", "Sharpe Ratio", "Max Drawdown"]]
# no limit
PortfolioBacktest.summary_stats(btr["Risky Wealth"].pct_change().dropna())[["Annualized Return", "Annualized Vol", "Sharpe Ratio", "Max Drawdown"]]
# and for the total market
btr = PortfolioBacktest.run_cppi(tmi_return["1999":], drawdown=0.20)
ax = btr["Wealth"].plot(figsize=(12,5))
btr["Risky Wealth"].plot(ax=ax, style="--")
# with CPPI Insurance
PortfolioBacktest.summary_stats(btr["Wealth"].pct_change().dropna())[["Annualized Return", "Annualized Vol", "Sharpe Ratio", "Max Drawdown"]]
# without CPPI Insurance
PortfolioBacktest.summary_stats(btr["Risky Wealth"].pct_change().dropna())[["Annualized Return", "Annualized Vol", "Sharpe Ratio", "Max Drawdown"]]