Photo by Lee Ferrell on Unsplash

Modern Portfolio Theory with Python

Julian Chang
9 min readOct 26, 2021

Introduction

Modern Portfolio Theory (MPT) ties understanding of risk and statistics by assuming a normal distribution of returns on assets. Linking investor’s perception of risk to the uncertainty of returns (standard deviation). Thanks to articles by roesener and Parth Tyagi, I learnt how to construct a portfolio according to MPT. And I wondered how it can apply to more than equities. The objective of this article will be to explain implementation of MPT concepts in a Python code.

Portfolio will use multi-asset allocations i.e. Equity, Fixed Income, Commodities, Gold and Long-volatility funds. The unique choice of classes is based on the work by fund manager Chris Cole. Instead of single stocks the assets will use market or fund indices. Github code

Returns

Before we dive into the computation of the returns on an asset and portfolio, let us briefly visit the definition of portfolio and returns.

Portfolio: A portfolio is a collection of financial instruments like stocks, bonds, commodities, cash and cash equivalents , as well as their fund counterparts. (Investopedia)

In this article, we will have our portfolio containing 5 major asset classes, with the target of sustained growth over 100 years.

  1. equity (S&P500 “SNP” + Emerging Market “EEM”)
  2. FI (Treasury Total Return “IEF”+ Investment Grade Bond Indices “AGG”)
  3. Commodities (Bloomberg Commodity Index “BCM”)
  4. Gold (LBMA Gold Index “GLD”)
  5. Long-volitility (Eurohedge long vol funds Index “LOV”)

[*] ‘Adj Close’ is taken for equities, FI, Commodities indices. Dividends are also included in price to find total return on asset. Eurohedge long-vol funds index only reports indices on a monthly as apposed to daily pricing

The preview of our data is shown below:

Share and Index Prices

Returns: Refers to the annualised gain or loss on our asset/portfolio over a fixed time frame. In this analysis, we make a return as the percentage change in the closing price of the asset over the previous day’s closing price. We will compute the returns using .pct_change() function in python. Then take the log difference of the percentage returns.

Note: the first row is Null if there doesn’t exist a row prior to that to facilitate the computation of percentage change.

% log difference Prices
# get average daily return times 365.25 trading days a year
e_r = df.mean()*365.25

# plot exp return
e_r.plot(kind='bar')
plt.title('Expected Annual Return');

SNP highest, Commodities lowest.

risk-free rate(IEF) is greater than investment grade bonds

long-vol still outperforms commodities

Return of a portfolio is defined as the weighted sum of the returns of the assets in the portfolio.

Risk and Volatility

MPT does assume a normal distribution in returns. So the variance in the returns is considered to be normal. The Standard Deviation is often considered to be the volatility or risk involved with the investment. Just like returns, the standard deviation should be annualised over the 365.25 days in a year.

Standard Deviation (Volatility):

sd = df.std()*np.sqrt(365.25) 
sd.plot(kind=’bar’)
plt.title(‘Standard Deviation (Volatility)’);

Fixed income (i.e. IEF, AGG ) have the lowest. Although interesting that investment grade bonds have lower vol than EEM however the dividends are distributed

Commodities (BCM, LOV) have twice as much

Equity has 3 times as much as fixed income

although EEM has the highest vol it doesn’t have the highest returns which should not be expected because higher volatility or risk is associated with more returns

Product Covariances

cov_matrix = df.cov()
cov_matrix
sns.heatmap(df.corr(),annot=True);
Correlation of all assets

Equities (SNP, EEM) have a high correlation

Equities (SNP, EEM) have a slight negative correlation to the risk-free return (IEF) (? due to substitution effect?)

Fixed Income (IEF, AGG) have a correlation

Fixed Income (IEF, AGG) have no to negative correclation with commodities

Commodities and gold have a small correlation

long vol is least correlated to all portfolios

Portfolio Returns and Risk

“Diversification is the only free lunch (in investing)” — Harry Markowitz.

Harry Markowitz won a nobel prize for discovering MPT, so I think it’s pretty safe to follow his advice on investing. Diversification means buying many products with varying correlations so that the aggregate value of the entire portfolio grows over time. The covariance matrix will help us calculate the expected variance and risk related to a portfolio with multiple products in them.

An investor’s objective maximising returns with a constraint of cash. So it is the job of the portfolio manager to allocate investments wisely to maximise returns. At the same time, each investment carries risk. In general higher returns is often associated with higher risk (volatility). We can get into allocation later. For now we want to calculate the returns and risk of any portfolio

Portfolio Weights

Portfolio composition can be expressed as a matrix of portfolio weights, w

for example if

#     SNP EEM  IEF  AGG  BCM  GLD  LOVw = [ 0.4,0.1, 0.1, 0.1, 0.1, 0.1, 0.1]

Then SNP is 40% of the portfolio, EEM is 10% of the portfolio, IEF is 10% of the portfolio and so on

Portfolio Returns

returns = np.dot(weights,e_r)

Return of a portfolio is the weighted sum of returns over that period

Portfolio Covariance

cov_matrix.mul(weights,axis=0).mul(weights,axis=1).sum().sum()

Portfolio variance is derived from covariance because multiple products have different variances.

Portfolio Standard Deviation

ann_sd = np.sqrt(var*365.25)

Standard deviation is calculated from the resultant portfolio variance

Efficient Frontier

Now we know how to get the return and risk for any portfolio, we want to tabulate this for many permutations of a weighted portfolio. Thankfully we can get our computers to calculate Returns and Risk can be calculated for any combination of portfolio weights (w). Even more we create a loop to make a randomly weighted portfolio and calculate the Return and Risk each time. Then the results are tabulated.

Sample of 3 randomly weighted portfolios with associated returns and volatility

Thats nice, but whats the point? Well Markowitz understood that by plotting returns against risk we can observe the boundary which he called the efficient frontier.

min_var_port = portfolios.loc[portfolios[‘volatility’].idxmin()]portfolios.plot.scatter(x=’volatility’,y=’returns’,grid=True,\
marker=’o’, s=10, alpha=0.3,figsize=[10,10])
plt.scatter(x=min_var_port[1],y=min_var_port[0],\
color=’r’, marker=’*’, s=500)
plt.title(‘Efficient Frontier and least risky portfolio’);

Each blue dot represents a randomly weighted portfolio and is plot against their historical return and risk (volatility). Multiple levels of risk can be observed that for any level of return over, then determine what that portfolio composition would be. More likely; if an investor wants to reduce their risk but still obtain a given return they would want to be on the left edge of this blue plot. Along this edge, the investment risk is always the least for a given return.

The red star is the plot with the left most plot with least risk with below return and volatility.

returns       0.0446
volatility 0.0433

Risk-adjusted Portfolio

Sharpe Ratio

Sharpe Ratio is the ratio of return to risk. The larger the ratio, the more return per unit risk. rf is the risk free rate of return, we assume a return of 0.08% which is the US Treasury 1-month dividend rate. The risk free rate of return is the return (in this case dividend) for not investing in an asset that is not cash.

A new column with the sharpe ratio for each portfolio is calculated

portfolios['sharpe'] = (portfolios['returns']-rf)/portfolios['volatility']

The yellow star has the greatest calculate sharpe ratio with below return and volatility. We see that the returns is higher than the least risky portfolio, but more importantly it has the highest return per unit risk.

returns       0.0557
volatility 0.0466

Capital Allocation Line

The Capital Allocation Line (CAL) here assumes an investor allocates a weighted portfolio of cash and/or the optimised sharpe ratio portfolio. The line is plotted on the same axis of the Efficient Frontier.

The line (purple line in plot below) intersects the y-axis where the investor holds 100% risk free asset, where the rate of return is the dividend of a bond. The line intersects Efficient Frontier when the investor holds 100% of the Sharpe Optimised portfolio. If the portfolio hold is risk taking it can take loans where Sharpe Optimised portfolio is more that 100% and cash is less than 0%.

There is a linear relationship in returns, because the entire portfolio only has 2 components and as the risky portfolio weight decreases the returns decrease monotonously.

Utility Function

This function is from an economic model. Utility is as expressed as returns and is discounted by the level of realised risk. the coefficient of risk aversion is A. If an invest is less risk averse A is small. We assume 25 < A < 35. Expected return, E(R) is proportionate to the level of utility .

Here we will use 30 as a conservative level

u = er - .5*a*(sd**2)

Final optimised portfolio

To come up with the final optimised portfolio below, we find the point on the Capital Allocation Line with the greatest utility.

Assumptions

  • investors always want to reduce risk
  • investors want the greatest return for risk
investors_port = cal.iloc[cal['utility'].idxmax()]portfolios.plot.scatter(x='volatility',y='returns',grid=True,\
marker='o', s=10, alpha=0.3,figsize=[10,10])
plt.scatter(x=min_var_port[1],y=min_var_port[0],\
color='y', marker='*', s=500)
plt.scatter(x=sharpe_max[1],y=sharpe_max[0],\
color='r', marker='*', s=500)
plt.plot(cal_x, cal_y, color='purple')
plt.plot(investors_port[2], investors_port[1], '*', color='lightgreen');
Plot for Final Optimised Portfolio

The bright green dot indicates the finalised portfolio with moderate risk appetite and the portfolio mix is below:

utility       0.0206
returns 0.0405
volatility 0.0337
SNP_weight 0.115
EEM_weight 0.007
IEF_weight 0.436
AGG_weight 0.088
BCM_weight 0.001
GLD_weight 0.049
LOV_weight 0.026
sharpe 0.851
Cash 0.277

Greatest weight is in treasury bills which take up 44% then cash up to 28% then the risky equity is low at 12%. Nonetheless it has given an annualised return of 4% p.a.

Validating greatest utility, return, volatility

There are infinite alternatives to using the Capital Allocation Line. Here, the utility for each randomised portfolio can be calculated, solving for the maximum utility.

Then then compare the returns of the Capital Allocation Line for the same risk.

Greatest Utility on the Capital Allocation Line
Greatest Utility among randomised portfolios

CAL has utility values greater than 0 but the random portfolios on the Efficient Frontier have negative values. Therefore, CAL allocates with higher return with even lower volatility. Also the point on the Efficient Frontier does not maximise return to volatility.

Conclusion

Since different products and assets have varied correlations MPT helps to highlight the spectrum of combinations of portfolios result in a continuum of risk vs returns. These relationships can be plotted with python above. Then using a risk discounted return, capital allocation and maximum utility criterium is used to determine a final optimal portfolio. This portfolio then provides the best risk-adjusted returns from 2004–12 to 2021–09.

Now if historical events are actually an indicator for future performance I would be very confident but this assumption may not hold true.

Disclaimer: I am not an investment advisor. This is not to be considered as a financial advice for buying or selling of stocks, bonds or dealing in any other securities.Conduct your own due diligence, or consult a licensed financial advisor or broker before making any and all investment decisions.

About the author

I work in Pratt and Whitney. Currently studying Data Science at General Assembly. You can reach out to me at changjulian17@gmail.com or https://www.linkedin.com/in/julian-chang/

--

--

Julian Chang

Data Scientist Student (Singapore, 🇸🇬) Looking for a job as a Data Analyst / Scientist