Skip to content

VaR/ES with portfolio_method='single' crashes when mu/sigma/m3/m4 are NULL (no auto-computation of moments) #197

Description

@braverock

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

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:

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:

    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():

    # '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:

# 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:

# 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").

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions