Equity Factor Portfolio Case Study

R/Finance 2017

Ross Bennett

May 19, 2017

Overview

  • ASRS Background
  • Identifying a set of factors
  • Comparing Single Factor Indexes
  • Multi-Factor Portfolio Construction
  • Conculsion

Background

  • Arizona State Retirement System (ASRS) total fund ~ $33 billion
    • Investment staff is a team of 10 people, 3-4 focused on equities
  • Project scope
    • Research equity factors
    • Review factor indexes
    • Construct a multi-factor portfolio constrained to U.S. Equity Large and Mid Cap stocks

Factor Zoo

Harvey, Liu, and Zhu (2015) report 59 new factors discovered between 2010 and 2012.

  • Key Criteria
    • Substantiated by academic and practicioner research.
    • Demonstrated historical return premium expected to persist.
    • Investable and defined with a systematic, rules based approach.
  • Academic and practicioner research has identified a set of factors that have been shown to deliver a premium over the long-run.
    • Market, Size, Value, Momentum, Yield, Volatility, Quality, and Liquidity

Are All Single-Factor Indexes Created Equal?

spoiler

Single-Factor Indexes Are Not Equal

Qualitative Features

  • Similar factor definitions, but unique index construction process across providers.
  • Different universe of assets depending on provider and parent index.

Quantitative Features

  • Risk and return characteristics
  • Caveat of relying on historical backtest data, several of the indexes have less than 5 years of live performance.

SPOILER: No! Do your due dilligence.

Single-Factor Indexes: Size

Size
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.1095 0.2136 0.5123
b 0.0967 0.2158 0.4482
c 0.1011 0.2154 0.4693
d 0.1062 0.1932 0.5499
SP500 0.0784 0.1962 0.3996

Single-Factor Indexes: Value

Value
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.0878 0.2058 0.4267
b 0.0939 0.2118 0.4434
c 0.0724 0.2094 0.3456
d 0.0973 0.2579 0.3773
e 0.0764 0.2510 0.3044
f 0.1045 0.1947 0.5364
SP500 0.0784 0.1962 0.3996

Single-Factor Indexes: Momentum

Momentum
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.0806 0.1931 0.4173
b 0.0929 0.1977 0.4701
c 0.0753 0.1919 0.3926
d 0.0916 0.1885 0.4858
SP500 0.0784 0.1962 0.3996

Single-Factor Indexes: Volatility

Volatility
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.0781 0.1785 0.4378
b 0.0890 0.1625 0.5479
c 0.0931 0.1466 0.6353
d 0.1069 0.1409 0.7586
e 0.0945 0.1643 0.5748
f 0.1000 0.1629 0.6143
SP500 0.0784 0.1962 0.3996

Single-Factor Indexes: Quality

Quality
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.0936 0.1805 0.5185
b 0.0836 0.1846 0.4528
c 0.1009 0.1825 0.5529
d 0.1067 0.1810 0.5897
e 0.1075 0.1804 0.5958
SP500 0.0784 0.1962 0.3996

Single-Factor Indexes: Yield

Yield
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
a 0.0753 0.1836 0.4100
b 0.0746 0.1822 0.4095
c 0.0994 0.1797 0.5528
d 0.0927 0.2221 0.4174
SP500 0.0586 0.1961 0.2991

Multi-Factor Portfolio Construction

Multi-Factor Index Construction

  • Different factors targeted
  • Top-down vs. Bottom-up Construction Process

Optimization

  • Top-down approach with a set of single-factor indexes as building blocks
    • Portfolio optimization with objectives to minimize risk, minimize component contribution to risk, and maximize constant relative risk aversion
    • Heuristics: equal weight and inverse volatility
  • Historical daily total return data from 2002-06-24 through 2016-09-30

Portfolio Optimization: Portfolio Specification

Use the package PortfolioAnalytics to set up and run the optimizations. Define the portfolio specification and constraints.

library(PortfolioAnalytics)
R.4f <- R[,four.factor]
rp.seq <- generatesequence(min = 0, max = 1, by = 0.001)
port.spec <- portfolio.spec(assets = colnames(R.4f), 
                            weight_seq = rp.seq)
port.spec <- add.constraint(portfolio = port.spec, type = "weight_sum", 
                            min_sum = 0.99, max_sum = 1.01)
port.spec <- add.constraint(portfolio = port.spec, type = "box",
                            min = 0.15, max = 0.4)
# generate set of random portfolios
rp <- random_portfolios(port.spec, 5000)

Portfolio Optimization: Add Objectives

# minimum standard deviation portfolio
port.min.sd <- add.objective(portfolio = port.spec, type = "risk", 
                             name = "StdDev")
# minimum expected shortfall
port.min.es <- add.objective(portfolio = port.spec, type = "risk", 
                             name = "ES", arguments = list(p = 0.95))
# component contribution to risk (standard deviation)
port.erc.sd <- add.objective(portfolio = port.min.sd, 
                             type = "risk_budget", name = "StdDev",
                             min_concentration = TRUE)
# component contribution to risk (expected shortfall)
port.erc.es <- add.objective(portfolio = port.min.es, 
                             type = "risk_budget", name = "ES", 
                             arguments = list(p = 0.95), 
                             min_concentration = TRUE)
# constant relative risk aversion
port.crra <- add.objective(portfolio = port.spec, type = "return", 
                           name = "CRRA", arguments = list(lambda = 5))

Portfolio Optimization: CRRA

Fourth order expansion of the Constant Relative Risk Aversion (CRRA) Utility Function as in Martellini and Ziemann (2010) and Boudt, Lu, and Peeters (2015).

EUλ(w)=λ2m(2)(w)+λ(λ+1)6m(3)(w)λ(λ+1)(λ+2)24m(4)(w)

CRRA <- function(R, weights, lambda, sigma, m3, m4){
  weights <- matrix(weights, ncol=1)
  M2.w <- t(weights) %*% sigma %*% weights
  M3.w <- t(weights) %*% m3 %*% (weights %x% weights)
  M4.w <- t(weights) %*% m4 %*% (weights %x% weights %x% weights)
  term1 <- 0.5 * lambda * M2.w
  term2 <- (1 / 6) * lambda * (lambda + 1) * M3.w
  term3 <- (1 / 24) * lambda * (lambda + 1) * (lambda + 2) * M4.w
  out <- -term1 + term2 - term3
  out
}

Run Optimization

training <- 504
rolling <- 504
rebal <- "quarters"
opt.min.sd <- optimize.portfolio.rebalancing(R.4f, 
                                             portfolio=port.min.sd, 
                                             optimize_method="random", 
                                             trace=TRUE, rp=rp, 
                                             rebalance_on=rebal, 
                                             training_period=training, 
                                             rolling_window=rolling)
w.min.sd <- normalizeWeights(extractWeights(opt.min.sd))
R.min.sd <- Return.portfolio(R.4f, w.min.sd)
colnames(R.min.sd) <- "min.sd"

Optimal Weights

chart.Weights(opt.min.sd, main = 'Optimal Weights: Min SD')

4-Factor Performance Summary

R.4f.opt <- cbind(R.min.sd, R.erc.sd, R.min.es, R.erc.es, R.crra, R.4f.ew)
charts.PerformanceSummary(R.4f.opt, main = '4-Factor Performance')

Four Factor vs. Six Factor Comparison

4F and 6F Optimizations
Annualized Return Annualized Std Dev Annualized Sharpe (Rf=0%)
crra.6f 0.0996 0.1713 0.5815
min.sd.6f 0.0994 0.1718 0.5788
min.es.6f 0.0988 0.1711 0.5775
erc.es.6f 0.0978 0.1779 0.5498
erc.sd.6f 0.0975 0.1786 0.5459
ew.6f 0.0975 0.1786 0.5459
min.sd.4f 0.0952 0.1758 0.5413
crra.4f 0.0952 0.1758 0.5413
min.es.4f 0.0948 0.1755 0.5399
erc.es.4f 0.0953 0.1789 0.5328
erc.sd.4f 0.0954 0.1794 0.5319
ew.4f 0.0956 0.1805 0.5299

Six-Factor CRRA Optimal Weights

Portfolio Simulation

  • Random portfolios to simulate portfolios of zero skill managers
  • Feasible portfolios by construction
  • Monte Carlo requires a model specification
  • Time series dependency with bootstrap
  • No guarantee of a feasible portfolio with bootstrap or Monte Carlo Simulation
  • Understand impact of constraints

Portfolio Simulation

port.lo <- add.constraint(portfolio = port.spec, type = "box",
                          min = 0, max = 1)
rp.lo <- random_portfolios(port.lo, 5000)
port.box <- add.constraint(portfolio = port.spec, type = "box",
                           min = 0.15, max = 0.4)
rp.box <- random_portfolios(port.box, 5000)

sim.lo.d <- simulate.portfolio(R.4f['2004-06-30/'], rp.lo, 
                               simulations = 1000, 
                               rebalance_on = rebal)
sim.lo.f <- simulate.portfolio(R.4f['2004-06-30/'], rp.lo, 
                               dynamic = FALSE, simulations = 1000, 
                               rebalance_on = rebal)
sim.lo <- cbind(sim.lo.d, sim.lo.f)

# repeat for box constraints

Simulation Performance Summary

Simulation

Standard Deviation Histogram

Simulation Standard Deviation

Sharpe Ratio Density

Sharpe Ratio Histogram

Simulation Sharpe Ratio

Conclusion

Further work

  • Bottom-up optimization
  • Better estimates and forecasts
  • Factor timing

Tools available to apply quantitative rigor

Do your due dilligence!

References

Boudt, Kris, Wanbo Lu, and Benedict Peeters. 2015. “Higher Order Comoments of Multifactor Models and Asset Allocation.” Finance Research Letters 13. Elsevier: 225–33.

Harvey, Campbell R, Yan Liu, and Heqing Zhu. 2015.“. And the Cross-Section of Expected Returns.” Review of Financial Studies. Soc Financial Studies, hhv059.

Martellini, Lionel, and Volker Ziemann. 2010. “Improved Estimates of Higher-Order Comoments and Implications for Portfolio Selection.” Review of Financial Studies 23 (4). Soc Financial Studies: 1467–1502.