Hierarchical Risk Parity: Introducing Graph Theory and Machine Learning in Portfolio Optimizer
In this short post, I will introduce the Hierarchical Risk Parity portfolio optimization algorithm, initially described by Marcos Lopez de Prado^{1}, and recently implemented in Portfolio Optimizer.
I will not go into the details of this algorithm, though, but simply describe some of its general ideas together with their associated implementation tweaks in Portfolio Optimizer.
Hierarchical risk parity algorithm overview
Step 1  Hierarchical clustering of the assets
The first step of the Hierarchical Risk Parity algorithm consists in using a hierarchical clustering algorithm to group similar assets together based on their pairwise correlations.
In the original paper, a single linkage clustering algorithm is used^{1}.
In Portfolio Optimizer, 4 hierarchical clustering algorithms are supported:
 (Default) Single linkage
 Complete linkage
 Average linkage
 Ward’s linkage
A good summary of the pros and cons of each of these algorithms can be found in the dissertation of Jochen Papenbrock^{2}.
That being said, practical performances is what ultimately matters, and there seem to be no agreement in the literature or in the industry about which algorithm to choose.
For example, the performances of the Hierarchical Risk Parity algorithm are reported as deteriorating when single linkage is NOT used^{3}, but Ward’s linkage is used in the computation of the institutional index FIVE Robust MultiAsset Index…
Anyway, as an illustration of this first step, the following assets correlation matrix taken from a Munich Re^{4} research note:
is transformed into the following hierarchical clustering tree, visualized as a dendrogram:
Step 2  Quasidiagonalization of the assets correlation matrix
The second step of the Hierarchical Risk Parity algorithm consists in reordering the rows and the columns of the assets correlation matrix according to the order of the leaves of the hierarchical clustering tree computed in the first step^{5}.
Because similar assets are grouped together in the first step, this permutation of the assets correlation matrix tends to reveal a block diagonal structure, hence the name of this second step.
One possible problem with this step is that the order of the leaves of a hierarchical clustering tree is not unique.
Worse, for $n$ assets, there are actually $2^{n1}$ such orders^{6}!
In Portfolio Optimizer, 2 leaf ordering methods are supported:
 (Default) Leaf ordering following clusters heights, identical to what is implemented in the R function hclust
 Leaf ordering minimizing the distance between adjacent leaves^{6}
The second method is the most appropriate w.r.t. the aim of this second step, but has a running time of one or two orders of magnitude higher than the computation of the hierarchical clustering tree itself, which makes it very costly!
For a small universe of assets ($n \leq 100$), though, the impact on performances is minimal.
As an illustration of this second step, the assets correlation matrix of Figure 1 becomes, with the default leaf ordering method:
and becomes, with the optimal leaf ordering method:
In both Figure 3 and Figure 4, the block diagonal structure hidden in the initial assets correlation matrix now clearly appears.
Step 3  Recursive bisection and assets weights computation
The third step of the Hierarchical Risk Parity algorithm consists in recursively bisecting the reordered assets correlation matrix computed in the second step and computing assets weights in inverse proportion to both their variance and their clusters variance.
In the original paper, Marcos Lopez de Prado suggests that constraints on assets weights might be incorporated to this step^{1}.
This is what is done in Portfolio Optimizer, which supports minimum and maximum assets weights constraints^{7} thanks to a variation of the method described in Pfitzinger and Katzke^{8}.
Hierarchical risk parity algorithm usage with Portfolio Optimizer
As a quick practical example of Portfolio Optimizer usage, I propose to reproduce the example provided as Python code in the original paper^{1}.
For this, it suffices to call the Portfolio Optimizer endpoint /portfolio/optimization/hierarchicalriskparity
with the following input data:
This results in the following output, which matches with the expected assets weights^{9}:
{
"assetsWeights": [0.06999366422693354, 0.07592150585740284, 0.10838947598985076, 0.19029103653662804, 0.09719886787191244, 0.10191545037929818, 0.06618867657198627, 0.09095933462021928, 0.07123881246563615, 0.1279031754801325]
}
Last words
I hope that you will find this new portfolio optimization algorithm useful.
As usual, feel free to reach out if you need any algorithm to be implemented, or if you would like support in integrating Portfolio Optimizer!
–

See Lopez de Prado, M. (2016). Building diversified portfolios that outperform outofsample. Journal of Portfolio Management, 42(4), 59–69. ↩ ↩^{2} ↩^{3} ↩^{4}

See Papenbrock, Jochen, Asset Clusters and Asset Networks in Financial Risk Management and Portfolio Optimization. ↩

See Thomas Raffinot, The Hierarchical Equal Risk Contribution Portfolio. ↩

See Markus Jaeger, Stephan Krugel, Towards robust portfolios, Hierarchical risk parity: persistent diversification uncovered by graph theory and machine learning. ↩

Such a procedure is called matrix seriation. ↩

See Ziv BarJoseph, David K. Gifford, Tommi S. Jaakkola, Fast optimal leaf ordering for hierarchical clustering, Bioinformatics, Volume 17, Issue suppl_1, June 2001, Pages S22–S29. ↩ ↩^{2}

This is in contrast to some other closedsource implementations of the Hierarchical Risk Parity algorithm, in which such constraints are managed by a postprocess. ↩

See Johann Pfitzinger & Nico Katzke, 2019. A constrained hierarchical risk parity algorithm with clusterbased capital allocation. Working Papers 14/2019, Stellenbosch University, Department of Economics. ↩

Adaptations to the Python code provided in the original paper^{1} are required in order to execute it in Python 3, and the computed covariance matrix is not the same between Python 2 and Python 3! ↩