Modern Portfolio Theory

Modern portfolio theory (MPT) uses a statistical model to show how, by diversifying investments, an investor can optimize their portfolio by finding the optimal asset allocation for a given risk. Rather than traditional value investing where investors attempt to pick investments based on some form of "fundamental analysis" such as high dividend yields or low price-to-earning or price-to-book ratios, MPT deals in the statistical volatility (risk) and return of an investment. Additionally, the covariance between investments is used to determine how different mixes of investments change the overall volatility of the portfolio. The investor is assumed to desire the maximum expected return with lowest risk and the theory shows how to find that asset allocation. 

moneychimp has a very well done tutorial on the concepts of MPT. The Wikipedia article is also a good starting point and includes more details and formulas. Finally, Dr. Sharpe, inventor of the Sharpe ratio and winner of the Nobel Prize in Economics, at Stanford has an excellent web page with many examples and sample code for doing macro investment analysis including MPT.

The Project

After reading Dr. Sharpe's articles and examples, I wanted to try his analysis with real investment data. The MPT approach appealed to me as someone who had never believed in the value investing theory that an individual investment can be accurately priced. The statistical model of risk vs. return makes more sense to me and seems less arbitrary. Additionally, MPT dove-tails nicely with the idea of using index funds for diversification and finally, it's simpler to work with as any investment can be analyzed.

To complete this project, I wrote a Python program for downloading historical data from Yahoo and some Octave scripts to plot the data using Dr. Sharpe's function.

Required Software

I used Cygwin/X with the python, octaveand octave-forgepackages installed. Octave is an excellent open source Matlab clone. None of my code is platform dependent and should work on any system with Python and Octave installed.

Conveniently for us, Dr. Sharpe has written a Matlab function to calculate a portfolio for a given set of investments. It can be downloaded here: gqp.m

My scripts are here:

Downloading the historical data

The python script mpt_getdata.pytakes a list of ticker symbols and bounds from a file and downloads the historical investment data from Yahoo.

Running mpt_getdata.py to download historical data
> ./mpt_getdata.py stocks
IVE
VTI
IVW
IJH
IJK
IJS
IJR
IJT

As input, the script takes a file with a stock ticker on each line followed by a lower and upper bound of holdings for the investment. The bounds are expressed as values between one and zero.

stocks
IVE 0 1
VTI 0 1
IVW 0 1
IJH 0 1
IJK 0 1
IJS 0 1
IJR 0 1
IJT 0 1

The data downloaded from Yahoo is the monthly closing price adjusted for dividends. It comes in the format of comma separated values (csv) files.

IVE.csv
Date,Open,High,Low,Close,Volume,Adj. Close*
1-Sep-06,70.66,71.69,69.66,71.19,393400,71.19
1-Aug-06,69.18,70.50,68.28,70.30,391586,70.30
3-Jul-06,68.61,69.50,66.26,69.33,264410,69.33
1-Jun-06,69.00,70.04,65.64,68.71,340731,68.71
1-May-06,70.65,71.81,67.16,68.75,229909,68.42
3-Apr-06,69.30,70.74,68.24,70.62,342215,70.28
1-Mar-06,68.07,69.72,67.06,68.76,204760,68.43
1-Feb-06,66.90,68.60,65.91,67.65,334221,67.32
3-Jan-06,65.25,67.53,64.96,66.90,290845,66.58

NOTE: If the data for a particular ticker has already been downloaded, it won't be downloaded again. To force the script to re-fetch the data, simply delete the csvfile.

Analyzing the Data

Now that the data has been downloaded, we'll use Octave to analyze it. First we'll load the mpt_setup.mscript created by mpt_getdata.pyto setup the initial vector of investment names and bounds. Then, we'll plot the optimal portfolios over the risk tolerances (0,100).

IMPORTANT:All the historical data will be truncated to the investment with the shortest history. For example, if you have one investment with only one year of historical data, the history of all the other investments will be truncated to one year.

Analyzing the data in Octave
> octave
octave:1> mpt_setup
octave:2> mpt_invest(investments,lb,ub,0,100)
IVE.csv 0.000000
VTI.csv 0.000000
IVW.csv 0.000000
IJH.csv 0.000000
IJK.csv 0.000000
IJS.csv 1.000000
IJR.csv 0.000000
IJT.csv 0.000000
sd_expected = 5.1895
e_expected = 1.0125
This creates two graphs and outputs some data on the console.
Graph 1
Graph 1
This graph plots each of the investments as a standard deviation vs. average return. The higher the standard deviation the higher the risk, but the investor should expect a subsequently higher average return. In this plot, the riskiest investment with a standard deviation of 5.2 is IJS, but it also has the highest average return of 1.2% a month or 14.4% a year. The safest investment is IVW with a standard deviation of 3.8, but an average yearly return of  (0.14% * 12) = 1.68%. The curving PORTFOLIO line shows the expected return of the MPT portfolio for a given standard deviation. Note that for an identical amount of risk (standard deviation), the MPT portfolio will have a higher expected return than an individual investment. This is the power of MPT.

The second graph shows what investments comprise the portfolio for a given risk tolerance.


Graph 2
We can see that as our risk tolerance changes, the asset allocation of our portfolio changes. A low risk portfolio would contain IVW, IJH and VTI whereas a higher risk portfolio would have IJH, IJS and IJR. Eventually, as our risk tolerance increases, the MPT portfolio contains only IJS which is our highest risk, but also highest performing investment.

NOTE
: The risk plotted here is the risk tolerance, not the standard deviation of the previous chart.

Finally, we have calculated the MPT portfolio for the midpoint of our risk tolerance graph. In this case we plotted from (0,100) so the script will show the MPT portfolio for a risk tolerance of (100-0)/2 = 50. In this case, 100% of the portfolio is invested in IJS. The expected monthly return is 1.01% with a standard deviation of 5.19.

Our MPT portfolio
IVE.csv 0.000000
VTI.csv 0.000000
IVW.csv 0.000000
IJH.csv 0.000000
IJK.csv 0.000000
IJS.csv 1.000000
IJR.csv 0.000000
IJT.csv 0.000000
sd_expected = 5.1895
e_expected = 1.0125

For most investors, this is probably too much risk. We can re-calculate the MPT portfolio over a lower range of risk tolerances and get a different portfolio.

Analyzing the data in Octave
octave:3> mpt_invest(investments,lb,ub,0,20)
IVE.csv 0.000000
VTI.csv 0.188330
IVW.csv 0.221459
IJH.csv 0.590210
IJK.csv 0.000000
IJS.csv 0.000000
IJR.csv 0.000000
IJT.csv 0.000000
sd_expected = 3.9976
e_expected = 0.56279
This is the MPT portfolio for a risk tolerance of (20-0)/2 = 10. Note that the expected monthly return has dropped to 0.56%, but standard deviation has also dropped to 4.00. The new portfolio is invested in 59% IJH, 22% IVW and 19% VTI.

You can continue to experiment with this function to find a combination of risk and return that you are comfortable with. Remember, if you change the investments you are using by editing the stocksfile, rerun mpt_getdata.pyand mpt_setupin octave to load the new data. You can also try limiting the upper bounds of some investments in your stocksfile to prevent your portfolio from having too much of any one investment.

A note about Standard Deviation and Expected Return
Assuming, our historical returns are normally distributed, we can calculate the range of expected yearly results in the first standard deviation given the e_expectedand sd_expected.
 
Standard deviation range
octave:4> mpt_stdrange(3.9976,.56279)
ans = 0.58782
ans = 1.0675
ans = 1.5472

In the example above, with a standard deviation of 3.9976 and average monthly return of 0.56279 the range is 0.58782 to 1.5472 with a mean of 1.0675. This means that $1 invested in this portfolio will have a 68% chance (size of one standard deviation) of being worth between $0.56 and $1.55 with an expected average value of $1.07.

Contact
If you have any comments, questions or problems, I'd love to hear from you!
email

Future Improvements

  1. Error checking of bounds values
  2. Combining Python and Octave scripts into one

Bugs

  1. Using a non-zero lower bound for any investment may cause an invalid staring vector (x0) creating undefined results.