The Enhanced Portfolio Optimization (EPO) method, described in Pedersen, Babu, and Levine (2021), addresses a long-standing puzzle in quantitative finance: standard mean-variance optimization (MVO) has the highest possible Sharpe ratio in theory, yet it performs so poorly out-of-sample that many investors abandon optimization altogether in favor of naive rules such as the 1/N portfolio.
Pedersen, Babu, and Levine trace this failure to a specific set of “problem portfolios.” Decomposing the correlation matrix into principal components (long-short portfolios that are uncorrelated with each other and ranked by variance), they show that the least important components are the ones that wreck MVO: because these portfolios have the lowest ex-ante risk, estimation error tends to underestimate their true volatility, while any noise in expected-return estimates is large relative to that low risk. The optimizer sees these noise-driven portfolios as offering deceptively high Sharpe ratios and consequently takes large, highly-leveraged bets on them — bets that perform poorly out-of-sample.
The fix proposed by the paper, the “Simple EPO”, is a small change with an outsized effect: shrink the off-diagonal correlations toward zero before optimizing. Correlation shrinkage raises the estimated volatility of exactly the problem portfolios (the unimportant principal components), which in turn shrinks their inflated Sharpe ratios back toward the levels actually observed out-of-sample. This single adjustment stabilizes MVO and, empirically, produces large improvements in realized Sharpe ratio and statistically significant alpha relative to the market, 1/N, and standard factor benchmarks.
One shrinkage parameter, $w \in [0, 1]$, controls the whole procedure:
The package also implements the “Anchored EPO”, which lets the investor keep the optimized portfolio close to a reference or benchmark portfolio (“anchor”) — for example a strategic asset allocation, a benchmark index, or the 1/N portfolio. Here $w = 0$ again yields standard MVO, $w = 1$ collapses the solution onto the anchor, and intermediate values produce Black-Litterman-style portfolios in which $w$ plays the role of the investor’s confidence in the anchor relative to the signal. Unlike Black-Litterman, however, the anchor need not be the market portfolio, which makes the Anchored EPO considerably more general.
Beyond its empirical performance, one of the paper’s central contributions is theoretical: it shows that the same EPO solution nests standard MVO, reverse-MVO, the Black-Litterman model, robust optimization under an ellipsoidal uncertainty set on expected returns, and ridge-regression-style regularization as special cases — unifying several strands of the portfolio-optimization literature under a single, transparent shrinkage parameter.
Install the official version from CRAN with:
install.packages("epo")
Install the development version from github with:
# install.packages("devtools")
# devtools::install_github("Reckziegel/epo")
install.packages("epo", repos = c('https://reckziegel.r-universe.dev', 'https://cloud.r-project.org'))
library(epo)
x <- diff(log(EuStockMarkets)) # stock returns
s <- colMeans(x) # it could be any signal
##################
### The Simple EPO
##################
# Traditional Mean-Variance Analysis
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0)
#> [1] 0.1914569 0.9894828 -0.3681779 0.1872382
# 100% Shrinkage
epo(x = x, signal = s, lambda = 10, method = "simple", w = 1)
#> [1] 0.2352863 0.3659986 0.1375249 0.2611902
# 50% Classical MVO and 50% Shrinkage
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5)
#> [1] 0.223281853 0.564005906 -0.009868083 0.222580324
####################
### The 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)
#> [1] 0.1914569 0.9894828 -0.3681779 0.1872382
# 100% on the Anchor portfolio
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark)
#> [1] 0.25 0.25 0.25 0.25
# 50% on Mean-Variance Analysis and 50% on the Anchor Portfolio
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)
#> [1] 0.2374674 0.4557503 0.1004711 0.2063111
?epo documents the arguments and gives runnable examples for both
the Simple and the Anchored EPO.