- Discuss Portfolio Optimization
- Introduce PortfolioAnalytics
- Demonstrate PortfolioAnalytics with Examples
Ross Bennett
"Modern" Portfolio Theory (MPT) was introduced by Harry Markowitz in 1952.
In general, MPT states that an investor's objective is to maximize portfolio expected return for a given amount of risk.
General Objectives
How do we define risk? What about more complex objectives and constraints?
PortfolioAnalytics is an R package designed to provide numerical solutions and visualizations for portfolio optimization problems with complex constraints and objectives.
Linear and Quadratic Programming Solvers
Global (stochastic or continuous solvers)
PortfolioAnalytics has three methods to generate random portfolios.
gridSearch
function in the NMOF package.args(portfolio.spec)
## function (assets = NULL, category_labels = NULL, weight_seq = NULL,
## message = FALSE)
## NULL
Initializes the portfolio object that holds portfolio level data, constraints, and objectives
args(add.constraint)
## function (portfolio, type, enabled = TRUE, message = FALSE, ...,
## indexnum = NULL)
## NULL
Supported Constraint Types
args(add.objective)
## function (portfolio, constraints = NULL, type, name, arguments = NULL,
## enabled = TRUE, ..., indexnum = NULL)
## NULL
Supported Objective types
args(optimize.portfolio)
## function (R, portfolio = NULL, constraints = NULL, objectives = NULL,
## optimize_method = c("DEoptim", "random", "ROI", "pso", "GenSA"),
## search_size = 20000, trace = FALSE, ..., rp = NULL, momentFUN = "set.portfolio.moments",
## message = FALSE)
## NULL
args(optimize.portfolio.rebalancing)
## function (R, portfolio = NULL, constraints = NULL, objectives = NULL,
## optimize_method = c("DEoptim", "random", "ROI"), search_size = 20000,
## trace = FALSE, ..., rp = NULL, rebalance_on = NULL, training_period = NULL,
## trailing_periods = NULL)
## NULL
Visualization | Data Extraction |
---|---|
plot | extractObjectiveMeasures |
chart.Concentration | extractStats |
chart.EfficientFrontier | extractWeights |
chart.RiskReward | |
chart.RiskBudget | summary |
chart.Weights |
Here we will look at portfolio optimization in the context of stocks.
equity.data <- cbind(largecap_weekly[,1:15],
midcap_weekly[,1:15],
smallcap_weekly[,1:5])
Here we consider a portfolio of stocks. Our objective is to maximize portfolio return with a target of 0.0015 and minimize portfolio StdDev with a target of 0.02 subject to dollar neutral, beta, box, and position limit constraints.
portf.dn <- portfolio.spec(stocks)
# Add constraint such that the portfolio weights sum to 0*
portf.dn <- add.constraint(portf.dn, type="weight_sum",
min_sum=-0.01, max_sum=0.01)
# Add box constraint such that no asset can have a weight of greater than
# 20% or less than -20%
portf.dn <- add.constraint(portf.dn, type="box", min=-0.2, max=0.2)
# Add constraint such that we have at most 20 positions
portf.dn <- add.constraint(portf.dn, type="position_limit", max_pos=20)
# Add constraint such that the portfolio beta is between -0.25 and 0.25
betas <- t(CAPM.beta(equity.data, market, Rf))
portf.dn <- add.constraint(portf.dn, type="factor_exposure", B=betas,
lower=-0.25, upper=0.25)
# Add objective to maximize portfolio return with a target of 0.0015
portf.dn.StdDev <- add.objective(portf.dn, type="return", name="mean",
target=0.0015)
# Add objective to minimize portfolio StdDev with a target of 0.02
portf.dn.StdDev <- add.objective(portf.dn.StdDev, type="risk", name="StdDev",
target=0.02)
# Generate random portfolios
rp <- random_portfolios(portf.dn, 10000, "sample")
# Run the optimization
opt.dn <- optimize.portfolio(equity.data, portf.dn.StdDev,
optimize_method="random", rp=rp,
trace=TRUE)
plot(opt.dn, main="Dollar Neutral Portfolio", risk.col="StdDev", neighbors=10)
Here we will look at portfolio optimization in the context of portfolio of hedge funds.
Relative Value | Directional |
---|---|
Convertible Arbitrage (CA) | CTA Global (CTAG) |
Equity Market Neutral (EMN) | Emerging Markets (EM) |
Fixed Income Arbitrage (FIA) | Global Macro (GM) |
R <- edhec[,c("Convertible.Arbitrage", "Equity.Market.Neutral",
"Fixed.Income.Arbitrage",
"CTA.Global", "Emerging.Markets", "Global.Macro")]
# Abreviate column names for convenience and plotting
colnames(R) <- c("CA", "EMN", "FIA", "CTAG", "EM", "GM")
Consider an allocation to hedge funds using the EDHEC-Risk Alternative Index as a proxy. This will be an extended example starting with an objective to minimize modified expected shortfall, then add risk budget percent contribution limit, and finally add equal risk contribution limit.
# Specify an initial portfolio
funds <- colnames(R)
portf.init <- portfolio.spec(funds)
# Add constraint such that the weights sum to 1*
portf.init <- add.constraint(portf.init, type="weight_sum",
min_sum=0.99, max_sum=1.01)
# Add box constraint such that no asset can have a weight of greater than
# 40% or less than 5%
portf.init <- add.constraint(portf.init, type="box",
min=0.05, max=0.4)
# Add return objective with multiplier=0 such that the portfolio mean
# return is calculated, but does not impact optimization
portf.init <- add.objective(portf.init, type="return",
name="mean", multiplier=0)
# Add objective to minimize expected shortfall
portf.minES <- add.objective(portf.init, type="risk", name="ES")
# Add objective to set upper bound on percentage component contribution
portf.minES.RB <- add.objective(portf.minES, type="risk_budget",
name="ES", max_prisk=0.3)
# Relax box constraints
portf.minES.RB$constraints[[2]]$max <- rep(1,ncol(R))
# Add objective to minimize concentration of modified ES
# component contribution
portf.minES.EqRB <- add.objective(portf.minES, type="risk_budget",
name="ES", min_concentration=TRUE)
# Relax box constraints
portf.minES.EqRB <- add.constraint(portf.minES.EqRB, type="box",
min=0.05, max=1, indexnum=2)
# Combine the 3 portfolios
portf <- combine.portfolios(list(minES=portf.minES,
minES.RB=portf.minES.RB,
minES.EqRB=portf.minES.EqRB))
# Run the optimization
opt.minES <- optimize.portfolio(R, portf, optimize_method="DEoptim",
search_size=5000, trace=TRUE, traceDE=0)
# Set rebalancing frequency
rebal.freq <- "quarters"
# Training Period
training <- 120
# Trailing Period
trailing <- 72
bt.opt.minES <- optimize.portfolio.rebalancing(R, portf,
optimize_method="DEoptim",
rebalance_on=rebal.freq,
training_period=training,
trailing_periods=trailing,
search_size=5000,
traceDE=0)
ret.bt.opt <- do.call(cbind, lapply(bt.opt.minES,
function(x) summary(x)$portfolio_returns))
colnames(ret.bt.opt) <- c("min ES", "min ES RB", "min ES Eq RB")
charts.PerformanceSummary(ret.bt.opt)
Consider an allocation to hedge funds using the EDHEC-Risk Alternative Index as a proxy. Our objective to maximize the fourth order expansion of the Constant Relative Risk Aversion (CRRA) expected utility function as in the Boudt paper and Martellini paper. We use the same data as Example 3.
\[ EU_{\lambda}(w) = - \frac{\lambda}{2} m_{(2)}(w) + \frac{\lambda (\lambda + 1)}{6} m_{(3)}(w) - \frac{\lambda (\lambda + 1) (\lambda + 2)}{24} m_{(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 <- (1 / 2) * 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
}
The default function for momentFUN
is set.portfolio.moments
. We need to write our own function to estimate the moments for our objective function.
crra.moments <- function(R, ...) {
out <- list()
out$mu <- colMeans(R)
out$sigma <- cov(R)
out$m3 <- PerformanceAnalytics:::M3.MM(R)
out$m4 <- PerformanceAnalytics:::M4.MM(R)
out
}
# Specify portfolio
portf.crra <- portfolio.spec(funds)
# Add constraint such that the weights sum to 1
portf.crra <- add.constraint(portf.crra, type="weight_sum",
min_sum=0.99, max_sum=1.01)
# Add box constraint such that no asset can have a weight of greater than
# 40% or less than 5%
portf.crra <- add.constraint(portf.crra, type="box",
min=0.05, max=0.4)
# Add objective to maximize CRRA
portf.crra <- add.objective(portf.crra, type="return",
name="CRRA", arguments=list(lambda=10))
# Dummy objectives for plotting and/or further analysis
portf.crra <- add.objective(portf.crra, type="return", name="mean", multiplier=0)
portf.crra <- add.objective(portf.crra, type="risk", name="ES", multiplier=0)
portf.crra <- add.objective(portf.crra, type="risk", name="StdDev", multiplier=0)
opt.crra <- optimize.portfolio(R, portf.crra, optimize_method="DEoptim",
search_size=5000, trace=TRUE, traceDE=0,
momentFUN="crra.moments")
head(extractStats(opt.crra),4)
## CRRA.CRRA mean ES StdDev out w.CA w.EMN
## .DE.portf.1 -0.0009786 0.005906 0.03407 0.01367 0.0009786 0.1667 0.1667
## .DE.portf.2 -0.0007585 0.005716 0.02532 0.01215 0.0007585 0.1560 0.3420
## .DE.portf.3 -0.0019451 0.006380 0.05068 0.01901 0.0019451 0.1180 0.1200
## .DE.portf.4 -0.0007816 0.005536 0.02591 0.01232 0.0007816 0.0900 0.1840
## w.FIA w.CTAG w.EM w.GM
## .DE.portf.1 0.1667 0.1667 0.1667 0.1667
## .DE.portf.2 0.0560 0.2180 0.1280 0.0920
## .DE.portf.3 0.0880 0.1300 0.3900 0.1620
## .DE.portf.4 0.2800 0.2720 0.1160 0.0640
chart.RiskReward(opt.crra, risk.col = "ES")
chart.RiskReward(opt.crra, risk.col = "StdDev")
bt.opt.crra <- optimize.portfolio.rebalancing(R, portf.crra,
optimize_method="DEoptim",
search_size=5000, trace=TRUE,
traceDE=0,
momentFUN="crra.moments",
rebalance_on=rebal.freq,
training_period=training,
trailing_periods=trailing)
ret.crra <- summary(bt.opt.crra)$portfolio_returns
colnames(ret.crra) <- "CRRA"
chart.Weights(bt.opt.crra, main="CRRA Weights", col=bluemono)
charts.PerformanceSummary(cbind(ret.bt.opt, ret.crra),
main="Optimization Performance")
Many thanks to...
PortfolioAnalytics is on R-Forge in the ReturnAnalytics project
Source code for the slides
and view it here