Forum Sign in Register

Portfolio Optimization in R with PortfolioAnalytics: CVaR Objectives, Real Constraints and Rebalancing Backtests

Started by Support 1 week ago · 0 replies RSS

Mean-variance optimization is easy to describe and treacherous to use: feed it raw historical estimates and it will cheerfully concentrate your capital in whatever happened to perform best, then fall apart out of sample. The R package PortfolioAnalytics exists to make portfolio construction survivable in practice — it lets you specify realistic constraints and non-Gaussian objectives declaratively, solve with several backends, and (critically) backtest the whole optimization with periodic rebalancing. This article covers the workflow.

The design: portfolios as specifications

PortfolioAnalytics separates what you want from how it is solved. You build a specification object, then stack constraints and objectives onto it:


library(PortfolioAnalytics)
library(PerformanceAnalytics)

data(edhec) # monthly returns, 13 strategies
R <- edhec[, 1:8]

port <- portfolio.spec(assets = colnames(R))
port <- add.constraint(port, type = "full_investment") # weights sum to 1
port <- add.constraint(port, type = "long_only")
port <- add.constraint(port, type = "box",
min = 0.02, max = 0.30) # per-asset bounds
port <- add.objective(port, type = "risk", name = "ES",
arguments = list(p = 0.95)) # minimize 95% CVaR


That last line is the point of the package: the risk objective is Expected Shortfall (CVaR), not variance. Variance punishes upside and downside symmetrically and understates tail risk in skewed, fat-tailed return series; ES asks directly "how bad is the average of my worst 5% of months?" — usually the question you actually care about. You can just as easily minimize standard deviation, maximize mean-per-ES, add turnover penalties, or combine several weighted objectives.

Constraints that make portfolios deployable

Real mandates are constraint-driven, and this is where the package shines:

  • Box constraints — min/max per asset, the simplest defense against corner solutions that pile everything into one name.
  • Group constraints — cap exposure by sector, region or strategy bucket (e.g. crypto strategies jointly below 20%).
  • Turnover and transaction-cost constraints — limit how far each rebalance can move from current weights.
  • Position limits — cap the number of non-zero positions for a sparse, followable portfolio.


Solvers: when the problem stops being convex

Minimum-variance with linear constraints is a clean quadratic program (
optimize_method = "quadprog"
or the ROI plugins). But once the objective is ES with position limits, the problem is no longer convex, and PortfolioAnalytics leans on global methods:
"DEoptim"
(differential evolution) and
"random"
portfolios. Random portfolios deserve special mention — thousands of feasible weight vectors are generated and evaluated, which not only optimizes but maps the feasible space: plotting all candidates in risk-return space shows how much better the "optimum" really is than a typical feasible portfolio. Often the honest answer is "not much", which is itself valuable information.

The part most people skip: backtesting the optimization

A single optimization on the full sample is an in-sample exercise. The deployable test is
optimize.portfolio.rebalancing()
:


bt <- optimize.portfolio.rebalancing(
R, port,
optimize_method = "random", search_size = 5000,
rebalance_on = "quarters",
training_period = 60, # first fit: 60 months
rolling_window = 60 # re-estimate on a moving window
)
wts <- extractWeights(bt)
perf <- Return.portfolio(R, weights = wts)
charts.PerformanceSummary(perf)


Every quarter the optimizer sees only the trailing window, produces weights, and the resulting return stream is genuinely out of sample. Compare it against equal weight — the benchmark that decades of research show is embarrassingly hard to beat once estimation error and costs are counted. If your optimized portfolio cannot beat 1/N net of turnover, ship 1/N.

Pitfalls

  • Estimation error dominates. Expected-return estimates are so noisy that unconstrained optimizers amplify noise into extreme weights. Risk-only objectives (min-variance, min-ES) with sensible boxes are far more robust than full mean-risk optimization.
  • More history is not automatically better — a 15-year window happily averages across dead regimes. Match the estimation window to how fast your assets' relationships drift.
  • Monthly data for monthly decisions. Optimizing monthly weights from daily noise adds churn; aggregate first.
  • Random-search results vary with the seed — set it, and raise search_size until the frontier stabilizes.


The bottom line

PortfolioAnalytics turns portfolio construction in R from a textbook formula into an auditable process: declarative constraints, tail-risk objectives, global solvers for the non-convex cases, and — the feature that matters most — walk-forward rebalancing backtests that tell you whether the whole apparatus actually beats equal weight. Optimize the risk, constrain the weights, and let the out-of-sample equity curve have the final word.

Sign in to reply.