Skip to main content

Module hrp

Module hrp 

Source
Expand description

Hierarchical Risk Parity (AFML chapter 16, Snippets 16.1–16.4).

HRP allocates without inverting the covariance matrix, in three steps:

  1. Tree clustering (§16.4.1): correlations become distances d = sqrt((1 - rho) / 2), and by default assets are merged by single linkage on the distance between columns of that matrix, d~_ij = sqrt(sum_n (d_ni - d_nj)^2), as AFML’s Snippet 16.4 does. HrpDistance::Correlation clusters on d itself instead (mlfinlab’s choice, and this library’s before #167); the two often give different trees.
  2. Quasi-diagonalisation (§16.4.2): assets are reordered so similar ones are adjacent.
  3. Recursive bisection (§16.4.3): the ordered list is split in halves and weight is divided between the halves in inverse proportion to their inverse-variance cluster variances.

The result is long-only and fully invested (weights sum to 1). From prices, returns are simple returns and the covariance is the unannualised sample covariance (HRP weights do not depend on the scale). Weights are in the column order of the inputs.

use nalgebra::DMatrix;
use openquant::hrp::{HierarchicalRiskParity, HrpDistance};

// Assets 0 and 1 are nearly the same bet; asset 2 is independent. All have variance 0.04.
let covariance = DMatrix::from_row_slice(3, 3, &[
    0.040, 0.036, 0.000,
    0.036, 0.040, 0.000,
    0.000, 0.000, 0.040,
]);
let names: Vec<String> = ["a", "b", "c"].map(String::from).to_vec();

let mut model = HierarchicalRiskParity::new();
model.allocate(&names, None, None, Some(&covariance), None, false)?;

assert_eq!(model.clusters[0], [0, 1]); // a and b merge first
assert!((model.weights.iter().sum::<f64>() - 1.0).abs() < 1e-12);
// Bisection splits {a, b} from {c}: the pair's variance is 0.038, so c gets
// 0.038 / (0.038 + 0.040) and a and b share the rest equally.
assert!((model.weights[2] - 0.038 / 0.078).abs() < 1e-12);
assert!((model.weights[0] - model.weights[1]).abs() < 1e-12);

// Clustering on the pairwise distances instead builds the same tree here (it need not).
let mut pairwise = HierarchicalRiskParity::with_distance(HrpDistance::Correlation);
pairwise.allocate(&names, None, None, Some(&covariance), None, false)?;
assert_eq!(pairwise.clusters, model.clusters);

Structs§

HierarchicalRiskParity
Hierarchical Risk Parity allocator and its results.
HrpDendrogram
A scipy-style dendrogram description from HierarchicalRiskParity::plot_clusters.

Enums§

HrpDistance
Which distance the single-linkage tree is built on (AFML §16.4.1).
HrpError
Errors returned by HierarchicalRiskParity.