- Discuss Portfolio Optimization
- Introduce PortfolioAnalytics
- Demonstrate PortfolioAnalytics with Examples
May 19, 2017
"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.
Common 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.
Supports:
p <- portfolio.spec(assets = c('AAPL', 'MSFT', 'GOOG')) print(p)
## ************************************************** ## PortfolioAnalytics Portfolio Specification ## ************************************************** ## ## Call: ## portfolio.spec(assets = c("AAPL", "MSFT", "GOOG")) ## ## Number of assets: 3 ## Asset Names ## [1] "AAPL" "MSFT" "GOOG"
Initializes the portfolio object that holds portfolio level data, constraints, and objectives
# weights sum to 1 p <- add.constraint(portfolio = p, type = 'weight_sum', min_sum = 1, max_sum = 1) # special type for full investment constraint # add.constraint(portfolio = p, type = 'full_investment') # box constraints p <- add.constraint(portfolio = p, type = 'box', min = 0.2, max = 0.6)
Supported Constraint Types
p <- add.objective(portfolio = p, type = 'risk', name = 'StdDev')
Supported Objective types
opt <- optimize.portfolio(R, portfolio=p, optimize_method='random', search_size=2000) opt.rebal <- optimize.portfolio.rebalancing(R, portfolio=p, optimize_method='random', search_size=2000, rebalance_on='quarters', training_period=60, rolling_window=60)
Visualization | Data Extraction |
---|---|
plot | extractObjectiveMeasures |
chart.Concentration | extractStats |
chart.EfficientFrontier | extractWeights |
chart.RiskReward | |
chart.RiskBudget | summary |
chart.Weights |
Linear and Quadratic Programming Solvers
Global Solvers
PortfolioAnalytics has three methods to generate random portfolios.
gridSearch
function in the NMOF package.n
regimesn
portfolio specifications, 1 for each regimeLedoit and Wolf (2003):
"The central message of this paper is that nobody should be using the sample covariance matrix for the purpose of portfolio optimization."
Consider a long only U.S. Equity portfolio allocation problem. Goal is to outperform the S&P 500 (SPY as a proxy).
Data
Approach
Baseline portfolio specification to minimize portfolio standard deviation, subject to full investment and long only constraints.
# base portfolio specification rp.seq <- generatesequence(min = 0, max = 1, by = 0.002) p <- portfolio.spec(assets = colnames(R), weight_seq = rp.seq) p <- add.constraint(portfolio = p, type = 'weight_sum', min_sum = 0.99, max_sum = 1.01) p <- add.constraint(portfolio = p, type = 'box', min = 0, max = 1) p <- add.objective(portfolio = p, type = 'return', name = 'mean', multiplier = 0) p <- add.objective(portfolio = p, type = 'risk', name = 'StdDev')
Add box constraints such that for each asset the minimum weight is 5% and the maximum weight is 20%.
# portfolio specification with box constraints p.box <- p p.box <- add.constraint(portfolio = p.box, type = 'box', min = 0.05, max = 0.20, indexnum = 2)
Run a single period optimization for the portfolio specifications
# generate the random portfolios rp <- random_portfolios(portfolio = p, permutations = rp.n, method = 'sample') rp <- normalize.weights(rp) rp.box <- random_portfolios(portfolio = p.box, permutations = rp.n, method = 'sample') rp.box <- normalize.weights(rp.box) # run the optimizations opt.base <- optimize.portfolio(R = tail(R, 36), portfolio = p, rp = rp, optimize_method = 'random', trace = TRUE) opt.box <- optimize.portfolio(R = tail(R, 36), portfolio = p.box, rp = rp.box, optimize_method = 'random', trace = TRUE)
Impact of box constraints on the feasible space of the in sample period.
opt.base.rebal <- optimize.portfolio.rebalancing(R = R, portfolio = p, optimize_method = 'random', rp = rp, trace = TRUE, rebalance_on = 'quarters', training_period = 36, rolling_window = 36) opt.base.rebal.r <- Return.portfolio(R, weights = extractWeights(opt.base.rebal)) colnames(opt.base.rebal.r) <- 'base' opt.box.rebal <- optimize.portfolio.rebalancing(R = R, portfolio = p.box, optimize_method = 'random', rp = rp.box, trace = TRUE, rebalance_on = 'quarters', training_period = 36, rolling_window = 36) opt.box.rebal.r <- Return.portfolio(R, weights = extractWeights(opt.box.rebal)) colnames(opt.box.rebal.r) <- 'box'
chart.Weights(opt.base.rebal, main = 'Baseline Portfolio Optimal Weights')
chart.Weights(opt.box.rebal, main = 'Box Constrained Portfolio Optimal Weights')
opt.r <- cbind(opt.base.rebal.r, opt.box.rebal.r) charts.PerformanceSummary(opt.r, main = "Performance Summary")
Define a custom objective function for penalized tracking error
te.target <- function(R, weights, Rb, min.te = 0.02, max.te = 0.05, scale = 12){ r <- Return.portfolio(R = R, weights = weights) Rb <- Rb[index(r)] te <- sd(r - Rb) * sqrt(scale) # penalize tracking error outside of [min.te, max.te] range out <- 0 if(te > max.te) out <- (te - max.te) * 10000 if(te < min.te) out <- (min.te - te) * 10000 out }
te.target
as a named listp.te <- p p.te <- add.objective(portfolio = p, type = 'risk', name = 'te.target', arguments = list(Rb = R.mkt, scale = 12, min.te = 0.03, max.te = 0.05)) opt.te.rebal <- optimize.portfolio.rebalancing(R = R, portfolio = p.te, optimize_method = 'random', rp = rp, trace = TRUE, rebalance_on = rebal.period, training_period = n.train, rolling_window = n.roll) opt.te.rebal.r <- Return.portfolio(R, weights = extractWeights(opt.te.rebal)) colnames(opt.te.rebal.r) <- 'te.target'
opt.r <- na.omit(cbind(opt.base.rebal.r, opt.box.rebal.r, opt.te.rebal.r, R.mkt)) charts.PerformanceSummary(opt.r, main = "Performance Summary")
Simulate portfolio returns using the random portfolios generated from the baseline portfolio specification and box constrained portfolio specification.
sim.base <- simulate.portfolio(R, rp = rp, simulations = 5000, rebalance_on = 'quarters') sim.box <- simulate.portfolio(R, rp = rp.box, simulations = 5000, rebalance_on = 'quarters') sim <- cbind(sim.box, sim.base) charts.PerformanceSummary(sim, colorset = c(rep("salmon", ncol(sim.box)), rep('gray', ncol(sim.base))), legend.loc = NULL, main = "Simulated Performance Summary") legend('topleft', legend = c('constrained', 'unconstrained'), fill = c('salmon', 'gray'), bty = 'n')
Source code for the slides
and view it here
DataCamp Course