braverock / braverock/PerformanceAnalytics
VaR/ES with portfolio_method='single' crashes when mu/sigma/m3/m4 are NULL (no auto-computation of moments)
- Dominant language
- R
- Stars
- 239
- Forks
- 104
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
`VaR()` and `ES()` crash with an unhelpful error when called with `portfolio_method='single'` and either `method='modified'` or `method='gaussian'` if the caller does not explicitly supply moment parameters (`mu`, `sigma`, `m3`, `m4`). The multi-asset branches of the same functions auto-compute missing moments from `R`, but the `single` portfolio branch does not.
## Affected versions
Reproduced with PerformanceAnalytics 2.0.9 (current CRAN).
## Steps to reproduce
```r
library(PerformanceAnalytics)
data(edhec)
R <- edhec[, 1:4]
w <- rep(0.25, 4)
# Crash 1: method='modified', moments not supplied
VaR(R = R, weights = w, portfolio_method = "single", method = "modified", invert = FALSE)
# Error in skewness.MM(w, sigma, M3) : M3 must be a matrix
# Crash 2: method='gaussian', moments not supplied
VaR(R = R, weights = w, portfolio_method = "single", method = "gaussian", invert = FALSE)
# Error in t(w) %*% mu : requires numeric/complex matrix/vector arguments
# Same crashes with ES():
ES(R = R, weights = w, portfolio_method = "single", method = "modified", invert = FALSE)
# Error in skewness.MM(w, sigma, M3) : M3 must be a matrix
ES(R = R, weights = w, portfolio_method = "single", method = "gaussian", invert = FALSE)
# Error in t(w) %*% mu : requires numeric/complex matrix/vector arguments
```
Workaround — supply moments explicitly:
```r
VaR(R = R, weights = w, portfolio_method = "single", method = "modified",
mu = apply(R, 2, mean),
sigma = cov(R),
m3 = M3.MM(R, as.mat = FALSE),
m4 = M4.MM(R, as.mat = FALSE),
invert = FALSE)
# [1] 0.02442032 <-- works
```
## Root cause
Inside `VaR()` / `ES()`, the `switch(portfolio_method, ...)` has two main branches:
1. **Multi-asset branch** (`portfolio_method != "single"`) — explicitly auto-computes missing moments before use:
```r
if (is.null(m3)) m3 = M3.MM(R, as.mat = FALSE)
if (is.null(m4)) m4 = M4.MM(R, as.mat = FALSE)
```
2. **Single-portfolio branch** (`portfolio_method == "single"`) — does **not** auto-compute moments, yet still passes `mu`, `sigma`, `m3`, `m4` (which remain `NULL`) to `mVaR.MM()` / `GVaR.MM()`:
```r
# 'single' branch, method='modified':
rVaR = mVaR.MM(w = weights, mu = mu, sigma = sigma, M3 = m3, M4 = m4, p = p)
# mu=NULL, sigma=NULL, m3=NULL, m4=NULL --> skewness.MM(w, NULL, NULL)
# --> M3.mat2vec(NULL) --> "M3 must be a matrix"
# 'single' branch, method='gaussian':
rVaR = GVaR.MM(w = weights, mu = mu, sigma = sigma, p = p)
# mu=NULL --> multivariate_mean(w, NULL) = t(w) %*% NULL
# --> "requires numeric/complex matrix/vector arguments"
```
## Downstream impact in PortfolioAnalytics
This bug surfaces whenever `applyFUN()` (in **PortfolioAnalytics**) calls a function like `SharpeRatio()` that internally invokes `VaR()` or `ES()` with `portfolio_method='single'` but without passing explicit moments. `SharpeRatio` has no `m3`/`m4` formals, so even if `applyFUN` computes higher-order moments and puts them in `nargs`, `pmatch()` cannot match them to `SharpeRatio`'s formals, and they never reach the inner `VaR`/`ES` call:
```r
# This is what chart.Concentration does when risk.col/return.col don't match extractStats:
applyFUN(R = R, weights = wts, FUN = "SharpeRatio", arguments = NULL)
# -> SharpeRatio(R, weights=...) -> VaR(R, weights=..., portfolio_method='single')
# -> crash: "M3 must be a matrix"
```
## Proposed fix
In the `portfolio_method == "single"` branch of both `VaR()` and `ES()`, add the same moment-auto-computation guard that already exists in the multi-asset branch:
```r
# Before the switch(method, ...) block in the single+weights path:
if (is.null(mu)) mu <- apply(R, 2, mean)
if (is.null(sigma)) sigma <- cov(R)
if (is.null(m3)) m3 <- M3.MM(R, as.mat = FALSE)
if (is.null(m4)) m4 <- M4.MM(R, as.mat = FALSE)
```
This mirrors the existing behavior in the multi-asset branch and makes the API consistent: callers should not need to pre-compute moments just to call a single-portfolio VaR/ES.
## Additional note: `SharpeRatio` cannot accept m3/m4
Even if PortfolioAnalytics passes pre-computed moments via `applyFUN`, `SharpeRatio()` has no `m3`/`m4` formals and no documented way to forward them to its internal `VaR`/`ES` calls. Once the above fix is applied to `VaR`/`ES`, this secondary issue becomes moot, but it would still be good to accept (and pass through) `m3`/`m4` in the `SharpeRatio` `...` argument when `FUN %in% c("VaR", "ES")`.
Contributor guide
Assessment
This issue has not been assessed yet.