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) allocation entry points.
This module exposes the top-level allocation functions and re-exports the
supporting building blocks so the public pyhrp.hrp API is unchanged:
- hrp: Compute HRP portfolio weights from prices
- schur_hrp: Compute Schur Complementary Allocation weights from prices
- build_tree: Build a hierarchical cluster tree (see :mod:pyhrp.dendrogram)
- compute_cov / compute_corr: Second-moment estimators (see :mod:pyhrp.covariance)
- Dendrogram: Clustering result container (see :mod:pyhrp.dendrogram)
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/dendrogram.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/dendrogram.py
one_over_n()
¶
Yield the hierarchical 1/N portfolios level by level for this tree.
Container-level convenience wrapper around :func:pyhrp.algos.one_over_n;
the allocation core is imported lazily so importing the dendrogram module
does not pull in the allocation module.
Yields:
| Type | Description |
|---|---|
Generator[tuple[int, Portfolio]]
|
tuple[int, Portfolio]: The level number and the equal-weight portfolio |
Generator[tuple[int, Portfolio]]
|
at that level. |
Source code in src/pyhrp/dendrogram.py
plot(**kwargs)
¶
Build and return a plotly dendrogram figure.
Delegates to :func:pyhrp.plot.plot_dendrogram; the plotly dependency
is imported lazily so importing the allocation core stays plotly-free.
Source code in src/pyhrp/dendrogram.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.dendrogram 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/dendrogram.py
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
Allocator contract¶
All three allocators take the same inputs — a Cluster tree (root) plus
the asset names — and never mutate the tree they are given: weights are always
rebuilt from scratch, so every allocator is idempotent and a tree can be reused.
risk_parity and schur_risk_parity share the recursive _allocate_with
scaffolding and each return the fully weighted root Cluster. one_over_n
intentionally differs in its output: it is a generator that yields the
equal-weight portfolio one tree level at a time (see its docstring), because its
purpose is to expose the allocation as it deepens rather than a single final
result.
one_over_n(root, assets)
¶
Generate 1/N (equal-weight) portfolios one tree level at a time.
This implements a hierarchical 1/N strategy where weights are distributed equally among the leaves of each cluster, and the weight budget halves at each successive level of the tree.
Unlike :func:risk_parity and :func:schur_risk_parity — which rebuild a
single final allocation and return the root Cluster — this allocator is
intentionally a generator: its purpose is to expose the equal-weight
allocation as the tree deepens, yielding one portfolio per level. It shares
the sibling input contract (a Cluster tree plus the asset names) and, like
them, does not mutate the tree: weights accumulate in a local buffer, so a
leaf that terminates at a shallow level keeps its weight in the deeper levels
(each yielded portfolio is therefore a complete allocation over all assets),
and re-running on the same tree yields an identical sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Cluster
|
The root node of the cluster tree. |
required |
assets
|
list[str]
|
Asset names; a leaf's value indexes into this list. |
required |
Yields:
| Type | Description |
|---|---|
Generator[tuple[int, Portfolio]]
|
tuple[int, Portfolio]: The level number and the (cumulative) equal-weight |
Generator[tuple[int, Portfolio]]
|
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.root, dg.assets))
>>> 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 |
Note
The plotly dependency is imported lazily so importing the allocation core (which pulls in this module) stays plotly-free.
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
Plotly visualization for hierarchical clustering dendrograms.
Kept separate from the allocation core (hrp.py) so that the optional
plotting dependency (plotly) and the visualization logic do not couple the
algorithm modules. Dendrogram.plot delegates here.
plot_dendrogram(dendrogram, **kwargs)
¶
Build and return a plotly dendrogram figure for a Dendrogram.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dendrogram
|
Dendrogram
|
Clustering result carrying the linkage matrix and asset labels to render. |
required |
**kwargs
|
object
|
Extra keyword arguments forwarded to
|
{}
|
Returns:
| Type | Description |
|---|---|
Figure
|
go.Figure: A plotly figure drawing the dendrogram as line traces. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the dendrogram has no linkage matrix to plot. |
Examples:
>>> import polars as pl
>>> from pyhrp.hrp import build_tree
>>> from pyhrp.plot import plot_dendrogram
>>> cor = pl.DataFrame({"A": [1.0, 0.5], "B": [0.5, 1.0]})
>>> fig = plot_dendrogram(build_tree(cor, method="ward"))
>>> type(fig).__name__
'Figure'
Source code in src/pyhrp/plot.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
¶
Bases: Generic[T]
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
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 130 131 132 | |
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 |