Skip to content

Commit

Permalink
rename hcaa algo
Browse files Browse the repository at this point in the history
  • Loading branch information
aditya1702 committed Jun 21, 2020
1 parent 6617c55 commit d8af49d
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 53 deletions.
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ This project is licensed under an all rights reserved licence.
portfolio_optimisation/mean_variance
portfolio_optimisation/critical_line_algorithm
portfolio_optimisation/hierarchical_risk_parity
portfolio_optimisation/hierarchical_clustering_asset_allocation
portfolio_optimisation/hierarchical_equal_risk_contribution
portfolio_optimisation/nested_clustered_optimisation
portfolio_optimisation/theory_implied_correlation

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. _portfolio_optimisation-hierarchical_clustering_asset_allocation:
.. _portfolio_optimisation-hierarchical_equal_risk_contribution:

.. |br| raw:: html

Expand Down Expand Up @@ -28,13 +28,11 @@
for users to use them.

===============================================
Hierarchical Clustering Asset Allocation (HCAA)
Hierarchical Equal Risk Contribution (HERC)
===============================================

The Hierarchical Clustering based Asset Allocation (HCAA) method takes inspiration from the Hierarchical Risk Parity (HRP)
algorithm and uses machine learning to allocate weights efficiently. While both the HCAA and HRP algorithms use hierarchical tree
clustering and machine learning to allocate their weights, there are some subtle differences between the two. Lets look at a quick
overview of how the HCAA algorithm works:
The Hierarchical Equal Risk Contribution (HERC) method takes inspiration from the Hierarchical Risk Parity (HRP)
algorithm and the Hierarchical Clustering based Asset Allocation (HCAA) and uses machine learning to allocate weights efficiently. While both the HERC and HRP algorithms use hierarchical tree clustering to allocate their weights, there are some subtle differences between the two. Lets look at a quick overview of how the HERC algorithm works:

Overview of the Algorithm
#########################
Expand All @@ -56,9 +54,9 @@ dendrogram).
Calculate Optimal Number of Clusters
************************************

This step is where HCAA deviates from the traditional HRP algorithm. The hierarchical risk parity method uses single linkage
This step is where HERC deviates from the traditional HRP algorithm. The hierarchical risk parity method uses single linkage
and grows the tree to maximum depth. However, the number of clusters identified by growing the tree maximally may not be the
optimal one and can lead to sub-optimal results. **This is why before allocating the weights, HCAA calculates the optimal number of clusters and cuts the hierarchical tree formed in Step-1 to the required height and clusters**. Currently, the Gap Index is used for
optimal one and can lead to sub-optimal results. **This is why before allocating the weights, HERC calculates the optimal number of clusters and cuts the hierarchical tree formed in Step-1 to the required height and clusters**. Currently, the Gap Index is used for
calculating the required number of clusters.

.. image:: portfolio_optimisation_images/gap.png
Expand All @@ -77,7 +75,7 @@ difference between the recursive bisections of the two algorithms.
As seen in the above image, at each step, the weights in HRP trickle down the tree by breaking it down the middle based on the
number of assets. Although, this uses the hierarchical tree identified in Step-1, it does not make use of the exact structure
of the dendrogram while calculating the cluster contributions. This is a fundamental disadvantage of HRP which is improved
upon by HCAA by dividing the tree, at each step, based on the structure induced by the dendrogram.
upon by HERC by dividing the tree, at each step, based on the structure induced by the dendrogram.

At each level of the tree, an Equal Risk Contribution allocation is used i.e. the weights are:

Expand All @@ -94,7 +92,7 @@ are the risk contributions of left and right clusters.
and can underestimate the true risk of a portfolio which is why there are many other important risk metrics used by
investment managers that can correctly reflect the true risk of a portfolio/asset. With respect to this, the original HRP
algorithm can be tweaked to allocate its weights based on different risk representations of the clusters and generate
better weights. The HCAA method in mlfinlab provides the following risk metrics:
better weights. The HERC method in mlfinlab provides the following risk metrics:

1. ``variance`` : Variance of the clusters is used as a risk metric.
2. ``standard_deviation`` : Standard deviation of the clusters is used as a risk metric.
Expand All @@ -118,10 +116,13 @@ weight of the :math:`i^{th}` cluster calculated in Step-3.

.. tip::
|h4| Underlying Literature |h4_|
This implementation is based on the following two papers written by Thomas Raffinot.
This implementation is based on the following two papers written by Thomas Raffinot:

* `Hierarchical Clustering based Asset Allocation <https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3237540>`_
* `Hierarchical Equal Risk Contribution <https://ssrn.com/abstract=2840729>`_

You can read more about the Gap Index method in this paper:

* `Gap Index Paper <https://statweb.stanford.edu/~gwalther/gap>`_

Implementation
Expand Down Expand Up @@ -154,7 +155,7 @@ Implementation

.. tip::
|h4| Different Linkage Methods |h4_|
The following linkage methods are supported by the HCAA class in mlfinlab. (The following is taken directly from and we highly
The following linkage methods are supported by the HERC class in mlfinlab. (The following is taken directly from and we highly
recommend you read):

`Papenbrock, J., 2011. Asset Clusters and Asset Networks in Financial Risk Management and Portfolio Optimization (Doctoral
Expand Down Expand Up @@ -199,8 +200,8 @@ Plotting

.. code-block::
# Instantiate HCAA Class
hcaa = HierarchicalClusteringAssetAllocation()
# Instantiate HERC Class
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=stock_prices, risk_measure='equal_weighting')
# Plot Dendrogram
Expand All @@ -215,6 +216,6 @@ Research Notebooks

The following research notebooks provide a more detailed exploration of the algorithm.

* `How to use mlfinlab's HierarchicalClusteringAssetAllocation class`_
* `How to use mlfinlab's HierarchicalEqualRiskContribution class`_

.. _How to use mlfinlab's HierarchicalClusteringAssetAllocation class: https://github.com/hudson-and-thames/research/blob/master/Portfolio%20Optimisation%20Tutorials/Hierarchical%20Clustering%20Asset%20Allocation%20(HCAA)/HCAA%20Tutorial%20Notebook.ipynb
.. _How to use mlfinlab's HierarchicalEqualRiskContribution class: https://github.com/hudson-and-thames/research/blob/master/Portfolio%20Optimisation%20Tutorials/Hierarchical%20Clustering%20Asset%20Allocation%20(HERC)/HERC%20Tutorial%20Notebook.ipynb
2 changes: 1 addition & 1 deletion mlfinlab/portfolio_optimization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mlfinlab.portfolio_optimization.cla import CriticalLineAlgorithm
from mlfinlab.portfolio_optimization.hrp import HierarchicalRiskParity
from mlfinlab.portfolio_optimization.mean_variance import MeanVarianceOptimisation
from mlfinlab.portfolio_optimization.hcaa import HierarchicalClusteringAssetAllocation
from mlfinlab.portfolio_optimization.herc import HierarchicalEqualRiskContribution
from mlfinlab.portfolio_optimization.risk_metrics import RiskMetrics
from mlfinlab.portfolio_optimization.returns_estimators import ReturnsEstimators
from mlfinlab.portfolio_optimization.nco import NCO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from mlfinlab.portfolio_optimization.risk_estimators import RiskEstimators


class HierarchicalClusteringAssetAllocation:
class HierarchicalEqualRiskContribution:
"""
This class implements the Hierarchical Equal Risk Contribution (HERC) algorithm and it's extended components mentioned in the
following papers: `Raffinot, Thomas, The Hierarchical Equal Risk Contribution Portfolio (August 23,
Expand Down Expand Up @@ -48,7 +48,7 @@ def allocate(self, asset_names=None, asset_prices=None, asset_returns=None, cova
risk_measure='equal_weighting', linkage='ward', optimal_num_clusters=None):
# pylint: disable=too-many-branches
"""
Calculate asset allocations using the HCAA algorithm.
Calculate asset allocations using the Hierarchical Equal Risk Contribution algorithm.
:param asset_names: (list) A list of strings containing the asset names.
:param asset_prices: (pd.DataFrame) A dataframe of historical asset prices (daily close)
Expand Down
1 change: 1 addition & 0 deletions mlfinlab/portfolio_optimization/tic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import scipy.cluster.hierarchy as sch
from mlfinlab.portfolio_optimization.risk_estimators import RiskEstimators


class TIC:
"""
This class implements the Theory-Implied Correlation (TIC) algorithm and the correlation matrix distance
Expand Down
66 changes: 33 additions & 33 deletions mlfinlab/tests/test_hcaa.py → mlfinlab/tests/test_herc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import os
import numpy as np
import pandas as pd
from mlfinlab.portfolio_optimization.hcaa import HierarchicalClusteringAssetAllocation
from mlfinlab.portfolio_optimization.herc import HierarchicalEqualRiskContribution
from mlfinlab.portfolio_optimization.returns_estimators import ReturnsEstimators


class TestHCAA(unittest.TestCase):
class TestHERC(unittest.TestCase):
# pylint: disable=too-many-public-methods
"""
Tests different functions of the HCAA algorithm class.
Tests different functions of the HERC algorithm class.
"""

def setUp(self):
Expand All @@ -26,11 +26,11 @@ def setUp(self):

def test_hcaa_equal_weight(self):
"""
Test the weights calculated by the HCAA algorithm - if all the weights are positive and
Test the weights calculated by the HERC algorithm - if all the weights are positive and
their sum is equal to 1.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
asset_names=self.data.columns,
optimal_num_clusters=5,
Expand All @@ -42,11 +42,11 @@ def test_hcaa_equal_weight(self):

def test_hcaa_min_variance(self):
"""
Test the weights calculated by the HCAA algorithm - if all the weights are positive and
Test the weights calculated by the HERC algorithm - if all the weights are positive and
their sum is equal to 1.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
asset_names=self.data.columns,
optimal_num_clusters=5,
Expand All @@ -58,11 +58,11 @@ def test_hcaa_min_variance(self):

def test_hcaa_min_standard_deviation(self):
"""
Test the weights calculated by the HCAA algorithm - if all the weights are positive and
Test the weights calculated by the HERC algorithm - if all the weights are positive and
their sum is equal to 1.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
asset_names=self.data.columns,
optimal_num_clusters=4,
Expand All @@ -79,18 +79,18 @@ def test_value_error_for_expected_shortfall(self):
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_names=self.data.columns,
optimal_num_clusters=5,
risk_measure='expected_shortfall')

def test_hcaa_expected_shortfall(self):
"""
Test the weights calculated by the HCAA algorithm - if all the weights are positive and
Test the weights calculated by the HERC algorithm - if all the weights are positive and
their sum is equal to 1.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
asset_names=self.data.columns,
optimal_num_clusters=5,
Expand All @@ -102,11 +102,11 @@ def test_hcaa_expected_shortfall(self):

def test_hcaa_conditional_drawdown_risk(self):
"""
Test the weights calculated by the HCAA algorithm - if all the weights are positive and
Test the weights calculated by the HERC algorithm - if all the weights are positive and
their sum is equal to 1.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
asset_names=self.data.columns,
optimal_num_clusters=5,
Expand All @@ -118,10 +118,10 @@ def test_hcaa_conditional_drawdown_risk(self):

def test_quasi_diagnalization(self):
"""
Test the quasi-diagnalisation step of HCAA algorithm.
Test the quasi-diagnalisation step of HERC algorithm.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
linkage='single',
optimal_num_clusters=5,
Expand All @@ -135,7 +135,7 @@ def test_value_error_for_non_dataframe_input(self):
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data.values, asset_names=self.data.columns)

def test_value_error_for_non_date_index(self):
Expand All @@ -144,7 +144,7 @@ def test_value_error_for_non_date_index(self):
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
data = self.data.reset_index()
hcaa.allocate(asset_prices=data, asset_names=self.data.columns)

Expand All @@ -154,15 +154,15 @@ def test_all_inputs_none(self):
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_names=self.data.columns)

def test_hcaa_with_input_as_returns(self):
"""
Test HCAA when passing asset returns dataframe as input.
Test HERC when passing asset returns dataframe as input.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
hcaa.allocate(asset_returns=returns, asset_names=self.data.columns)
weights = hcaa.weights.values[0]
Expand All @@ -172,10 +172,10 @@ def test_hcaa_with_input_as_returns(self):

def test_hcaa_with_asset_returns_as_none(self):
"""
Test HCAA when asset returns are not required for calculating the weights.
Test HERC when asset returns are not required for calculating the weights.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
hcaa.allocate(asset_names=self.data.columns,
covariance_matrix=returns.cov(),
Expand All @@ -188,10 +188,10 @@ def test_hcaa_with_asset_returns_as_none(self):

def test_hcaa_with_input_as_covariance_matrix(self):
"""
Test HCAA when passing a covariance matrix as input.
Test HERC when passing a covariance matrix as input.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
hcaa.allocate(asset_names=self.data.columns,
covariance_matrix=returns.cov(),
Expand All @@ -204,19 +204,19 @@ def test_hcaa_with_input_as_covariance_matrix(self):

def test_value_error_for_risk_measure(self):
"""
Test HCAA when a different allocation metric string is used.
Test HERC when a different allocation metric string is used.
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_names=self.data.columns, asset_prices=self.data, risk_measure='random_metric')

def test_no_asset_names(self):
"""
Test HCAA when not supplying a list of asset names.
Test HERC when not supplying a list of asset names.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data,
optimal_num_clusters=6)
weights = hcaa.weights.values[0]
Expand All @@ -226,10 +226,10 @@ def test_no_asset_names(self):

def test_no_asset_names_with_asset_returns(self):
"""
Test HCAA when not supplying a list of asset names and when the user passes asset_returns.
Test HERC when not supplying a list of asset names and when the user passes asset_returns.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
hcaa.allocate(asset_returns=returns,
optimal_num_clusters=6)
Expand All @@ -244,7 +244,7 @@ def test_value_error_with_no_asset_names(self):
"""

with self.assertRaises(ValueError):
hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
returns = ReturnsEstimators().calculate_returns(asset_prices=self.data)
hcaa.allocate(asset_returns=returns.values,
optimal_num_clusters=6)
Expand All @@ -254,7 +254,7 @@ def test_dendrogram_plot(self):
Test if dendrogram plot object is correctly rendered.
"""

hcaa = HierarchicalClusteringAssetAllocation()
hcaa = HierarchicalEqualRiskContribution()
hcaa.allocate(asset_prices=self.data, optimal_num_clusters=5)
dendrogram = hcaa.plot_clusters(assets=self.data.columns)
assert dendrogram.get('icoord')
Expand Down

0 comments on commit d8af49d

Please sign in to comment.