Luck-Corrected Peer Performance Analysis with PeerPerformance

knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7,
                      fig.height = 3.4)

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)

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]

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)

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)

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)
round(msharpe(rets[, 1:6], level = 0.95), 3)

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)

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)

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



Try the PeerPerformance package in your browser

Any scripts or data that you put into this service are public.

PeerPerformance documentation built on Aug. 3, 2026, 1:08 a.m.