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.

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

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.

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