library(PortfolioAnalytics) require(methods)
"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.load("figures/rp_viz.rda") rp_viz$show('inline')
args(portfolio.spec)
Initializes the portfolio object that holds portfolio level data, constraints, and objectives
args(add.constraint)
Supported Constraint Types
args(add.objective)
Supported Objective types
args(optimize.portfolio) args(optimize.portfolio.rebalancing)
Visualization | Data Extraction ------------- | ---------- plot | extractObjectiveMeasures chart.Concentration | extractStats chart.EfficientFrontier | extractWeights chart.RiskReward | print 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)
chart.RiskBudget(opt.minES[[2]], main="Risk Budget Limit", risk.type="percentage", neighbors=10) chart.RiskBudget(opt.minES[[3]], main="Equal ES Component Contribution", risk.type="percentage", neighbors=10)
# 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)
--- &twocol
load("figures/bt_w3.rda") load("figures/bt_rb3.rda")
*** =left
bt_rb3$set( height = 400 ,width = 450 ) bt_rb3$chart( stacked=T ) bt_rb3$setLib("nvd3") bt_rb3$setTemplate(afterScript="<script></script>") bt_rb3$show('inline')
*** =right
bt_w3$set( height = 400 ,width = 450 ) bt_w3$chart( stacked=T ) bt_w3$setLib("nvd3") bt_w3$setTemplate(afterScript="<script></script>") bt_w3$show('inline')
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")
load("optimization_results/opt.crra.rda")
head(extractStats(opt.crra),4)
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
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.