Effective Number of Bets of Risk Parity Strategies and Tactical Risk Parity by ENB

This post will aim to explore the effective number of bets (https://kylebalkissoon.wordpress.com/2014/04/15/effective-number-of-bets/) of various risk parity strategies (https://kylebalkissoon.wordpress.com/2014/04/14/risk-parity-using-various-risk-measures-volatility-expected-shortfall-semi-deviation-maximum-draw-down/).

 

Why?

Let’s see if it explains why some strategies outperform and others underperformed. If it is because we are not betting efficiently (ENB<Assets) relative to the other strategies it may indicate that a certain method or type is better or worse than another.

 

So I took the most recent portfolio weights of the previous risk parity strategy and plugged them into ENB and plotted them.

 

Risk Parity ENB 2

 

As you can clearly see vol/semi dev have the highest ENB, if we were to compare with performance:

Performance of risk-parity strategies

This is generally in line with the performance.

 

If you have been following along you’re probably thinking, that’s great that we know which one is the best backwards looking, but what happens if I wanted to rotate my risk metric by which one gave me the greatest ENB at a point in time?

So let’s rerun the previous risk parity experiment and switch to the most diversified one by enb at any point in time.

Professional Note: I would highly advise against doing this in practice as ENB did not exist for all the time (Meucci’s first paper was in 2009 iirc) and the analytical framework that made me choose ENB as a risk metric incorporates my views on the past, which would essentially be cheating. To properly do this you would need a model to select an optimal risk metric out of a potential candidate set.

 

ENB Risk Parity Tactical

 

 

As you can see the technique clearly outperforms. Managing risk and diversifying can be an effective tool in portfolio construction.

 

 

 

 

Code for ENB and evaluation

library(PerformanceAnalytics)
library(quantmod)
 
Effective_Number_of_bets = function(R,w){
  num_assets = ncol(R)
  ##Calculate covariance matrix
  sigma = cov(R)
  ###Calculate eign vectors E and eigenvalues 
  eigen_vectors = eigen(sigma)$vectors
  eigen_values = eigen(sigma)$values
 
  principal_variances = NULL
  for( i in 1:ncol(R)){
    principal_variances[i] = var(as.numeric(as.vector(eigen_vectors[,i])%*%t(R)))
 
  }
  principal_portfolio_return = 1/(eigen_vectors)%*%t(R)
  principal_weights = eigen_vectors^-1%*%w
 
 
  ##Calculated Weighted Ret, Rw in Meucci's slides
  weighted_ret = t(principal_weights)%*%principal_portfolio_return
 
  ##Variance Concentration
  var_concentration = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(var_concentration) = c('assets','variance')
  for( i in 1:num_assets){
    var_concentration[i,]$assets = i
 
    var_concentration[i,]$variance = (weighted_ret[i]^2)*principal_variances[i]
 
  }
 
  var_concentration$variance =  var_concentration$variance/sum( var_concentration$variance)
 
  ##volatility concentration
  volatility_concentration = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(volatility_concentration) = c('assets','volatility')
  for( i in 1:num_assets){
    volatility_concentration[i,]$assets = i
    volatility_concentration[i,]$volatility = var_concentration$variance[i]/sd(weighted_ret) 
 
 
  }
 
  volatility_concentration$volatility = volatility_concentration$volatility/sum(volatility_concentration$volatility)
 
  ##Diversification distribtuion "probability mass"
  diversification_distribution = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(diversification_distribution) = c('assets','mass')
  for( i in 1:num_assets){
    diversification_distribution[i,]$assets = i
 
    diversification_distribution[i,]$mass = var_concentration$variance[i]/var(as.numeric(weighted_ret))
 
 
 
 
  }
  diversification_distribution$mass = diversification_distribution$mass/sum(diversification_distribution$mass)
  ###Effective number of bets
  effective_number_of_bets = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(effective_number_of_bets) = c('assets','enb')
  for( i in 1:num_assets){
    effective_number_of_bets$assets[i] = i
 
    effective_number_of_bets$enb[i] = exp((-1)*sum(diversification_distribution[1:i,2]*log(diversification_distribution[1:i,2])))
 
 
  }
 
 
 
  ans = list(var_concentration,volatility_concentration,diversification_distribution,effective_number_of_bets)
  names(ans) = c('var_con','vol_con','div_dist','enb')
  return(ans)}
 
###Symlist
symbol_list = c('SPY','XLF','XLE','XLU','XLK','XLB','XLP','XLY','XLI','XLV','TLT','GLD')
 
getSymbols(symbol_list, from = '1990-01-01')
 
 
securities_matrix = NULL
for( sym in symbol_list){
  securities_matrix = merge.xts(securities_matrix,ROC(Ad(get(paste(sym))),type='discrete'))
 
 
}
 
##Start in 2005, as GLD has inception of 2004-11
securities_matrix = securities_matrix['2005/2015-01-01']
 
###Risk Metrics
 
 
 
weight_matrix_es = xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_vol= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_sd= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_mdd= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
 
monthly_dates_for_rebal = index(weight_matrix_es[endpoints(weight_matrix_es)])
 
for(i in 253:nrow(securities_matrix)){
 
  info_set = first(securities_matrix,i-1)
  ##Estimate ES
 
  if(index(securities_matrix[i])%in%monthly_dates_for_rebal){
  es_est = ES(info_set)
  es_w = 1/(es_est*ncol(es_est))  
  es_w = es_w/sum(es_w)
  weight_matrix_es[i,] = es_w
 
  ##VOL est
  vol_est = StdDev(info_set)
  vol_w = 1/(vol_est*ncol(vol_est))
  vol_w = vol_w/sum(vol_w)
  weight_matrix_vol[i,] = vol_w
 
  sd_est = SemiDeviation(info_set)
  sd_w = 1/sd_est*ncol(sd_est)
  sd_w = sd_w/sum(sd_w)
  weight_matrix_sd[i,] = sd_w
 
  mdd_est = maxDrawdown(info_set)
  mdd_w = 1/mdd_est*ncol(mdd_est)
  mdd_w = mdd_est/sum(mdd_w)
  weight_matrix_mdd[i,] = mdd_w}
  else{
    weight_matrix_es[i,] = weight_matrix_es[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_es[i,] = weight_matrix_es[i,]/sum(weight_matrix_es[i,])
 
    weight_matrix_vol[i,] = weight_matrix_vol[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_vol[i,] = weight_matrix_vol[i,]/sum(weight_matrix_vol[i,])
 
    weight_matrix_sd[i,] = weight_matrix_sd[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_sd[i,] = weight_matrix_sd[i,]/sum(weight_matrix_sd[i,])
 
 
    weight_matrix_mdd[i,] = weight_matrix_mdd[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_mdd[i,] = weight_matrix_mdd[i,]/sum(weight_matrix_mdd[i,])
 
 
  }
}
 
port_es = xts(rowSums(weight_matrix_es*securities_matrix),order.by=index(securities_matrix))
port_vol = xts(rowSums(weight_matrix_vol*securities_matrix),order.by=index(securities_matrix))
port_sd = xts(rowSums(weight_matrix_sd*securities_matrix),order.by=index(securities_matrix))
port_mdd = xts(rowSums(weight_matrix_mdd*securities_matrix),order.by=index(securities_matrix))
 
my_mat = merge.xts(port_es,port_vol,port_sd,port_mdd,securities_matrix$SPY.Adjusted)
colnames(my_mat) = c('expected shortfall','volatility','semi deviation','max drawdown','sp500')
 
###How efficient are our allocations
 
es_enb = Effective_Number_of_bets(securities_matrix,as.numeric(last(weight_matrix_es)))
vol_enb = Effective_Number_of_bets(securities_matrix,as.numeric(last(weight_matrix_vol)))
sd_enb = Effective_Number_of_bets(securities_matrix,as.numeric(last(weight_matrix_sd)))
mdd_enb = Effective_Number_of_bets(securities_matrix,as.numeric(last(weight_matrix_mdd)))
 
 
matrix_for_step_plots = data.frame(es_enb$enb[,1],es_enb$enb[,2],vol_enb$enb[,2],sd_enb$enb[,2],mdd_enb$enb[,2])
colnames(matrix_for_step_plots) = c('assets','ES','Vol','SemiDev','MaxDD')
 
matplot(x=matrix_for_step_plots$assets,y=matrix_for_step_plots[,2:5],type='s',xlab='Assets',ylab='effective number of bets')
legend('topleft',legend=c('ES','Vol','SemiDev','MaxDD'),col=1:4,pch=1)
plot(matrix_for_step_plots[,1],matrix_for_step_plots[,2:5])

Created by Pretty R at inside-R.org

 

Code for Tactical ENB

library(PerformanceAnalytics)
library(quantmod)
 
Effective_Number_of_bets = function(R,w){
  num_assets = ncol(R)
  ##Calculate covariance matrix
  sigma = cov(R)
  ###Calculate eign vectors E and eigenvalues 
  eigen_vectors = eigen(sigma)$vectors
  eigen_values = eigen(sigma)$values
 
  principal_variances = NULL
  for( i in 1:ncol(R)){
    principal_variances[i] = var(as.numeric(as.vector(eigen_vectors[,i])%*%t(R)))
 
  }
  principal_portfolio_return = 1/(eigen_vectors)%*%t(R)
  principal_weights = eigen_vectors^-1%*%w
 
 
  ##Calculated Weighted Ret, Rw in Meucci's slides
  weighted_ret = t(principal_weights)%*%principal_portfolio_return
 
  ##Variance Concentration
  var_concentration = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(var_concentration) = c('assets','variance')
  for( i in 1:num_assets){
    var_concentration[i,]$assets = i
 
    var_concentration[i,]$variance = (weighted_ret[i]^2)*principal_variances[i]
 
  }
 
  var_concentration$variance =  var_concentration$variance/sum( var_concentration$variance)
 
  ##volatility concentration
  volatility_concentration = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(volatility_concentration) = c('assets','volatility')
  for( i in 1:num_assets){
    volatility_concentration[i,]$assets = i
    volatility_concentration[i,]$volatility = var_concentration$variance[i]/sd(weighted_ret) 
 
 
  }
 
  volatility_concentration$volatility = volatility_concentration$volatility/sum(volatility_concentration$volatility)
 
  ##Diversification distribtuion "probability mass"
  diversification_distribution = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(diversification_distribution) = c('assets','mass')
  for( i in 1:num_assets){
    diversification_distribution[i,]$assets = i
 
    diversification_distribution[i,]$mass = var_concentration$variance[i]/var(as.numeric(weighted_ret))
 
 
 
 
  }
  diversification_distribution$mass = diversification_distribution$mass/sum(diversification_distribution$mass)
  ###Effective number of bets
  effective_number_of_bets = data.frame(matrix(ncol=2,nrow=num_assets))
  colnames(effective_number_of_bets) = c('assets','enb')
  for( i in 1:num_assets){
    effective_number_of_bets$assets[i] = i
 
    effective_number_of_bets$enb[i] = exp((-1)*sum(diversification_distribution[1:i,2]*log(diversification_distribution[1:i,2])))
 
 
  }
 
 
 
  ans = list(var_concentration,volatility_concentration,diversification_distribution,effective_number_of_bets)
  names(ans) = c('var_con','vol_con','div_dist','enb')
  return(ans)}
 
###Symlist
symbol_list = c('SPY','XLF','XLE','XLU','XLK','XLB','XLP','XLY','XLI','XLV','TLT','GLD')
 
getSymbols(symbol_list, from = '1990-01-01')
 
 
securities_matrix = NULL
for( sym in symbol_list){
  securities_matrix = merge.xts(securities_matrix,ROC(Ad(get(paste(sym))),type='discrete'))
 
 
}
 
##Start in 2005, as GLD has inception of 2004-11
securities_matrix = securities_matrix['2005/2015-01-01']
 
###Risk Metrics
 
 
 
weight_matrix_es = xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_vol= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_sd= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_mdd= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
weight_matrix_enb= xts(matrix(nrow=nrow(securities_matrix),ncol=ncol(securities_matrix)),order.by=index(securities_matrix))
enb_ranks= xts(matrix(nrow=nrow(securities_matrix),ncol=4),order.by=index(securities_matrix))
 
monthly_dates_for_rebal = index(weight_matrix_es[endpoints(weight_matrix_es)])
 
for(i in 253:nrow(securities_matrix)){
 
  info_set = last(first(securities_matrix,i-1),252)
  ##Estimate ES
 
  if(index(securities_matrix[i])%in%monthly_dates_for_rebal){
  es_est = ES(info_set)
  es_w = 1/(es_est*ncol(es_est))  
  es_w = es_w/sum(es_w)
  weight_matrix_es[i,] = es_w
 
  ##VOL est
  vol_est = StdDev(info_set)
  vol_w = 1/(vol_est*ncol(vol_est))
  vol_w = vol_w/sum(vol_w)
  weight_matrix_vol[i,] = vol_w
 
  sd_est = SemiDeviation(info_set)
  sd_w = 1/sd_est*ncol(sd_est)
  sd_w = sd_w/sum(sd_w)
  weight_matrix_sd[i,] = sd_w
 
  mdd_est = maxDrawdown(info_set)
  mdd_w = 1/mdd_est*ncol(mdd_est)
  mdd_w = mdd_est/sum(mdd_w)
  weight_matrix_mdd[i,] = mdd_w
 
  ##Apply ENB to rank the strategies
  ##Calculate enb
  enb_es = Effective_Number_of_bets(info_set,as.numeric(es_w))$enb$enb[12]
  enb_vol = Effective_Number_of_bets(info_set,as.numeric(vol_w))$enb$enb[12]
  enb_sd = Effective_Number_of_bets(info_set,as.numeric(sd_w))$enb$enb[12]
  enb_mdd = Effective_Number_of_bets(info_set,as.numeric(mdd_w))$enb$enb[12]
  enb_vec = c(enb_es,enb_vol,enb_sd,enb_mdd)
  ##Who is the best?
  best_enb = which.max(enb_vec)
  enb_ranks[i,] = enb_vec
  weight_matrix_enb[i,] = get(c('es_w','vol_w','sd_w','mdd_w')[best_enb])
 
  }
  else{
    weight_matrix_es[i,] = weight_matrix_es[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_es[i,] = weight_matrix_es[i,]/sum(weight_matrix_es[i,])
 
    weight_matrix_vol[i,] = weight_matrix_vol[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_vol[i,] = weight_matrix_vol[i,]/sum(weight_matrix_vol[i,])
 
    weight_matrix_sd[i,] = weight_matrix_sd[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_sd[i,] = weight_matrix_sd[i,]/sum(weight_matrix_sd[i,])
 
 
    weight_matrix_mdd[i,] = weight_matrix_mdd[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_mdd[i,] = weight_matrix_mdd[i,]/sum(weight_matrix_mdd[i,])
 
    weight_matrix_enb[i,] = weight_matrix_enb[i-1,]*(1+securities_matrix[i-1,])
    weight_matrix_enb[i,] = weight_matrix_enb[i,]/sum(weight_matrix_enb[i,])
  }
}
 
port_es = xts(rowSums(weight_matrix_es*securities_matrix),order.by=index(securities_matrix))
port_vol = xts(rowSums(weight_matrix_vol*securities_matrix),order.by=index(securities_matrix))
port_sd = xts(rowSums(weight_matrix_sd*securities_matrix),order.by=index(securities_matrix))
port_mdd = xts(rowSums(weight_matrix_mdd*securities_matrix),order.by=index(securities_matrix))
port_enb = xts(rowSums(weight_matrix_enb*securities_matrix),order.by=index(securities_matrix))
 
my_mat = merge.xts(port_es,port_vol,port_sd,port_mdd,port_enb,securities_matrix$SPY.Adjusted)
colnames(my_mat) = c('expected shortfall','volatility','semi deviation','max drawdown','enb','sp500')
chart.CumReturns(my_mat,legend.loc='topleft',wealth.index=TRUE,main='Performance of various risk parity strategies')

Created by Pretty R at inside-R.org

 

Advertisements

One comment

  1. Thanks for sharing this codes. I was searching for this since a long time. Thanks for sharing.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: