.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/portfolio/risk_based/plot_04_hierarchical_risk_parity.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_portfolio_risk_based_plot_04_hierarchical_risk_parity.py: Lopez de Prado's hierarchical risk parity ========================================= Marcos Lopez de Prado (2016) observed that inverting a noisy covariance matrix, as every quadratic optimizer does, amplifies its errors. His hierarchical risk parity never inverts it: it clusters assets by the correlation distance :math:`\sqrt{(1-\rho_{ij})/2}`, reorders them along the dendrogram so similar assets sit together, then splits capital top down between halves in inverse proportion to their variance. Its risk varies less from one estimation sample to the next than the minimum-variance portfolio's, though with few observations per asset minimum variance can still be less risky on average. .. GENERATED FROM PYTHON SOURCE LINES 17-35 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from scipy.cluster.hierarchy import dendrogram from financialkit.portfolio import hierarchical_risk_parity, min_variance_portfolio rng = np.random.default_rng(10) # Three hidden clusters of correlated assets. d, k = 15, 3 labels = np.repeat(np.arange(k), d // k) corr = np.where(labels[:, None] == labels[None, :], 0.7, 0.15) np.fill_diagonal(corr, 1.0) perm = rng.permutation(d) corr = corr[np.ix_(perm, perm)] vol = rng.uniform(0.1, 0.3, d) cov = corr * np.outer(vol, vol) L = np.linalg.cholesky(cov) .. GENERATED FROM PYTHON SOURCE LINES 36-38 The dendrogram and the quasi-diagonalized correlation matrix ------------------------------------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 38-51 .. code-block:: Python X = rng.standard_normal((120, d)) @ L.T S = np.cov(X, rowvar=False) hrp = hierarchical_risk_parity(S) fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 3.8)) dendrogram(hrp.linkage, ax=ax1, color_threshold=0.5) ax1.set_title("single-linkage dendrogram") ax2.imshow(np.corrcoef(X, rowvar=False), cmap="RdBu_r", vmin=-1, vmax=1) ax2.set_title("original order") ax3.imshow(np.corrcoef(X, rowvar=False)[np.ix_(hrp.order, hrp.order)], cmap="RdBu_r", vmin=-1, vmax=1) ax3.set_title("quasi-diagonal order") fig.tight_layout() .. image-sg:: /api/gallery/portfolio/risk_based/images/sphx_glr_plot_04_hierarchical_risk_parity_001.png :alt: single-linkage dendrogram, original order, quasi-diagonal order :srcset: /api/gallery/portfolio/risk_based/images/sphx_glr_plot_04_hierarchical_risk_parity_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 52-57 Stability out of sample ----------------------- Re-estimate on 100 fresh samples of 60 observations and compare each portfolio's true volatility: HRP's spread is the narrower one. .. GENERATED FROM PYTHON SOURCE LINES 57-73 .. code-block:: Python vols = {"HRP": [], "minimum variance": [], "1/N": []} for _ in range(100): X = rng.standard_normal((60, d)) @ L.T S = np.cov(X, rowvar=False) for name, w in [("HRP", hierarchical_risk_parity(S).weights), ("minimum variance", min_variance_portfolio(S).weights), ("1/N", np.full(d, 1 / d))]: vols[name].append(np.sqrt(w @ cov @ w)) fig, ax = plt.subplots(figsize=(7, 3.5)) ax.boxplot(list(vols.values()), tick_labels=list(vols)) ax.axhline(min_variance_portfolio(cov).volatility, color="C2", ls="--", label="true minimum variance") ax.set_ylabel("true volatility") ax.legend() fig.tight_layout() for name, v in vols.items(): print(f"{name:>16}: mean vol {np.mean(v):.3f}, spread {np.std(v):.3f}") plt.show() .. image-sg:: /api/gallery/portfolio/risk_based/images/sphx_glr_plot_04_hierarchical_risk_parity_002.png :alt: plot 04 hierarchical risk parity :srcset: /api/gallery/portfolio/risk_based/images/sphx_glr_plot_04_hierarchical_risk_parity_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none HRP: mean vol 0.113, spread 0.002 minimum variance: mean vol 0.088, spread 0.005 1/N: mean vol 0.135, spread 0.000 .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.154 seconds) .. _sphx_glr_download_api_gallery_portfolio_risk_based_plot_04_hierarchical_risk_parity.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/portfolio/risk_based/plot_04_hierarchical_risk_parity.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_04_hierarchical_risk_parity.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_04_hierarchical_risk_parity.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_04_hierarchical_risk_parity.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_