Skip to content

Add ERCOpt: Equal Risk Contribution (Risk Parity) portfolio optimizer#743

Open
hass-nation wants to merge 1 commit into
PyPortfolio:mainfrom
hass-nation:feat/erc-optimizer
Open

Add ERCOpt: Equal Risk Contribution (Risk Parity) portfolio optimizer#743
hass-nation wants to merge 1 commit into
PyPortfolio:mainfrom
hass-nation:feat/erc-optimizer

Conversation

@hass-nation

Copy link
Copy Markdown

Summary

Adds ERCOpt to hierarchical_portfolio.py, implementing the Equal Risk Contribution (ERC) portfolio (Maillard, Roncalli & Teiletche 2010), also known as the Risk Parity portfolio.

Motivation

ERC fills a gap between the existing HRPOpt (hierarchical, cluster-based) and mean-variance optimizers: it is a simple, robust, model-free portfolio where every asset contributes the same fraction of total portfolio variance, requiring only a covariance estimate and no expected returns.

ERC has been shown to be more robust out-of-sample than max-Sharpe or min-variance portfolios (Maillard et al. 2010) and is widely used in practice.

Definition

Find w ≥ 0, sum(w) = 1 such that:

w_i · (Σw)_i / (w'Σw) = 1/n   for all i

When Σ is diagonal this reduces to inverse-volatility weighting: w_i ∝ 1/σ_i.

Solver: Spinu (2013) CCD

At each step, the exact one-dimensional sub-problem is solved for coordinate i:

Σᵢᵢ·wᵢ² + (Σw − Σᵢᵢ·wᵢ)·wᵢ − 1/n = 0   →   positive root

Crucially, weights are NOT normalised between coordinate updates — normalisation happens once after the full pass. This unconstrained formulation converges reliably for any PD covariance matrix, including those with negative off-diagonal entries (where Roncalli's multiplicative update fails).

API

Matches HRPOpt exactly:

from pypfopt import ERCOpt

# From returns
erc = ERCOpt(returns=returns_df)
weights = erc.optimize()
erc.portfolio_performance(verbose=True)

# From a pre-computed covariance matrix
erc = ERCOpt(cov_matrix=cov_df)
weights = erc.optimize()

Files changed

  • pypfopt/hierarchical_portfolio.py_erc_weights_ccd helper + ERCOpt class
  • pypfopt/__init__.py — exports ERCOpt
  • tests/test_erc.py — 29 tests

Test plan

  • 29/29 new tests pass — CCD on diagonal covariance (analytic check), negative-correlation covariance, identical assets (equal weights), two-asset analytic solution, tickers preserved, clean_weights, portfolio_performance from returns and cov-only, error before optimize
  • 294/294 existing tests pass (0 regressions; 2 test_plotting.py failures are pre-existing headless-display failures)
  • sum(weights) = 1 and all weights ≥ 0 verified
  • ERC condition RC_i = 1/n * (w'Σw) verified to rtol=1e-6

References

  • Maillard, S., Roncalli, T., & Teiletche, J. (2010). The Properties of Equally Weighted Risk Contribution Portfolios. Journal of Portfolio Management, 36(4), 60-70.
  • Spinu, F. (2013). An Algorithm for Computing Risk Parity Weights. SSRN working paper.

🤖 Generated with Claude Code

Adds ERCOpt to hierarchical_portfolio.py, implementing the Equal Risk
Contribution (ERC) portfolio (Maillard, Roncalli & Teiletche 2010).

ERCOpt finds weights w ≥ 0, sum(w)=1 such that each asset contributes an
equal fraction of total portfolio variance:

    w_i · (Σw)_i / (w'Σw) = 1/n   for all i

This is also known as the Risk Parity portfolio; when Σ is diagonal it
reduces to inverse-volatility weighting.

The optimizer uses the Spinu (2013) cyclical coordinate descent algorithm,
which solves the exact one-dimensional sub-problem at each coordinate:

    Σᵢᵢ·wᵢ² + (Σw − Σᵢᵢ·wᵢ)·wᵢ − 1/n = 0   (positive root)

Weights are NOT normalised between coordinate updates, which is crucial for
convergence — the unconstrained Spinu formulation normalises only after the
full pass over all assets.

API matches HRPOpt exactly:

    erc = ERCOpt(returns=returns_df)          # or cov_matrix=cov_df
    weights = erc.optimize()
    erc.portfolio_performance(verbose=True)

Also adds _erc_weights_ccd as a standalone helper (used internally).

29 new tests; 294 existing tests pass (0 regressions).

References
----------
Maillard, S., Roncalli, T., & Teiletche, J. (2010). The Properties of Equally
Weighted Risk Contribution Portfolios. Journal of Portfolio Management, 36(4), 60-70.

Spinu, F. (2013). An Algorithm for Computing Risk Parity Weights. SSRN.

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