Home Verified Methods Reports Papers Methodology About
Home › Worked examples › HRP Verification Report: Six Allocators, Two Defects Fixed

HRP Verification Report: Six Allocators, Two Defects Fixed

Verdict

The allocator produces weights that sum to one, contain no shorts, and drift far less out of sample than an unconstrained minimum-variance optimiser on the same panel (+1.3% against +13.6%). Two defects were found and fixed before it did, and one over-specified test was corrected rather than the code.

Verification record
v1.0 · first run 16 August 2026 · published as a report 4 September 2026
Implementation under test
quantmedia-research/hierarchical-risk-parity/hrp.py, in the public repository
Environment
Python 3.11.9; numpy 2.4.6; pandas 3.0.5; scipy 1.17.1 — all newer than the package pins, so this doubles as a forward-compatibility check
Verifier
Cemil Ertürk
Status
Independent. Not peer reviewed. Synthetic data only; no claim about any real security.

On this synthetic panel, HRP with single linkage has out-of-sample volatility of 0.1419, compared with 0.1489 for unconstrained minimum variance. Other baselines narrow that difference. The comparison is useful precisely because all six results are visible.

Reproduction record
Checked 4 September 2026. Seed: 20260808.
Source
hierarchical-risk-parity at revision 70ae45c.
Environment used
Python 3.11.9, NumPy 2.4.6, pandas 3.0.5, SciPy 1.17.1.
Data
Synthetic. These examples test the implementation and illustrate the method; they do not measure trading performance on real securities.

Set up the experiment

The panel contains 20 assets. The first 120 periods fit the weights; the next 400 evaluate them without refitting. The in-sample mean absolute correlation is 0.240 and the covariance condition number is about 43.

cd quantmedia-research/hierarchical-risk-parity python compare_mvo.py

Compare the output

Volatility is annualized in the example using 252 periods per year. Drift is 100 × (out-of-sample volatility / in-sample volatility − 1), calculated before the displayed volatility figures are rounded.

AllocatorVolatility inVolatility outDrift
HRP (single)0.14010.1419+1.3%
HRP (Ward)0.14190.1425+0.4%
Minimum variance0.13110.1489+13.6%
Minimum variance, clipped to long-only0.13250.1460+10.2%
Minimum variance, shrinkage 0.30.13300.1432+7.7%
Equal weight0.14740.1443−2.1%

The long-only baseline clips negative weights and renormalizes the result. It is a simple comparison, not a constrained optimization solver. The full comparison CSV also includes concentration, short exposure, drawdown and Sharpe. Out-of-sample Sharpe is negative for every allocator in this example.

Read the result carefully

Minimum variance has the lowest fitted volatility and the largest increase outside the fitted sample. Shrinkage reduces that increase. Equal weight has a small decrease; a negative drift is possible and is not an implementation error.

A small drift means the two volatility estimates are similar. It does not by itself imply the highest return, the lowest risk or the best portfolio. HRP single and Ward illustrate that even the linkage choice changes the result.

Check the implementation

The 13 tests include weights summing to one, nonnegative HRP weights, distance-matrix properties, and a two-asset case with a known 80/20 allocation. The implementation copies the correlation array before modifying it and symmetrizes the distance matrix to handle floating-point noise.

# From the repository root: python quantmedia-research/tests/test_hrp.py # Expected: 13 passed

Limits and a useful next experiment

This is one fixed-seed panel with no transaction costs and no weight refitting. It does not establish which allocator wins across assets or market regimes. That experiment has now been run, with the panel's true covariance as the yardstick: see HRP vs minimum variance: where the drift comes from. Its numbers are not folded into this report.

Defects found during verification

1. Read-only array under pandas 3

The distance matrix was built by calling np.fill_diagonal on DataFrame.values. pandas 3 returns a read-only view, so the call raised. Fix: copy first. This is the kind of defect a “works on my machine” implementation carries for years.

2. Asymmetric distance matrix

Floating-point noise left the correlation-distance matrix very slightly asymmetric, and scipy.spatial.distance.squareform rejects that. Fix: symmetrise explicitly with 0.5·(D + Dᵀ).

A third item was a test, not the code: an assertion that linkage methods must differ was over-specified for one unlucky seed, on which single and Ward linkage happened to agree. The methods were confirmed to differ on other seeds, and the test was fixed rather than the implementation. Adjusting a bad test is legitimate; adjusting a test to hide a real gap is not.

What this verification does not establish

Reproduce

python quantmedia-research/verify_examples.py asserts outputs/comparison.csv and outputs/weights.csv match the committed files. Then python tests/test_hrp.py: expected 13 passed.