Path: blob/master/sandbox/RFinance2014/optimize.R
1433 views
# script used to run the portfolio optimizations12# Examples to consider3# Example 1: Consider a portfolio of stocks. Full investment and long4# only (or box) constraints. Objective to minimize portfolio variance.5# Demonstrate a custom moments function to compare a sample covariance6# matrix estimate and a robust covariance matrix estimate. An alternative7# to a MCD estimate is ledoit-wolf shrinkage, DCC GARCH model,8# factor model, etc.910# Example 2: Consider a portfolio of stocks. Dollar neutral, beta11# neutral, box constraints, and leverage_exposure constraints. Objective12# to minimize portfolio StdDev. This will demonstrate some of the13# more advanced constraint types. Could also introduce position limit14# constraints here in this example.1516# Example 3: Consider an allocation to hedge funds using the17# EDHEC-Risk Alternative Index as a proxy. This will be an extended18# example starting with an objective to minimize portfolio expected19# shortfall, then risk budget percent contribution limit, then equal20# risk contribution limit.2122# Example 4: Consider an allocation to hedge funds using the23# EDHEC-Risk Alternative Index as a proxy.2425# Option 1 for example 426# Objective to maximize a risk adjusted return measure27# (e.g.Calmar Ratio, Sterling Ratio, Sortino Ratio, or Upside Potential28# Ratio)2930# I prefer doing this option31# Option 2 for example 432# Objective to maximize the33# fourth order expansion of the Constant Relative Risk Aversion (CRRA)34# expected utility function. Demonstrate a custom moment function and35# a custom objective function.3637# Set the directory to save the optimization results38results.dir <- "optimization_results"3940# mix of blue, green, and red hues41my_colors <- c("#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c")4243# Load the packages44library(PortfolioAnalytics)45library(foreach)46library(ROI)47library(ROI.plugin.quadprog)4849# for running via Rscript50library(methods)5152# Source in the lwShrink function53source("R/lwShrink.R")5455# Example 1 and Example 2 will use the crsp_weekly data56# Example 3 and Example 4 will use the edhec data57source("data_prep.R")585960##### Example 1 #####61stocks <- colnames(equity.data)62# Specify an initial portfolio63portf.init <- portfolio.spec(stocks)64# Add constraints65# weights sum to 166portf.minvar <- add.constraint(portf.init, type="full_investment")67# box constraints68portf.minvar <- add.constraint(portf.minvar, type="box", min=0.01, max=0.45)6970# Add objective71# objective to minimize portfolio variance72portf.minvar <- add.objective(portf.minvar, type="risk", name="var")7374# Backtesting parameters75# Set rebalancing frequency76rebal.freq <- "quarters"77# Training Period78training <- 40079# Trailing Period80trailing <- 2508182# Run optimization83# Sample Covariance Matrix Estimate8485# By default, momentFUN uses set.portfolio.moments which computes the sample86# moment estimates8788cat("Example 1: running minimum variance with sample covariance matrix89estimate backtest\n")90if(file.exists(paste(results.dir, "opt.minVarSample.rda", sep="/"))){91cat("file already exists\n")92} else {93opt.minVarSample <- optimize.portfolio.rebalancing(equity.data, portf.minvar,94optimize_method="ROI",95rebalance_on=rebal.freq,96training_period=training,97trailing_periods=trailing)98cat("opt.minVarSample complete. Saving results to ", results.dir, "\n")99save(opt.minVarSample, file=paste(results.dir, "opt.minVarSample.rda", sep="/"))100}101102# Custom moment function to use Ledoit-Wolf shinkage covariance matrix estimate103lw.sigma <- function(R, ...){104out <- list()105# estimate covariance matrix via robust covariance matrix estimate,106# ledoit-wolf shrinkage, GARCH, factor model, etc.107# set.seed(1234)108# out$sigma <- MASS::cov.rob(R, method="mcd", ...)$cov109out$sigma <- lwShrink(R)$cov110#print(index(last(R)))111return(out)112}113114cat("Example 1: running minimum variance with Ledoit-Wolf shrinkage covariance115matrix estimate backtest\n")116if(file.exists(paste(results.dir, "opt.minVarLW.rda", sep="/"))){117cat("file already exists\n")118} else{119# Using Ledoit-Wolf Shrinkage Covariance Matrix Estimate120opt.minVarLW <- optimize.portfolio.rebalancing(equity.data, portf.minvar,121optimize_method="ROI",122momentFUN=lw.sigma,123rebalance_on=rebal.freq,124training_period=training,125trailing_periods=trailing)126cat("opt.minVarLW complete. Saving results to ", results.dir, "\n")127save(opt.minVarLW, file=paste(results.dir, "opt.minVarLW.rda", sep="/"))128}129130##### Example 2 #####131portf.init <- portfolio.spec(stocks)132133# weights sum to 0134portf.dn <- add.constraint(portf.init, type="weight_sum",135min_sum=-0.01, max_sum=0.01)136137# Add box constraints such that no stock has weight less than -20% or138# greater than 20%139portf.dn <- add.constraint(portf.dn, type="box",140min=-0.2, max=0.2)141# Add position limit constraint such that the portfolio has a maximum142# of 20 non-zero positions143portf.dn <- add.constraint(portf.dn, type="position_limit", max_pos=20)144145# Compute the betas of each stock146betas <- t(CAPM.beta(equity.data, market, Rf))147148# Add factor exposure constraint to limit portfolio beta149portf.dn <- add.constraint(portf.dn, type="factor_exposure", B=betas,150lower=-0.25, upper=0.25)151# portf.dn <- add.constraint(portf.dn, type="leverage_exposure", leverage=2)152153# generate random portfolios154if(file.exists(paste(results.dir, "rp.rda", sep="/"))){155cat("random portfolios already generated\n")156} else {157cat("generating random portfolios\n")158rp <- random_portfolios(portf.dn, 10000, eliminate=TRUE)159cat("random portfolios generated. Saving rp to ", results.dir, "\n")160save(rp, file=paste(results.dir, "rp.rda", sep="/"))161}162163# Add objective to maximize return164portf.dn.StdDev <- add.objective(portf.dn, type="return", name="mean",165target=0.0015)166# Add objective to target a portfolio standard deviation of 2%167portf.dn.StdDev <- add.objective(portf.dn.StdDev, type="risk", name="StdDev",168target=0.02)169170cat("Example 2: running dollar neutral optimization\n")171if(file.exists(paste(results.dir, "opt.dn.rda", sep="/"))){172cat("file already exists\n")173} else {174# Run optimization175opt.dn <- optimize.portfolio(equity.data, portf.dn.StdDev,176optimize_method="random", rp=rp,177trace=TRUE)178cat("opt.dn complete. Saving results to ", results.dir, "\n")179save(opt.dn, file=paste(results.dir, "opt.dn.rda", sep="/"))180}181182##### Example 3 #####183# Example 3 will consider three portfolios184# - minES185# - minES with component contribution limit186# - minES with equal risk contribution187188funds <- colnames(R)189portf.init <- portfolio.spec(funds)190portf.init <- add.constraint(portf.init, type="weight_sum",191min_sum=0.99, max_sum=1.01)192193portf.init <- add.constraint(portf.init, type="box",194min=0.05, max=0.4)195196# Set multiplier=0 so that it is calculated, but does not affect the optimization197portf.init <- add.objective(portf.init, type="return",198name="mean", multiplier=0)199200# Add objective to minimize expected shortfall201portf.minES <- add.objective(portf.init, type="risk", name="ES")202203# Add risk budget objective with upper limit on percentage contribution204portf.minES.RB <- add.objective(portf.minES, type="risk_budget",205name="ES", max_prisk=0.3)206207# Relax the box constraint208portf.minES.RB$constraints[[2]]$max <- rep(1,ncol(R))209# print.default(portf.minES.RB$constraints[[2]])210211# Add risk budget objective to minimize concentration of percentage component212# contribution to risk. Concentration is defined as the Herfindahl-Hirschman213# Index (HHI). $\sum_i x_i^2$214portf.minES.EqRB <- add.objective(portf.minES, type="risk_budget",215name="ES", min_concentration=TRUE)216# relax the box constraint217portf.minES.EqRB <- add.constraint(portf.minES.EqRB, type="box",218min=0.05, max=1, indexnum=2)219# portf.minES.RB$constraints[[2]]$max <- rep(1,ncol(R))220# print.default(portf.minES.EqRB$constraints[[2]])221222# Add risk budget objective to minES portfolio with multiplier=0 so that it223# is calculated, but does not affect optimization224portf.minES <- add.objective(portf.minES, type="risk_budget",225name="ES", multiplier=0)226227# Combine the portfolios so we can make a single call to228# optimize.portfolio229portf <- combine.portfolios(list(minES=portf.minES,230minES.RB=portf.minES.RB,231minES.EqRB=portf.minES.EqRB))232233cat("Example 3: running minimum ES optimizations\n")234if(file.exists(paste(results.dir, "opt.minES.rda", sep="/"))){235cat("file already exists\n")236} else {237# Run the optimization238opt.minES <- optimize.portfolio(R, portf, optimize_method="DEoptim",239search_size=5000, trace=TRUE, traceDE=0,240message=TRUE)241cat("opt.minES complete. Saving results to ", results.dir, "\n")242save(opt.minES, file=paste(results.dir, "opt.minES.rda", sep="/"))243}244245# Now we want to evaluate the optimization through time246247# Rebalancing parameters248# Set rebalancing frequency249rebal.freq <- "quarters"250# Training Period251training <- 120252# Trailing Period253trailing <- 72254255cat("Example 3: running minimum ES backtests\n")256if(file.exists(paste(results.dir, "bt.opt.minES.rda", sep="/"))){257cat("file already exists\n")258} else {259# Backtest260bt.opt.minES <- optimize.portfolio.rebalancing(R, portf,261optimize_method="DEoptim",262rebalance_on=rebal.freq,263training_period=training,264trailing_periods=trailing,265search_size=5000,266traceDE=0, message=TRUE)267cat("bt.opt.minES complete. Saving results to ", results.dir, "\n")268save(bt.opt.minES, file=paste(results.dir, "bt.opt.minES.rda", sep="/"))269}270271##### Example 4 #####272273# Simple function to compute the moments used in CRRA274crra.moments <- function(R, ...){275out <- list()276out$mu <- colMeans(R)277out$sigma <- cov(R)278out$m3 <- PerformanceAnalytics:::M3.MM(R)279out$m4 <- PerformanceAnalytics:::M4.MM(R)280out281}282283284# Fourth order expansion of CRRA expected utility285CRRA <- function(R, weights, lambda, sigma, m3, m4){286weights <- matrix(weights, ncol=1)287M2.w <- t(weights) %*% sigma %*% weights288M3.w <- t(weights) %*% m3 %*% (weights %x% weights)289M4.w <- t(weights) %*% m4 %*% (weights %x% weights %x% weights)290term1 <- 0.5 * lambda * M2.w291term2 <- (1 / 6) * lambda * (lambda + 1) * M3.w292term3 <- (1 / 24) * lambda * (lambda + 1) * (lambda + 2) * M4.w293out <- -term1 + term2 - term3294out295}296297# test the CRRA function298portf.crra <- portfolio.spec(funds)299portf.crra <- add.constraint(portf.crra, type="weight_sum",300min_sum=0.99, max_sum=1.01)301302portf.crra <- add.constraint(portf.crra, type="box",303min=0.05, max=0.4)304305portf.crra <- add.objective(portf.crra, type="return",306name="CRRA", arguments=list(lambda=10))307308# I just want these for plotting309# Set multiplier=0 so that it is calculated, but does not affect the optimization310portf.crra <- add.objective(portf.crra, type="return", name="mean", multiplier=0)311portf.crra <- add.objective(portf.crra, type="risk", name="ES", multiplier=0)312portf.crra <- add.objective(portf.crra, type="risk", name="StdDev", multiplier=0)313314cat("Example 4: running maximum CRRA optimization\n")315if(file.exists(paste(results.dir, "opt.crra.rda", sep="/"))){316cat("file already exists\n")317} else {318# Run the optimization319opt.crra <- optimize.portfolio(R, portf.crra, optimize_method="DEoptim",320search_size=5000, trace=TRUE, traceDE=0,321momentFUN="crra.moments")322cat("opt.crra complete. Saving results to ", results.dir, "\n")323save(opt.crra, file=paste(results.dir, "opt.crra.rda", sep="/"))324}325326cat("Example 4: running maximum CRRA backtest\n")327if(file.exists(paste(results.dir, "bt.opt.crra.rda", sep="/"))){328cat("file already exists\n")329} else {330# Run the optimization with rebalancing331bt.opt.crra <- optimize.portfolio.rebalancing(R, portf.crra,332optimize_method="DEoptim",333search_size=5000, trace=TRUE,334traceDE=0,335momentFUN="crra.moments",336rebalance_on=rebal.freq,337training_period=training,338trailing_periods=trailing)339cat("bt.opt.crra complete. Saving results to ", results.dir, "\n")340save(bt.opt.crra, file=paste(results.dir, "bt.opt.crra.rda", sep="/"))341}342343##### RP Demo #####344cat("Random portfolio method comparison\n")345if(file.exists("figures/rp_plot.png") & file.exists("figures/rp_viz.rda")){346cat("file already exists\n")347} else {348portf.lo <- portfolio.spec(colnames(R))349portf.lo <- add.constraint(portf.lo, type="weight_sum",350min_sum=0.99, max_sum=1.01)351352portf.lo <- add.constraint(portf.lo, type="long_only")353354# Use the long only portfolio previously created355# Generate random portfolios using the 3 methods356rp1 <- random_portfolios(portf.lo, permutations=2000,357rp_method='sample')358rp2 <- random_portfolios(portf.lo, permutations=2000,359rp_method='simplex')360rp3 <- random_portfolios(portf.lo, permutations=2000,361rp_method='grid')362363# Calculate the portfolio mean return and standard deviation364rp1_mean <- apply(rp1, 1, function(x) mean(R %*% x))365rp1_StdDev <- apply(rp1, 1, function(x) StdDev(R, weights=x))366rp2_mean <- apply(rp2, 1, function(x) mean(R %*% x))367rp2_StdDev <- apply(rp2, 1, function(x) StdDev(R, weights=x))368rp3_mean <- apply(rp3, 1, function(x) mean(R %*% x))369rp3_StdDev <- apply(rp3, 1, function(x) StdDev(R, weights=x))370371x.assets <- StdDev(R)372y.assets <- colMeans(R)373###374require(rCharts)375# create an interactive plot using rCharts and nvd3 scatterChart376tmp1 <- data.frame(name="sample", mean=rp1_mean, sd=rp1_StdDev)377tmp2 <- data.frame(name="simplex", mean=rp2_mean, sd=rp2_StdDev)378tmp3 <- data.frame(name="grid", mean=rp3_mean, sd=rp3_StdDev)379tmp <- rbind(tmp1, tmp2, tmp3)380rp_viz <- nPlot(mean ~ sd, group="name", data=tmp, type="scatterChart")381rp_viz$xAxis(382axisLabel = 'Risk (std. dev.)'383,tickFormat = "#!d3.format('0.4f')!#"384)385rp_viz$yAxis(386axisLabel = 'Return'387,tickFormat = "#!d3.format('0.4f')!#"388)389rp_viz$chart(color = my_colors[c(2,4,6)])390#set left margin so y axis label will show up391rp_viz$chart( margin = list(left = 100) )392# rp_viz$chart(393# tooltipContent = "#!394# function(a,b,c,d) {395# //d has all the info you need396# return( '<h3>' + d.point.series + '</h3>Return: ' + d.point.y + '<br>Risk: ' + d.point.x)397# }398# !#")399####if you do not want fisheye/magnify400####let me know, and will show how to remove401####this will solve the tooltip problem402save(rp_viz, file="figures/rp_viz.rda")403###404x.lower <- min(x.assets) * 0.9405x.upper <- max(x.assets) * 1.1406y.lower <- min(y.assets) * 0.9407y.upper <- max(y.assets) * 1.1408409png("figures/rp_plot.png", height = 500, width = 1000)410# plot feasible portfolios411plot(x=rp1_StdDev, y=rp1_mean, col=my_colors[2], main="Random Portfolio Methods",412ylab="mean", xlab="StdDev", xlim=c(x.lower, x.upper),413ylim=c(y.lower, y.upper))414points(x=rp2_StdDev, y=rp2_mean, col=my_colors[4], pch=2)415points(x=rp3_StdDev, y=rp3_mean, col=my_colors[6], pch=5)416points(x=x.assets, y=y.assets)417text(x=x.assets, y=y.assets, labels=colnames(R), pos=4, cex=0.8)418legend("bottomright", legend=c("sample", "simplex", "grid"),419col=my_colors[c(2,4,6)],420pch=c(1, 2, 5), bty="n")421dev.off()422}423424cat("Random portfolio simplex method fev biasing\n")425if(file.exists("figures/fev_plot.png")){426cat("file already exists\n")427} else {428png("figures/fev_plot.png", height = 500, width = 1000)429fev <- 0:5430x.assets <- StdDev(R)431y.assets <- colMeans(R)432par(mfrow=c(2, 3))433for(i in 1:length(fev)){434rp <- rp_simplex(portfolio=portf.lo, permutations=2000, fev=fev[i])435tmp.mean <- apply(rp, 1, function(x) mean(R %*% x))436tmp.StdDev <- apply(rp, 1, function(x) StdDev(R=R, weights=x))437x.lower <- min(c(tmp.StdDev, x.assets)) * 0.85438x.upper <- max(c(tmp.StdDev, x.assets)) * 1.15439y.lower <- min(c(tmp.mean, y.assets)) * 0.85440y.upper <- max(c(tmp.mean, y.assets)) * 1.15441plot(x=tmp.StdDev, y=tmp.mean, main=paste("FEV =", fev[i]),442ylab="mean", xlab="StdDev", col=rgb(0, 0, 100, 50, maxColorValue=255),443xlim=c(x.lower, x.upper),444ylim=c(y.lower, y.upper))445points(x=x.assets, y=y.assets)446text(x=x.assets, y=y.assets, labels=colnames(R), pos=4, cex=0.8)447}448par(mfrow=c(1,1))449dev.off()450}451452# # Calculate the turnover per period453# turnover.rebalancing <- function(object){454# weights <- extractWeights(object)455# n <- nrow(weights)456# out <- vector("numeric", n)457# out[1] <- NA458# for(i in 2:n){459# out[i] <- out[i] <- sum(abs(as.numeric(weights[i,]) - as.numeric(weights[i-1,])))460# }461# xts(out, index(weights))462# }463#464# # Calculate the diversification per period465# diversification.rebalancing <- function(object){466# weights <- extractWeights(object)467# n <- nrow(weights)468# out <- vector("numeric", n)469# for(i in 1:n){470# out[i] <- 1 - sum(weights[i,]^2)471# }472# xts(out, index(weights))473# }474475476