Skip to content

feat(hierarchical): add HERCOpt (HERC) and NCOpt (NCO) portfolio allocation#742

Open
hass-nation wants to merge 1 commit into
PyPortfolio:mainfrom
hass-nation:feat/herc-nco-hierarchical
Open

feat(hierarchical): add HERCOpt (HERC) and NCOpt (NCO) portfolio allocation#742
hass-nation wants to merge 1 commit into
PyPortfolio:mainfrom
hass-nation:feat/herc-nco-hierarchical

Conversation

@hass-nation

Copy link
Copy Markdown

Summary

Extends pypfopt.hierarchical_portfolio with two new portfolio allocation methods that the module docstring already identifies as missing:

  • HERCOpt — Hierarchical Equal Risk Contribution (Raffinot 2018)
  • NCOpt — Nested Cluster Optimization (Lopez de Prado 2019)

Both classes share the same API as HRPOpt and are exported from pypfopt.__init__.


HERCOpt — Hierarchical Equal Risk Contribution

HERC extends HRP by replacing inverse-variance (IVP) cluster weights with Equal Risk Contribution (ERC) weights at every level of the hierarchy. The recursive bisection structure is identical to HRP, but the variance of each sub-cluster is computed as the variance of its ERC portfolio rather than its IVP portfolio.

Because ERC accounts for within-cluster correlations, HERC produces more balanced risk allocation when intra-cluster correlations are high — a common situation in equity portfolios where sector peers cluster together.

from pypfopt import HERCOpt

herc = HERCOpt(returns=returns_df)       # or: cov_matrix=S
weights = herc.optimize(linkage_method="ward")
herc.portfolio_performance(verbose=True)

The ERC weights are solved via the multiplicative cyclical coordinate descent update of Roncalli (2013), which converges reliably and requires no SciPy optimization calls for individual clusters.


NCOpt — Nested Cluster Optimization

NCO partitions the asset universe into clusters, optimizes within each cluster, and then optimizes across the resulting cluster-portfolios in a two-level nested procedure.

from pypfopt import NCOpt

nco = NCOpt(returns=returns_df)
weights = nco.optimize(
    n_clusters=4,
    internal_opt="min_variance",   # within-cluster: "min_variance", "erc", "equal"
    meta_opt="erc",                # across-cluster:  "min_variance", "erc", "equal"
    linkage_method="ward",
)
nco.portfolio_performance(verbose=True)

# Cluster assignment is available after optimize()
print(nco.clusters)   # {0: ['AAPL', 'GOOG', ...], 1: [...], ...}

The meta-covariance across clusters is computed analytically from the full covariance matrix (w_i' Sigma w_j), so NCO works correctly even when only a covariance matrix (no return history) is supplied.

Supports all 9 combinations of (internal_opt x meta_opt).


Private helpers (also usable by downstream code)

Function Description
_erc_weights_ccd(cov) ERC via multiplicative CCD (Roncalli 2013)
_min_var_weights(cov) Long-only min-variance: analytic when feasible, SLSQP fallback

Files changed

File Change
pypfopt/hierarchical_portfolio.py +HERCOpt, +NCOpt, +2 helpers (~500 lines)
pypfopt/__init__.py Export HERCOpt, NCOpt
tests/test_herc_nco.py 53 tests, 9 test classes (new file)

Tests

53 tests, 0 failures   (all existing HRP tests still pass)

Coverage includes: ERC weight correctness (equal RC property), min-var analytic solution, instantiation errors, weight validity (sum=1, non-negative), all 9 NCO method combinations, cov-only mode, comparison vs HRP (weights differ meaningfully), portfolio performance.

References

  1. Raffinot, T. (2018). Hierarchical clustering-based asset allocation. Journal of Portfolio Management, 44(2), 89-99.
  2. Lopez de Prado, M. (2019). A Robust Estimator of the Efficient Frontier. SSRN Working Paper. https://ssrn.com/abstract=3469961
  3. Roncalli, T. (2013). Introduction to Risk Parity and Budgeting. CRC Press.

Generated with Claude Code

Extends the hierarchical portfolio module with two additional methods:

HERCOpt - Hierarchical Equal Risk Contribution (Raffinot 2018)
  Equal Risk Contribution (ERC) weights at every level of the hierarchy
  instead of the inverse-variance weights used by HRP. ERC accounts for
  within-cluster correlations, producing more balanced risk allocation
  when intra-cluster correlations are high. Implemented via cyclical
  coordinate descent (Roncalli 2013).

NCOpt - Nested Cluster Optimization (Lopez de Prado 2019)
  Two-level nested procedure: within-cluster optimization followed by
  meta-portfolio optimization across clusters. Supports three objectives
  at each level - min_variance, erc, and equal-weight - giving 9
  combinations. The meta-covariance is computed analytically from the
  full covariance matrix, so no return history is required.

Both classes:
  - Share the same API as HRPOpt (fit from returns or cov_matrix)
  - Export portfolio_performance() with expected return and Sharpe ratio
  - Are exported from pypfopt.__init__ and __all__
  - Accept all scipy linkage methods

Also adds two private helpers:
  - _erc_weights_ccd: ERC via multiplicative CCD (Roncalli 2013)
  - _min_var_weights: long-only min-variance with analytic + SLSQP fallback

53 tests across 9 test classes; all existing HRP tests still pass.

References
----------
Raffinot, T. (2018). Hierarchical clustering-based asset allocation.
Journal of Portfolio Management, 44(2), 89-99.

Lopez de Prado, M. (2019). A Robust Estimator of the Efficient Frontier.
SSRN Working Paper. https://ssrn.com/abstract=3469961

Roncalli, T. (2013). Introduction to Risk Parity and Budgeting.
CRC Press.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant