Including mean return and variance into the risk parity formulation

[1]:
%matplotlib inline
[2]:
import matplotlib.pyplot as plt
%config InlineBackend.figure_format = "retina"
from matplotlib import rcParams
rcParams["savefig.dpi"] = 100
rcParams["figure.dpi"] = 100
rcParams["font.size"] = 14
[3]:
import riskparityportfolio as rp
import numpy as np

In this notebook, we will check out the necessary interface to take into account the mean return and the variance while optimizing risk parity portfolios.

Adding the mean return

riskparity.py allows users to account for the mean return of the designed portfolio, i.e., we solve the following optimization problem:

\begin{array}{ll} \underset{\mathbf{w}}{\textsf{minimize}} & R(\mathbf{w}) - \alpha \mathbf{w}^{\top}\boldsymbol{\mu}\\ \textsf{subject to} & \mathbf{w} \geq \mathbf{0}, \mathbf{1}^\top\mathbf{w} = 1 \end{array}

In this notebook, we will construct the Mean Return x Risk Concentration profile for a simple universe with three assets. Like in the previous notebook, let’s assume their covariance matrix, mean vector, and our budget vector are as follows:

\[\begin{split}\boldsymbol{\Sigma} = \left[ \begin{array}{ccc} 1.0000 & 0.0015 & -0.0119\\ 0.0015 & 1.0000 & -0.0308\\ -0.0119 & -0.0308 & 1.0000 \end{array} \right]\end{split}\]
\[\begin{split}\boldsymbol{\mu} = \left[ \begin{array}{c} 0.1837 \\ 0.3465 \\ 0.5210 \end{array} \right]\end{split}\]
\[\begin{split}\mathbf{b} = \left[ \begin{array}{c} 0.1594 \\ 0.0126 \\ 0.8280 \end{array} \right]\end{split}\]
[4]:
Sigma = np.vstack((np.array((1.0000, 0.0015, -0.0119)),
                   np.array((0.0015, 1.0000, -0.0308)),
                   np.array((-0.0119, -0.0308, 1.0000))))
[5]:
Sigma
[5]:
array([[ 1.    ,  0.0015, -0.0119],
       [ 0.0015,  1.    , -0.0308],
       [-0.0119, -0.0308,  1.    ]])
[6]:
mu = np.array([0.1837, 0.3465, 0.5210])
[7]:
mu
[7]:
array([0.1837, 0.3465, 0.521 ])
[8]:
b = np.array((0.1594, 0.0126, 0.8280))
[9]:
b
[9]:
array([0.1594, 0.0126, 0.828 ])
[10]:
my_portfolio = rp.RiskParityPortfolio(covariance=Sigma, budget=b)
[11]:
risk_parity = []
mean_return = []
for alpha in 10 ** np.arange(-5, 0, .25):
    my_portfolio.add_mean_return(alpha=alpha, mean=mu)
    my_portfolio.design()
    risk_parity.append(my_portfolio.risk_concentration.evaluate())
    mean_return.append(my_portfolio.mean_return)
/Users/mirca/opt/miniconda3/lib/python3.7/site-packages/jax/lib/xla_bridge.py:120: UserWarning: No GPU/TPU found, falling back to CPU.
  warnings.warn('No GPU/TPU found, falling back to CPU.')
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 0/500 [00:00<?, ?it/s]
  0%|          | 1/500 [00:00<00:18, 26.28it/s]
  0%|          | 1/500 [00:00<00:18, 26.71it/s]
  0%|          | 1/500 [00:00<00:18, 26.98it/s]
  0%|          | 1/500 [00:00<00:18, 27.30it/s]
  0%|          | 1/500 [00:00<00:18, 27.14it/s]
  0%|          | 1/500 [00:00<00:18, 26.49it/s]
  0%|          | 2/500 [00:00<00:09, 51.48it/s]
  0%|          | 2/500 [00:00<00:09, 53.50it/s]
  0%|          | 2/500 [00:00<00:09, 51.68it/s]
  1%|          | 3/500 [00:00<00:06, 74.91it/s]
  4%|▍         | 19/500 [00:00<00:01, 280.60it/s]
  0%|          | 2/500 [00:00<00:09, 51.13it/s]
  1%|          | 3/500 [00:00<00:06, 72.70it/s]
  1%|          | 4/500 [00:00<00:05, 92.90it/s]
[12]:
plt.plot(risk_parity, mean_return, 'ko')
plt.plot(risk_parity, mean_return, 'k-')
plt.ylabel("mean return")
plt.xlabel("risk parity")
[12]:
Text(0.5, 0, 'risk parity')
../_images/tutorials_including-mean-return-and-variance_18_1.png

Adding the variance

Likewise, riskparity.py allows users to include the variance of the portfolio into the objective function:

\begin{array}{ll} \underset{\mathbf{w}}{\textsf{minimize}} & R(\mathbf{w}) + \lambda \mathbf{w}^{\top}\boldsymbol{\Sigma}\mathbf{w}\\ \textsf{subject to} & \mathbf{w} \geq \mathbf{0}, \mathbf{1}^\top\mathbf{w} = 1 \end{array}

Let’s also investigate the Volatility x Risk Concentration profile using the same parameters as in the previous example.

[13]:
my_portfolio = rp.RiskParityPortfolio(covariance=Sigma, budget=b)
[14]:
risk_parity = []
volatility = []
for lmd in 10 ** np.arange(-5, 0, .25):
    my_portfolio.add_variance(lmd=lmd)
    my_portfolio.design()
    risk_parity.append(my_portfolio.risk_concentration.evaluate())
    volatility.append(my_portfolio.volatility)
  0%|          | 2/500 [00:00<00:13, 36.18it/s]
  0%|          | 2/500 [00:00<00:13, 37.80it/s]
  0%|          | 2/500 [00:00<00:10, 48.04it/s]
  0%|          | 2/500 [00:00<00:09, 49.96it/s]
  1%|          | 3/500 [00:00<00:07, 68.46it/s]
  1%|          | 3/500 [00:00<00:07, 70.67it/s]
  1%|          | 3/500 [00:00<00:07, 70.17it/s]
  1%|          | 3/500 [00:00<00:06, 74.43it/s]
  1%|          | 4/500 [00:00<00:05, 96.96it/s]
  1%|          | 4/500 [00:00<00:05, 96.13it/s]
  1%|          | 4/500 [00:00<00:05, 91.48it/s]
  1%|          | 4/500 [00:00<00:05, 93.94it/s]
  1%|          | 3/500 [00:00<00:06, 76.35it/s]
  0%|          | 2/500 [00:00<00:09, 51.68it/s]
  1%|          | 4/500 [00:00<00:06, 81.39it/s]
  1%|          | 5/500 [00:00<00:04, 112.15it/s]
  1%|          | 6/500 [00:00<00:03, 142.19it/s]
  1%|▏         | 7/500 [00:00<00:03, 150.57it/s]
  1%|▏         | 7/500 [00:00<00:03, 150.13it/s]
  1%|▏         | 7/500 [00:00<00:03, 140.67it/s]
[15]:
plt.plot(risk_parity, volatility, 'ko')
plt.plot(risk_parity, volatility, 'k-')
plt.ylabel("volatility")
plt.xlabel("risk parity")
[15]:
Text(0.5, 0, 'risk parity')
../_images/tutorials_including-mean-return-and-variance_25_1.png
[ ]: