| Title: | Enhanced Portfolio Optimization (EPO) |
|---|---|
| Description: | Implements the Enhanced Portfolio Optimization (EPO) method of Pedersen, Babu and Levine (2021) <doi:10.1080/0015198X.2020.1854543>. EPO stabilizes mean-variance optimization by shrinking correlations toward zero, which addresses estimation error in both the risk model and expected returns. Both the "Simple EPO" and the "Anchored EPO" (an anchor/benchmark-aware, Black-Litterman-like extension) are provided. |
| Authors: | Bernardo Reckziegel [aut, cre, cph] |
| Maintainer: | Bernardo Reckziegel <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0.9000 |
| Built: | 2026-09-27 11:45:39 UTC |
| Source: | https://github.com/reckziegel/epo |
Computes the optimal portfolio allocation using the Enhanced Portfolio Optimization (EPO) method of Pedersen, Babu, and Levine (2021).
epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## Default S3 method: epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'tbl' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'xts' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'matrix' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE )epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## Default S3 method: epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'tbl' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'xts' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE ) ## S3 method for class 'matrix' epo( x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE )
x |
A data-set with asset returns. It should be a |
signal |
A |
lambda |
A |
method |
A |
w |
A |
anchor |
A |
normalize |
A |
endogenous |
A |
Standard mean-variance optimization (MVO) is highly sensitive to
estimation error in the correlation matrix and in expected returns. This
error is concentrated in the least important principal components of the
correlation matrix (the "problem portfolios"), whose risk tends to be
underestimated and whose expected return tends to be overestimated. EPO
fixes this by shrinking the off-diagonal correlations toward zero by a
factor w before running MVO, which increases the estimated volatility
(and lowers the implied Sharpe ratio) of exactly the problem portfolios.
Two flavors of EPO are implemented, both governed by a single shrinkage
parameter, w, between 0 (no shrinkage, i.e. standard MVO) and 1
(maximum shrinkage):
method = "simple" implements the "Simple EPO" (paper's equation 16).
The allocation is given by ,
where is the variance-covariance matrix rebuilt from the
shrunk correlation matrix . At
w = 1 all correlations are set to zero, which is equivalent (up to scaling)
to not optimizing at all.
method = "anchored" implements the "Anchored EPO" (paper's equation 17),
which pulls the solution toward a reference/benchmark portfolio, the anchor.
At w = 0 the solution is standard MVO; at w = 1 the solution collapses
onto the anchor; values in between produce Black-Litterman-style portfolios
in which w controls the confidence placed in the anchor relative to the
signal. Unlike Black-Litterman, the anchor need not be the market portfolio.
A numeric vector with the optimal portfolio weights, one per
column of x.
Pedersen, L. H., Babu, A., and Levine, A. (2021). Enhanced Portfolio Optimization. Financial Analysts Journal, 77(2), 124-151. doi:10.1080/0015198X.2020.1854543
x <- diff(log(EuStockMarkets)) # stock returns s <- colMeans(x) # it could be any signal ################## ### Simple EPO ### ################## # Traditional Mean-Variance Analysis epo(x = x, signal = s, lambda = 10, method = "simple", w = 0) # 100% Shrinkage epo(x = x, signal = s, lambda = 10, method = "simple", w = 1) # 50% Classical MVO and 50% Shrinkage epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5) #################### ### Anchored EPO ### #################### benchmark <- rep(0.25, 4) # 1/N Portfolio # Traditional Mean-Variance Analysis epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.0, anchor = benchmark) # 100% on the Anchor portfolio epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark) # Somewhere between the two worlds epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)x <- diff(log(EuStockMarkets)) # stock returns s <- colMeans(x) # it could be any signal ################## ### Simple EPO ### ################## # Traditional Mean-Variance Analysis epo(x = x, signal = s, lambda = 10, method = "simple", w = 0) # 100% Shrinkage epo(x = x, signal = s, lambda = 10, method = "simple", w = 1) # 50% Classical MVO and 50% Shrinkage epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5) #################### ### Anchored EPO ### #################### benchmark <- rep(0.25, 4) # 1/N Portfolio # Traditional Mean-Variance Analysis epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.0, anchor = benchmark) # 100% on the Anchor portfolio epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark) # Somewhere between the two worlds epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)