Minimum Investment and Number of Assets Portfolio Cardinality Constraints
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
The Minimum Investment and Number of Assets Portfolio Cardinality Constraints are practical constraints that are not easily incorporated in the standard mean-variance optimization framework. To help us impose these real life constraints, I will introduce extra binary variables and will use mixed binary linear and quadratic programming solvers.
Let’s continue with our discussion from Introduction to Asset Allocation post and examine range of portfolio weights and number of assets for each portfolio on the efficient frontier.
# load Systematic Investor Toolbox setInternet2(TRUE) source(gzcon(url('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', 'rb'))) #-------------------------------------------------------------------------- # Create Efficient Frontier #-------------------------------------------------------------------------- ia = aa.test.create.ia() n = ia$n # x.i >= 0 constraints = new.constraints(diag(n), rep(0, n), type = '>=') # x.i <= 0.8 constraints = add.constraints(diag(n), rep(0.8, n), type = '<=', constraints) # SUM x.i = 1 constraints = add.constraints(rep(1, n), 1, type = '=', constraints) # create efficient frontier(s) ef.risk = portopt(ia, constraints, 50, 'Risk') ef.mad = portopt(ia, constraints, 50, 'MAD', min.mad.portfolio) # Plot range of portfolio weights and number of assets in each portfolio on efficient frontier layout(1:2) par(mar = c(4,4,2,1), cex = 0.8) y = iif(ef.risk$weight > 0.000001, ef.risk$weight, NA) plot(as.vector(sort(100 * y)), pch=20, xaxt='n', ylim = c(0, 80), xlab='', ylab='Weight', main='Portfolio Weights') abline(h=0, col = 'red') abline(h=10, col = 'red') plot(100* ef.risk$risk, rowSums(!is.na(y), na.rm = T), pch=20, type='b', xlab='Risk', ylab='# Assets', main='Number of Assets') # Plot multiple Efficient Frontiers & Transition Maps layout( matrix(1:4, nrow = 2) ) plot.ef(ia, list(ef.risk, ef.mad), portfolio.risk, F) plot.ef(ia, list(ef.risk, ef.mad), portfolio.mad, F) plot.transitopn.map(ef.risk) plot.transitopn.map(ef.mad)
The portfolio weights range from 0% to 80%, and the number of assets range from 2 to 4 assets. First, let’s consider a minimum investment constraint. Suppose that if I decide to allocate to an asset class, I want to allocate at least 10%. This statement can be incorporated as a constraint using binary [0/1] variables:
#-------------------------------------------------------------------------- # Minimum Investment Constraint is 10% # Add binary[0/1] variables # 0.1 * b.i <= x.i <= 0.8 * b.i #-------------------------------------------------------------------------- # SUM x.i = 1 constraints = new.constraints(rep(1, n), 1, type = '=') # New add binary constraint # adjust prior constraints: add b.i constraints$A = rbind( constraints$A, matrix(0, n, ncol(constraints$A)) ) # index of binary variables b.i constraints$binary.index = (n+1):(2*n) # 0.1 * b <= x.i <= 0.8 * b # x.i >= 0.1 * b constraints = add.constraints(rbind(diag(n), -0.1 * diag(n)), rep(0, n), type = '>=', constraints) # x.i <= 0.8 * b constraints = add.constraints(rbind(diag(n), -0.8 * diag(n)), rep(0, n), type = '<=', constraints) # create efficient frontier(s) ef.risk = portopt(ia, constraints, 50, 'Risk') ef.risk$weight = ef.risk$weight[, 1:n] ef.mad = portopt(ia, constraints, 50, 'MAD', min.mad.portfolio) ef.mad$weight = ef.mad$weight[, 1:n]
As expected, the portfolio weights range from 10% to 80%, and there is no allocation less than 10%.
To tackle number of assets constraint, I will also use binary [0/1] variables. Let’s say I want all portfolios on the efficient frontier to have allocation to exactly 3 assets, here are the additional constraints (note that for this example, I assumed that the smallest allocation to any asset class is 0.001%) :
#-------------------------------------------------------------------------- # Limit number of assets to 3 # Add binary[0/1] variables # 0.00001 * b <= x.i <= 0.8 * b # SUM b.i = 3 #-------------------------------------------------------------------------- # SUM x.i = 1 constraints = new.constraints(rep(1, n), 1, type = '=') # New add binary constraint # adjust prior constraints: add b.i constraints$A = rbind( constraints$A, matrix(0, n, ncol(constraints$A)) ) # index of binary variables b.i constraints$binary.index = (n+1):(2*n) # 0.00001 * b <= x.i <= 0.8 * b # x.i >= 0.00001 * b constraints = add.constraints(rbind(diag(n), -0.00001 * diag(n)), rep(0, n), type = '>=', constraints) # x.i <= 0.8 * b constraints = add.constraints(rbind(diag(n), -0.8 * diag(n)), rep(0, n), type = '<=', constraints) # SUM b.i = 3 constraints = add.constraints(c(rep(0,n), rep(1,n)), 3, type = '=', constraints) # create efficient frontier(s) ef.risk = portopt(ia, constraints, 50, 'Risk') ef.risk$weight = ef.risk$weight[, 1:n] ef.mad = portopt(ia, constraints, 50, 'MAD', min.mad.portfolio) ef.mad$weight = ef.mad$weight[, 1:n]
As expected, all portfolios on the efficient frontier have exactly allocation to 3 assets.
Please let me know what other real-life portfolio construction constraints you want me to discuss.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.