The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Luck-Corrected Peer Performance Analysis with PeerPerformance

David Ardia, Murilo Andre Peres Pereira, Benjamin Seguin

Overview

PeerPerformance evaluates the performance of investment funds relative to their peers, with a correction for luck that is robust to false discoveries. For each fund \(i\) it estimates three peer performance ratios that sum to one:

The estimators implement the methodology of Ardia and Boudt (2018, Journal of Banking & Finance); the modified Sharpe ratio test follows Ardia and Boudt (2015, Finance Research Letters). A naive percentile rank ignores that many funds may be statistically indistinguishable; the false-discovery-rate (FDR) correction of Storey (2002) corrects for this.

library(PeerPerformance)
data("hfdata")          # 60 monthly returns for 100 anonymized hedge funds
dim(hfdata)
#> [1]  60 100

Alpha peer performance screening

alphaScreening() runs all pairwise tests and returns the three ratios for each fund. With factors = NULL it compares raw returns; supply a factors matrix to compare risk-adjusted alphas.

set.seed(1234)
rets <- hfdata[, 1:15]
sc <- alphaScreening(rets, control = list(nCore = 1))
round(rbind(pipos = sc$pipos, pizero = sc$pizero, pineg = sc$pineg), 3)[, 1:6]
#>         [,1] [,2]  [,3] [,4] [,5] [,6]
#> pipos  0.000    0 0.025    0 0.46 0.00
#> pizero 0.975    1 0.975    1 0.54 0.54
#> pineg  0.025    0 0.000    0 0.00 0.46

The three ratios sum to one for every fund. A summary() method ranks the funds and reports the win/loss counts:

summary(sc, top = 3)
#> 
#> Peer performance screening summary (alpha)
#>   Funds: 15
#> 
#>   alpha distribution:
#>     min -0.0060 | 25% -0.0002 | med 0.0022
#>     mean 0.0021 | 75% 0.0035 | max 0.0124
#> 
#> Top funds (by alpha):
#>     fund  n npeer estimate lambda pizero  pipos  pineg wins losses net rank
#>  Fund 12 60    14   0.0124 0.7000 0.0000 1.0000 0.0000    2      0   2    1
#>  Fund 13 60    14   0.0080 0.7000 0.0000 0.9286 0.0714    0      0   0    2
#>  Fund 15 60    14   0.0040 0.6000 0.3573 0.6427 0.0000    1      0   1    3
#> 
#> Top funds (by outperformance ratio pi+):
#>     fund  n npeer estimate lambda pizero  pipos  pineg wins losses net rank
#>  Fund 12 60    14   0.0124 0.7000 0.0000 1.0000 0.0000    2      0   2    1
#>  Fund 13 60    14   0.0080 0.7000 0.0000 0.9286 0.0714    0      0   0    2
#>  Fund 15 60    14   0.0040 0.6000 0.3573 0.6427 0.0000    1      0   1    3

The ratios are point estimates; confint() attaches confidence intervals by a nonparametric peer (pairwise) bootstrap that resamples each fund’s peers:

set.seed(1234)
round(confint(sc, parm = "pipos")[1:6, ], 3)
#>        2.5% 97.5%
#> Fund 1    0 0.140
#> Fund 2    0 0.000
#> Fund 3    0 0.386
#> Fund 4    0 0.369
#> Fund 5    0 0.821
#> Fund 6    0 0.214

The plot() method reproduces the Ardia and Boudt (2018) screening plot (funds sorted by performance; stacked out-/equal-/under-performance bars with the naive percentile-rank diagonal):

plot(alphaScreening(hfdata[, 1:30], control = list(nCore = 1)))

Sharpe and modified Sharpe ratios

round(sharpe(rets[, 1:6]), 3)
#> Fund 1 Fund 2 Fund 3 Fund 4 Fund 5 Fund 6 
#>  0.020 -0.023  0.109  0.111  0.210 -0.010
round(msharpe(rets[, 1:6], level = 0.95), 3)
#> Fund 1 Fund 2 Fund 3 Fund 4 Fund 5 Fund 6 
#>  0.011 -0.014  0.061  0.072  0.120 -0.005

Equality of two funds’ (modified) Sharpe ratios is tested with sharpeTesting() / msharpeTesting(). The asymptotic test is the default; a studentized bootstrap (recommended for short or autocorrelated series) is requested with control = list(type = 2).

res <- msharpeTesting(hfdata[, 1], hfdata[, 2], level = 0.95)
c(dmsharpe = res$dmsharpe, tstat = res$tstat, pval = res$pval)
#>   dmsharpe      tstat       pval 
#> 0.02482295 0.46138131 0.64452506

sharpeScreening() and msharpeScreening() build the peer performance ratios from Sharpe (resp. modified Sharpe) comparisons instead of alphas.

Comparing a fund (or group) against a separate peer group

The Y argument (or the convenience wrapper targetPeerPerformance()) screens a chosen fund, or subset of funds, against a separate universe.

focal <- hfdata[, 1]
peers <- hfdata[, 11:40]
res_xy <- alphaScreening(focal, Y = peers, control = list(nCore = 1))
round(c(pizero = res_xy$pizero, pipos = res_xy$pipos, pineg = res_xy$pineg), 3)
#> pizero  pipos  pineg 
#>  0.222  0.011  0.767

Control parameters

All screening/testing functions share a control list. Common fields include nCore (parallel cores), lambda (the \(\lambda\) threshold for \(\hat\pi^0\), NULL = data-driven), gammaPos/gammaNeg (the one-sided thresholds, default 0.4/0.6), hac (HAC standard errors), and for the Sharpe/mSR routines type (asymptotic/bootstrap), nBoot, and bBoot. See ?alphaScreening.

Note that with the data-driven lambda (the default) the bias correction of \(\hat\pi^0\) dominates the run time on large universes. Setting control = list(fastAdjust = TRUE) inverts it in vectorised form and is several times faster, at the price of differing from the published code path by a few \(10^{-5}\); and setting a fixed lambda skips the selection entirely.

References

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.