Coverage for src/pyhrp/hrp.py: 100%
24 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-03 05:36 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-11-03 05:36 +0000
1"""Hierarchical Risk Parity (HRP) allocation entry points.
3This module owns the top-level allocation functions:
4- hrp: Compute HRP portfolio weights from prices
5- schur_hrp: Compute Schur Complementary Allocation weights from prices
7For backward compatibility the supporting building blocks stay importable from
8``pyhrp.hrp``, but they are owned (and exported) by the modules that define them:
9- build_tree, Dendrogram: see :mod:`pyhrp.dendrogram`
10- compute_returns, compute_cov, compute_corr: see :mod:`pyhrp.covariance`
11"""
13from __future__ import annotations
15from typing import Literal
17import polars as pl
19from .algos import risk_parity, schur_risk_parity
20from .cluster import Cluster
21from .covariance import check_finite_matrix, compute_returns
22from .covariance import compute_corr as compute_corr
23from .covariance import compute_cov as compute_cov
24from .dendrogram import Dendrogram as Dendrogram
25from .dendrogram import build_tree as build_tree
27__all__ = ["hrp", "schur_hrp"]
29Method = Literal["single", "complete", "average", "ward"]
32def _prepare(
33 prices: pl.DataFrame, node: Cluster | None, method: Method, bisection: bool
34) -> tuple[pl.DataFrame, Cluster]:
35 """Turn prices into the covariance matrix and the cluster tree the allocators need.
37 The correlation matrix is only computed when no ``node`` is supplied, since it
38 is used for nothing but building the tree.
40 Args:
41 prices (pl.DataFrame): Asset price time series (columns are assets, rows are dates)
42 node (Cluster, optional): Root of a prebuilt cluster tree, used as-is if given
43 method (Method): Linkage method passed to :func:`build_tree`
44 bisection (bool): Whether to use bisection for tree construction
46 Returns:
47 tuple[pl.DataFrame, Cluster]: The (finite) covariance matrix and the tree root
48 """
49 returns = compute_returns(prices)
50 cov = check_finite_matrix(compute_cov(returns), name="covariance matrix")
51 if node is None:
52 node = build_tree(compute_corr(returns), method=method, bisection=bisection).root
53 return cov, node
56def hrp(
57 prices: pl.DataFrame,
58 node: Cluster | None = None,
59 method: Method = "ward",
60 bisection: bool = False,
61) -> Cluster:
62 """Compute the hierarchical risk parity portfolio weights.
64 This is the main entry point for the HRP algorithm. It calculates returns from prices,
65 builds a hierarchical clustering tree if not provided, and applies risk parity weights.
67 Note:
68 A ``node`` passed in is weighted in place and returned; see the allocator
69 contract in :mod:`pyhrp.algos`. Passing ``dendrogram.root`` therefore
70 rewrites the portfolios inside that ``Dendrogram``, even though the
71 ``Dendrogram`` itself is a frozen dataclass.
73 Args:
74 prices (pl.DataFrame): Asset price time series (columns are assets, rows are dates)
75 node (Cluster, optional): Root node of the hierarchical clustering tree.
76 If None, a tree will be built from the correlation matrix.
77 method (Literal["single", "complete", "average", "ward"]): Linkage method to use for distance calculation
78 - "single": minimum distance between points (nearest neighbor)
79 - "complete": maximum distance between points (furthest neighbor)
80 - "average": average distance between all points
81 - "ward": Ward variance minimization
82 bisection (bool): Whether to use bisection method for tree construction
84 Returns:
85 Cluster: The root cluster with portfolio weights assigned according to HRP
86 """
87 cov, node = _prepare(prices, node, method, bisection)
88 return risk_parity(root=node, cov=cov)
91def schur_hrp(
92 prices: pl.DataFrame,
93 node: Cluster | None = None,
94 method: Method = "ward",
95 bisection: bool = False,
96 gamma: float = 0.5,
97) -> Cluster:
98 """Compute Schur Complementary Allocation portfolio weights.
100 Extends HRP by augmenting each sub-covariance block with off-diagonal information
101 via Schur complements before splitting risk between clusters. Introduced by Peter Cotton
102 (arXiv:2411.05807). At gamma=0 this is identical to HRP; at gamma=1 it recovers the
103 global minimum-variance portfolio through the same recursive hierarchy.
105 Note:
106 A ``node`` passed in is weighted in place and returned; see the allocator
107 contract in :mod:`pyhrp.algos`. Passing ``dendrogram.root`` therefore
108 rewrites the portfolios inside that ``Dendrogram``, even though the
109 ``Dendrogram`` itself is a frozen dataclass.
111 Args:
112 prices (pl.DataFrame): Asset price time series (columns are assets, rows are dates)
113 node (Cluster, optional): Root node of the hierarchical clustering tree.
114 If None, a tree will be built from the correlation matrix.
115 method (Literal["single", "complete", "average", "ward"]): Linkage method for clustering
116 bisection (bool): Whether to use bisection method for tree construction
117 gamma (float): Schur interpolation parameter in [0, 1].
118 0 recovers standard HRP; 1 recovers minimum-variance portfolio.
120 Returns:
121 Cluster: The root cluster with portfolio weights assigned
122 """
123 cov, node = _prepare(prices, node, method, bisection)
124 return schur_risk_parity(root=node, cov=cov, gamma=gamma)