May 4, 2019
This script will generate a simulation and plot of correlated return streams in a portfolio. The objective is to replicate "The Holy Grail" study of investing from Ray Dalio (see references). This demonstrates the effect of adding uncorrelated assets to a portfolio. Inputs are mean of return stream, standard deviation (risk) of return stream, list of correlation percentages to study, and number of assets in the portfolio.
References:
[0a] "The Holy Grail" from https://www.youtube.com/watch?v=Nu4lHaSh7D4,
[0b] Principles, Ray Dalio, pg 57
[1] Random number generation adapted from: https://realpython.com/python-random/
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def corr2cov(p: np.ndarray, s: np.ndarray) -> np.ndarray:
"""Covariance matrix from correlation & standard deviations"""
d = np.diag(s)
return d @ p @ d
mean_assets = .1
stdev_assets = .1
correlation_test_list = [ .60, .40, .20, .10, 0 ]
df = pd.DataFrame()
for correlation in correlation_test_list:
# set empty list to gather results
result_list = []
# assume we want 1 to 20 assets
for num_assets in range(1,21):
# Start with a correlation matrix and standard deviations.
# Retrieve the correlation between assets for this run, and the correlation
# of a variable with itself is 1.0.
# Create dynamically sized correlation matrix based on number of assets.
corr_matrix = np.zeros((num_assets, num_assets), float)
corr_matrix.fill(correlation)
np.fill_diagonal(corr_matrix, 1)
# Standard deviations/means of assets, respectively
stdev = np.empty(num_assets, dtype='float')
stdev.fill(stdev_assets)
mean = np.empty(num_assets, dtype='float')
mean.fill(mean_assets)
cov = corr2cov(corr_matrix, stdev)
# `size` is the length of time series for 2d data
# (500 months, days, and so on)
data = np.random.multivariate_normal(mean=mean, cov=cov, size=500)
# look at portfolio of average returns
portfolio_returns = np.mean(data, axis=1)
portfolio_risk = np.std(portfolio_returns)
# gather results
result_list.append(portfolio_risk)
# collect data in dataframe
df.insert(0,'Corr '+str(correlation),result_list,True)
plt.figure(figsize=(12,5))
plt.plot(df)
plt.title('The Holy Grail')
plt.legend(df.columns)
plt.xlabel('Number of Assets/Alpha in Portfolio')
plt.ylabel('Portfolio Standard Deviation')
plt.show()
As you increase the number of returns streams in your portfolio, you don't necessarily obtain healthy diversification, unless they are uncorrelated. Given the assumptions above for this portfolio, this study shows that:
When you reach this marginal utility threshold, you are able to most effectively generate the same return with decreased risk.
Adding uncorrelated return streams to a portfolio helps you reduce fluctuations while receiving the same expected return.