Coverage for src/pyhrp/hrp.py: 100%

20 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-30 04:15 +0000

1"""Hierarchical Risk Parity (HRP) allocation entry points. 

2 

3This module exposes the top-level allocation functions and re-exports the 

4supporting building blocks so the public ``pyhrp.hrp`` API is unchanged: 

5- hrp: Compute HRP portfolio weights from prices 

6- schur_hrp: Compute Schur Complementary Allocation weights from prices 

7- build_tree: Build a hierarchical cluster tree (see :mod:`pyhrp.dendrogram`) 

8- compute_cov / compute_corr: Second-moment estimators (see :mod:`pyhrp.covariance`) 

9- Dendrogram: Clustering result container (see :mod:`pyhrp.dendrogram`) 

10""" 

11 

12from __future__ import annotations 

13 

14from typing import Literal 

15 

16import polars as pl 

17 

18from .algos import risk_parity, schur_risk_parity 

19from .cluster import Cluster 

20from .covariance import _returns, compute_corr, compute_cov 

21from .dendrogram import Dendrogram, build_tree 

22 

23__all__ = ["Dendrogram", "build_tree", "compute_corr", "compute_cov", "hrp", "schur_hrp"] 

24 

25 

26def hrp( 

27 prices: pl.DataFrame, 

28 node: Cluster | None = None, 

29 method: Literal["single", "complete", "average", "ward"] = "ward", 

30 bisection: bool = False, 

31) -> Cluster: 

32 """Compute the hierarchical risk parity portfolio weights. 

33 

34 This is the main entry point for the HRP algorithm. It calculates returns from prices, 

35 builds a hierarchical clustering tree if not provided, and applies risk parity weights. 

36 

37 Args: 

38 prices (pl.DataFrame): Asset price time series (columns are assets, rows are dates) 

39 node (Cluster, optional): Root node of the hierarchical clustering tree. 

40 If None, a tree will be built from the correlation matrix. 

41 method (Literal["single", "complete", "average", "ward"]): Linkage method to use for distance calculation 

42 - "single": minimum distance between points (nearest neighbor) 

43 - "complete": maximum distance between points (furthest neighbor) 

44 - "average": average distance between all points 

45 - "ward": Ward variance minimization 

46 bisection (bool): Whether to use bisection method for tree construction 

47 

48 Returns: 

49 Cluster: The root cluster with portfolio weights assigned according to HRP 

50 

51 Examples: 

52 >>> import polars as pl 

53 >>> from pyhrp.hrp import hrp 

54 >>> prices = pl.DataFrame({"A": [100.0, 101.0, 99.0, 102.0], "B": [50.0, 51.0, 49.0, 52.0]}) 

55 >>> root = hrp(prices, method="ward") 

56 >>> round(sum(root.portfolio.weights.values()), 6) 

57 1.0 

58 """ 

59 returns = _returns(prices) 

60 cov = compute_cov(returns) 

61 cor = compute_corr(returns) 

62 node = node or build_tree(cor, method=method, bisection=bisection).root 

63 

64 return risk_parity(root=node, cov=cov) 

65 

66 

67def schur_hrp( 

68 prices: pl.DataFrame, 

69 node: Cluster | None = None, 

70 method: Literal["single", "complete", "average", "ward"] = "ward", 

71 bisection: bool = False, 

72 gamma: float = 0.5, 

73) -> Cluster: 

74 """Compute Schur Complementary Allocation portfolio weights. 

75 

76 Extends HRP by augmenting each sub-covariance block with off-diagonal information 

77 via Schur complements before splitting risk between clusters. Introduced by Peter Cotton 

78 (arXiv:2411.05807). At gamma=0 this is identical to HRP; at gamma=1 it recovers the 

79 global minimum-variance portfolio through the same recursive hierarchy. 

80 

81 Args: 

82 prices (pl.DataFrame): Asset price time series (columns are assets, rows are dates) 

83 node (Cluster, optional): Root node of the hierarchical clustering tree. 

84 If None, a tree will be built from the correlation matrix. 

85 method (Literal["single", "complete", "average", "ward"]): Linkage method for clustering 

86 bisection (bool): Whether to use bisection method for tree construction 

87 gamma (float): Schur interpolation parameter in [0, 1]. 

88 0 recovers standard HRP; 1 recovers minimum-variance portfolio. 

89 

90 Returns: 

91 Cluster: The root cluster with portfolio weights assigned 

92 

93 Examples: 

94 >>> import polars as pl 

95 >>> from pyhrp.hrp import schur_hrp 

96 >>> prices = pl.DataFrame({"A": [100.0, 101.0, 99.0, 102.0], "B": [50.0, 51.0, 49.0, 52.0]}) 

97 >>> root = schur_hrp(prices, method="ward", gamma=0.5) 

98 >>> round(sum(root.portfolio.weights.values()), 6) 

99 1.0 

100 """ 

101 returns = _returns(prices) 

102 cov = compute_cov(returns) 

103 cor = compute_corr(returns) 

104 node = node or build_tree(cor, method=method, bisection=bisection).root 

105 

106 return schur_risk_parity(root=node, cov=cov, gamma=gamma)