API Reference¶
The public API of pyhrp. Import the main entry points directly from the top-level package:
from pyhrp.hrp import hrp, build_tree, Dendrogram
from pyhrp.algos import risk_parity, one_over_n
from pyhrp.cluster import Cluster, Portfolio
Hierarchical Risk Parity (HRP) algorithm implementation.
This module implements the core HRP algorithm and related functions: - hrp: Main function to compute HRP portfolio weights - build_tree: Function to build hierarchical cluster tree from correlation matrix - compute_cov: Function to compute a covariance matrix from returns - compute_corr: Function to compute a correlation matrix from returns - Dendrogram: Class to store and visualize hierarchical clustering results
Dendrogram
dataclass
¶
Container for hierarchical clustering dendrogram data and visualization.
This class stores the results of hierarchical clustering and provides methods for accessing and visualizing the dendrogram structure.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
Cluster
|
The root node of the hierarchical clustering tree |
assets |
list[str]
|
Names of assets included in the clustering |
linkage |
ndarray | None
|
Linkage matrix in scipy format for plotting |
distance |
DataFrame | None
|
Distance matrix used for clustering |
method |
str | None
|
Linkage method used for clustering |
Source code in src/pyhrp/hrp.py
ids
property
¶
Node values in the order left -> right as they appear in the dendrogram.
names
property
¶
The asset names as induced by the order of ids.
__post_init__()
¶
Validate dataclass fields after initialization.
Ensures that the optional distance matrix, when provided, is a polars DataFrame with columns aligned to the asset list, and verifies that the number of leaves in the cluster tree matches the number of assets.
Source code in src/pyhrp/hrp.py
plot(**kwargs)
¶
Build and return a plotly dendrogram figure.
Source code in src/pyhrp/hrp.py
build_tree(cor, method='ward', bisection=False)
¶
Build hierarchical cluster tree from correlation matrix.
This function converts a correlation matrix to a distance matrix, performs hierarchical clustering, and returns a Dendrogram object containing the resulting tree structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cor
|
DataFrame
|
Correlation matrix of asset returns (columns are assets) |
required |
method
|
Literal['single', 'complete', 'average', 'ward']
|
Linkage method for hierarchical clustering - "single": minimum distance between points (nearest neighbor) - "complete": maximum distance between points (furthest neighbor) - "average": average distance between all points - "ward": Ward variance minimization |
'ward'
|
bisection
|
bool
|
Whether to use bisection method for tree construction |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Dendrogram |
Dendrogram
|
Object containing the hierarchical clustering tree, with: - root: Root cluster node - linkage: Linkage matrix for plotting - assets: List of assets - method: Clustering method used - distance: Distance matrix |
Examples:
>>> import polars as pl
>>> from pyhrp.hrp import build_tree
>>> cor = pl.DataFrame({"A": [1.0, 0.5], "B": [0.5, 1.0]})
>>> dg = build_tree(cor, method="ward")
>>> dg.root.leaf_count
2
Source code in src/pyhrp/hrp.py
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | |
compute_corr(df)
¶
Compute correlation matrix from a DataFrame of returns.
compute_cov(df)
¶
Compute covariance matrix from a DataFrame of returns.
hrp(prices, node=None, method='ward', bisection=False)
¶
Compute the hierarchical risk parity portfolio weights.
This is the main entry point for the HRP algorithm. It calculates returns from prices, builds a hierarchical clustering tree if not provided, and applies risk parity weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
DataFrame
|
Asset price time series (columns are assets, rows are dates) |
required |
node
|
Cluster
|
Root node of the hierarchical clustering tree. If None, a tree will be built from the correlation matrix. |
None
|
method
|
Literal['single', 'complete', 'average', 'ward']
|
Linkage method to use for distance calculation - "single": minimum distance between points (nearest neighbor) - "complete": maximum distance between points (furthest neighbor) - "average": average distance between all points - "ward": Ward variance minimization |
'ward'
|
bisection
|
bool
|
Whether to use bisection method for tree construction |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Cluster |
Cluster
|
The root cluster with portfolio weights assigned according to HRP |
Examples:
>>> import polars as pl
>>> from pyhrp.hrp import hrp
>>> prices = pl.DataFrame({"A": [100.0, 101.0, 99.0, 102.0], "B": [50.0, 51.0, 49.0, 52.0]})
>>> root = hrp(prices, method="ward")
>>> round(sum(root.portfolio.weights.values()), 6)
1.0
Source code in src/pyhrp/hrp.py
schur_hrp(prices, node=None, method='ward', bisection=False, gamma=0.5)
¶
Compute Schur Complementary Allocation portfolio weights.
Extends HRP by augmenting each sub-covariance block with off-diagonal information via Schur complements before splitting risk between clusters. Introduced by Peter Cotton (arXiv:2411.05807). At gamma=0 this is identical to HRP; at gamma=1 it recovers the global minimum-variance portfolio through the same recursive hierarchy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
DataFrame
|
Asset price time series (columns are assets, rows are dates) |
required |
node
|
Cluster
|
Root node of the hierarchical clustering tree. If None, a tree will be built from the correlation matrix. |
None
|
method
|
Literal['single', 'complete', 'average', 'ward']
|
Linkage method for clustering |
'ward'
|
bisection
|
bool
|
Whether to use bisection method for tree construction |
False
|
gamma
|
float
|
Schur interpolation parameter in [0, 1]. 0 recovers standard HRP; 1 recovers minimum-variance portfolio. |
0.5
|
Returns:
| Name | Type | Description |
|---|---|---|
Cluster |
Cluster
|
The root cluster with portfolio weights assigned |
Examples:
>>> import polars as pl
>>> from pyhrp.hrp import schur_hrp
>>> prices = pl.DataFrame({"A": [100.0, 101.0, 99.0, 102.0], "B": [50.0, 51.0, 49.0, 52.0]})
>>> root = schur_hrp(prices, method="ward", gamma=0.5)
>>> round(sum(root.portfolio.weights.values()), 6)
1.0
Source code in src/pyhrp/hrp.py
Portfolio optimization algorithms for hierarchical risk parity.
This module implements various portfolio optimization algorithms: - risk_parity: The main hierarchical risk parity algorithm - schur_risk_parity: Schur Complementary Allocation (Cotton, arXiv:2411.05807) - one_over_n: A simple equal-weight allocation strategy
one_over_n(dendrogram)
¶
Generate portfolios using the 1/N (equal weight) strategy at each tree level.
This function implements a hierarchical 1/N strategy where weights are distributed equally among assets within each cluster at each level of the tree. The weight assigned to each cluster decreases by half at each level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dendrogram
|
Dendrogram
|
A dendrogram object containing the hierarchical clustering tree and the list of assets |
required |
Yields:
| Type | Description |
|---|---|
Generator[tuple[int, Portfolio]]
|
tuple[int, Portfolio]: A tuple containing the level number and the portfolio at that level |
Examples:
>>> import polars as pl
>>> from pyhrp.hrp import build_tree
>>> from pyhrp.algos import one_over_n
>>> cor = pl.DataFrame({"A": [1.0, 0.3], "B": [0.3, 1.0]})
>>> dg = build_tree(cor, method="ward")
>>> levels = list(one_over_n(dg))
>>> len(levels) > 0
True
Source code in src/pyhrp/algos.py
risk_parity(root, cov)
¶
Compute hierarchical risk parity weights for a cluster tree.
This is the main algorithm for hierarchical risk parity. It recursively traverses the cluster tree and assigns weights to each node based on the risk parity principle.
Note
The tree is modified in place: the portfolio of every node is rebuilt from scratch, so the function is idempotent and a tree can be reused with a different covariance matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Cluster
|
The root node of the cluster tree |
required |
cov
|
DataFrame
|
Covariance matrix of asset returns |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Cluster |
Cluster
|
The root node with portfolio weights assigned |
Examples:
>>> import polars as pl
>>> from pyhrp.cluster import Cluster
>>> from pyhrp.algos import risk_parity
>>> cov = pl.DataFrame({"A": [4.0, 0.0], "B": [0.0, 1.0]})
>>> root = Cluster(2, left=Cluster(0), right=Cluster(1))
>>> cluster = risk_parity(root=root, cov=cov)
>>> round(cluster.portfolio["B"], 1)
0.8
Source code in src/pyhrp/algos.py
schur_risk_parity(root, cov, gamma=0.5)
¶
Compute Schur Complementary Allocation weights for a cluster tree.
An extension of HRP introduced by Peter Cotton (arXiv:2411.05807) that augments sub-covariance matrices with off-diagonal block information via Schur complements. At gamma=0 this recovers standard HRP; at gamma=1 it recovers the minimum-variance portfolio through the same recursive structure.
Note
The tree is modified in place: the portfolio of every node is rebuilt from scratch, so the function is idempotent and a tree can be reused with a different covariance matrix or gamma.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Cluster
|
The root node of the cluster tree |
required |
cov
|
DataFrame
|
Covariance matrix of asset returns |
required |
gamma
|
float
|
Interpolation parameter in [0, 1]. 0 = HRP, 1 = minimum variance. |
0.5
|
Returns:
| Name | Type | Description |
|---|---|---|
Cluster |
Cluster
|
The root node with portfolio weights assigned |
Raises:
| Type | Description |
|---|---|
ValueError
|
If gamma is outside the interval [0, 1]. |
Examples:
>>> import polars as pl
>>> from pyhrp.cluster import Cluster
>>> from pyhrp.algos import schur_risk_parity
>>> cov = pl.DataFrame({"A": [4.0, 0.0], "B": [0.0, 1.0]})
>>> root = Cluster(2, left=Cluster(0), right=Cluster(1))
>>> cluster = schur_risk_parity(root=root, cov=cov, gamma=0.5)
>>> round(cluster.portfolio["B"], 1)
0.8
Source code in src/pyhrp/algos.py
Data structures for hierarchical risk parity portfolio optimization.
This module defines the core data structures used in the hierarchical risk parity algorithm: - Portfolio: Manages a collection of asset weights (strings identify assets) - Cluster: Represents a node in the hierarchical clustering tree
Cluster
¶
Bases: Node[int]
Represents a cluster in the hierarchical clustering tree.
Clusters are the nodes of the graphs we build. Each cluster is aware of the left and the right cluster it is connecting to. Each cluster also has an associated portfolio.
Attributes:
| Name | Type | Description |
|---|---|---|
portfolio |
Portfolio
|
The portfolio associated with this cluster |
Source code in src/pyhrp/cluster.py
leaves
property
¶
Get all reachable leaf nodes in left-to-right dendrogram order.
Returns:
| Type | Description |
|---|---|
list[Cluster]
|
list[Cluster]: List of all leaf nodes reachable from this cluster |
__init__(value, left=None, right=None)
¶
Initialize a new Cluster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
int
|
The identifier for this cluster |
required |
left
|
Cluster
|
The left child cluster |
None
|
right
|
Cluster
|
The right child cluster |
None
|
Source code in src/pyhrp/cluster.py
Portfolio
dataclass
¶
Container for portfolio asset weights.
This lightweight class stores and manipulates a mapping from asset names to their portfolio weights, and provides convenience helpers for analysis and visualization.
Attributes:
| Name | Type | Description |
|---|---|---|
_weights |
dict[str, float]
|
Internal mapping from asset symbol to weight. |
Source code in src/pyhrp/cluster.py
assets
property
¶
List of asset names present in the portfolio.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Asset identifiers in insertion order (Python 3.7+ dict order). |
weights
property
¶
Get all weights as a dict sorted alphabetically by asset name.
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
dict[str, float]: Mapping from asset name to weight, sorted by name. |
__getitem__(item)
¶
Return the weight for a given asset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
str
|
Asset name/symbol. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
The weight associated with the asset. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the asset is not present in the portfolio. |
Source code in src/pyhrp/cluster.py
__setitem__(key, value)
¶
Set or update the weight for an asset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Asset name/symbol. |
required |
value
|
float
|
Portfolio weight for the asset. |
required |
plot(names)
¶
Plot the portfolio weights as a bar chart.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
list[str]
|
List of asset names to include in the plot |
required |
Returns:
| Type | Description |
|---|---|
Figure
|
go.Figure: The plotly figure |
Source code in src/pyhrp/cluster.py
variance(cov)
¶
Calculate the variance of the portfolio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cov
|
DataFrame
|
Covariance matrix where columns and rows correspond to assets in the same order as columns list. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
Portfolio variance |
Source code in src/pyhrp/cluster.py
A lightweight binary tree implementation to replace the binarytree dependency.
This module provides a simple Node class that can be used to create binary trees. It implements only the functionality needed by the pyhrp package.
Node
¶
A binary tree node with left and right children.
This class implements the minimal functionality needed from the binarytree.Node class that is used in the pyhrp package.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
The value of the node |
|
left |
The left child node |
|
right |
The right child node |
Source code in src/pyhrp/treelib.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
is_leaf
property
¶
Check if this node is a leaf node (has no children).
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if this is a leaf node, False otherwise |
leaf_count
property
¶
Count the number of leaf nodes in the tree.
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Number of leaf nodes |
leaves
property
¶
Get all leaf nodes in the tree rooted at this node.
Returns:
| Type | Description |
|---|---|
Sequence[Node[T]]
|
List[Node]: List of all leaf nodes |
levels
property
¶
Get nodes by level in the tree.
Returns:
| Type | Description |
|---|---|
list[list[Node[T]]]
|
List[List[Node]]: List of lists of nodes at each level |
size
property
¶
Count the total number of nodes in the tree.
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Total number of nodes |
__init__(value, left=None, right=None)
¶
__iter__()
¶
Iterate through all nodes in the tree in level-order.
Returns:
| Type | Description |
|---|---|
Iterator[Node[T]]
|
Iterator[Node]: Iterator over all nodes |