hcaa
Concept Overview
Section titled “Concept Overview”Hierarchical Clustering Asset Allocation generalises HRP’s recursive bisection to risk measures other than variance. Seriation and the cluster tree are built the same way, but the split at each node weights the two sides by the chosen allocation_metric — cluster variance, standard deviation, Sharpe ratio, expected shortfall or conditional drawdown — so the same hierarchy can express a tail-risk budget rather than only a variance budget. Like HRP it never inverts the covariance matrix.
When to Use
Section titled “When to Use”Use it in place of hrp when your risk budget is not variance: expected shortfall or conditional drawdown for a drawdown-controlled mandate, Sharpe when you have return views you are willing to defend. Use hrp when you do not, since the variance split needs no expected-return estimate at all. The clustering is only as good as the distance fed to it, so build that with codependence rather than raw correlation, and sanity-check the cluster count with onc.
Mathematical Foundations
Section titled “Mathematical Foundations”Cluster Risk
Section titled “Cluster Risk”
where is the covariance sub-matrix of cluster and its inverse-variance weights, normalised to sum to one within the cluster.
Recursive Bisection Split
Section titled “Recursive Bisection Split”
where is the risk of cluster under the chosen allocation_metric: cluster variance (), standard deviation (), expected shortfall, or conditional drawdown. Lower risk on one side means a larger for that side. This generalises the HRP split, which is the minimum_variance case. Two branches invert the sign: sharpe_ratio allocates because higher is better there, and equal_weighting skips the split entirely.
Usage Examples
Section titled “Usage Examples”Fit HCAA allocator
Section titled “Fit HCAA allocator”use nalgebra::DMatrix;use openquant::hcaa::HierarchicalClusteringAssetAllocation;
let asset_names: Vec<String> = ["SPY", "TLT", "GLD", "HYG"].iter().map(|s| s.to_string()).collect();// rows = observations, cols = assets, in the same order as `asset_names`.let prices = DMatrix::from_fn(250, 4, |i, j| 100.0 + (i as f64) * 0.05 + (j as f64) * 3.0);
// The constructor argument selects how expected returns are estimated// ("mean" or "exponential"); it is not optional.let mut hcaa = HierarchicalClusteringAssetAllocation::new("mean");
// allocate() fills the struct in place and returns Result<(), HcaaError>.// It does not return the weights — read them from `hcaa.weights` afterwards.hcaa.allocate( &asset_names, Some(&prices), // asset_prices None, // asset_returns None, // covariance_matrix None, // expected_asset_returns "minimum_variance", // allocation_metric 0.05, // confidence_level, used by the tail-risk metrics None, // optimal_num_clusters — inferred when None None, // resample_by)?;
println!("weights: {:?}", hcaa.weights);println!("seriation order: {:?}", hcaa.ordered_indices);API Reference
Section titled “API Reference”Python API
Section titled “Python API”hcaa.allocate_hcaa
Rust API
Section titled “Rust API”HierarchicalClusteringAssetAllocationHcaaError
Risk Notes and Caveats
Section titled “Risk Notes and Caveats”- Cluster linkage choices influence allocations.
- Use with robust codependence distances when possible.