openquant.hcaa
Compiled from Rust (crates/pyopenquant). Signatures and types are from the shipped .pyi stubs; descriptions are the functions’ own docstrings, as help() shows them. All Python modules.
Functions
allocate_hcaa
allocate_hcaa(
asset_names: Sequence[str],
asset_prices: Sequence[Sequence[float]] | None = None,
asset_returns: Sequence[Sequence[float]] | None = None,
covariance_matrix: Sequence[Sequence[float]] | None = None,
expected_asset_returns: Sequence[float] | None = None,
allocation_metric: str = 'equal_weighting',
confidence_level: float = 0.05,
optimal_num_clusters: int | None = None,
resample_by: str | None = None,
calculate_expected_returns: str = 'mean',
distance: str | None = None,
linkage: str | None = None,
) -> tuple[list[float], list[int]]Hierarchical Clustering-based Asset Allocation weights (Raffinot, 2017).
Builds a hierarchical tree (Ward linkage by default; see linkage) from the correlation distance d = sqrt(2 (1 - rho)) (AFML section 16.4, Snippets 16.1-16.2; see distance) and splits weight down it from the root. At each of the top optimal_num_clusters - 1 merges the left child receives a share alpha set by allocation_metric, each side scored as its inverse-variance portfolio: "minimum_variance", "minimum_standard_deviation", "expected_shortfall" and "conditional_drawdown_risk" give 1 - risk_L / (risk_L + risk_R); "sharpe_ratio" gives sr_L / (sr_L + sr_R) when both are >= 0 and not both 0, and the minimum-variance share otherwise (two negative Sharpe ratios would favour the worse side); "equal_weighting" gives 0.5. Below the cut each cluster's weight is shared equally ("equal_weighting") or by inverse variance (every other metric). Raffinot's gap-statistic choice of the cluster count is not implemented: None means no cut. Returns from prices are simple returns; estimated expected returns are annualised by 252 periods, while covariance and tail measures are per-period. The 252 assumes daily rows; it cancels in the "sharpe_ratio" split, so intraday rows give the same weights.
Parameters
asset_names: list[str]One name per asset; its length fixes the number of assets
Nand their order.asset_prices: list[list[float]] | None, default NonePrices, one inner list per observation (oldest first) and
Ncolumns. Used only whenasset_returnsis None.asset_returns: list[list[float]] | None, default NonePer-period returns, one inner list per observation and
Ncolumns; takes precedence over prices.covariance_matrix: list[list[float]] | None, default NoneN x Ncovariance; if None, the sample covariance of the returns.expected_asset_returns: list[float] | None, default NoneNexpected returns for"sharpe_ratio"; if None they are estimated from the return history (asset_returns, or the returns ofasset_prices). Ignored by the other metrics.allocation_metric: str, default "equal_weighting"One of
"minimum_variance","minimum_standard_deviation","sharpe_ratio","equal_weighting","expected_shortfall","conditional_drawdown_risk".confidence_level: float, default 0.05Tail probability for the two tail metrics (0.05 = worst 5%); not validated, clamped into
[0, 1]when the quantile is taken.optimal_num_clusters: int | None, default NoneWhere to cut the tree, in
1..=N; None meansN(no cut).resample_by: str | None, default NoneFor prices only:
"W"/"week"/"weekly"keeps every 5th row,"M"/"month"/"monthly"every 21st (case-insensitive); anything else, or None, keeps every row.calculate_expected_returns: str, default "mean"How expected returns are estimated from the returns for
"sharpe_ratio":"mean"or"exponential"(exponentially weighted, span 500); case-insensitive.distance: str | None, default NoneDistance the tree is built on (case-insensitive):
"correlation"(the default when None; cluster onditself, Mantegna's correlation distance, as Raffinot and mlfinlab do) or"distance_of_distances"(the Euclidean distance between columns ofd, as AFML Snippet 16.4 and the HRP default compute).linkage: str | None, default NoneHow the distance between clusters is measured when the tree is built (case-insensitive), as scipy's
linkage(method=...):"ward"(the default when None; Ward's minimum-variance criterion, R'sward.D2, the default of mlfinlab's and R HierPortfolios' HCAA),"average"(mean pairwise distance),"complete"(largest pairwise distance) or"single"(smallest pairwise distance; HRP's tree, and this function's tree before the default changed to Ward). Ward builds compact, similar-sized clusters; if the universe holds many near-copies of one exposure (share classes, several trackers of one index), Ward can give that group about half the capital under"equal_weighting"or"minimum_standard_deviation": deduplicate it, use"minimum_variance", or passlinkage="complete"or"single".
Returns
- tuple[list[float], list[int]]
(weights, ordered_indices): non-negative weights summing to 1, one per asset inasset_namesorder, and the asset indices in quasi-diagonal (leaf) order.
Raises
- ValueError
If
distanceis not"correlation"or"distance_of_distances",linkageis not"single","complete","average"or"ward", a matrix is empty or ragged, or the core rejects the input (e.g. no prices, returns or covariance given, emptyasset_names, too few rows, a zero price, a non-positive covariance diagonal, an unknownallocation_metricorcalculate_expected_returns, mismatched shapes,"sharpe_ratio"with neither expected returns nor a return history, a tail metric without a return history, oroptimal_num_clustersof 0 or aboveN).