Why Portfolio Expected Shortfall Can Differ Across Calculation Modes
Summary
The document investigates why expected shortfall from the PerformanceAnalytics R package can differ when calculated on a portfolio return series versus component returns and weights. The accepted explanation points to inconsistent volatility normalization in the single-portfolio path: sample standard deviation and covariance use a divisor based on one fewer observation, while the higher-moment routine uses the number of observations. That difference changes the Gaussian expected shortfall estimate.
The answer compares component-based results with direct calculations from portfolio mean and volatility, and shows that rescaling volatility by the square root of the ratio between those observation counts reproduces the single-series result. It notes that the discrepancy is more visible in small samples and diminishes as the return history grows. The discussion focuses on the implementation behavior described for the tested package code; it does not establish whether later versions corrected the issue or how every method behaves.
Key ideas
- Expected shortfall estimates can differ depending on whether component or single-portfolio calculations are used.
- The explanation attributes the Gaussian discrepancy to different sample-size divisors in volatility calculations.
- Covariance and standard deviation use a divisor of one fewer observation, while the higher-moment routine uses the full count.
- The resulting mismatch is more pronounced with short return histories.
- The described rescaling aligns the single-series estimate with the component-based calculation in the example.
Tags
Full text
# Portfolio risk budgeting using CVaR function from PerformanceAnalytics in R
# Portfolio risk budgeting using CVaR function from PerformanceAnalytics in R
I have been looking at `CVaR` function in the `PerformanceAnalytics` package as an option to use in portfolio optimization. However, I cannot figure out the reason behind discrepancy that the function is giving for my test portfolio.
Here is a reproducable example:
```
library(PerformanceAnalytics)
set.seed(1234)
a = rnorm(n = 10, mean = 0, sd = 0.02)
b = rnorm(n = 10, mean = 0, sd = 0.03)
wght = c(0.4, 0.6) ## assign weights
dt = cbind(a, b)
startDate = as.Date("2016-01-01")
h = seq(1:10)
myDates = startDate + h
dt = as.data.frame(dt) ## dataframe of single assets
rownames(dt) = myDates
tmp = sweep(dt, 2, wght, `*`)
P = as.data.frame(rowSums(tmp)) ## resultant portfolio
colnames(P) = "Portfolio"
```
My issue arises here:
```
MES1 = ES(dt, p = 0.95, method = "modified", portfolio_method = "component",
weights = wght)
MES1$MES[1]
MES2 = ES(P, p = 0.95, method = "modified", portfolio_method = "component",
weights = 1)
MES2$MES[1]
```
I get the same results of `CVaR` for the portfolio in both cases. However, when I do not mention `portfolio_method = "component"` and `weights = 1` should I not get the same result?
```
ES(P, p = 0.95, method = "modified")
```
Here the function returns completely different result. I am currently inspecting the code on the authors' page, but would also like to know the views of those who went through this.
Some background information regarding the methodology and implementation can be found here and here.
## Answer by WaltS (score 1, accepted)
https://quant.stackexchange.com/a/34721
It looks like there's a bit of a problem with the code when the option `portfolio_method` = "single" is used. In the code below, your three calculations with `ES` are repeated using `method`="gaussian" and then compared with expected shortfall calcuated directly from the expression using mu and sigma. I've done the direct calculation with `dt` using the `cov()` function and with `P` using the `sd()` function. The resuls of all calcuations should be identical. Similar to your case, all agree except the `ES(P, portfolio_method = "single")` calculation.
Looking into the `ES` code, the different portfolio_method options use different code to calculate sigma and higher moments. In particular, for gaussian calculations, `ES` calculates sigma using `cov()` for `portfolio_method = "component"` and a general higher moment calculation function `centeredmoment()` for `portfolio_method = "single".` The problem is that for `n` = number of rows in the return vector, `cov()` and `sd()` normalize the moment calculations using `1/(n-1)` while the calculations using `centeredmoment()` use `1/n`. I've demonstrated this in the code below where the calculation for `ES_P_modied_sigma` rescales sigma to use the `1/n` normalization and gets the same result as `ES(P, portfolio_method ="single")` for the Gausian distributions.
The discrepancy is more apparent in your test calculation since you're using a small number of rows. As the number of rows increases, the descrepancy becomes much smaller. Nevertheless, it's something that should be corrected.
```
#
# repeat ES calculations using Gaussian distributions
#
MES1g = ES(dt, p = 0.95, method = "gaussian", portfolio_method = "component",
weights = wght)
MES2g = ES(P, p = 0.95, method = "gaussian", portfolio_method = "component",
weights = 1)
MES3g <- ES(P, p = 0.95, method = "gaussian")
#
# calculate Gaussian shortfalls directly using dt and wght and then P
#
prob <- .05
mean_dt <- apply(dt,2,mean)%*%wght
sigma_dt <- sqrt(t(wght)%*%cov(dt)%*%wght)
ES_dt <- -mean_dt + dnorm(qnorm(prob))*sigma_dt/prob
mean_P <- mean(P[,1])
sigma_P <- sd(P[,1])
ES_P <- -mean_P + dnorm(qnorm(prob))*sigma_P/prob
#
# redo calculation with P using sigma scaled by sqrt((nrow(P) - 1)/nrow(P))
#
ES_P_modied_sigma <- -mean_P + dnorm(qnorm(prob))*sigma_P/prob*(sqrt((nrow(P)-1)/nrow(P)))
#
# compare all results
# note that result ES_P_modified_sigma reproduces MES3g showing problem with normalization for sigma
#
c(MES1g$ES, MES2g$ES, MES3g)
c(ES_dt, ES_P, ES_P_modied_sigma)
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.