PeerPerformance evaluates the performance of investment
funds relative to their peers, using a Storey-type adjustment
for expected false positives. 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 have equal underlying performance; the Storey-type correction estimates and adjusts for that mass.
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.46The 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 3The ratios are point estimates; confint() attaches
peer-bootstrap intervals that resample each fund’s peers while holding
the pairwise statistics and selected \(\lambda\) fixed. They do not include
return-sampling or \(\lambda\)-selection uncertainty or provide
nominal coverage for the underlying population ratios:
set.seed(1234)
round(confint(sc, parm = "pipos")[1:6, ], 3)
#> 2.5% 97.5%
#> Fund 1 0 0.141
#> Fund 2 0 0.000
#> Fund 3 0 0.387
#> Fund 4 0 0.370
#> Fund 5 0 0.821
#> Fund 6 0 0.214The 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):
The Y argument (or the convenience wrapper
targetPeerPerformance()) screens a chosen fund, or subset
of funds, against a separate universe.
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.
Bootstrap blocks are formed after missing rows are removed, so
bBoot counts retained concordant observations rather than
calendar periods. 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.