├── .DS_Store ├── R ├── .DS_Store ├── hfdata.R ├── sharpeBlockSize.R ├── msharpeBlockSize.R ├── PeerPerformance.R ├── alphaTesting.R ├── computePi.R ├── functions.R ├── sharpeScreening.R ├── msharpeScreening.R ├── alphaScreening.R ├── sharpeTesting.R └── msharpeTesting.R ├── data ├── .DS_Store └── hfdata.rdata ├── man ├── .DS_Store ├── hfdata.Rd ├── sharpe.Rd ├── msharpe.Rd ├── alphaTesting.Rd ├── PeerPerformance.Rd ├── sharpeTesting.Rd ├── msharpeTesting.Rd ├── sharpeScreening.Rd ├── msharpeScreening.Rd └── alphaScreening.Rd ├── tests ├── testthat.R └── testthat │ ├── test_testing.R │ └── test_screening.R ├── CRAN-SUBMISSION ├── .Rbuildignore ├── cran-comments.md ├── inst ├── COPYRIGHTS └── CITATION ├── PeerPerformance.Rproj ├── NAMESPACE ├── DESCRIPTION ├── .gitignore ├── NEWS.md ├── README.md └── precran.R /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdiaD/PeerPerformance/HEAD/.DS_Store -------------------------------------------------------------------------------- /R/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdiaD/PeerPerformance/HEAD/R/.DS_Store -------------------------------------------------------------------------------- /data/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdiaD/PeerPerformance/HEAD/data/.DS_Store -------------------------------------------------------------------------------- /man/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdiaD/PeerPerformance/HEAD/man/.DS_Store -------------------------------------------------------------------------------- /data/hfdata.rdata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArdiaD/PeerPerformance/HEAD/data/hfdata.rdata -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library("testthat") 2 | library("PeerPerformance") 3 | 4 | #test_check("PeerPerformance") 5 | -------------------------------------------------------------------------------- /CRAN-SUBMISSION: -------------------------------------------------------------------------------- 1 | Version: 2.3.2 2 | Date: 2025-08-18 15:49:12 UTC 3 | SHA: 3490bb1b9ffe5bc19830b2c6e71796cb7064eb0a 4 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^renv$ 2 | ^renv\.lock$ 3 | ^.*\.Rproj$ 4 | ^\.Rproj\.user$ 5 | ^README\.md$ 6 | ^Contributing.md$ 7 | ^\.github$ 8 | ^CRAN-SUBMISSION$ 9 | ^cran-comments\.md$ 10 | ^precran\.R$ 11 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * Checked on local macOS Sequoia 15.6 install, R version 4.5.1 (2025-06-13) 3 | * Checked on Windows (on win-builder) R devel and R release 4 | 5 | ## R CMD check results 6 | 0 errors | 0 warnings | 0 notes 7 | -------------------------------------------------------------------------------- /inst/COPYRIGHTS: -------------------------------------------------------------------------------- 1 | COPYRIGHT STATUS 2 | ---------------- 3 | 4 | This code is 5 | 6 | Copyright (C) 2012-2023 David Ardia and Kris Boudt 7 | 8 | All code is subject to the GNU General Public License, Version 2. See 9 | the file COPYING for the exact conditions under which you may redistribute it. -------------------------------------------------------------------------------- /R/hfdata.R: -------------------------------------------------------------------------------- 1 | #' @name hfdata 2 | #' @title Hedge fund data 3 | #' @description The matrix \code{hfdata} contains randomized and modified monthly returns of hedge funds. 4 | #' @source HFR database 5 | #' @docType data 6 | #' @keywords datasets 7 | #' @usage data(hfdata) 8 | #' @format A matrix of size 60 x 100, containing 60 monthly net returns for 100 hedge funds. Returns have been modified. 9 | NULL -------------------------------------------------------------------------------- /PeerPerformance.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(alphaScreening) 4 | export(alphaTesting) 5 | export(msharpe) 6 | export(msharpeScreening) 7 | export(msharpeTesting) 8 | export(sharpe) 9 | export(sharpeScreening) 10 | export(sharpeTesting) 11 | import(compiler) 12 | import(lmtest) 13 | import(sandwich) 14 | importFrom(compiler,cmpfun) 15 | importFrom(parallel,clusterApplyLB) 16 | importFrom(parallel,makeCluster) 17 | importFrom(parallel,stopCluster) 18 | importFrom(stats,qnorm) 19 | -------------------------------------------------------------------------------- /man/hfdata.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hfdata.R 3 | \docType{data} 4 | \name{hfdata} 5 | \alias{hfdata} 6 | \title{Hedge fund data} 7 | \format{ 8 | A matrix of size 60 x 100, containing 60 monthly net returns for 100 hedge funds. Returns have been modified. 9 | } 10 | \source{ 11 | HFR database 12 | } 13 | \usage{ 14 | data(hfdata) 15 | } 16 | \description{ 17 | The matrix \code{hfdata} contains randomized and modified monthly returns of hedge funds. 18 | } 19 | \keyword{datasets} 20 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: PeerPerformance 2 | Version: 2.3.2 3 | Title: Luck-Corrected Peer Performance Analysis in R 4 | Authors@R: c(person("David", "Ardia", role = c("aut", "cre"), 5 | email = "david.ardia.ch@gmail.com", 6 | comment = c(ORCID = "0000-0003-2823-782X")), 7 | person("Kris", "Boudt", role = "aut", 8 | comment = c(ORCID = "0000-0002-1000-5142")), 9 | person("Nabil", "Bouamara", role = "ctb"), 10 | person("Sebastien", "Legros", role = "ctb"), 11 | person("Benjamin", "Seguin", role = "ctb")) 12 | Depends: parallel, sandwich, lmtest, compiler 13 | Suggests: testthat 14 | Description: Provides functions to perform the peer performance 15 | analysis of funds' returns as described in Ardia and Boudt (2018) . 16 | BugReports: https://github.com/ArdiaD/PeerPerformance/issues 17 | URL: https://github.com/ArdiaD/PeerPerformance 18 | License: GPL (>= 2) 19 | RoxygenNote: 7.3.2 20 | Encoding: UTF-8 21 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite 'PeerPerformance' in publications use:") 2 | 3 | bibentry(bibtype = "article", 4 | title = "The peer performance ratios of hedge funds", 5 | author = c(person("David","Ardia"), 6 | person("Kris","Boudt")), 7 | year = "2018", 8 | journal = "Journal of Banking and Finance", 9 | volume = "87", 10 | pages = "351-368", 11 | doi = "10.1016/j.jbankfin.2017.10.014", 12 | textVersion = paste("Ardia, D., Boudt, K. (2018).", 13 | "The peer performance ratios of hedge funds.", 14 | "Journal of Banking and Finance 87, pp 351-368", 15 | "doi:10.1016/j.jbankfin.2017.10.014") 16 | ) 17 | 18 | bibentry(bibtype = "article", 19 | title = "Testing equality of modified {S}harpe ratios", 20 | author = c(person("David","Ardia"), 21 | person("Kris","Boudt")), 22 | year = "2015", 23 | journal = "Finance Research Letters", 24 | volume = "13", 25 | pages = "97-104", 26 | doi = "10.1016/j.frl.2015.02.008", 27 | textVersion = paste("Ardia, D., Boudt, K. (2015).", 28 | "Testing equality of modified Sharpe ratios.", 29 | "Finance Research Letters 13, pp 97-104.", 30 | "doi:10.1016/j.frl.2015.02.008") 31 | ) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | inst/lib 2 | .Rproj.user 3 | src/*.o 4 | src/*.so 5 | src/*.dll 6 | src/symbols.rds 7 | .Rhistory 8 | .RData 9 | 10 | ## QtCreator 11 | Rcpp.pro 12 | Rcpp.pro.user 13 | *.autosave 14 | .#* 15 | inst/.DS_Store 16 | 17 | # Retrieved 2017-Oct-12 from https://github.com/github/gitignore/blob/master/R.gitignore 18 | # Licensed under CC0-1.0 https://github.com/github/gitignore/blob/master/LICENSE 19 | # This file and the .Rbuildignore 20 | #.Rbuildignore 21 | #.gitignore 22 | # R project folder 23 | *.Rproj 24 | # Folder containing logo 25 | inst/rawdata 26 | inst/sticker 27 | # Folder containing data (because of confidentiality) 28 | data/englandwales.rda 29 | data/french.rda 30 | data/idl.rda 31 | data/idl2021.rda 32 | data/italian.rda 33 | # History files 34 | .Rhistory 35 | .Rapp.history 36 | # Session Data files 37 | .RData 38 | .Ruserdata 39 | # Example code in package build process 40 | *-Ex.R 41 | # Output files from R CMD build 42 | /*.tar.gz 43 | # Output files from R CMD check 44 | /*.Rcheck/ 45 | # RStudio files 46 | .Rproj.user/ 47 | # produced vignettes 48 | vignettes/*.html 49 | vignettes/*.pdf 50 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 51 | .httr-oauth 52 | # knitr and R markdown default cache directories 53 | /*_cache/ 54 | /cache/ 55 | # Temporary files created by R markdown 56 | *.utf8.md 57 | *.knit.md 58 | # File that is not used and is superceded 59 | R/npsurv.R 60 | R/parametric_backup.R 61 | tests 62 | inst/doc 63 | aucun 64 | /doc/ 65 | /Meta/ 66 | TODO 67 | -------------------------------------------------------------------------------- /tests/testthat/test_testing.R: -------------------------------------------------------------------------------- 1 | context("Testing") 2 | 3 | f.test.pval <- function(pval) { 4 | out <- pval >= 0 && pval <= 1 5 | return(out) 6 | } 7 | 8 | data('hfdata') 9 | N = dim(hfdata)[2] 10 | 11 | x = hfdata[,1] 12 | y = hfdata[,2] 13 | 14 | test_that("Sharpe and modified Sharpe", { 15 | tmp = sharpe(hfdata) 16 | expect_true(is.vector(tmp)) 17 | expect_true(length(tmp) == N) 18 | 19 | tmp = msharpe(hfdata) 20 | expect_true(is.vector(tmp)) 21 | expect_true(length(tmp) == N) 22 | }) 23 | 24 | test_that("Sharpe testing", { 25 | tmp = sharpeTesting(x, y, control = list()) 26 | expect_true(f.test.pval(tmp$pval)) 27 | set.seed(123) 28 | tmp = sharpeTesting(x, y, control = list(type = 2)) 29 | expect_true(f.test.pval(tmp$pval)) 30 | tmp = sharpeTesting(x, y, control = list(ttype = 1)) 31 | expect_true(f.test.pval(tmp$pval)) 32 | tmp = sharpeTesting(x, y, control = list(hac = TRUE)) 33 | expect_true(f.test.pval(tmp$pval)) 34 | tmp = sharpeTesting(x, y, control = list(type = 2, pBoot = 2)) 35 | expect_true(f.test.pval(tmp$pval)) 36 | }) 37 | 38 | test_that("Modified Sharpe testing", { 39 | tmp = msharpeTesting(x, y, control = list()) 40 | expect_true(f.test.pval(tmp$pval)) 41 | set.seed(123) 42 | tmp = msharpeTesting(x, y, control = list(type = 2)) 43 | expect_true(f.test.pval(tmp$pval)) 44 | tmp = msharpeTesting(x, y, control = list(ttype = 1)) 45 | expect_true(f.test.pval(tmp$pval)) 46 | tmp = msharpeTesting(x, y, control = list(hac = TRUE)) 47 | expect_true(f.test.pval(tmp$pval)) 48 | tmp = msharpeTesting(x, y, control = list(type = 2, pBoot = 2)) 49 | expect_true(f.test.pval(tmp$pval)) 50 | }) 51 | 52 | -------------------------------------------------------------------------------- /man/sharpe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/functions.R 3 | \name{sharpe} 4 | \alias{sharpe} 5 | \title{Compute Sharpe ratio} 6 | \usage{ 7 | sharpe(X, na.rm = TRUE) 8 | } 9 | \arguments{ 10 | \item{X}{Vector (of length \eqn{T}) or matrix (of size \eqn{T \times 11 | N}{TxN}) of returns for \eqn{N} funds. \code{NA} values are allowed.} 12 | 13 | \item{na.rm}{A logical value indicating whether \code{NA} values should be 14 | stripped before the computation. Default \code{na.rm = TRUE}} 15 | } 16 | \value{ 17 | A scalar or a vector (of size \eqn{N}) with the Sharpe ratios. 18 | } 19 | \description{ 20 | Function which computes the Sharpe ratio. 21 | } 22 | \details{ 23 | The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 24 | absolute risk adjusted performance of hedge funds. 25 | } 26 | \examples{ 27 | ## Load the data 28 | data('hfdata') 29 | 30 | ## Compute the Sharpe ratio 31 | out = sharpe(hfdata) 32 | print(out) 33 | 34 | out = sharpe(hfdata, na.rm = FALSE) 35 | print(out) 36 | } 37 | \references{ 38 | Ardia, D., Boudt, K. (2015). 39 | Testing equality of modified Sharpe ratios. 40 | \emph{Finance Research Letters} \bold{13}, pp.97--104. 41 | \doi{10.1016/j.frl.2015.02.008} 42 | 43 | Ardia, D., Boudt, K. (2016). 44 | The Peer Ratios Performance of Hedge Funds. 45 | \emph{Working paper}. 46 | \doi{10.2139/ssrn.2000901} 47 | 48 | Sharpe, W.F. (1994). 49 | The Sharpe ratio. 50 | \emph{Journal of Portfolio Management} \bold{21}(1), pp.49--58. 51 | \doi{10.3905/jpm.1994.409501} 52 | } 53 | \seealso{ 54 | \code{\link{sharpeTesting}}, \code{\link{sharpeScreening}} and 55 | \code{\link{msharpe}}. 56 | } 57 | \author{ 58 | David Ardia and Kris Boudt. 59 | } 60 | \keyword{htest} 61 | -------------------------------------------------------------------------------- /man/msharpe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/functions.R 3 | \name{msharpe} 4 | \alias{msharpe} 5 | \title{Compute modified Sharpe ratio} 6 | \usage{ 7 | msharpe(X, level = 0.9, na.rm = TRUE, na.neg = TRUE) 8 | } 9 | \arguments{ 10 | \item{X}{Vector (of length \eqn{T}) or matrix (of size \eqn{T \times 11 | N}{TxN}) of returns. \code{NA} values are allowed.} 12 | 13 | \item{level}{Modified Value-at-Risk level. Default: \code{level = 0.90}.} 14 | 15 | \item{na.rm}{A logical value indicating whether \code{NA} values should be 16 | stripped before the computation. Default \code{na.rm = TRUE}.} 17 | 18 | \item{na.neg}{A logical value indicating whether \code{NA} values should be 19 | returned if a negative modified Value-at-Risk is obtained. Default 20 | \code{na.neg = TRUE}.} 21 | } 22 | \value{ 23 | Scalar or a vector (of size \eqn{N}) with the modified Sharpe 24 | ratios. 25 | } 26 | \description{ 27 | Function which computes the modified Sharpe ratio 28 | } 29 | \details{ 30 | The modified Sharpe ratio (Favre and Galeano 2002) is one industry 31 | standard for measuring the absolute risk adjusted performance of hedge 32 | funds. 33 | } 34 | \examples{ 35 | ## Load the data (randomized data of monthly hedge fund returns) 36 | data('hfdata') 37 | 38 | out = msharpe(hfdata) 39 | print(out) 40 | 41 | out = msharpe(hfdata, na.rm = FALSE) 42 | print(out) 43 | } 44 | \references{ 45 | Ardia, D., Boudt, K. (2015). 46 | Testing equality of modified Sharpe ratios. 47 | \emph{Finance Research Letters} \bold{13}, 97--104. 48 | 49 | Ardia, D., Boudt, K. (2018). 50 | The peer performance ratios of hedge Funds. 51 | \emph{Journal of Banking and Finance} \bold{87}, 351--368. 52 | 53 | Favre, L., Galeano, J.A. (2002). 54 | Mean-modified Value-at-Risk Optimization with Hedge Funds. 55 | \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 56 | 57 | Gregoriou, G. N., Gueyie, J.-P. (2003). 58 | Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 59 | \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 60 | } 61 | \seealso{ 62 | \code{\link{msharpeTesting}}, \code{\link{msharpeScreening}} and 63 | \code{\link{sharpe}}. 64 | } 65 | \author{ 66 | David Ardia and Kris Boudt. 67 | } 68 | \keyword{htest} 69 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # Version 2.3.2 (DA) 2 | - Several fixes in documentation and good practices 3 | 4 | # Version 2.3.1 (DA) 5 | - Doc fixed 6 | 7 | # Version 2.3.0 (DA,SL) 8 | - Doc fixed 9 | - alphaScreening now also outputs the betas 10 | - Bug in lambda resampling fixed 11 | 12 | # Version 2.2.5 (DA) 13 | - Doc fixed 14 | 15 | # Version 2.2.3 (DA) 16 | - url fixed 17 | 18 | # Version 2.2.1 (DA) 19 | - Switch to parallel package 20 | - References updated 21 | 22 | # Version 2.1.4 (DA) 23 | - Small fix in counting NA 24 | 25 | # Version 2.1.3 (DA) 26 | - Documentation fixes 27 | 28 | # Version 2.1.2 (DA) 29 | - Documentation fixes 30 | 31 | # Version 2.1.1 (DA) 32 | - Documentation fixes 33 | - First CRAN release 34 | 35 | # Version 2.1.0 (DA) 36 | - New PeerPerformance documentation 37 | - Compiler imported directly within function 38 | 39 | # Version 2.0.11 (DA) 40 | - Examples added 41 | - Block length not exported anymore 42 | 43 | # Version 2.0.10 (DA) 44 | - Roxygen documentation 45 | - testthat added 46 | - Format of code 47 | 48 | # Version 2.0.9 (DA) 49 | - Update CITATION and DESCRIPTION 50 | 51 | # Version 2.0.8 (DA) 52 | - Small improvements with compiler 53 | - Citations updated 54 | 55 | # Version 2.0.6 and 2.0.7 (DA) 56 | - Various improvements 57 | - Small fix in documentation 58 | 59 | # Version 2.0.5 (DA) 60 | - Fix in documentation for modified Sharpe testing 61 | - Small fix in pvalue computation by bootstrap (symmetric) 62 | 63 | # Version 2.0.4 (DA) 64 | - Bug fix for alpha screening when NA are in the dataset 65 | - Risk-free rate removed 66 | - tstat used for attribution 67 | 68 | # Version 2.0.3 (DA) 69 | - Major functions contain risk-free rates (zero by default) 70 | - Documentation updated 71 | - Adjustment factor robustified 72 | 73 | # Version 2.0.2 (DA) 74 | - alphaScreening fixed 75 | - alphaScreening now encompasses hac estimation with sandwich and lmtest 76 | - citation file updated 77 | 78 | # Version 2.0.1 (DA) 79 | - New package's name 80 | - New package's number 81 | - New package's structure 82 | 83 | # Version 1-00.15 (DA) 84 | - pi+ fixed 85 | - Default settings for lambda = NULL 86 | 87 | # Version 1-00.14 (DA) 88 | - Fix of errors in examples 89 | 90 | # Version 1-00.13 (DA) 91 | - Control parameters for lambda data driven (NULL) 92 | - Documentation updated 93 | - Function for optimal lambda corrected and enhanced 94 | - Function pizero and pi corrected 95 | 96 | # Version 1-00.01 (DA) 97 | - First release 98 | - Package includes (parallel) alpha and sharpe screening algorithms 99 | -------------------------------------------------------------------------------- /tests/testthat/test_screening.R: -------------------------------------------------------------------------------- 1 | context("Screening") 2 | 3 | f.test.pval = function(pval) { 4 | out = pval >= 0 && pval <= 1 5 | return(out) 6 | } 7 | 8 | data('hfdata') 9 | N = 10 10 | rets = hfdata[,1:10] 11 | 12 | test_that("Alpha", { 13 | tmp = alphaScreening(rets, control = list(nCore = 1)) 14 | expect_equal(length(tmp$n), N) 15 | expect_equal(length(tmp$alpha), N) 16 | expect_equal(length(tmp$npeer), N) 17 | expect_equal(length(tmp$lambda), N) 18 | expect_equal(dim(tmp$dalpha), c(N,N)) 19 | expect_equal(dim(tmp$tstat), c(N,N)) 20 | expect_equal(dim(tmp$pval), c(N,N)) 21 | 22 | tmp = alphaScreening(rets, control = list(nCore = 1, hac = TRUE)) 23 | expect_equal(length(tmp$n), N) 24 | expect_equal(length(tmp$alpha), N) 25 | expect_equal(length(tmp$npeer), N) 26 | expect_equal(length(tmp$lambda), N) 27 | expect_equal(dim(tmp$dalpha), c(N,N)) 28 | expect_equal(dim(tmp$tstat), c(N,N)) 29 | expect_equal(dim(tmp$pval), c(N,N)) 30 | }) 31 | 32 | test_that("Sharpe", { 33 | tmp = sharpeScreening(rets, control = list(nCore = 1)) 34 | expect_equal(length(tmp$n), N) 35 | expect_equal(length(tmp$sharpe), N) 36 | expect_equal(length(tmp$npeer), N) 37 | expect_equal(length(tmp$lambda), N) 38 | expect_equal(dim(tmp$dsharpe), c(N,N)) 39 | expect_equal(dim(tmp$tstat), c(N,N)) 40 | expect_equal(dim(tmp$pval), c(N,N)) 41 | 42 | tmp = sharpeScreening(rets, control = list(nCore = 1, hac = TRUE)) 43 | expect_equal(length(tmp$n), N) 44 | expect_equal(length(tmp$sharpe), N) 45 | expect_equal(length(tmp$npeer), N) 46 | expect_equal(length(tmp$lambda), N) 47 | expect_equal(dim(tmp$dsharpe), c(N,N)) 48 | expect_equal(dim(tmp$tstat), c(N,N)) 49 | expect_equal(dim(tmp$pval), c(N,N)) 50 | }) 51 | 52 | test_that("Modified Sharpe", { 53 | tmp = msharpeScreening(rets, level = 0.9, control = list(nCore = 1)) 54 | expect_equal(length(tmp$n), N) 55 | expect_equal(length(tmp$msharpe), N) 56 | expect_equal(length(tmp$npeer), N) 57 | expect_equal(length(tmp$lambda), N) 58 | expect_equal(dim(tmp$dmsharpe), c(N,N)) 59 | expect_equal(dim(tmp$tstat), c(N,N)) 60 | expect_equal(dim(tmp$pval), c(N,N)) 61 | 62 | tmp = msharpeScreening(rets, level = 0.95, control = list(nCore = 1, hac = TRUE)) 63 | expect_equal(length(tmp$n), N) 64 | expect_equal(length(tmp$msharpe), N) 65 | expect_equal(length(tmp$npeer), N) 66 | expect_equal(length(tmp$lambda), N) 67 | expect_equal(dim(tmp$dmsharpe), c(N,N)) 68 | expect_equal(dim(tmp$tstat), c(N,N)) 69 | expect_equal(dim(tmp$pval), c(N,N)) 70 | }) 71 | 72 | -------------------------------------------------------------------------------- /R/sharpeBlockSize.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for the optimal block length computation for the 2 | ## Sharpe ratio 3 | 4 | # #' @name .sharpeBlockSize 5 | # #' @title See sharpeBlockSize 6 | # #' @importFrom stats rgeom 7 | # #' @import compiler 8 | .sharpeBlockSize <- function(x, y, control = list(), b.vec = c(1, 3, 6, 10), 9 | alpha = 0.05, M = 199, K = 500, b.av = 5, 10 | T.start = 50) { 11 | 12 | sb.sequence <- function(T, b.av, length = T) { 13 | 14 | index.sequence <- c(1:T, 1:T) 15 | sequence <- rep.int(0, length + T) 16 | current <- 0 17 | while (current < length) { 18 | start <- sample(1:T, 1) 19 | b <- stats::rgeom(1, 1/b.av) + 1 20 | sequence[(current + 1):(current + b)] <- index.sequence[start:(start + 21 | b - 1)] 22 | current <- current + b 23 | } 24 | out <- sequence[1:length] 25 | return(out) 26 | 27 | } 28 | 29 | x <- as.matrix(x) 30 | y <- as.matrix(y) 31 | rets <- cbind(x, y) 32 | 33 | # process control parameters 34 | ctr <- processControl(control) 35 | 36 | b.len <- length(b.vec) 37 | emp.reject.probs <- rep.int(0, b.len) 38 | d <- sharpe.ratio.diff(x, y, ctr$ttype) 39 | T <- length(x) 40 | Var.data <- matrix(data = 0, nrow = T.start + T, ncol = 2) 41 | Var.data[1, ] <- rets[1, ] 42 | fit1 <- stats::lm(x[2:T] ~ x[1:(T - 1)] + y[1:(T - 1)]) 43 | fit2 <- stats::lm(y[2:T] ~ x[1:(T - 1)] + y[1:(T - 1)]) 44 | coef1 <- as.numeric(fit1$coef) 45 | coef2 <- as.numeric(fit2$coef) 46 | resid.mat <- cbind(as.numeric(fit1$resid), as.numeric(fit2$resid)) 47 | for (k in 1:K) { 48 | ids <- sb.sequence(T - 1, b.av, T.start + T - 1) 49 | resid.mat.star <- rbind(c(0, 0), resid.mat[ids, ]) 50 | for (t in 2:(T.start + T)) { 51 | Var.data[t, 1] <- coef1[1] + coef1[2] * Var.data[t - 1, 1] + 52 | coef1[3] * Var.data[t - 1, 2] + resid.mat.star[t, 1] 53 | Var.data[t, 2] <- coef2[1] + coef2[2] * Var.data[t - 1, 2] + 54 | coef2[3] * Var.data[t - 1, 2] + resid.mat.star[t, 2] 55 | } 56 | Var.data.trunc <- Var.data[(T.start + 1):(T.start + T), ] 57 | for (j in 1:b.len) { 58 | bsids <- bootIndices(T, M, b.vec[j]) 59 | tmp <- sharpeTestBootstrap(Var.data.trunc, bsids, b.vec[j], 60 | ctr$ttype, pBoot = 1, d) 61 | if (tmp$pval <= alpha) { 62 | emp.reject.probs[j] <- emp.reject.probs[j] + 1 63 | } 64 | } 65 | } 66 | emp.reject.probs <- emp.reject.probs/K 67 | b.order <- order(abs(emp.reject.probs - alpha)) 68 | b.opt <- b.vec[b.order[1]] 69 | return(b.opt) 70 | } 71 | sharpeBlockSize <- compiler::cmpfun(.sharpeBlockSize) 72 | -------------------------------------------------------------------------------- /R/msharpeBlockSize.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for the optimal block length computation for the 2 | ## modified Sharpe ratio 3 | 4 | # #' @name .msharpeBlockSize 5 | # #' @title See msharpeBlockSize 6 | # #' @importFrom stats rgeom 7 | # #' @import compiler 8 | .msharpeBlockSize <- function(x, y, level = 0.9, na.neg = TRUE, control = list(), 9 | b.vec = c(1, 3, 6, 10), alpha = 0.05, M = 199, K = 500, b.av = 5, T.start = 50) { 10 | 11 | sb.sequence <- function(T, b.av, length = T) { 12 | 13 | index.sequence <- c(1:T, 1:T) 14 | sequence <- rep.int(0, length + T) 15 | current <- 0 16 | while (current < length) { 17 | start <- sample(1:T, 1) 18 | b <- stats::rgeom(1, 1/b.av) + 1 19 | sequence[(current + 1):(current + b)] <- index.sequence[start:(start + 20 | b - 1)] 21 | current <- current + b 22 | } 23 | out <- sequence[1:length] 24 | return(out) 25 | 26 | } 27 | 28 | x <- as.matrix(x) 29 | y <- as.matrix(y) 30 | rets <- cbind(x, y) 31 | 32 | # process control parameters 33 | ctr <- processControl(control) 34 | 35 | b.len <- length(b.vec) 36 | emp.reject.probs <- rep.int(0, b.len) 37 | d <- msharpe.ratio.diff(x, y, level, na.neg, ctr$ttype) 38 | T <- length(x) 39 | Var.data <- matrix(data = 0, nrow = T.start + T, ncol = 2) 40 | Var.data[1, ] <- rets[1, ] 41 | fit1 <- stats::lm(x[2:T] ~ x[1:(T - 1)] + y[1:(T - 1)]) 42 | fit2 <- stats::lm(y[2:T] ~ x[1:(T - 1)] + y[1:(T - 1)]) 43 | coef1 <- as.numeric(fit1$coef) 44 | coef2 <- as.numeric(fit2$coef) 45 | resid.mat <- cbind(as.numeric(fit1$resid), as.numeric(fit2$resid)) 46 | for (k in 1:K) { 47 | ids <- sb.sequence(T - 1, b.av, T.start + T - 1) 48 | resid.mat.star <- rbind(c(0, 0), resid.mat[ids, ]) 49 | for (t in 2:(T.start + T)) { 50 | Var.data[t, 1] <- coef1[1] + coef1[2] * Var.data[t - 1, 1] + 51 | coef1[3] * Var.data[t - 1, 2] + resid.mat.star[t, 1] 52 | Var.data[t, 2] <- coef2[1] + coef2[2] * Var.data[t - 1, 2] + 53 | coef2[3] * Var.data[t - 1, 2] + resid.mat.star[t, 2] 54 | } 55 | Var.data.trunc <- Var.data[(T.start + 1):(T.start + T), ] 56 | for (j in 1:b.len) { 57 | bsids <- bootIndices(T, M, b.vec[j]) 58 | tmp <- msharpeTestBootstrap(Var.data.trunc, level, na.neg, 59 | bsids, b.vec[j], ctr$ttype, pBoot = 1, d) 60 | if (tmp$pval <= alpha) { 61 | emp.reject.probs[j] <- emp.reject.probs[j] + 1 62 | } 63 | } 64 | } 65 | emp.reject.probs <- emp.reject.probs/K 66 | b.order <- order(abs(emp.reject.probs - alpha)) 67 | b.opt <- b.vec[b.order[1]] 68 | return(b.opt) 69 | 70 | } 71 | msharpeBlockSize <- compiler::cmpfun(.msharpeBlockSize) 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PeerPerformance: Luck-Corrected Peer Performance Analysis in R 2 | 3 | `PeerPerformance` is an R package for the peer-performance evaluation of financial investments with 4 | luck-correction. In particular, it implements the peer performance ratios 5 | of [Ardia and Boudt (2018)](https://doi.org/10.1016/j.jbankfin.2017.10.014) which measure the percentage of peers a focal fund outperforms and underperforms, after 6 | correction for luck. It is useful for fund or portfolio managers to 7 | benchmark their investments or screen a universe of new funds. 8 | In addition, it implements the testing framework for the Sharpe and modified Sharpe ratios, described 9 | in [Ledoit and Wolf (2008)](https://doi.org/10.1016/j.jempfin.2008.03.002) 10 | and [Ardia and Boudt (2015)](https://doi.org/10.1016/j.frl.2015.02.008). See also Ardia et al. (2022,2023) for applications in sustainable finance. 11 | 12 | ## Please cite the package in publications! 13 | 14 | By using `PeerPerformance` you agree to the following rules: 15 | 16 | 1) You must cite [Ardia and Boudt (2018)](https://doi.org/10.1016/j.jbankfin.2017.10.014) in working papers and published papers that use `PeerPerformance`. 17 | 2) You must place the following URL in a footnote to help others find `PeerPerformance`: [https://CRAN.R-project.org/package=PeerPerformance](https://CRAN.R-project.org/package=PeerPerformance) 18 | 3) You assume all risk for the use of `PeerPerformance`. 19 | 20 | Ardia, D., Boudt, K. (2018). 21 | The peer performance ratios of hedge funds. 22 | _Journal of Banking and Finance_, 87, 351-368. 23 | [https://doi.org/10.1016/j.jbankfin.2017.10.014](https://doi.org/10.1016/j.jbankfin.2017.10.014) 24 | [https://doi.org/10.2139/ssrn.2000901](https://doi.org/10.2139/ssrn.2000901) 25 | 26 | ## Other references 27 | 28 | Ardia, D., Boudt, K. (2015). 29 | Testing equality of modified Sharpe ratios. 30 | _Finance Research Letters_, 13, 97-104. 31 | [https://doi.org/10.1016/j.frl.2015.02.008](https://doi.org/10.1016/j.frl.2015.02.008) 32 | [https://doi.org/10.2139/ssrn.2516591](https://doi.org/10.2139/ssrn.2516591) 33 | 34 | Ardia, D., Bluteau, K., Tran, D. (2022). 35 | How easy is it for investment managers to deploy their talent in green and brown stocks? 36 | _Finance Research Letters_, 48, 102992. 37 | [https://doi.org/10.1016/j.frl.2022.102992](https://doi.org/10.1016/j.frl.2022.102992) 38 | [https://doi.org/10.2139/ssrn.4009286](https://doi.org/10.2139/ssrn.4009286) 39 | 40 | Ardia, D., Bluteau, K., Lortie-Cloutier, G., Tran, D. (2023). 41 | Factor exposure heterogeneity in green and brown stocks. 42 | _Finance Research Letters_, 55, Part A, pp.103900. 43 | [https://doi.org/10.1016/j.frl.2023.103900](https://doi.org/10.1016/j.frl.2023.103900) 44 | [https://doi.org/10.2139/ssrn.4362696](https://doi.org/10.2139/ssrn.4362696) 45 | 46 | Ledoit, O., Wolf, M. (2008). 47 | Robust performance hypothesis testing with the Sharpe ratio. 48 | _Journal of Empirical Finance_, 15(5), 850-859. 49 | -------------------------------------------------------------------------------- /man/alphaTesting.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/alphaTesting.R 3 | \name{alphaTesting} 4 | \alias{alphaTesting} 5 | \title{Testing the difference of alpha outperformance ratios} 6 | \usage{ 7 | alphaTesting(x, y, factors = NULL, control = list(), screen_beta = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 11 | values are allowed.} 12 | 13 | \item{y}{Vector (of length \eqn{T}) returns for the second fund. \code{NA} 14 | values are allowed.} 15 | 16 | \item{factors}{Matrix \eqn{(T \times K)}{(TxK)} of \eqn{T} returns for the 17 | \eqn{K} factors. \code{NA} values are allowed.} 18 | 19 | \item{control}{Control parameters (see *Details*).} 20 | 21 | \item{screen_beta}{Boolean to screen all factors' coefficients (beta). 22 | Default: \code{screen_beta=FALSE} (i.e. only outputs the alpha). 23 | If \code{screen_beta=TRUE}, each element of the returned list will have a new first dimension 24 | representing each coefficient (the first one being alpha)} 25 | } 26 | \value{ 27 | A list with the following components:\cr 28 | 29 | \code{n}: Number of non-\code{NA} concordant observations.\cr 30 | 31 | \code{alpha}: Vector (of length 2) of unconditional alpha outperformance ratios.\cr 32 | 33 | \code{dalpha}: alpha outperformance ratios difference.\cr 34 | 35 | \code{tstat}: t-stat of alpha outperformance ratios differences.\cr 36 | 37 | \code{pval}: pvalues of test of alpha outperformance ratios differences. 38 | } 39 | \description{ 40 | Function which performs the testing of the difference of alpha outperformance ratios. 41 | } 42 | \details{ 43 | The alpha measure (Treynor and Black 1973, Carhart 1997, Fung and Hsieh 44 | 2004) is one industry standard for measuring the absolute risk adjusted 45 | performance of hedge funds. This function performs 46 | the testing of alpha outperformance ratio difference for two funds. 47 | 48 | For the testing, only the intersection of non-\code{NA} observations for the 49 | two funds are used. 50 | 51 | The argument \code{control} is a list that can supply any of the following 52 | components: 53 | \itemize{ 54 | \item \code{'hac'} Heteroscedastic-autocorrelation consistent 55 | standard errors. Default: \code{hac = FALSE}. 56 | } 57 | } 58 | \note{ 59 | Further details on the methodology with an application to the hedge 60 | fund industry is given in in Ardia and Boudt (2018). 61 | 62 | Some internal functions where adapted from Michael Wolf MATLAB code. 63 | } 64 | \examples{ 65 | ## Load the data (randomized data of monthly hedge fund returns) 66 | data("hfdata") 67 | x = hfdata[,1] 68 | y = hfdata[,2] 69 | 70 | ## Run alpha testing 71 | alphaTesting(x, y) 72 | } 73 | \references{ 74 | Ardia, D., Boudt, K. (2015). 75 | Testing equality of modified Sharpe ratios. 76 | \emph{Finance Research Letters} \bold{13}, 97--104. 77 | 78 | Ardia, D., Boudt, K. (2018). 79 | The peer performance ratios of hedge funds. 80 | \emph{Journal of Banking and Finance} \bold{87}, 351--368. 81 | 82 | Barras, L., Scaillet, O., Wermers, R. (2010). 83 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 84 | \emph{Journal of Finance} \bold{65}(1), 179--216. 85 | 86 | Sharpe, W.F. (1994). 87 | The Sharpe ratio. 88 | \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 89 | 90 | Ledoit, O., Wolf, M. (2008). 91 | Robust performance hypothesis testing with the Sharpe ratio. 92 | \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 93 | 94 | Storey, J. (2002). 95 | A direct approach to false discovery rates. 96 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 97 | } 98 | \seealso{ 99 | \code{\link{alphaScreening}}.. 100 | } 101 | \author{ 102 | David Ardia and Kris Boudt. 103 | } 104 | \keyword{htest} 105 | -------------------------------------------------------------------------------- /man/PeerPerformance.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/PeerPerformance.R 3 | \name{PeerPerformance} 4 | \alias{PeerPerformance} 5 | \alias{PeerPerformance-package} 6 | \title{PeerPerformance: Luck-corrected peer performance analysis in R} 7 | \description{ 8 | \code{PeerPerformance} is an \R package for the peer-performance evaluation of financial investments with 9 | luck-correction, useful in the financial industry. In particular, it implements the peer performance ratios of Ardia and Boudt 10 | (2018) which measure the percentage of peers a focal (hedge) fund outperforms and underperforms, after 11 | correction for luck. It is useful for fund or portfolio managers to benchmark their investments or screen a universe of new funds. 12 | In addition, the package implements the testing framework for the Sharpe and modified Sharpe ratios, described in 13 | Ledoit and Wolf (2008) and Ardia and Boudt (2015). See also Ardia et al. (2022,2023) for applications in sustainable finance. 14 | } 15 | \note{ 16 | By using \code{PeerPerformance} you agree to the following rules: (1) You must cite Ardia and Boudt (2018) in 17 | working papers and published papers that use \code{PeerPerformance} (use \code{citation("PeerPerformance")}), (2) you 18 | must place the URL \url{https://CRAN.R-project.org/package=PeerPerformance} in a footnote to help 19 | others find \code{PeerPerformance}, and (3) you assume all risk for the use of \code{PeerPerformance}. 20 | 21 | Full description of the methodologies implemented in the various functions is available 22 | in Ledoit and Wolf (2008) and Ardia and Boudt (2015, 2018). 23 | } 24 | \section{Functions}{ 25 | 26 | \itemize{ 27 | \item Alpha: \code{\link{alphaTesting}} and \code{\link{alphaScreening}}; 28 | \item Sharpe ratio: \code{\link{sharpe}}, \code{\link{sharpeTesting}} and \code{\link{sharpeScreening}}; 29 | \item Modified Share ratio: \code{\link{msharpe}}, \code{\link{msharpeTesting}} and \code{\link{msharpeScreening}}; 30 | \item Screening: \code{\link{alphaScreening}}, \code{\link{sharpeScreening}}, and \code{\link{msharpeScreening}}. 31 | } 32 | } 33 | 34 | \section{Update}{ 35 | 36 | The latest version of the package is available at \url{https://github.com/ArdiaD/PeerPerformance} 37 | } 38 | 39 | \references{ 40 | Ardia, D., Boudt, K. (2015). 41 | Testing equality of modified Sharpe ratios. 42 | \emph{Finance Research Letters} \bold{13}, 97--104. 43 | 44 | Ardia, D., Boudt, K. (2018). 45 | The peer performance ratios of hedge Funds. 46 | \emph{Journal of Banking and Finance} \bold{87}, 351--368. 47 | 48 | Ardia, D., Bluteau, K., Tran, D. (2022). 49 | How easy is it for investment managers to deploy their talent in green and brown stocks? 50 | \emph{Finance Research Letters} \bold{48}, 102992. 51 | 52 | Ardia, D., Bluteau, K., Lortie-Cloutier, G., Tran, D. (2023). 53 | Factor exposure heterogeneity in green and brown stocks. 54 | \emph{Finance Research Letters} \bold{55}, Part A, 103900. 55 | 56 | Barras, L., Scaillet, O., Wermers, R. (2010). 57 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 58 | \emph{Journal of Finance} \bold{65}(1), 179--216. 59 | 60 | Favre, L., Galeano, J.A. (2002). 61 | Mean-modified Value-at-Risk Optimization with Hedge Funds. 62 | \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 63 | 64 | Gregoriou, G. N., Gueyie, J.-P. (2003). 65 | Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 66 | \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 67 | 68 | Ledoit, O., Wolf, M. (2008). 69 | Robust performance hypothesis testing with the Sharpe ratio. 70 | \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 71 | 72 | Sharpe, W.F. (1994). 73 | The Sharpe ratio. 74 | \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 75 | 76 | Storey, J. (2002). 77 | A direct approach to false discovery rates. 78 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 79 | } 80 | \author{ 81 | David Ardia and Kris Boudt. 82 | } 83 | -------------------------------------------------------------------------------- /R/PeerPerformance.R: -------------------------------------------------------------------------------- 1 | #' @name PeerPerformance 2 | #' @aliases PeerPerformance-package 3 | #' @title PeerPerformance: Luck-corrected peer performance analysis in R 4 | #' @description \code{PeerPerformance} is an \R package for the peer-performance evaluation of financial investments with 5 | #' luck-correction, useful in the financial industry. In particular, it implements the peer performance ratios of Ardia and Boudt 6 | #' (2018) which measure the percentage of peers a focal (hedge) fund outperforms and underperforms, after 7 | #' correction for luck. It is useful for fund or portfolio managers to benchmark their investments or screen a universe of new funds. 8 | #' In addition, the package implements the testing framework for the Sharpe and modified Sharpe ratios, described in 9 | #' Ledoit and Wolf (2008) and Ardia and Boudt (2015). See also Ardia et al. (2022,2023) for applications in sustainable finance. 10 | #' @section Functions: 11 | #' \itemize{ 12 | #' \item Alpha: \code{\link{alphaTesting}} and \code{\link{alphaScreening}}; 13 | #' \item Sharpe ratio: \code{\link{sharpe}}, \code{\link{sharpeTesting}} and \code{\link{sharpeScreening}}; 14 | #' \item Modified Share ratio: \code{\link{msharpe}}, \code{\link{msharpeTesting}} and \code{\link{msharpeScreening}}; 15 | #' \item Screening: \code{\link{alphaScreening}}, \code{\link{sharpeScreening}}, and \code{\link{msharpeScreening}}. 16 | #' } 17 | #' @section Update: 18 | #' The latest version of the package is available at \url{https://github.com/ArdiaD/PeerPerformance} 19 | #' @author David Ardia and Kris Boudt. 20 | #' @note By using \code{PeerPerformance} you agree to the following rules: (1) You must cite Ardia and Boudt (2018) in 21 | #' working papers and published papers that use \code{PeerPerformance} (use \code{citation("PeerPerformance")}), (2) you 22 | #' must place the URL \url{https://CRAN.R-project.org/package=PeerPerformance} in a footnote to help 23 | #' others find \code{PeerPerformance}, and (3) you assume all risk for the use of \code{PeerPerformance}. 24 | #' @note Full description of the methodologies implemented in the various functions is available 25 | #' in Ledoit and Wolf (2008) and Ardia and Boudt (2015, 2018). 26 | #' @references 27 | #' Ardia, D., Boudt, K. (2015). 28 | #' Testing equality of modified Sharpe ratios. 29 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 30 | #' 31 | #' Ardia, D., Boudt, K. (2018). 32 | #' The peer performance ratios of hedge Funds. 33 | #' \emph{Journal of Banking and Finance} \bold{87}, 351--368. 34 | #' 35 | #' Ardia, D., Bluteau, K., Tran, D. (2022). 36 | #' How easy is it for investment managers to deploy their talent in green and brown stocks? 37 | #' \emph{Finance Research Letters} \bold{48}, 102992. 38 | #' 39 | #' Ardia, D., Bluteau, K., Lortie-Cloutier, G., Tran, D. (2023). 40 | #' Factor exposure heterogeneity in green and brown stocks. 41 | #' \emph{Finance Research Letters} \bold{55}, Part A, 103900. 42 | #' 43 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 44 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 45 | #' \emph{Journal of Finance} \bold{65}(1), 179--216. 46 | #' 47 | #' Favre, L., Galeano, J.A. (2002). 48 | #' Mean-modified Value-at-Risk Optimization with Hedge Funds. 49 | #' \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 50 | #' 51 | #' Gregoriou, G. N., Gueyie, J.-P. (2003). 52 | #' Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 53 | #' \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 54 | #' 55 | #' Ledoit, O., Wolf, M. (2008). 56 | #' Robust performance hypothesis testing with the Sharpe ratio. 57 | #' \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 58 | #' 59 | #' Sharpe, W.F. (1994). 60 | #' The Sharpe ratio. 61 | #' \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 62 | #' 63 | #' Storey, J. (2002). 64 | #' A direct approach to false discovery rates. 65 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 66 | #' @import lmtest 67 | #' @import sandwich 68 | #' @import compiler 69 | NULL 70 | -------------------------------------------------------------------------------- /man/sharpeTesting.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sharpeTesting.R 3 | \name{sharpeTesting} 4 | \alias{sharpeTesting} 5 | \title{Testing the difference of Sharpe ratios} 6 | \usage{ 7 | sharpeTesting(x, y, control = list()) 8 | } 9 | \arguments{ 10 | \item{x}{Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 11 | values are allowed.} 12 | 13 | \item{y}{Vector (of length \eqn{T}) returns for the second fund. \code{NA} 14 | values are allowed.} 15 | 16 | \item{control}{Control parameters (see *Details*).} 17 | } 18 | \value{ 19 | A list with the following components:\cr 20 | 21 | \code{n}: Number of non-\code{NA} concordant observations.\cr 22 | 23 | \code{sharpe}: Vector (of length 2) of unconditional Sharpe ratios.\cr 24 | 25 | \code{dsharpe}: Sharpe ratios difference.\cr 26 | 27 | \code{tstat}: t-stat of Sharpe ratios differences.\cr 28 | 29 | \code{pval}: pvalues of test of Sharpe ratios differences. 30 | } 31 | \description{ 32 | Function which performs the testing of the difference of Sharpe ratios. 33 | } 34 | \details{ 35 | The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 36 | absolute risk adjusted performance of hedge funds. This function performs 37 | the testing of Sharpe ratio difference for two funds using the approach by 38 | Ledoit and Wolf (2002). 39 | 40 | For the testing, only the intersection of non-\code{NA} observations for the 41 | two funds are used. 42 | 43 | The argument \code{control} is a list that can supply any of the following 44 | components: 45 | \itemize{ 46 | \item \code{'type'} Asymptotic approach (\code{type = 1}) or 47 | studentized circular bootstrap approach (\code{type = 2}). Default: 48 | \code{type = 1}. 49 | \item \code{'ttype'} Test based on ratio (\code{type = 1}) 50 | or product (\code{type = 2}). Default: \code{type = 2}. 51 | \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 52 | errors. Default: \code{hac = FALSE}. 53 | \item \code{'nBoot'} Number of bootstrap replications for computing the p-value. Default: \code{nBoot = 54 | 499}. 55 | \item \code{'bBoot'} Block length in the circular bootstrap. Default: 56 | \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 57 | block-length. 58 | \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 59 | asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 60 | } 61 | } 62 | \note{ 63 | Further details on the methodology with an application to the hedge 64 | fund industry is given in in Ardia and Boudt (2018). 65 | 66 | Some internal functions where adapted from Michael Wolf MATLAB code. 67 | } 68 | \examples{ 69 | ## Load the data (randomized data of monthly hedge fund returns) 70 | data("hfdata") 71 | x = hfdata[,1] 72 | y = hfdata[,2] 73 | 74 | ## Run Sharpe testing (asymptotic) 75 | ctr = list(type = 1) 76 | out = sharpeTesting(x, y, control = ctr) 77 | print(out) 78 | 79 | ## Run Sharpe testing (asymptotic hac) 80 | ctr = list(type = 1, hac = TRUE) 81 | out = sharpeTesting(x, y, control = ctr) 82 | print(out) 83 | 84 | ## Run Sharpe testing (iid bootstrap) 85 | set.seed(1234) 86 | ctr = list(type = 2, nBoot = 100) 87 | out = sharpeTesting(x, y, control = ctr) 88 | print(out) 89 | 90 | ## Run Sharpe testing (circular bootstrap) 91 | set.seed(1234) 92 | ctr = list(type = 2, nBoot = 100, bBoot = 5) 93 | out = sharpeTesting(x, y, control = ctr) 94 | print(out) 95 | } 96 | \references{ 97 | Ardia, D., Boudt, K. (2015). 98 | Testing equality of modified Sharpe ratios. 99 | \emph{Finance Research Letters} \bold{13}, 97--104. 100 | 101 | Ardia, D., Boudt, K. (2018). 102 | The peer performance ratios of hedge funds. 103 | \emph{Journal of Banking and Finance} \bold{87}, 351-.368. 104 | 105 | Barras, L., Scaillet, O., Wermers, R. (2010). 106 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 107 | \emph{Journal of Finance} \bold{65}(1), 179--216. 108 | 109 | Sharpe, W.F. (1994). 110 | The Sharpe ratio. 111 | \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 112 | 113 | Ledoit, O., Wolf, M. (2008). 114 | Robust performance hypothesis testing with the Sharpe ratio. 115 | \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 116 | 117 | Storey, J. (2002). 118 | A direct approach to false discovery rates. 119 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 120 | } 121 | \seealso{ 122 | \code{\link{sharpe}}, \code{\link{sharpeScreening}} and 123 | \code{\link{msharpeTesting}}. 124 | } 125 | \author{ 126 | David Ardia and Kris Boudt. 127 | } 128 | \keyword{htest} 129 | -------------------------------------------------------------------------------- /precran.R: -------------------------------------------------------------------------------- 1 | # precran.R — Run typical pre-CRAN checks 2 | # Usage from the package root: 3 | # Rscript precran.R 4 | # or source("precran.R") from an interactive session. 5 | 6 | message("==== Pre-CRAN preflight starting ====") 7 | 8 | # 1) Ensure required helper packages are available 9 | required <- c( 10 | "devtools", # document, build, check, win-builder helpers 11 | "rcmdcheck", # programmatic R CMD check 12 | "spelling", # spell-check Rd, vignettes, R 13 | "urlchecker", # validate and fix URLs in Rd, R, vignettes 14 | "lintr", # static code analysis 15 | "styler", # code style checks 16 | "desc", # DESCRIPTION helpers 17 | "rhub" # R-hub v2 remote checks (Windows/Linux) 18 | ) 19 | 20 | missing <- setdiff(required, rownames(installed.packages())) 21 | if (length(missing)) { 22 | message("Installing missing packages: ", paste(missing, collapse = ", ")) 23 | install.packages(missing, repos = "https://cloud.r-project.org") 24 | } 25 | 26 | suppressPackageStartupMessages({ 27 | lapply(required, requireNamespace, quietly = TRUE, character.only = TRUE) 28 | }) 29 | 30 | # 2) Basic metadata sanity checks 31 | message("\n==> Checking DESCRIPTION sanity") 32 | d <- desc::desc(file = "DESCRIPTION") 33 | fields_needed <- c("Package", "Version", "Title", "Description", "License") 34 | missing_fields <- fields_needed[!fields_needed %in% d$fields()] 35 | if (length(missing_fields)) { 36 | stop("Missing DESCRIPTION fields: ", paste(missing_fields, collapse = ", ")) 37 | } 38 | if (grepl("TODO|TBD|FILL ME|FIXME", d$get("Description"))) { 39 | stop("DESCRIPTION 'Description' contains placeholders. Please fix.") 40 | } 41 | message("DESCRIPTION looks OK") 42 | 43 | # 3) Regenerate docs and namespace 44 | message("\n==> Generating Rd files and NAMESPACE via roxygen2") 45 | devtools::document(quiet = TRUE) 46 | 47 | # # 4) Optional: build README if a README.Rmd exists 48 | # if (file.exists("README.Rmd")) { 49 | # message("\n==> Building README") 50 | # try(devtools::build_readme(), silent = TRUE) 51 | # } 52 | 53 | # 5) NEWS and CRAN-RELEASE presence check (non-fatal) 54 | if (!file.exists("NEWS.md")) message("Note: Consider maintaining a NEWS.md") 55 | if (!file.exists("CRAN-SUBMISSION") && !file.exists("CRAN-RELEASE")) { 56 | message("Note: Consider using usethis::use_cran_comments() and keeping CRAN comments/notes") 57 | } 58 | 59 | # 6) URL checks 60 | message("\n==> Checking URLs") 61 | bad <- urlchecker::url_check() 62 | if (nrow(bad)) { 63 | print(bad) 64 | message("You can try: urlchecker::url_update() to auto-fix redirects.") 65 | } else { 66 | message("All URLs OK") 67 | } 68 | 69 | # 7) Spell check 70 | message("\n==> Spell checking package") 71 | sp <- spelling::spell_check_package() 72 | if (nrow(sp)) { 73 | print(sp[, c("file", "line", "word")]) 74 | message("Add accepted words to inst/WORDLIST or fix typos.") 75 | } else { 76 | message("No spelling issues found") 77 | } 78 | 79 | # 8) Lint R code 80 | message("\n==> Linting R code") 81 | lint_res <- lintr::lint_package() 82 | if (length(lint_res)) { 83 | print(lint_res) 84 | message("Consider running styler::style_pkg() or address lintr suggestions.") 85 | } else { 86 | message("No lints found") 87 | } 88 | 89 | # 9) Local CRAN-style check 90 | message("\n==> Local R CMD check --as-cran") 91 | # rcmdcheck returns an object with summaries; devtools::check() opens viewer; prefer rcmdcheck here 92 | chk <- rcmdcheck::rcmdcheck(args = c("--as-cran"), error_on = "never", build_args = "--no-manual") 93 | summary <- rcmdcheck::parse_check(chk$results) 94 | print(chk) 95 | if (length(chk$errors)) message("Errors: ", length(chk$errors)) 96 | if (length(chk$warnings)) message("Warnings: ", length(chk$warnings)) 97 | if (length(chk$notes)) message("Notes: ", length(chk$notes)) 98 | 99 | # 10) Build source tarball 100 | message("\n==> Building source tarball") 101 | pkgfile <- devtools::build(path = "dist", quiet = TRUE) 102 | message("Built: ", pkgfile) 103 | 104 | # 11) Remote checks with R-hub v2 (Windows + Linux) 105 | message("\n==> R-hub v2 checks (Windows + Linux)") 106 | # Requires a GitHub repo or local setup; see ?rhubv2 107 | # If your package is on GitHub, set gh_url to your repo. Otherwise, rhub will infer from current git remote. 108 | # Common platforms include: 'windows-x86_64-release', 'ubuntu-gcc-release'. 109 | # Use rhub::rhub_check() with defaults or specify platforms explicitly. 110 | try({ 111 | #p <- rhub::rhub_platforms() 112 | rhub::rhub_check(platforms = "ubuntu-latest (r-release)") 113 | 114 | # pick <- function(rx) { 115 | # x <- p$name[grepl(rx, p$name, ignore.case = TRUE)] 116 | # if (length(x)) x[1] else character(0) 117 | # } 118 | # 119 | # plats <- unique(c( 120 | # pick("^ubuntu.*r-release"), 121 | # pick("^macos.*r-release"), 122 | # pick("^windows.*r-release") 123 | # )) 124 | # 125 | # print(plats) # sanity check; should be non-empty exact names 126 | # rhub::rhub_check(platforms = plats) 127 | }, silent = TRUE) 128 | 129 | # 12) Optional: Win-builder checks via devtools 130 | message("\n==> Optional: win-builder checks") 131 | message("Skip with options(precran.skip_winbuilder = TRUE) to bypass") 132 | if (!isTRUE(getOption("precran.skip_winbuilder", FALSE))) { 133 | try(devtools::check_win_release(), silent = TRUE) 134 | try(devtools::check_win_devel(), silent = TRUE) 135 | } 136 | 137 | message("\n==== Pre-CRAN preflight complete ====") 138 | -------------------------------------------------------------------------------- /man/msharpeTesting.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/msharpeTesting.R 3 | \name{msharpeTesting} 4 | \alias{msharpeTesting} 5 | \title{Testing the difference of modified Sharpe ratios} 6 | \usage{ 7 | msharpeTesting(x, y, level = 0.9, na.neg = TRUE, control = list()) 8 | } 9 | \arguments{ 10 | \item{x}{Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 11 | values are allowed.} 12 | 13 | \item{y}{Vector (of length \eqn{T}) of returns for the second fund. \code{NA} 14 | values are allowed.} 15 | 16 | \item{level}{Modified Value-at-Risk level. Default: \code{level = 0.90}.} 17 | 18 | \item{na.neg}{A logical value indicating whether \code{NA} values should be 19 | returned if a negative modified Value-at-Risk is obtained. Default 20 | \code{na.neg = TRUE}.} 21 | 22 | \item{control}{Control parameters (see *Details*).} 23 | } 24 | \value{ 25 | A list with the following components:\cr 26 | 27 | \code{n}: Number of non-\code{NA} concordant observations.\cr 28 | 29 | \code{msharpe}: Vector (of length 2) of unconditional modified Sharpe 30 | ratios.\cr 31 | 32 | \code{dmsharpe}: Modified Sharpe ratios difference.\cr 33 | 34 | \code{tstat}: t-stat of modified Sharpe ratios differences.\cr 35 | 36 | \code{pval}: pvalues of test of modified Sharpe ratios differences. 37 | } 38 | \description{ 39 | Function which performs the testing of the difference of modified Sharpe 40 | ratios. 41 | } 42 | \details{ 43 | The modified Sharpe ratio (Favre and Galeano 2002) is one industry 44 | standard for measuring the absolute risk adjusted performance of hedge 45 | funds. This function performs the testing of modified Sharpe ratio 46 | difference for two funds using a similar approach than Ledoit and Wolf 47 | (2002). See also Gregoriou and Gueyie (2003). 48 | 49 | For the testing, only the intersection of non-\code{NA} observations for the 50 | two funds are used. 51 | 52 | The argument \code{control} is a list that can supply any of the following 53 | components: 54 | \itemize{ 55 | \item \code{'type'} Asymptotic approach (\code{type = 1}) or 56 | studentized circular bootstrap approach (\code{type = 2}). Default: 57 | \code{type = 1}. 58 | \item \code{'ttype'} Test based on ratio (\code{type = 1}) 59 | or product (\code{type = 2}). Default: \code{type = 2}. 60 | \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 61 | errors. Default: \code{hac = FALSE}. 62 | \item \code{'minObs'} Minimum number of concordant observations to compute the ratios. Default: \code{minObs = 63 | 10}. 64 | \item \code{'nBoot'} Number of bootstrap replications for computing the 65 | p-value. Default: \code{nBoot = 499}. 66 | \item \code{'bBoot'} Block length in 67 | the circular bootstrap. Default: \code{bBoot = 1}, i.e. iid bootstrap. 68 | \code{bBoot = 0} uses optimal block-length. 69 | \item \code{'pBoot'} Symmetric 70 | p-value (\code{pBoot = 1}) or asymmetric p-value (\code{pBoot = 2}). 71 | Default: \code{pBoot = 1}. 72 | } 73 | } 74 | \note{ 75 | Further details on the methodology with an application to the hedge 76 | fund industry is given in Ardia and Boudt (2018). 77 | 78 | Some internal functions where adapted from Michael Wolf MATLAB code. 79 | } 80 | \examples{ 81 | ## Load the data (randomized data of monthly hedge fund returns) 82 | data("hfdata") 83 | x = hfdata[,1] 84 | y = hfdata[,2] 85 | 86 | ## Run modified Sharpe testing (asymptotic) 87 | ctr = list(type = 1) 88 | out = msharpeTesting(x, y, level = 0.95, control = ctr) 89 | print(out) 90 | 91 | ## Run modified Sharpe testing (asymptotic hac) 92 | ctr = list(type = 1, hac = TRUE) 93 | out = msharpeTesting(x, y, level = 0.95, control = ctr) 94 | print(out) 95 | 96 | ## Run modified Sharpe testing (iid bootstrap) 97 | set.seed(1234) 98 | ctr = list(type = 2, nBoot = 250) 99 | out = msharpeTesting(x, y, level = 0.95, control = ctr) 100 | print(out) 101 | 102 | ## Run modified Sharpe testing (circular bootstrap) 103 | set.seed(1234) 104 | ctr = list(type = 2, nBoot = 250, bBoot = 5) 105 | out = msharpeTesting(x, y, level = 0.95, control = ctr) 106 | print(out) 107 | } 108 | \references{ 109 | Ardia, D., Boudt, K. (2015). 110 | Testing equality of modified Sharpe ratios. 111 | \emph{Finance Research Letters} \bold{13}, pp.97--104. 112 | \doi{10.1016/j.frl.2015.02.008} 113 | 114 | Ardia, D., Boudt, K. (2018). 115 | The peer performance ratios of hedge funds. 116 | \emph{Journal of Banking and Finance} \bold{87}, pp.351-.368. 117 | \doi{10.1016/j.jbankfin.2017.10.014} 118 | 119 | Barras, L., Scaillet, O., Wermers, R. (2010). 120 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 121 | \emph{Journal of Finance} \bold{65}(1), pp.179--216. 122 | 123 | Favre, L., Galeano, J.A. (2002). 124 | Mean-modified Value-at-Risk Optimization with Hedge Funds. 125 | \emph{Journal of Alternative Investments} \bold{5}(2), pp.21--25. 126 | 127 | Gregoriou, G. N., Gueyie, J.-P. (2003). 128 | Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 129 | \emph{Journal of Wealth Management} \bold{6}(3), pp.77--83. 130 | 131 | Ledoit, O., Wolf, M. (2008). 132 | Robust performance hypothesis testing with the Sharpe ratio. 133 | \emph{Journal of Empirical Finance} \bold{15}(5), pp.850--859. 134 | 135 | Storey, J. (2002). 136 | A direct approach to false discovery rates. 137 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), pp.479--498. 138 | } 139 | \seealso{ 140 | \code{\link{msharpe}}, \code{\link{msharpeScreening}} and 141 | \code{\link{sharpeTesting}}. 142 | } 143 | \author{ 144 | David Ardia and Kris Boudt. 145 | } 146 | \keyword{htest} 147 | -------------------------------------------------------------------------------- /R/alphaTesting.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for Sharpe ratio testing 2 | 3 | #@name .alphaTesting 4 | #@description See alphaTesting 5 | # .alphaTesting <- function(x, y, factors=NULL, control=list()){ 6 | # X <- cbind(x, y) 7 | # screen <- alphaScreening(X, factors, control) 8 | # out <- list(n = screen$n[[1]], alpha = screen$alpha, dalpha = screen$dalpha[1,2], 9 | # tstat = screen$tstat[1,2], pval = screen$pval[1,2]) 10 | # class(out) <- "TESTING" 11 | # return(out) 12 | # } 13 | 14 | .alphaTesting <- function(x, y, factors=NULL, control=list(), screen_beta=FALSE){ 15 | # process control 16 | ctr <- processControl(control) 17 | hac <- ctr$hac 18 | X <- as.matrix(x) 19 | Y <- as.matrix(y) 20 | dXY <- X - Y 21 | nObs <- colSums(is.finite(dXY)) 22 | N <- 2 23 | 24 | 25 | if (screen_beta & !is.null(factors)) { 26 | n_coef <- ncol(factors) + 1 27 | row_return <- 1:n_coef 28 | } else { 29 | row_return <- 1 30 | } 31 | 32 | if (is.null(factors)) { 33 | fit <- stats::lm(dXY ~ 1, na.action = stats::na.omit) 34 | fitX <- stats::lm(X ~ 1, na.action = stats::na.omit) 35 | fitY <- stats::lm(Y ~ 1, na.action = stats::na.omit) 36 | } else { 37 | beta <- factors 38 | fit <- stats::lm(dXY ~ 1 + beta, na.action = stats::na.omit) 39 | fitX <- stats::lm(X ~ 1 + beta, na.action = stats::na.omit) 40 | fitY <- stats::lm(Y ~ 1 + beta, na.action = stats::na.omit) 41 | } # end of factors/no factors 42 | 43 | # HAC within loop. 44 | if (!hac) { 45 | sumfit <- summary(fit) 46 | sumfitX <- summary(fitX) 47 | sumfitY <- summary(fitY) 48 | alpha <- cbind(sumfitX$coef[row_return,1], sumfitY$coef[row_return,1]) 49 | pval <- sumfit$coef[row_return, 4] 50 | dalpha <- sumfit$coef[row_return, 1] 51 | tstat <- sumfit$coef[row_return, 3] 52 | } else { 53 | sumfit <- lmtest::coeftest(fit, vcov. = sandwich::vcovHAC(fit)) 54 | sumfitX <- lmtest::coeftest(fitX, vcov. = sandwich::vcovHAC(fitX)) 55 | sumfitY <- lmtest::coeftest(fitY, vcov. = sandwich::vcovHAC(fitY)) 56 | alpha <- c(sumfitX[row_return,1], sumfitY[row_return,1]) 57 | pval <- sumfit[row_return, 4] 58 | dalpha <- sumfit[row_return, 1] 59 | tstat <- sumfit[row_return, 3] 60 | } 61 | 62 | out <- list(n = nObs, 63 | alpha = alpha, 64 | dalpha = dalpha, 65 | tstat = tstat, 66 | pval = pval, 67 | screen_beta = screen_beta) 68 | class(out) <- "TESTING" 69 | return(out) 70 | } 71 | 72 | #' @name alphaTesting 73 | #' @title Testing the difference of alpha outperformance ratios 74 | #' @description Function which performs the testing of the difference of alpha outperformance ratios. 75 | #' @details The alpha measure (Treynor and Black 1973, Carhart 1997, Fung and Hsieh 76 | #' 2004) is one industry standard for measuring the absolute risk adjusted 77 | #' performance of hedge funds. This function performs 78 | #' the testing of alpha outperformance ratio difference for two funds. 79 | #' 80 | #' For the testing, only the intersection of non-\code{NA} observations for the 81 | #' two funds are used. 82 | #' 83 | ##' The argument \code{control} is a list that can supply any of the following 84 | #' components: 85 | #' \itemize{ 86 | #' \item \code{'hac'} Heteroscedastic-autocorrelation consistent 87 | #' standard errors. Default: \code{hac = FALSE}. 88 | #' } 89 | #' @param x Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 90 | #' values are allowed. 91 | #' @param y Vector (of length \eqn{T}) returns for the second fund. \code{NA} 92 | #' values are allowed. 93 | #' @param factors Matrix \eqn{(T \times K)}{(TxK)} of \eqn{T} returns for the 94 | #' \eqn{K} factors. \code{NA} values are allowed. 95 | #' @param screen_beta Boolean to screen all factors' coefficients (beta). 96 | #' Default: \code{screen_beta=FALSE} (i.e. only outputs the alpha). 97 | #' If \code{screen_beta=TRUE}, each element of the returned list will have a new first dimension 98 | #' representing each coefficient (the first one being alpha) 99 | #' 100 | #' @param control Control parameters (see *Details*). 101 | #' @return A list with the following components:\cr 102 | #' 103 | #' \code{n}: Number of non-\code{NA} concordant observations.\cr 104 | #' 105 | #' \code{alpha}: Vector (of length 2) of unconditional alpha outperformance ratios.\cr 106 | #' 107 | #' \code{dalpha}: alpha outperformance ratios difference.\cr 108 | #' 109 | #' \code{tstat}: t-stat of alpha outperformance ratios differences.\cr 110 | #' 111 | #' \code{pval}: pvalues of test of alpha outperformance ratios differences. 112 | #' @note Further details on the methodology with an application to the hedge 113 | #' fund industry is given in in Ardia and Boudt (2018). 114 | #' 115 | #' Some internal functions where adapted from Michael Wolf MATLAB code. 116 | #' @author David Ardia and Kris Boudt. 117 | #' @seealso \code{\link{alphaScreening}}.. 118 | #' @references 119 | #' Ardia, D., Boudt, K. (2015). 120 | #' Testing equality of modified Sharpe ratios. 121 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 122 | #' 123 | #' Ardia, D., Boudt, K. (2018). 124 | #' The peer performance ratios of hedge funds. 125 | #' \emph{Journal of Banking and Finance} \bold{87}, 351--368. 126 | #' 127 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 128 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 129 | #' \emph{Journal of Finance} \bold{65}(1), 179--216. 130 | #' 131 | #' Sharpe, W.F. (1994). 132 | #' The Sharpe ratio. 133 | #' \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 134 | #' 135 | #' Ledoit, O., Wolf, M. (2008). 136 | #' Robust performance hypothesis testing with the Sharpe ratio. 137 | #' \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 138 | #' 139 | #' Storey, J. (2002). 140 | #' A direct approach to false discovery rates. 141 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 142 | #' @keywords htest 143 | #' @examples 144 | #' ## Load the data (randomized data of monthly hedge fund returns) 145 | #' data("hfdata") 146 | #' x = hfdata[,1] 147 | #' y = hfdata[,2] 148 | #' 149 | #' ## Run alpha testing 150 | #' alphaTesting(x, y) 151 | 152 | #' @export 153 | #' @import compiler 154 | alphaTesting <- compiler::cmpfun(.alphaTesting) 155 | -------------------------------------------------------------------------------- /man/sharpeScreening.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sharpeScreening.R 3 | \name{sharpeScreening} 4 | \alias{sharpeScreening} 5 | \title{Screening using the Sharpe outperformance ratio} 6 | \usage{ 7 | sharpeScreening(X, control = list()) 8 | } 9 | \arguments{ 10 | \item{X}{Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 11 | funds. \code{NA} values are allowed.} 12 | 13 | \item{control}{Control parameters (see *Details*).} 14 | } 15 | \value{ 16 | A list with the following components:\cr 17 | 18 | \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 19 | observations.\cr 20 | 21 | \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 22 | 23 | \code{sharpe}: Vector (of length \eqn{N}) of unconditional Sharpe ratios.\cr 24 | 25 | \code{dsharpe}: Matrix (of size \eqn{N \times N}{NxN}) of Sharpe ratios 26 | differences.\cr 27 | 28 | \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 29 | 30 | \code{pval}: Matrix (of size \eqn{N \times N}{NxN}) of pvalues of test for Sharpe 31 | ratios differences.\cr 32 | 33 | \code{lambda}: vector (of length \eqn{N}) of lambda values.\cr 34 | 35 | \code{pizero}: vector (of length \eqn{N}) of probability of equal 36 | performance.\cr 37 | 38 | \code{pipos}: vector (of length \eqn{N}) of probability of outperformance 39 | performance.\cr 40 | 41 | \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 42 | performance. 43 | } 44 | \description{ 45 | Function which performs the screening of a universe of returns, and 46 | computes the Sharpe outperformance ratio. 47 | } 48 | \details{ 49 | The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 50 | absolute risk adjusted performance of hedge funds. We propose to complement 51 | the Sharpe ratio with the fund's outperformance ratio, defined as the 52 | percentage number of funds that have a significantly lower Sharpe ratio. In 53 | a pairwise testing framework, a fund can have a significantly higher Sharpe 54 | ratio because of luck. We correct for this by applying the false discovery 55 | rate approach by Storey (2002). 56 | 57 | For the testing, only the intersection of non-\code{NA} observations for the 58 | two funds are used. 59 | 60 | The methodology proceeds as follows: 61 | \itemize{ 62 | \item (1) compute all 63 | pairwise tests of Sharpe differences using the bootstrap approach of Ledoit 64 | and Wolf (2002). This means that for a universe of \eqn{N} funds, we perform 65 | \eqn{N(N-1)/2}{N*(N-1)/2} tests. The algorithm has been parallelized and the 66 | computational burden can be split across several cores. The number of cores 67 | can be defined in \code{control}, see below. 68 | \item (2) for each fund, the 69 | false discovery rate approach by Storey (2002) is used to determine the 70 | proportions over, equal, and underperforming funds, in terms of Sharpe ratio, 71 | in the database. 72 | } 73 | The argument \code{control} is a list that can supply any of the following 74 | components: 75 | \itemize{ 76 | \item \code{'type'} Asymptotic approach (\code{type = 1}) or 77 | studentized circular bootstrap approach (\code{type = 2}). Default: 78 | \code{type = 1}. 79 | \item \code{'ttype'} Test based on ratio (\code{type = 1}) 80 | or product (\code{type = 2}). Default: \code{type = 2}. 81 | \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 82 | errors. Default: \code{hac = FALSE}. 83 | \item \code{'nBoot'} Number of bootstrap replications for computing the p-value. Default: \code{nBoot = 84 | 499}. 85 | \item \code{'bBoot'} Block length in the circular bootstrap. Default: 86 | \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 87 | block-length. 88 | \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 89 | asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 90 | \item \code{'nCore'} Number of cores to be used. Default: \code{nCore = 1}. 91 | \item \code{'minObs'} Minimum number of concordant observations to compute 92 | the ratios. Default: \code{minObs = 10}. 93 | \item \code{'minObsPi'} Minimum 94 | number of observations to compute pi0. Default: \code{minObsPi = 1}. 95 | \item \code{'lambda'} Threshold value to compute pi0. Default: \code{lambda 96 | = NULL}, i.e. data driven choice. 97 | } 98 | } 99 | \note{ 100 | Further details on the methodology with an application to the hedge 101 | fund industry is given in in Ardia and Boudt (2018). 102 | 103 | Some internal functions where adapted from Michael Wolf MATLAB code. 104 | 105 | Application of the false discovery rate approach applied to the mutual fund 106 | industry has been presented in Barraz, Scaillet and Wermers (2010). 107 | } 108 | \examples{ 109 | ## Load the data (randomized data of monthly hedge fund returns) 110 | data("hfdata") 111 | rets = hfdata[,1:4] 112 | 113 | ## Sharpe screening 114 | sharpeScreening(rets, control = list(nCore = 1)) 115 | 116 | ## Sharpe screening with bootstrap and HAC standard deviation 117 | sharpeScreening(rets, control = list(nCore = 1, type = 2, hac = TRUE)) 118 | } 119 | \references{ 120 | Ardia, D., Boudt, K. (2015). 121 | Testing equality of modified Sharpe ratios. 122 | \emph{Finance Research Letters} \bold{13}, 97--104. 123 | 124 | Ardia, D., Boudt, K. (2018). 125 | The peer performance ratios of hedge funds. 126 | \emph{Journal of Banking and Finance} \bold{87}, 351--368. 127 | 128 | Barras, L., Scaillet, O., Wermers, R. (2010). 129 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 130 | \emph{Journal of Finance} \bold{65}(1), 179--216. 131 | 132 | Sharpe, W.F. (1994). 133 | The Sharpe ratio. 134 | \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 135 | 136 | Ledoit, O., Wolf, M. (2008). 137 | Robust performance hypothesis testing with the Sharpe ratio. 138 | \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 139 | 140 | Storey, J. (2002). 141 | A direct approach to false discovery rates. 142 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 143 | } 144 | \seealso{ 145 | \code{\link{sharpe}}, \code{\link{sharpeTesting}}, 146 | \code{\link{msharpeScreening}} and \code{\link{alphaScreening}}. 147 | } 148 | \author{ 149 | David Ardia and Kris Boudt. 150 | } 151 | \keyword{htest} 152 | -------------------------------------------------------------------------------- /man/msharpeScreening.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/msharpeScreening.R 3 | \name{msharpeScreening} 4 | \alias{msharpeScreening} 5 | \title{Screening using the modified Sharpe outperformance ratio} 6 | \usage{ 7 | msharpeScreening(X, level = 0.9, na.neg = TRUE, control = list()) 8 | } 9 | \arguments{ 10 | \item{X}{Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 11 | funds. \code{NA} values are allowed.} 12 | 13 | \item{level}{Modified Value-at-Risk level. Default: \code{level = 0.90}.} 14 | 15 | \item{na.neg}{A logical value indicating whether \code{NA} values should be 16 | returned if a negative modified Value-at-Risk is obtained. Default 17 | \code{na.neg = TRUE}.} 18 | 19 | \item{control}{Control parameters (see *Details*).} 20 | } 21 | \value{ 22 | A list with the following components:\cr 23 | 24 | \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 25 | observations.\cr 26 | 27 | \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 28 | 29 | \code{msharpe}: Vector (of length \eqn{N}) of unconditional modified Sharpe 30 | ratios.\cr 31 | 32 | \code{dmsharpe}: Matrix (of size \eqn{N \times N}{NxN}) of modified Sharpe 33 | ratios differences.\cr 34 | 35 | \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 36 | 37 | \code{pval}: Matrix (of size \eqn{N \times N}{NxN}) of p-values of test for 38 | modified Sharpe ratios differences.\cr 39 | 40 | \code{lambda}: Vector (of length \eqn{N}) of lambda values.\cr 41 | 42 | \code{pizero}: Vector (of length \eqn{N}) of probability of equal 43 | performance.\cr 44 | 45 | \code{pipos}: Vector (of length \eqn{N}) of probability of outperformance 46 | performance.\cr 47 | 48 | \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 49 | performance. 50 | } 51 | \description{ 52 | Function which performs the screening of a universe of returns, and 53 | computes the modified Sharpe outperformance ratio. 54 | } 55 | \details{ 56 | The modified Sharpe ratio (Favre and Galeano 2002, Gregoriou and Gueyie 57 | 2003) is one industry standard for measuring the absolute risk adjusted 58 | performance of hedge funds. We propose to complement the modified Sharpe 59 | ratio with the fund's outperformance ratio, defined as the percentage number 60 | of funds that have a significantly lower modified Sharpe ratio. In a 61 | pairwise testing framework, a fund can have a significantly higher modified 62 | Sharpe ratio because of luck. We correct for this by applying the false 63 | discovery rate approach by Storey (2002). 64 | 65 | For the testing, only the intersection of non-\code{NA} observations for the 66 | two funds are used. 67 | 68 | The argument \code{control} is a list that can supply any of the following 69 | components: 70 | \itemize{ 71 | \item \code{'type'} Asymptotic approach (\code{type = 1}) or 72 | studentized circular bootstrap approach (\code{type = 2}). Default: 73 | \code{type = 1}. 74 | \item \code{'ttype'} Test based on ratio (\code{type = 1}) 75 | or product (\code{type = 2}). Default: \code{type = 2}. 76 | \item \code{'hac'} heteroscedastic-autocorrelation consistent standard 77 | errors. Default: \code{hac = FALSE}. 78 | \item \code{'nBoot'} Number of 79 | bootstrap replications for computing the p-value. Default: \code{nBoot = 80 | 499}. 81 | \item \code{'bBoot'} Block length in the circular bootstrap. Default: 82 | \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 83 | block-length. 84 | \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 85 | asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 86 | \item \code{'nCore'} Number of cores to be used. Default: \code{nCore = 1}. 87 | \item \code{'minObs'} Minimum number of concordant observations to compute 88 | the ratios. Default: \code{minObs = 10}. 89 | \item \code{'minObsPi'} Minimum number of observations to compute pi0. Default: \code{minObsPi = 1}. 90 | \item \code{'lambda'} Threshold value to compute pi0. Default: \code{lambda 91 | = NULL}, i.e. data driven choice. 92 | } 93 | } 94 | \note{ 95 | Further details on the methodology with an application to the hedge 96 | fund industry is given in in Ardia and Boudt (2018). 97 | 98 | Some internal functions where adapted from Wolf's R code. 99 | 100 | Application of the false discovery rate approach applied to the mutual fund 101 | industry has been presented in Barraz, Scaillet and Wermers (2010). 102 | } 103 | \examples{ 104 | ## Load the data (randomized data of monthly hedge fund returns) 105 | data("hfdata") 106 | rets = hfdata[,1:4] 107 | 108 | ## Modified Sharpe screening 109 | msharpeScreening(rets, control = list(nCore = 1)) 110 | 111 | ## Modified Sharpe screening with bootstrap and HAC standard deviation 112 | msharpeScreening(rets, control = list(nCore = 1, type = 2, hac = TRUE)) 113 | } 114 | \references{ 115 | Ardia, D., Boudt, K. (2015). 116 | Testing equality of modified Sharpe ratios. 117 | \emph{Finance Research Letters} \bold{13}, 97--104. 118 | 119 | Ardia, D., Boudt, K. (2018). 120 | The peer performance ratios of hedge funds. 121 | \emph{Journal of Banking and Finance} \bold{87}, 351--368. 122 | 123 | Barras, L., Scaillet, O., Wermers, R. (2010). 124 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 125 | \emph{Journal of Finance} \bold{65}(1), 179--216. 126 | 127 | Favre, L., Galeano, J.A. (2002). 128 | Mean-modified Value-at-Risk Optimization with Hedge Funds. 129 | \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 130 | 131 | Gregoriou, G. N., Gueyie, J.-P. (2003). 132 | Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 133 | \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 134 | 135 | Ledoit, O., Wolf, M. (2008). 136 | Robust performance hypothesis testing with the Sharpe ratio. 137 | \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 138 | 139 | Storey, J. (2002). 140 | A direct approach to false discovery rates. 141 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 142 | } 143 | \seealso{ 144 | \code{\link{msharpe}}, \code{\link{msharpeTesting}}, 145 | \code{\link{sharpeScreening}} and \code{\link{alphaScreening}}. 146 | } 147 | \author{ 148 | David Ardia and Kris Boudt. 149 | } 150 | \keyword{htest} 151 | -------------------------------------------------------------------------------- /man/alphaScreening.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/alphaScreening.R 3 | \name{alphaScreening} 4 | \alias{alphaScreening} 5 | \title{Screening using the alpha outperformance ratio} 6 | \usage{ 7 | alphaScreening(X, factors = NULL, control = list(), screen_beta = FALSE) 8 | } 9 | \arguments{ 10 | \item{X}{Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 11 | funds. \code{NA} values are allowed.} 12 | 13 | \item{factors}{Matrix \eqn{(T \times K)}{(TxK)} of \eqn{T} returns for the 14 | \eqn{K} factors. \code{NA} values are allowed.} 15 | 16 | \item{control}{Control parameters (see *Details*).} 17 | 18 | \item{screen_beta}{Boolean to screen all factors' coefficients (beta). 19 | Default: \code{screen_beta=FALSE} (i.e. only outputs the alpha). 20 | If \code{screen_beta=TRUE}, each element of the returned list will have a new first dimension 21 | representing each coefficient (the first one being alpha)} 22 | } 23 | \value{ 24 | A list with the following components:\cr 25 | 26 | \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 27 | observations.\cr 28 | 29 | \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 30 | 31 | \code{alpha}: Vector (of length \eqn{N}) of unconditional alpha.\cr 32 | 33 | \code{dalpha}: Matrix (of size \eqn{N \times N}{NxN}) of alpha 34 | differences.\cr 35 | 36 | \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 37 | 38 | \code{pval}: Matrix (of size \eqn{N \times N}) of p-values of test for alpha 39 | differences.\cr 40 | 41 | \code{lambda}: Vector (of length \eqn{N}) of lambda values.\cr 42 | 43 | \code{pizero}: Vector (of length \eqn{N}) of probability of equal 44 | performance.\cr 45 | 46 | \code{pipos}: Vector (of length \eqn{N}) of probability of outperformance 47 | performance.\cr 48 | 49 | \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 50 | performance. 51 | } 52 | \description{ 53 | Function which performs the screening of a universe of returns, and 54 | computes the alpha outperformance ratio. 55 | } 56 | \details{ 57 | The alpha measure (Treynor and Black 1973, Carhart 1997, Fung and Hsieh 58 | 2004) is one industry standard for measuring the absolute risk adjusted 59 | performance of hedge funds. We propose to complement the alpha measure with 60 | the fund's alpha outperformance ratio, defined as the percentage number of 61 | funds that have a significantly lower alpha. In a pairwise testing 62 | framework, a fund can have a significantly higher alpha because of luck. We 63 | correct for this by applying the false discovery rate approach by Storey (2002). 64 | 65 | The methodology proceeds as follows: 66 | \itemize{ 67 | \item (1) compute all pairwise tests of alpha differences. This means that for a universe of 68 | \eqn{N} funds, we perform \eqn{N(N-1)/2}{N*(N-1)/2} tests. The algorithm has 69 | been parallelized and the computational burden can be split across several 70 | cores. The number of cores can be defined in \code{control}, see below. 71 | \item (2) for each fund, the false discovery rate approach by Storey (2002) 72 | is used to determine the proportions of over, equal, and underperforming 73 | funds, in terms of alpha, in the database.} 74 | The argument \code{control} is a list that can supply any of the following 75 | components: 76 | \itemize{ 77 | \item \code{'hac'} Heteroscedastic-autocorrelation consistent 78 | standard errors. Default: \code{hac = FALSE}. 79 | \item \code{'minObs'} Minimum number of concordant observations to compute the ratios. Default: 80 | \code{minObs = 10}. 81 | \item \code{'minObsPi'} Minimum number of observations 82 | for computing the p-values). Default: \code{minObsPi = 1}. 83 | \item \code{'nCore'} Number of cores used to perform the screening. Default: 84 | \code{nCore = 1}. 85 | \item \code{'lambda'} Threshold value to compute pi0. 86 | Default: \code{lambda = NULL}, i.e. data driven choice. 87 | } 88 | } 89 | \note{ 90 | Further details on the methodology with an application to the hedge 91 | fund industry is given in Ardia and Boudt (2018). 92 | 93 | Application of the false discovery rate approach applied to the mutual fund 94 | industry has been presented in Barras, Scaillet and Wermers (2010). 95 | 96 | Currently, the HAC asymptotic and studentized circular block bootstrap 97 | presented in Ledoit and Wolf (2008) are not supported by the 98 | \code{alphaScreening} function. 99 | } 100 | \examples{ 101 | ## Load the data (randomized data of monthly hedge fund returns) 102 | data("hfdata") 103 | rets = hfdata[,1:4] 104 | 105 | ## Run alpha screening 106 | ctr = list(nCore = 1) 107 | alphaScreening(rets, control = ctr) 108 | 109 | ## Run alpha screening with HAC standard deviation 110 | ctr = list(nCore = 1, hac = TRUE) 111 | alphaScreening(rets, control = ctr) 112 | } 113 | \references{ 114 | Ardia, D., Boudt, K. (2015). 115 | Testing equality of modified Sharpe ratios. 116 | \emph{Finance Research Letters} \bold{13}, pp.97--104. 117 | \doi{10.1016/j.frl.2015.02.008} 118 | 119 | Ardia, D., Boudt, K. (2018). 120 | The peer performance ratios of hedge funds. 121 | \emph{Journal of Banking and Finance} \bold{87}, pp.351-.368. 122 | \doi{10.1016/j.jbankfin.2017.10.014} 123 | 124 | Barras, L., Scaillet, O., Wermers, R. (2010). 125 | False discoveries in mutual fund performance: Measuring luck in estimated alphas. 126 | \emph{Journal of Finance} \bold{65}(1), pp.179--216. 127 | 128 | Carhart, M. (1997). 129 | On persistence in mutual fund performance. 130 | \emph{Journal of Finance} \bold{52}(1), pp.57--82. 131 | 132 | Fama, E., French, K. (2010). 133 | Luck versus skill in the cross-section of mutual fund returns. 134 | \emph{Journal of Finance} \bold{65}(5), pp.1915--1947. 135 | 136 | Fung, W., Hsieh, D. (2004). 137 | Hedge fund benchmarks: A risk based approach. 138 | \emph{Financial Analysts Journal} \bold{60}(5), pp.65--80. 139 | 140 | Storey, J. (2002). 141 | A direct approach to false discovery rates. 142 | \emph{Journal of the Royal Statistical Society B} \bold{64}(3), pp.479--498. 143 | 144 | Treynor, J. L., Black, F. (1973). 145 | How to use security analysis to improve portfolio selection. 146 | \emph{Journal of Business} \bold{46}(1), pp.66--86. 147 | } 148 | \seealso{ 149 | \code{\link{sharpeScreening}} and \code{\link{msharpeScreening}}. 150 | } 151 | \author{ 152 | David Ardia and Kris Boudt. 153 | } 154 | \keyword{htest} 155 | -------------------------------------------------------------------------------- /R/computePi.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions used to compute pi and lambda 2 | 3 | # #' @name .computePi 4 | # #' @title Compute pi0, pi+ and pi- 5 | # #' @importFrom stats qnorm 6 | # #' @import compiler 7 | .computePi <- function(pval, dalpha, tstat, lambda = 0.5, nBoot = 500, 8 | bpos = 0.4, bneg = 0.6, adjust = TRUE) { 9 | 10 | if (!is.matrix(pval) & !is.array(pval)) { 11 | pval <- matrix(pval, nrow = 1) 12 | } 13 | if (!is.matrix(dalpha) & !is.array(dalpha)) { 14 | dalpha <- matrix(dalpha, nrow = 1) 15 | } 16 | if (!is.matrix(tstat) & !is.array(tstat)) { 17 | tstat <- matrix(tstat, nrow = 1) 18 | } 19 | 20 | if(length(dim(pval))==2){ 21 | m <- nrow(pval) 22 | factors_dim <- 1 23 | dim(pval) <- c(1, dim(pval)) 24 | dim(dalpha) <- c(1, dim(dalpha)) 25 | dim(tstat) <- c(1, dim(tstat)) 26 | }else{ 27 | m <- dim(pval)[2] 28 | factors_dim <- dim(pval)[1] 29 | } 30 | 31 | # n1 = ncol(pval) # n + 1 funds 32 | if (length(lambda) == 1) { 33 | lambda <- rep(lambda, m) 34 | } 35 | 36 | pizero <- pipos <- pineg <- lambda_ <- matrix(rep(NA, m*factors_dim), nrow=factors_dim) 37 | 38 | for(factor_dim in 1:factors_dim){ 39 | for (i in 1:m) { 40 | pvali <- pval[factor_dim, i, ] 41 | dalphai <- dalpha[factor_dim, i, ] 42 | tstati <- tstat[factor_dim, i, ] 43 | if (all(is.na(pvali))) { 44 | next 45 | } 46 | if (is.null(lambda)) { 47 | lambdai <- computeOptLambda(pval = pvali, nBoot = nBoot, adjust = adjust) 48 | } else { 49 | lambdai <- lambda[i] 50 | } 51 | 52 | pizeroi <- computePizero(pvali, lambda = lambdai, adjust = adjust) 53 | idxOK <- !is.na(pvali) & !is.na(dalphai) 54 | n <- sum(idxOK) # number of peers 55 | if (n <= 1) { 56 | next 57 | } 58 | 59 | ni0 <- pizeroi * n 60 | hn <- round(0.5 * n) 61 | piposi <- pinegi <- 0 62 | 63 | # idxOKnPizeroi = idxOK & pvali >= lambdai we need to fix this 64 | qpos <- stats::qnorm(p = bpos) 65 | qneg <- stats::qnorm(p = bneg) 66 | 67 | if (sum(dalphai[idxOK] >= 0) >= hn) { 68 | piposi <- (1/n) * min((n - ni0), max(sum(tstati[idxOK] >= qpos) - 69 | ni0 * (1 - bpos), 0)) 70 | pinegi <- min(max(1 - pizeroi - piposi, 0), 1) # numerical stability 71 | } else { 72 | pinegi <- (1/n) * min((n - ni0), max(sum(tstati[idxOK] <= qneg) - 73 | ni0 * bneg, 0)) 74 | piposi <- min(max(1 - pizeroi - pinegi, 0), 1) # numerical stability 75 | } 76 | 77 | pizero[factor_dim, i] <- pizeroi 78 | pipos[factor_dim, i] <- piposi 79 | pineg[factor_dim, i] <- pinegi 80 | lambda_[factor_dim, i] <- lambdai 81 | } 82 | } 83 | 84 | if(factors_dim == 1){ 85 | pizero <- pizero[1, ] 86 | pipos <- pipos[1, ] 87 | pineg <- pineg[1, ] 88 | lambda_ <- lambda_[1, ] 89 | } 90 | out <- list(pizero = pizero, pipos = pipos, pineg = pineg, lambda = lambda_) 91 | return(out) 92 | } 93 | computePi <- compiler::cmpfun(.computePi) 94 | 95 | # #' @name .computePizero 96 | # #' @import compiler 97 | .computePizero <- function(pval, lambda = 0.5, adjust = TRUE) { 98 | if (!is.matrix(pval)) { 99 | pval <- matrix(pval, nrow = 1) 100 | } 101 | 102 | n <- ncol(pval) 103 | pizero <- apply(pval>=lambda, 1, mean, na.rm=TRUE) 104 | # pizero <- mean(pval >= lambda, na.rm = TRUE) 105 | pizero <- pizero * (1/(1 - lambda)) 106 | pizero[pizero > 1] <- 1 107 | # adjust pi using truncated binomial 108 | if (adjust) { 109 | pizero <- adjustPi(pizero, n = n, lambda = lambda) 110 | } 111 | 112 | return(pizero) 113 | } 114 | computePizero <- compiler::cmpfun(.computePizero) 115 | 116 | # #' @name .adjustPi 117 | # #' @title Adjust estimated pi0 using quadratif fit 118 | # #' @importFrom stats dnorm pnorm uniroot 119 | # #' @import compiler 120 | .adjustPi <- function(pi.hat, n = 100, lambda = 0.5) { 121 | 122 | asym.hatpi0 <- function(pi0) { 123 | npi0 <- pi0 * n 124 | nlambda <- npi0 * (1 - lambda) 125 | if (nlambda >= n) { 126 | hatpi0 <- pi0 127 | } else { 128 | s2 <- nlambda * (n - nlambda)/(n^3 * (1 - lambda)^2) 129 | s <- sqrt(s2) 130 | zcrit <- (1 - pi0)/s 131 | hatpi0 <- pi0 + s * (-stats::dnorm(zcrit) + (1 - stats::pnorm(zcrit)) * 132 | zcrit) 133 | } 134 | return(hatpi0) 135 | } 136 | 137 | asym.inverse <- function(f, lower = -100, upper = 100) { 138 | FUN <- function(y) stats::uniroot((function(x) f(x) - y), lower = lower, 139 | upper = upper)$root 140 | return(FUN) 141 | } 142 | 143 | asym.invpi0 <- asym.inverse(asym.hatpi0, lower = 1e-05, upper = 1.5) 144 | 145 | m <- length(pi.hat) 146 | out <- vector("double", m) 147 | for (i in 1:m) { 148 | tmp <- pi.hat[i] 149 | test.it <- try({ 150 | tmp <- asym.invpi0(pi.hat[i]) 151 | }, silent = TRUE) 152 | if (inherits(test.it, "try-error")) { 153 | tmp <- pi.hat[i] 154 | } 155 | tmp[tmp > 1] <- 1 156 | tmp[tmp < 0] <- 0 157 | out[i] <- tmp 158 | } 159 | return(out) 160 | } 161 | adjustPi <- compiler::cmpfun(.adjustPi) 162 | 163 | # #' @name .computeOptLambda 164 | # #' @title Compute optimal lamba values 165 | # #' @importFrom stats runif 166 | # #' @import compiler 167 | .computeOptLambda <- function(pval, nBoot = 500, adjust = TRUE) { 168 | if (!is.matrix(pval)) { 169 | pval <- matrix(pval, nrow = 1) 170 | } 171 | 172 | n <- nrow(pval) # number of funds 173 | 174 | vlambda <- seq(0.3, 0.7, 0.1) 175 | nvlambda <- length(vlambda) 176 | 177 | mpizero <- matrix(data = NA, nrow = n, ncol = nvlambda) 178 | for (i in 1:nvlambda) { 179 | mpizero[, i] <- computePizero(pval, lambda = vlambda[i], adjust = adjust) 180 | } 181 | # pi0hat 182 | vminpizero <- apply(mpizero, 1, "min") 183 | 184 | idx <- !is.nan(pval) & !is.na(pval) 185 | nObs <- rowSums(idx) 186 | 187 | bsunif <- matrix(stats::runif(max(nObs) * nBoot), max(nObs), nBoot) 188 | optlambda <- rep(0.5, n) 189 | 190 | for (i in 1:n) { 191 | nObsi <- nObs[i] 192 | if (nObsi > 0) { 193 | bsidx <- ceiling(bsunif[1:nObsi, 1:nBoot] * nObsi) 194 | pvalb <- stats::na.omit(pval[i, ]) 195 | pvalb <- matrix(pvalb[bsidx], nrow = nBoot) 196 | 197 | mpizerob <- matrix(data = NA, nrow = nBoot, ncol = nvlambda) 198 | for (j in 1:nvlambda) { 199 | 200 | mpizerob[, j] <- computePizero(pvalb, lambda = vlambda[j], 201 | adjust = adjust) 202 | } 203 | vMSE <- colSums((mpizerob - vminpizero[i])^2) 204 | 205 | optlambda[i] <- vlambda[which.min(vMSE)] 206 | } 207 | } 208 | 209 | return(optlambda) 210 | } 211 | computeOptLambda <- compiler::cmpfun(.computeOptLambda) 212 | -------------------------------------------------------------------------------- /R/functions.R: -------------------------------------------------------------------------------- 1 | ## Set of various R functions 2 | 3 | #@name processControl 4 | #@title Control parameters processsing 5 | processControl <- function(control) { 6 | if (!is.list(control) || length(control) == 0) { 7 | control <- list(type = 1, ttype = 2, hac = FALSE, nBoot = 249, 8 | bBoot = 1, pBoot = 1, nCore = 1, minObs = 10, minObsPi = 1, 9 | lambda = NULL) 10 | } 11 | nam <- names(control) 12 | if (!("type" %in% nam) || is.null(control$type)) { 13 | control$type <- 1 14 | } 15 | if (!("ttype" %in% nam) || is.null(control$ttype)) { 16 | control$ttype <- 2 17 | } 18 | if (!("hac" %in% nam) || is.null(control$hac)) { 19 | control$hac <- FALSE 20 | } 21 | if (!("nBoot" %in% nam) || is.null(control$nBoot)) { 22 | control$nBoot <- 499 23 | } 24 | if (!("bBoot" %in% nam) || is.null(control$bBoot)) { 25 | control$bBoot <- 1 26 | } 27 | if (!("pBoot" %in% nam) || is.null(control$pBoot)) { 28 | control$pBoot <- 1 29 | } 30 | if (!("nCore" %in% nam) || is.null(control$nCore)) { 31 | control$nCore <- 1 32 | } 33 | if (!("minObs" %in% nam) || is.null(control$minObs)) { 34 | control$minObs <- 10 35 | } 36 | if (!("minObsPi" %in% nam) || is.null(control$minObsPi)) { 37 | control$minObsPi <- 1 38 | } 39 | if (!("lambda" %in% nam) || is.null(control$lambda)) { 40 | control$lambda <- NULL 41 | } 42 | return(control) 43 | } 44 | 45 | #@name alphaFactor 46 | #@title Compute alpha factor 47 | alphaFactor <- function(X, factors = NULL) { 48 | fit <- stats::lm(X ~ 1 + factors) 49 | alpha <- as.vector(fit$coef[1, ]) 50 | return(alpha) 51 | } 52 | 53 | #' @name sharpe 54 | #' @title Compute Sharpe ratio 55 | #' @description Function which computes the Sharpe ratio. 56 | #' @details The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 57 | #' absolute risk adjusted performance of hedge funds. 58 | #' @param X Vector (of length \eqn{T}) or matrix (of size \eqn{T \times 59 | #' N}{TxN}) of returns for \eqn{N} funds. \code{NA} values are allowed. 60 | #' @param na.rm A logical value indicating whether \code{NA} values should be 61 | #' stripped before the computation. Default \code{na.rm = TRUE} 62 | #' @return A scalar or a vector (of size \eqn{N}) with the Sharpe ratios. 63 | #' @author David Ardia and Kris Boudt. 64 | #' @seealso \code{\link{sharpeTesting}}, \code{\link{sharpeScreening}} and 65 | #' \code{\link{msharpe}}. 66 | #' @references 67 | #' Ardia, D., Boudt, K. (2015). 68 | #' Testing equality of modified Sharpe ratios. 69 | #' \emph{Finance Research Letters} \bold{13}, pp.97--104. 70 | #' \doi{10.1016/j.frl.2015.02.008} 71 | #' 72 | #' Ardia, D., Boudt, K. (2016). 73 | #' The Peer Ratios Performance of Hedge Funds. 74 | #' \emph{Working paper}. 75 | #' \doi{10.2139/ssrn.2000901} 76 | #' 77 | #' Sharpe, W.F. (1994). 78 | #' The Sharpe ratio. 79 | #' \emph{Journal of Portfolio Management} \bold{21}(1), pp.49--58. 80 | #' \doi{10.3905/jpm.1994.409501} 81 | #' @keywords htest 82 | #' @examples 83 | #' ## Load the data 84 | #' data('hfdata') 85 | #' 86 | #' ## Compute the Sharpe ratio 87 | #' out = sharpe(hfdata) 88 | #' print(out) 89 | #' 90 | #' out = sharpe(hfdata, na.rm = FALSE) 91 | #' print(out) 92 | #' @export 93 | sharpe <- function(X, na.rm = TRUE) { 94 | X <- as.matrix(X) 95 | N <- ncol(X) 96 | tmp <- .sharpe(X, na.rm) 97 | out <- tmp$mu.hat/tmp$sig.hat 98 | return(out) 99 | } 100 | 101 | #@name .sharpe 102 | #@title Compute Sharpe ratio 103 | .sharpe <- function(X, na.rm) { 104 | nObs <- colSums(!is.nan(X), na.rm = na.rm) 105 | mu.hat <- colMeans(X, na.rm = na.rm) 106 | X_ <- sweep(x = X, MARGIN = 2, STATS = mu.hat, FUN = "-") 107 | sig.hat <- sqrt(colSums(X_^2, na.rm = na.rm)/(nObs - 1)) 108 | out <- list(mu.hat = mu.hat, sig.hat = sig.hat) 109 | return(out) 110 | } 111 | 112 | #' @name msharpe 113 | #' @title Compute modified Sharpe ratio 114 | #' @description Function which computes the modified Sharpe ratio 115 | #' @details The modified Sharpe ratio (Favre and Galeano 2002) is one industry 116 | #' standard for measuring the absolute risk adjusted performance of hedge 117 | #' funds. 118 | #' @param X Vector (of length \eqn{T}) or matrix (of size \eqn{T \times 119 | #' N}{TxN}) of returns. \code{NA} values are allowed. 120 | #' @param level Modified Value-at-Risk level. Default: \code{level = 0.90}. 121 | #' @param na.rm A logical value indicating whether \code{NA} values should be 122 | #' stripped before the computation. Default \code{na.rm = TRUE}. 123 | #' @param na.neg A logical value indicating whether \code{NA} values should be 124 | #' returned if a negative modified Value-at-Risk is obtained. Default 125 | #' \code{na.neg = TRUE}. 126 | #' @return Scalar or a vector (of size \eqn{N}) with the modified Sharpe 127 | #' ratios. 128 | #' @author David Ardia and Kris Boudt. 129 | #' @seealso \code{\link{msharpeTesting}}, \code{\link{msharpeScreening}} and 130 | #' \code{\link{sharpe}}. 131 | #' @references 132 | #' Ardia, D., Boudt, K. (2015). 133 | #' Testing equality of modified Sharpe ratios. 134 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 135 | #' 136 | #' Ardia, D., Boudt, K. (2018). 137 | #' The peer performance ratios of hedge Funds. 138 | #' \emph{Journal of Banking and Finance} \bold{87}, 351--368. 139 | #' 140 | #' Favre, L., Galeano, J.A. (2002). 141 | #' Mean-modified Value-at-Risk Optimization with Hedge Funds. 142 | #' \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 143 | #' 144 | #' Gregoriou, G. N., Gueyie, J.-P. (2003). 145 | #' Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 146 | #' \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 147 | #' @keywords htest 148 | #' @examples 149 | #' ## Load the data (randomized data of monthly hedge fund returns) 150 | #' data('hfdata') 151 | #' 152 | #' out = msharpe(hfdata) 153 | #' print(out) 154 | #' 155 | #' out = msharpe(hfdata, na.rm = FALSE) 156 | #' print(out) 157 | #' @export 158 | #' @importFrom stats qnorm 159 | msharpe <- function(X, level = 0.9, na.rm = TRUE, na.neg = TRUE) { 160 | X <- as.matrix(X) 161 | N <- ncol(X) 162 | tmp <- .msharpe(X, level, na.rm, na.neg) 163 | out <- tmp$m1/tmp$mVaR 164 | return(out) 165 | } 166 | 167 | .msharpe <- function(X, level, na.rm, na.neg) { 168 | m1 <- colMeans(X, na.rm = na.rm) 169 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1, FUN = "-") 170 | m2 <- colMeans(X_^2, na.rm = na.rm) 171 | m3 <- colMeans(X_^3, na.rm = na.rm) 172 | m4 <- colMeans(X_^4, na.rm = na.rm) 173 | za <- stats::qnorm(1 - level) 174 | skew <- m3/m2^(3/2) 175 | kurt <- (m4/m2^2) - 3 176 | mVaR <- -m1 + sqrt(m2) * (-za - (1/6) * (za^2 - 1) * skew - (1/24) * 177 | (za^3 - 3 * za) * kurt + (1/36) * (2 * za^3 - 5 * za) * skew^2) 178 | if (na.neg) { 179 | mVaR[mVaR < 0] <- NA 180 | } 181 | out <- list(m1 = m1, mVaR = mVaR) 182 | return(out) 183 | } 184 | 185 | # #' @name .infoFund 186 | # #' @import compiler 187 | .infoFund <- function(X, factors = NULL, level = NULL, na.rm = TRUE, na.neg = TRUE, screen_beta=FALSE) { 188 | X <- as.matrix(X) 189 | N <- ncol(X) 190 | nObs <- colSums(is.finite(X)) 191 | muX <- colMeans(X, na.rm = na.rm) 192 | rX <- sweep(x = X, MARGIN = 2, STATS = muX, FUN = "-") 193 | sigX <- sqrt(colSums(rX^2, na.rm = na.rm)/(nObs - 1)) 194 | sharpe_ <- muX/sigX 195 | 196 | # if (is.null(factors)) { 197 | # fit <- stats::lm(X ~ 1) 198 | # } else { 199 | # fit <- stats::lm(X ~ 1 + factors) 200 | # } 201 | # alpha_ <- as.vector(fit$coef[1, ]) #output is a vector. 202 | 203 | # FIX 204 | # preallocate space. 205 | if(screen_beta){ 206 | alpha_ <- matrix(NA, nrow=ncol(factors)+1, ncol=ncol(X)) 207 | }else{ 208 | alpha_ <- rep(NA, ncol(X)) 209 | } 210 | 211 | if (is.null(factors)) { 212 | for (col in 1:ncol(X)) { 213 | fit <- stats::lm(X[,col] ~ 1) 214 | alpha_[col] <- fit$coef[1] 215 | } 216 | } else { 217 | for (col in 1:ncol(X)) { 218 | fit <- stats::lm(X[,col] ~ 1 + factors) 219 | if(screen_beta){ 220 | alpha_[, col] <- fit$coef 221 | }else{ 222 | alpha_[col] <- fit$coef[1] 223 | } 224 | 225 | } 226 | } 227 | 228 | msharpe_ <- NULL 229 | if (!is.null(level)) { 230 | msharpe_ <- msharpe(X, level = level, na.rm = na.rm, na.neg = na.neg) 231 | } 232 | 233 | out <- list(nObs = nObs, mu = muX, sig = sigX, sharpe = sharpe_, alpha = alpha_, 234 | msharpe = msharpe_) 235 | return(out) 236 | } 237 | infoFund <- compiler::cmpfun(.infoFund) 238 | 239 | # #' @name .bootIndices 240 | # #' @import compiler 241 | .bootIndices <- function(T, nBoot, bBoot) { 242 | idsBoot <- matrix(data = NA, nrow = T, ncol = nBoot) 243 | if (bBoot == 1) { 244 | idsBoot <- matrix(sample.int(T, size = T * nBoot, replace = TRUE), 245 | nrow = T, ncol = nBoot) 246 | } else { 247 | for (i in 1:nBoot) { 248 | l <- floor(T/bBoot) 249 | ids <- c(1:T, 1:bBoot) 250 | seqb <- vector("integer", T) 251 | start.points <- sample.int(T, size = l, replace = TRUE) 252 | for (j in (1:l)) { 253 | start <- start.points[j] 254 | seqb[((j - 1) * bBoot + 1):(j * bBoot)] <- ids[start:(start + 255 | bBoot - 1)] 256 | } 257 | idsBoot[, i] <- seqb 258 | } 259 | } 260 | return(idsBoot) 261 | } 262 | bootIndices <- compiler::cmpfun(.bootIndices) 263 | -------------------------------------------------------------------------------- /R/sharpeScreening.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for Sharpe screening 2 | 3 | # #' @name .sharpeScreening 4 | # #' @import compiler 5 | .sharpeScreening <- function(X, control = list()) { 6 | 7 | # process control 8 | ctr <- processControl(control) 9 | 10 | # size of inputs and outputs 11 | T <- nrow(X) 12 | N <- ncol(X) 13 | pval <- dsharpe <- tstat <- matrix(data = NA, N, N) 14 | 15 | # determine which pairs can be compared (in a matrix way) 16 | Y <- 1 * (!is.nan(X) & !is.na(X)) 17 | YY <- crossprod(Y) #YY = t(Y) %*% Y # row i indicates how many observations in common with column k 18 | YY[YY < ctr$minObs] <- 0 19 | YY[YY > 0] <- 1 20 | liststocks <- c(1:nrow(YY))[rowSums(YY) > ctr$minObsPi] 21 | 22 | # determine bootstrap indices (do it before to speed up computations) 23 | bsids <- bootIndices(T, ctr$nBoot, ctr$bBoot) 24 | 25 | if (length(liststocks) > 1) { 26 | cl <- parallel::makeCluster(ctr$nCore) 27 | 28 | liststocks <- liststocks[1:(length(liststocks) - 1)] 29 | 30 | z <- parallel::clusterApplyLB(cl = cl, x = as.list(liststocks), fun = sharpeScreeningi, 31 | rdata = X, T = T, N = N, nBoot = ctr$nBoot, bsids = bsids, 32 | minObs = ctr$minObs, type = ctr$type, hac = ctr$hac, b = ctr$bBoot, 33 | ttype = ctr$ttype, pBoot = ctr$pBoot) 34 | parallel::stopCluster(cl) 35 | 36 | for (i in 1:length(liststocks)) { 37 | out <- z[[i]] 38 | id <- liststocks[i] 39 | pval[id, id:N] <- pval[id:N, id] <- out[[2]][id:N] 40 | dsharpe[id, id:N] <- out[[1]][id:N] 41 | dsharpe[id:N, id] <- -out[[1]][id:N] 42 | tstat[id, id:N] <- out[[3]][id:N] 43 | tstat[id:N, id] <- -out[[3]][id:N] 44 | } 45 | } 46 | 47 | # pi 48 | pi <- computePi(pval = pval, dalpha = dsharpe, tstat = tstat, lambda = ctr$lambda, 49 | nBoot = ctr$nBoot) 50 | 51 | # info on the funds 52 | info <- infoFund(X) 53 | 54 | # form output 55 | out <- list(n = info$nObs, npeer = colSums(!is.na(pval)), sharpe = info$sharpe, 56 | dsharpe = dsharpe, pval = pval, tstat = tstat, lambda = pi$lambda, 57 | pizero = pi$pizero, pipos = pi$pipos, pineg = pi$pineg) 58 | class(out) <- "SCREENING" 59 | 60 | return(out) 61 | } 62 | 63 | #' @name sharpeScreening 64 | #' @title Screening using the Sharpe outperformance ratio 65 | #' @description Function which performs the screening of a universe of returns, and 66 | #' computes the Sharpe outperformance ratio. 67 | #' @details The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 68 | #' absolute risk adjusted performance of hedge funds. We propose to complement 69 | #' the Sharpe ratio with the fund's outperformance ratio, defined as the 70 | #' percentage number of funds that have a significantly lower Sharpe ratio. In 71 | #' a pairwise testing framework, a fund can have a significantly higher Sharpe 72 | #' ratio because of luck. We correct for this by applying the false discovery 73 | #' rate approach by Storey (2002). 74 | #' 75 | #' For the testing, only the intersection of non-\code{NA} observations for the 76 | #' two funds are used. 77 | #' 78 | #' The methodology proceeds as follows: 79 | #' \itemize{ 80 | #' \item (1) compute all 81 | #' pairwise tests of Sharpe differences using the bootstrap approach of Ledoit 82 | #' and Wolf (2002). This means that for a universe of \eqn{N} funds, we perform 83 | #' \eqn{N(N-1)/2}{N*(N-1)/2} tests. The algorithm has been parallelized and the 84 | #' computational burden can be split across several cores. The number of cores 85 | #' can be defined in \code{control}, see below. 86 | #' \item (2) for each fund, the 87 | #' false discovery rate approach by Storey (2002) is used to determine the 88 | #' proportions over, equal, and underperforming funds, in terms of Sharpe ratio, 89 | #' in the database. 90 | #' } 91 | #' The argument \code{control} is a list that can supply any of the following 92 | #' components: 93 | #' \itemize{ 94 | #' \item \code{'type'} Asymptotic approach (\code{type = 1}) or 95 | #' studentized circular bootstrap approach (\code{type = 2}). Default: 96 | #' \code{type = 1}. 97 | #' \item \code{'ttype'} Test based on ratio (\code{type = 1}) 98 | #' or product (\code{type = 2}). Default: \code{type = 2}. 99 | #' \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 100 | #' errors. Default: \code{hac = FALSE}. 101 | #' \item \code{'nBoot'} Number of bootstrap replications for computing the p-value. Default: \code{nBoot = 102 | #' 499}. 103 | #' \item \code{'bBoot'} Block length in the circular bootstrap. Default: 104 | #' \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 105 | #' block-length. 106 | #' \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 107 | #' asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 108 | #' \item \code{'nCore'} Number of cores to be used. Default: \code{nCore = 1}. 109 | #' \item \code{'minObs'} Minimum number of concordant observations to compute 110 | #' the ratios. Default: \code{minObs = 10}. 111 | #' \item \code{'minObsPi'} Minimum 112 | #' number of observations to compute pi0. Default: \code{minObsPi = 1}. 113 | #' \item \code{'lambda'} Threshold value to compute pi0. Default: \code{lambda 114 | #' = NULL}, i.e. data driven choice. 115 | #' } 116 | #' @param X Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 117 | #' funds. \code{NA} values are allowed. 118 | #' @param control Control parameters (see *Details*). 119 | #' @return A list with the following components:\cr 120 | #' 121 | #' \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 122 | #' observations.\cr 123 | #' 124 | #' \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 125 | #' 126 | #' \code{sharpe}: Vector (of length \eqn{N}) of unconditional Sharpe ratios.\cr 127 | #' 128 | #' \code{dsharpe}: Matrix (of size \eqn{N \times N}{NxN}) of Sharpe ratios 129 | #' differences.\cr 130 | #' 131 | #' \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 132 | #' 133 | #' \code{pval}: Matrix (of size \eqn{N \times N}{NxN}) of pvalues of test for Sharpe 134 | #' ratios differences.\cr 135 | #' 136 | #' \code{lambda}: vector (of length \eqn{N}) of lambda values.\cr 137 | #' 138 | #' \code{pizero}: vector (of length \eqn{N}) of probability of equal 139 | #' performance.\cr 140 | #' 141 | #' \code{pipos}: vector (of length \eqn{N}) of probability of outperformance 142 | #' performance.\cr 143 | #' 144 | #' \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 145 | #' performance. 146 | #' @note Further details on the methodology with an application to the hedge 147 | #' fund industry is given in in Ardia and Boudt (2018). 148 | #' 149 | #' Some internal functions where adapted from Michael Wolf MATLAB code. 150 | #' 151 | #' Application of the false discovery rate approach applied to the mutual fund 152 | #' industry has been presented in Barraz, Scaillet and Wermers (2010). 153 | #' @author David Ardia and Kris Boudt. 154 | #' @seealso \code{\link{sharpe}}, \code{\link{sharpeTesting}}, 155 | #' \code{\link{msharpeScreening}} and \code{\link{alphaScreening}}. 156 | #' @references 157 | #' Ardia, D., Boudt, K. (2015). 158 | #' Testing equality of modified Sharpe ratios. 159 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 160 | #' 161 | #' Ardia, D., Boudt, K. (2018). 162 | #' The peer performance ratios of hedge funds. 163 | #' \emph{Journal of Banking and Finance} \bold{87}, 351--368. 164 | #' 165 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 166 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 167 | #' \emph{Journal of Finance} \bold{65}(1), 179--216. 168 | #' 169 | #' Sharpe, W.F. (1994). 170 | #' The Sharpe ratio. 171 | #' \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 172 | #' 173 | #' Ledoit, O., Wolf, M. (2008). 174 | #' Robust performance hypothesis testing with the Sharpe ratio. 175 | #' \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 176 | #' 177 | #' Storey, J. (2002). 178 | #' A direct approach to false discovery rates. 179 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 180 | #' @keywords htest 181 | #' @examples 182 | #' ## Load the data (randomized data of monthly hedge fund returns) 183 | #' data("hfdata") 184 | #' rets = hfdata[,1:4] 185 | #' 186 | #' ## Sharpe screening 187 | #' sharpeScreening(rets, control = list(nCore = 1)) 188 | #' 189 | #' ## Sharpe screening with bootstrap and HAC standard deviation 190 | #' sharpeScreening(rets, control = list(nCore = 1, type = 2, hac = TRUE)) 191 | #' @export 192 | #' @import compiler 193 | sharpeScreening <- compiler::cmpfun(.sharpeScreening) 194 | 195 | #@name .sharpeScreeningi 196 | #@title Sharpe ratio screening for fund i again its peers 197 | .sharpeScreeningi <- function(i, rdata, T, N, nBoot, bsids, minObs, type, 198 | hac, b, ttype, pBoot) { 199 | 200 | nPeer <- N - i 201 | X <- matrix(rdata[, i], nrow = T, ncol = nPeer) 202 | Y <- matrix(rdata[, (i + 1):N], nrow = T, ncol = nPeer) 203 | 204 | dXY <- X - Y 205 | idx <- (!is.nan(dXY) & !is.na(dXY)) 206 | X[!idx] <- NA 207 | Y[!idx] <- NA 208 | nObs <- colSums(idx) 209 | 210 | pvali <- dsharpei <- tstati <- rep(NA, N) 211 | 212 | k <- 0 213 | for (j in (i + 1):N) { 214 | k <- k + 1 215 | if (nObs[k] < minObs) { 216 | next 217 | } 218 | rets <- cbind(X[idx[, k], 1], Y[idx[, k], k]) 219 | 220 | if (type == 1) { 221 | tmp <- sharpeTestAsymptotic(rets, hac, ttype) 222 | } else { 223 | tmp <- sharpeTestBootstrap(rets, bsids, b, ttype, pBoot) 224 | } 225 | 226 | dsharpei[j] <- tmp$dsharpe 227 | pvali[j] <- tmp$pval 228 | tstati[j] <- tmp$tstat 229 | } 230 | 231 | out <- list(dsharpei = dsharpei, pvali = pvali, tstati = tstati) 232 | return(out) 233 | } 234 | sharpeScreeningi <- compiler::cmpfun(.sharpeScreeningi) 235 | -------------------------------------------------------------------------------- /R/msharpeScreening.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for modified Sharpe screening 2 | 3 | # #' @name .msharpeScreening 4 | # #' @title See msharpeScreening 5 | # #' @importFrom parallel makeCluster clusterApplyLB stopCluster 6 | # #' @import compiler 7 | .msharpeScreening <- function(X, level = 0.9, na.neg = TRUE, control = list()) { 8 | 9 | # process control 10 | ctr <- processControl(control) 11 | 12 | # size of inputs and outputs 13 | T <- nrow(X) 14 | N <- ncol(X) 15 | pval <- dmsharpe <- tstat <- matrix(data = NA, N, N) 16 | 17 | # determine which pairs can be compared (in a matrix way) 18 | Y <- 1 * (!is.nan(X) & !is.na(X)) 19 | YY <- crossprod(Y) #YY = t(Y) %*% Y # row i indicates how many observations in common with column k 20 | YY[YY < ctr$minObs] <- 0 21 | YY[YY > 0] <- 1 22 | liststocks <- c(1:nrow(YY))[rowSums(YY) > ctr$minObsPi] 23 | 24 | # determine bootstrap indices (do it before to speed up computations) 25 | bsids <- bootIndices(T, ctr$nBoot, ctr$bBoot) 26 | 27 | if (length(liststocks) > 1) { 28 | cl <- parallel::makeCluster(ctr$nCore) 29 | 30 | liststocks <- liststocks[1:(length(liststocks) - 1)] 31 | 32 | z <- parallel::clusterApplyLB(cl = cl, x = as.list(liststocks), fun = msharpeScreeningi, 33 | rdata = X, level = level, T = T, N = N, na.neg = na.neg, nBoot = ctr$nBoot, 34 | bsids = bsids, minObs = ctr$minObs, type = ctr$type, hac = ctr$hac, 35 | b = ctr$bBoot, ttype = ctr$ttype, pBoot = ctr$pBoot) 36 | parallel::stopCluster(cl) 37 | 38 | for (i in 1:length(liststocks)) { 39 | out <- z[[i]] 40 | id <- liststocks[i] 41 | pval[id, id:N] <- pval[id:N, id] <- out[[2]][id:N] 42 | dmsharpe[id, id:N] <- out[[1]][id:N] 43 | dmsharpe[id:N, id] <- -out[[1]][id:N] 44 | tstat[id, id:N] <- out[[3]][id:N] 45 | tstat[id:N, id] <- -out[[3]][id:N] 46 | } 47 | } 48 | 49 | # pi 50 | pi <- computePi(pval = pval, dalpha = dmsharpe, tstat = tstat, lambda = ctr$lambda, 51 | nBoot = ctr$nBoot) 52 | 53 | # info on the funds 54 | info <- infoFund(X, level = level, na.neg = na.neg) 55 | 56 | # form output 57 | out <- list(n = info$nObs, npeer = colSums(!is.na(pval)), msharpe = info$msharpe, 58 | dmsharpe = dmsharpe, pval = pval, tstat = tstat, lambda = pi$lambda, 59 | pizero = pi$pizero, pipos = pi$pipos, pineg = pi$pineg) 60 | class(out) <- "SCREENING" 61 | 62 | return(out) 63 | } 64 | 65 | #' @name msharpeScreening 66 | #' @title Screening using the modified Sharpe outperformance ratio 67 | #' @description Function which performs the screening of a universe of returns, and 68 | #' computes the modified Sharpe outperformance ratio. 69 | #' @details The modified Sharpe ratio (Favre and Galeano 2002, Gregoriou and Gueyie 70 | #' 2003) is one industry standard for measuring the absolute risk adjusted 71 | #' performance of hedge funds. We propose to complement the modified Sharpe 72 | #' ratio with the fund's outperformance ratio, defined as the percentage number 73 | #' of funds that have a significantly lower modified Sharpe ratio. In a 74 | #' pairwise testing framework, a fund can have a significantly higher modified 75 | #' Sharpe ratio because of luck. We correct for this by applying the false 76 | #' discovery rate approach by Storey (2002). 77 | #' 78 | #' For the testing, only the intersection of non-\code{NA} observations for the 79 | #' two funds are used. 80 | #' 81 | #' The argument \code{control} is a list that can supply any of the following 82 | #' components: 83 | #' \itemize{ 84 | #' \item \code{'type'} Asymptotic approach (\code{type = 1}) or 85 | #' studentized circular bootstrap approach (\code{type = 2}). Default: 86 | #' \code{type = 1}. 87 | #' \item \code{'ttype'} Test based on ratio (\code{type = 1}) 88 | #' or product (\code{type = 2}). Default: \code{type = 2}. 89 | #' \item \code{'hac'} heteroscedastic-autocorrelation consistent standard 90 | #' errors. Default: \code{hac = FALSE}. 91 | #' \item \code{'nBoot'} Number of 92 | #' bootstrap replications for computing the p-value. Default: \code{nBoot = 93 | #' 499}. 94 | #' \item \code{'bBoot'} Block length in the circular bootstrap. Default: 95 | #' \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 96 | #' block-length. 97 | #' \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 98 | #' asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 99 | #' \item \code{'nCore'} Number of cores to be used. Default: \code{nCore = 1}. 100 | #' \item \code{'minObs'} Minimum number of concordant observations to compute 101 | #' the ratios. Default: \code{minObs = 10}. 102 | #' \item \code{'minObsPi'} Minimum number of observations to compute pi0. Default: \code{minObsPi = 1}. 103 | #' \item \code{'lambda'} Threshold value to compute pi0. Default: \code{lambda 104 | #' = NULL}, i.e. data driven choice. 105 | #' } 106 | #' @param X Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 107 | #' funds. \code{NA} values are allowed. 108 | #' @param level Modified Value-at-Risk level. Default: \code{level = 0.90}. 109 | #' @param na.neg A logical value indicating whether \code{NA} values should be 110 | #' returned if a negative modified Value-at-Risk is obtained. Default 111 | #' \code{na.neg = TRUE}. 112 | #' @param control Control parameters (see *Details*). 113 | #' @return A list with the following components:\cr 114 | #' 115 | #' \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 116 | #' observations.\cr 117 | #' 118 | #' \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 119 | #' 120 | #' \code{msharpe}: Vector (of length \eqn{N}) of unconditional modified Sharpe 121 | #' ratios.\cr 122 | #' 123 | #' \code{dmsharpe}: Matrix (of size \eqn{N \times N}{NxN}) of modified Sharpe 124 | #' ratios differences.\cr 125 | #' 126 | #' \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 127 | #' 128 | #' \code{pval}: Matrix (of size \eqn{N \times N}{NxN}) of p-values of test for 129 | #' modified Sharpe ratios differences.\cr 130 | #' 131 | #' \code{lambda}: Vector (of length \eqn{N}) of lambda values.\cr 132 | #' 133 | #' \code{pizero}: Vector (of length \eqn{N}) of probability of equal 134 | #' performance.\cr 135 | #' 136 | #' \code{pipos}: Vector (of length \eqn{N}) of probability of outperformance 137 | #' performance.\cr 138 | #' 139 | #' \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 140 | #' performance. 141 | #' @note Further details on the methodology with an application to the hedge 142 | #' fund industry is given in in Ardia and Boudt (2018). 143 | #' 144 | #' Some internal functions where adapted from Wolf's R code. 145 | #' 146 | #' Application of the false discovery rate approach applied to the mutual fund 147 | #' industry has been presented in Barraz, Scaillet and Wermers (2010). 148 | #' @author David Ardia and Kris Boudt. 149 | #' @seealso \code{\link{msharpe}}, \code{\link{msharpeTesting}}, 150 | #' \code{\link{sharpeScreening}} and \code{\link{alphaScreening}}. 151 | #' @references 152 | #' Ardia, D., Boudt, K. (2015). 153 | #' Testing equality of modified Sharpe ratios. 154 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 155 | #' 156 | #' Ardia, D., Boudt, K. (2018). 157 | #' The peer performance ratios of hedge funds. 158 | #' \emph{Journal of Banking and Finance} \bold{87}, 351--368. 159 | #' 160 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 161 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 162 | #' \emph{Journal of Finance} \bold{65}(1), 179--216. 163 | #' 164 | #' Favre, L., Galeano, J.A. (2002). 165 | #' Mean-modified Value-at-Risk Optimization with Hedge Funds. 166 | #' \emph{Journal of Alternative Investments} \bold{5}(2), 21--25. 167 | #' 168 | #' Gregoriou, G. N., Gueyie, J.-P. (2003). 169 | #' Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 170 | #' \emph{Journal of Wealth Management} \bold{6}(3), 77--83. 171 | #' 172 | #' Ledoit, O., Wolf, M. (2008). 173 | #' Robust performance hypothesis testing with the Sharpe ratio. 174 | #' \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 175 | #' 176 | #' Storey, J. (2002). 177 | #' A direct approach to false discovery rates. 178 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 179 | #' @keywords htest 180 | #' @examples 181 | #' ## Load the data (randomized data of monthly hedge fund returns) 182 | #' data("hfdata") 183 | #' rets = hfdata[,1:4] 184 | #' 185 | #' ## Modified Sharpe screening 186 | #' msharpeScreening(rets, control = list(nCore = 1)) 187 | #' 188 | #' ## Modified Sharpe screening with bootstrap and HAC standard deviation 189 | #' msharpeScreening(rets, control = list(nCore = 1, type = 2, hac = TRUE)) 190 | #' @export 191 | #' @importFrom compiler cmpfun 192 | msharpeScreening <- compiler::cmpfun(.msharpeScreening) 193 | 194 | #@name .msharpeScreeningi 195 | #@title Sharpe ratio screening for fund i again its peers 196 | .msharpeScreeningi <- function(i, rdata, level, T, N, nBoot, bsids, minObs, 197 | na.neg, type, hac, b, ttype, pBoot) { 198 | 199 | nPeer <- N - i 200 | X <- matrix(rdata[, i], nrow = T, ncol = nPeer) 201 | Y <- matrix(rdata[, (i + 1):N], nrow = T, ncol = nPeer) 202 | 203 | dXY <- X - Y 204 | idx <- (!is.nan(dXY) & !is.na(dXY)) 205 | X[!idx] <- NA 206 | Y[!idx] <- NA 207 | nObs <- colSums(idx) 208 | 209 | pvali <- dmsharpei <- tstati <- rep(NA, N) 210 | 211 | k <- 0 212 | for (j in (i + 1):N) { 213 | k <- k + 1 214 | if (nObs[k] < minObs) { 215 | next 216 | } 217 | rets <- cbind(X[idx[, k], 1], Y[idx[, k], k]) 218 | 219 | if (type == 1) { 220 | tmp <- msharpeTestAsymptotic(rets, level, na.neg, hac, ttype) 221 | } else { 222 | tmp <- msharpeTestBootstrap(rets, level, na.neg, bsids, b, 223 | ttype, pBoot) 224 | } 225 | 226 | dmsharpei[j] <- tmp$dmsharpe 227 | pvali[j] <- tmp$pval 228 | tstati[j] <- tmp$tstat 229 | } 230 | 231 | out <- list(dmsharpei = dmsharpei, pvali = pvali, tstati = tstati) 232 | return(out) 233 | } 234 | msharpeScreeningi <- compiler::cmpfun(.msharpeScreeningi) 235 | -------------------------------------------------------------------------------- /R/alphaScreening.R: -------------------------------------------------------------------------------- 1 | ## Set of R function for alpha screening 2 | 3 | #@name .alphaScreening 4 | #@description See alphaScreening 5 | .alphaScreening <- function(X, factors = NULL, control = list(), 6 | screen_beta=FALSE) { 7 | 8 | # process control 9 | ctr <- processControl(control) 10 | 11 | T <- nrow(X) 12 | N <- ncol(X) 13 | 14 | if (screen_beta & !is.null(factors)) { 15 | row_return <- 1:(1 + ncol(factors)) 16 | pval <- dalpha <- tstat <- array(rep(NA, N * N * (1 + ncol(factors))), 17 | dim = c((1 + ncol(factors)), N, N)) 18 | } else { 19 | row_return <- 1 20 | pval <- dalpha <- tstat <- array(rep(NA, N*N), dim = c(1, N, N)) 21 | } 22 | # pval <- dalpha <- tstat <- matrix(data = NA, N, N) 23 | 24 | # determine which pairs can be compared (in a matrix way) 25 | Y <- 1 * (!is.nan(X) & !is.na(X)) 26 | YY <- crossprod(Y) #YY = t(Y) %*% Y # row i indicates how many observations in common with column k 27 | YY[YY < ctr$minObs] <- 0 28 | YY[YY > 0] <- 1 29 | liststocks <- c(1:nrow(YY))[rowSums(YY) > ctr$minObsPi] 30 | 31 | if (length(liststocks) > 1) { 32 | cl <- parallel::makeCluster(ctr$nCore) 33 | 34 | liststocks <- liststocks[1:(length(liststocks) - 1)] 35 | 36 | z <- parallel::clusterApplyLB(cl = cl, x = as.list(liststocks), 37 | fun = alphaScreeningi, 38 | rdata = X, factors = factors, T = T, N = N, 39 | hac = ctr$hac, screen_beta) 40 | 41 | parallel::stopCluster(cl) 42 | 43 | for (i in 1:length(liststocks)) { 44 | out <- z[[i]] 45 | id <- liststocks[i] 46 | pval[row_return, id, id:N] <- pval[row_return, id:N, id] <- out[[2]][row_return, id:N] 47 | dalpha[row_return, id, id:N] <- out[[1]][row_return, id:N] 48 | dalpha[row_return, id:N, id] <- -out[[1]][row_return, id:N] 49 | tstat[row_return, id, id:N] <- out[[3]][row_return, id:N] 50 | tstat[row_return, id:N, id] <- -out[[3]][row_return, id:N] 51 | } 52 | } 53 | 54 | # pi 55 | pi <- computePi(pval = pval, dalpha = dalpha, tstat = tstat, lambda = ctr$lambda, 56 | nBoot = ctr$nBoot) 57 | 58 | 59 | # info on the funds 60 | info <- infoFund(X, factors = factors, screen_beta = screen_beta) 61 | 62 | if (screen_beta == FALSE) { 63 | pval <- pval[1, , ] 64 | dalpha <- dalpha[1, , ] 65 | tstat <- tstat[1, , ] 66 | npeer <- colSums(!is.na(pval)) 67 | } else { 68 | npeer <- apply(!is.na(pval), c(1, 3), sum) 69 | } 70 | 71 | # form output 72 | out <- list(n = info$nObs, npeer = npeer, alpha = info$alpha, 73 | dalpha = dalpha, pval = pval, tstat = tstat, lambda = pi$lambda, 74 | pizero = pi$pizero, pipos = pi$pipos, pineg = pi$pineg) 75 | class(out) <- "SCREENING" 76 | 77 | return(out) 78 | } 79 | 80 | #' @name alphaScreening 81 | #' @title Screening using the alpha outperformance ratio 82 | #' @description Function which performs the screening of a universe of returns, and 83 | #' computes the alpha outperformance ratio. 84 | #' @details The alpha measure (Treynor and Black 1973, Carhart 1997, Fung and Hsieh 85 | #' 2004) is one industry standard for measuring the absolute risk adjusted 86 | #' performance of hedge funds. We propose to complement the alpha measure with 87 | #' the fund's alpha outperformance ratio, defined as the percentage number of 88 | #' funds that have a significantly lower alpha. In a pairwise testing 89 | #' framework, a fund can have a significantly higher alpha because of luck. We 90 | #' correct for this by applying the false discovery rate approach by Storey (2002). 91 | #' 92 | #' The methodology proceeds as follows: 93 | #' \itemize{ 94 | #' \item (1) compute all pairwise tests of alpha differences. This means that for a universe of 95 | #' \eqn{N} funds, we perform \eqn{N(N-1)/2}{N*(N-1)/2} tests. The algorithm has 96 | #' been parallelized and the computational burden can be split across several 97 | #' cores. The number of cores can be defined in \code{control}, see below. 98 | #' \item (2) for each fund, the false discovery rate approach by Storey (2002) 99 | #' is used to determine the proportions of over, equal, and underperforming 100 | #' funds, in terms of alpha, in the database.} 101 | #' The argument \code{control} is a list that can supply any of the following 102 | #' components: 103 | #' \itemize{ 104 | #' \item \code{'hac'} Heteroscedastic-autocorrelation consistent 105 | #' standard errors. Default: \code{hac = FALSE}. 106 | #' \item \code{'minObs'} Minimum number of concordant observations to compute the ratios. Default: 107 | #' \code{minObs = 10}. 108 | #' \item \code{'minObsPi'} Minimum number of observations 109 | #' for computing the p-values). Default: \code{minObsPi = 1}. 110 | #' \item \code{'nCore'} Number of cores used to perform the screening. Default: 111 | #' \code{nCore = 1}. 112 | #' \item \code{'lambda'} Threshold value to compute pi0. 113 | #' Default: \code{lambda = NULL}, i.e. data driven choice. 114 | #' } 115 | #' @param X Matrix \eqn{(T \times N)}{(TxN)} of \eqn{T} returns for the \eqn{N} 116 | #' funds. \code{NA} values are allowed. 117 | #' @param factors Matrix \eqn{(T \times K)}{(TxK)} of \eqn{T} returns for the 118 | #' \eqn{K} factors. \code{NA} values are allowed. 119 | #' @param control Control parameters (see *Details*). 120 | #' @param screen_beta Boolean to screen all factors' coefficients (beta). 121 | #' Default: \code{screen_beta=FALSE} (i.e. only outputs the alpha). 122 | #' If \code{screen_beta=TRUE}, each element of the returned list will have a new first dimension 123 | #' representing each coefficient (the first one being alpha) 124 | #' @return A list with the following components:\cr 125 | #' 126 | #' \code{n}: Vector (of length \eqn{N}) of number of non-\code{NA} 127 | #' observations.\cr 128 | #' 129 | #' \code{npeer}: Vector (of length \eqn{N}) of number of available peers.\cr 130 | #' 131 | #' \code{alpha}: Vector (of length \eqn{N}) of unconditional alpha.\cr 132 | #' 133 | #' \code{dalpha}: Matrix (of size \eqn{N \times N}{NxN}) of alpha 134 | #' differences.\cr 135 | #' 136 | #' \code{tstat}: Matrix (of size \eqn{N \times N}{NxN}) of t-statistics.\cr 137 | #' 138 | #' \code{pval}: Matrix (of size \eqn{N \times N}) of p-values of test for alpha 139 | #' differences.\cr 140 | #' 141 | #' \code{lambda}: Vector (of length \eqn{N}) of lambda values.\cr 142 | #' 143 | #' \code{pizero}: Vector (of length \eqn{N}) of probability of equal 144 | #' performance.\cr 145 | #' 146 | #' \code{pipos}: Vector (of length \eqn{N}) of probability of outperformance 147 | #' performance.\cr 148 | #' 149 | #' \code{pineg}: Vector (of length \eqn{N}) of probability of underperformance 150 | #' performance. 151 | #' @note Further details on the methodology with an application to the hedge 152 | #' fund industry is given in Ardia and Boudt (2018). 153 | #' 154 | #' Application of the false discovery rate approach applied to the mutual fund 155 | #' industry has been presented in Barras, Scaillet and Wermers (2010). 156 | #' 157 | #' Currently, the HAC asymptotic and studentized circular block bootstrap 158 | #' presented in Ledoit and Wolf (2008) are not supported by the 159 | #' \code{alphaScreening} function. 160 | #' @author David Ardia and Kris Boudt. 161 | #' @seealso \code{\link{sharpeScreening}} and \code{\link{msharpeScreening}}. 162 | #' @references 163 | #' Ardia, D., Boudt, K. (2015). 164 | #' Testing equality of modified Sharpe ratios. 165 | #' \emph{Finance Research Letters} \bold{13}, pp.97--104. 166 | #' \doi{10.1016/j.frl.2015.02.008} 167 | #' 168 | #' Ardia, D., Boudt, K. (2018). 169 | #' The peer performance ratios of hedge funds. 170 | #' \emph{Journal of Banking and Finance} \bold{87}, pp.351-.368. 171 | #' \doi{10.1016/j.jbankfin.2017.10.014} 172 | #' 173 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 174 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 175 | #' \emph{Journal of Finance} \bold{65}(1), pp.179--216. 176 | #' 177 | #' Carhart, M. (1997). 178 | #' On persistence in mutual fund performance. 179 | #' \emph{Journal of Finance} \bold{52}(1), pp.57--82. 180 | #' 181 | #' Fama, E., French, K. (2010). 182 | #' Luck versus skill in the cross-section of mutual fund returns. 183 | #' \emph{Journal of Finance} \bold{65}(5), pp.1915--1947. 184 | #' 185 | #' Fung, W., Hsieh, D. (2004). 186 | #' Hedge fund benchmarks: A risk based approach. 187 | #' \emph{Financial Analysts Journal} \bold{60}(5), pp.65--80. 188 | #' 189 | #' Storey, J. (2002). 190 | #' A direct approach to false discovery rates. 191 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), pp.479--498. 192 | #' 193 | #' Treynor, J. L., Black, F. (1973). 194 | #' How to use security analysis to improve portfolio selection. 195 | #' \emph{Journal of Business} \bold{46}(1), pp.66--86. 196 | #' @keywords htest 197 | #' @examples 198 | #' ## Load the data (randomized data of monthly hedge fund returns) 199 | #' data("hfdata") 200 | #' rets = hfdata[,1:4] 201 | #' 202 | #' ## Run alpha screening 203 | #' ctr = list(nCore = 1) 204 | #' alphaScreening(rets, control = ctr) 205 | #' 206 | #' ## Run alpha screening with HAC standard deviation 207 | #' ctr = list(nCore = 1, hac = TRUE) 208 | #' alphaScreening(rets, control = ctr) 209 | #' @export 210 | #' @importFrom parallel makeCluster clusterApplyLB stopCluster 211 | #' @importFrom compiler cmpfun 212 | alphaScreening <- compiler::cmpfun(.alphaScreening) 213 | 214 | # #' @name .alphaScreeningi 215 | # #' @title Screening for fund i again its peers 216 | # #' @importFrom stats lm na.omit 217 | # #' @importFrom lmtest coeftest 218 | # #' @importFrom sandwich vcovHAC 219 | .alphaScreeningi <- function(i, rdata, factors, T, N, hac, screen_beta=FALSE) { 220 | 221 | 222 | if(screen_beta & !is.null(factors)){ 223 | row_return <- 1:(1+ncol(factors)) 224 | pvali <- dalphai <- tstati <- matrix(rep(NA, N*(1+ncol(factors))), ncol=N) 225 | }else{ 226 | row_return <- 1 227 | pvali <- dalphai <- tstati <- matrix(rep(NA, N), ncol=N) 228 | } 229 | 230 | nPeer <- N - i 231 | X <- matrix(rdata[, i], nrow = T, ncol = nPeer) 232 | Y <- matrix(rdata[, (i + 1):N], nrow = T, ncol = nPeer) 233 | dXY <- X - Y 234 | 235 | # Additional filter: Nonoverlapping observations 236 | # Iterate over selIds 237 | D <- !is.na(dXY) 238 | # See that it has no shared obs with the second one. 239 | selId.in <- which(colSums(D) != 0) 240 | selId.out <- selId.in + i 241 | 242 | if (nPeer == 1) { 243 | if (is.null(factors)) { 244 | fit <- stats::lm(dXY ~ 1, na.action = stats::na.omit) 245 | } else { 246 | fit <- stats::lm(dXY ~ 1 + factors, na.action = stats::na.omit) 247 | } # end of factors/no factors 248 | 249 | # HAC within loop. 250 | if (!hac) { 251 | sumfit <- summary(fit) 252 | pvali[row_return, N] <- sumfit$coef[row_return, 4] 253 | dalphai[row_return, N] <- sumfit$coef[row_return, 1] 254 | tstati[row_return, N] <- sumfit$coef[row_return, 3] 255 | } else { 256 | sumfit <- lmtest::coeftest(fit, vcov. = sandwich::vcovHAC(fit)) 257 | pvali[row_return, N] <- sumfit[row_return, 4] 258 | dalphai[row_return, N] <- sumfit[row_return, 1] 259 | tstati[row_return, N] <- sumfit[row_return, 3] 260 | } 261 | } else { 262 | # end of nPeer == 1 263 | 264 | # k selects the columns in dXY 265 | # k will match with redefined selId.in 266 | # j plugs them into the correct list, but it uses an efficient allocation "(i + 1):N" 267 | # j matches with selId.out 268 | # We make a correction for k in (20190004) 269 | 270 | # k <- 1 271 | # for (j in (i + 1):N) { 272 | for (idx in 1:length(selId.in)) { 273 | 274 | # proper indices 275 | k <- selId.in[idx] 276 | j <- selId.out[idx] 277 | 278 | if (is.null(factors)) { 279 | fit <- stats::lm(dXY[, k] ~ 1, na.action = stats::na.omit) 280 | } else { 281 | fit <- stats::lm(dXY[, k] ~ 1 + factors, na.action = stats::na.omit) 282 | } # end of factors/no factors 283 | 284 | # HAC within loop. 285 | if (!hac) { 286 | sumfit <- summary(fit) 287 | pvali[row_return, j] <- sumfit$coef[row_return, 4] 288 | dalphai[row_return, j] <- sumfit$coef[row_return, 1] 289 | tstati[row_return, j] <- sumfit$coef[row_return, 3] 290 | } else{ 291 | sumfit <- lmtest::coeftest(fit, vcov. = sandwich::vcovHAC(fit)) 292 | pvali[row_return, j] <- sumfit[row_return, 4] 293 | dalphai[row_return, j] <- sumfit[row_return, 1] 294 | tstati[row_return, j] <- sumfit[row_return, 3] 295 | } 296 | 297 | # k <- k + 1 298 | } 299 | } 300 | 301 | out <- list(dalphai = dalphai, pvali = pvali, tstati = tstati) 302 | return(out) 303 | } 304 | alphaScreeningi <- compiler::cmpfun(.alphaScreeningi) 305 | -------------------------------------------------------------------------------- /R/sharpeTesting.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for Sharpe ratio testing 2 | 3 | # #' @name .sharpeTesting 4 | # #' @import compiler 5 | .sharpeTesting <- function(x, y, control = list()) { 6 | 7 | x <- as.matrix(x) 8 | y <- as.matrix(y) 9 | 10 | # process control parameters 11 | ctr <- processControl(control) 12 | 13 | # check if enough data are available for testing 14 | dxy <- x - y 15 | idx <- (!is.nan(dxy) & !is.na(dxy)) 16 | rets <- cbind(x[idx], y[idx]) 17 | T <- sum(idx) 18 | if (T < ctr$minObs) { 19 | stop("intersection of 'x' and 'y' is shorter than 'minObs'") 20 | } 21 | 22 | # sharpe testing 23 | if (ctr$type == 1) { 24 | # ==> asymptotic approach 25 | tmp <- sharpeTestAsymptotic(rets, ctr$hac, ctr$ttype) 26 | } else { 27 | # ==> bootstrap approach (iid and circular block bootstrap) 28 | if (ctr$bBoot == 0) { 29 | ctr$bBoot <- sharpeBlockSize(x, y, ctr) 30 | } 31 | bsids <- bootIndices(T, ctr$nBoot, ctr$bBoot) 32 | tmp <- sharpeTestBootstrap(rets, bsids, ctr$bBoot, ctr$ttype, ctr$pBoot) 33 | } 34 | 35 | # info on the funds 36 | info <- infoFund(rets) 37 | 38 | ## form output 39 | out <- list(n = T, sharpe = info$sharpe, dsharpe = -diff(info$sharpe), 40 | tstat = as.vector(tmp$tstat), pval = as.vector(tmp$pval)) 41 | class(out) <- "TESTING" 42 | return(out) 43 | } 44 | 45 | #' @name sharpeTesting 46 | #' @title Testing the difference of Sharpe ratios 47 | #' @description Function which performs the testing of the difference of Sharpe ratios. 48 | #' @details The Sharpe ratio (Sharpe 1992) is one industry standard for measuring the 49 | #' absolute risk adjusted performance of hedge funds. This function performs 50 | #' the testing of Sharpe ratio difference for two funds using the approach by 51 | #' Ledoit and Wolf (2002). 52 | #' 53 | #' For the testing, only the intersection of non-\code{NA} observations for the 54 | #' two funds are used. 55 | #' 56 | #' The argument \code{control} is a list that can supply any of the following 57 | #' components: 58 | #' \itemize{ 59 | #' \item \code{'type'} Asymptotic approach (\code{type = 1}) or 60 | #' studentized circular bootstrap approach (\code{type = 2}). Default: 61 | #' \code{type = 1}. 62 | #' \item \code{'ttype'} Test based on ratio (\code{type = 1}) 63 | #' or product (\code{type = 2}). Default: \code{type = 2}. 64 | #' \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 65 | #' errors. Default: \code{hac = FALSE}. 66 | #' \item \code{'nBoot'} Number of bootstrap replications for computing the p-value. Default: \code{nBoot = 67 | #' 499}. 68 | #' \item \code{'bBoot'} Block length in the circular bootstrap. Default: 69 | #' \code{bBoot = 1}, i.e. iid bootstrap. \code{bBoot = 0} uses optimal 70 | #' block-length. 71 | #' \item \code{'pBoot'} Symmetric p-value (\code{pBoot = 1}) or 72 | #' asymmetric p-value (\code{pBoot = 2}). Default: \code{pBoot = 1}. 73 | #' } 74 | #' @param x Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 75 | #' values are allowed. 76 | #' @param y Vector (of length \eqn{T}) returns for the second fund. \code{NA} 77 | #' values are allowed. 78 | #' @param control Control parameters (see *Details*). 79 | #' @return A list with the following components:\cr 80 | #' 81 | #' \code{n}: Number of non-\code{NA} concordant observations.\cr 82 | #' 83 | #' \code{sharpe}: Vector (of length 2) of unconditional Sharpe ratios.\cr 84 | #' 85 | #' \code{dsharpe}: Sharpe ratios difference.\cr 86 | #' 87 | #' \code{tstat}: t-stat of Sharpe ratios differences.\cr 88 | #' 89 | #' \code{pval}: pvalues of test of Sharpe ratios differences. 90 | #' @note Further details on the methodology with an application to the hedge 91 | #' fund industry is given in in Ardia and Boudt (2018). 92 | #' 93 | #' Some internal functions where adapted from Michael Wolf MATLAB code. 94 | #' @author David Ardia and Kris Boudt. 95 | #' @seealso \code{\link{sharpe}}, \code{\link{sharpeScreening}} and 96 | #' \code{\link{msharpeTesting}}. 97 | #' @references 98 | #' Ardia, D., Boudt, K. (2015). 99 | #' Testing equality of modified Sharpe ratios. 100 | #' \emph{Finance Research Letters} \bold{13}, 97--104. 101 | #' 102 | #' Ardia, D., Boudt, K. (2018). 103 | #' The peer performance ratios of hedge funds. 104 | #' \emph{Journal of Banking and Finance} \bold{87}, 351-.368. 105 | #' 106 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 107 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 108 | #' \emph{Journal of Finance} \bold{65}(1), 179--216. 109 | #' 110 | #' Sharpe, W.F. (1994). 111 | #' The Sharpe ratio. 112 | #' \emph{Journal of Portfolio Management} \bold{21}(1), 49--58. 113 | #' 114 | #' Ledoit, O., Wolf, M. (2008). 115 | #' Robust performance hypothesis testing with the Sharpe ratio. 116 | #' \emph{Journal of Empirical Finance} \bold{15}(5), 850--859. 117 | #' 118 | #' Storey, J. (2002). 119 | #' A direct approach to false discovery rates. 120 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), 479--498. 121 | #' @keywords htest 122 | #' @examples 123 | #' ## Load the data (randomized data of monthly hedge fund returns) 124 | #' data("hfdata") 125 | #' x = hfdata[,1] 126 | #' y = hfdata[,2] 127 | #' 128 | #' ## Run Sharpe testing (asymptotic) 129 | #' ctr = list(type = 1) 130 | #' out = sharpeTesting(x, y, control = ctr) 131 | #' print(out) 132 | #' 133 | #' ## Run Sharpe testing (asymptotic hac) 134 | #' ctr = list(type = 1, hac = TRUE) 135 | #' out = sharpeTesting(x, y, control = ctr) 136 | #' print(out) 137 | #' 138 | #' ## Run Sharpe testing (iid bootstrap) 139 | #' set.seed(1234) 140 | #' ctr = list(type = 2, nBoot = 100) 141 | #' out = sharpeTesting(x, y, control = ctr) 142 | #' print(out) 143 | #' 144 | #' ## Run Sharpe testing (circular bootstrap) 145 | #' set.seed(1234) 146 | #' ctr = list(type = 2, nBoot = 100, bBoot = 5) 147 | #' out = sharpeTesting(x, y, control = ctr) 148 | #' print(out) 149 | #' @export 150 | #' @import compiler 151 | sharpeTesting <- compiler::cmpfun(.sharpeTesting) 152 | 153 | #@name .sharpe.ratio.diff 154 | #@title Difference of sharpe ratios 155 | .sharpe.ratio.diff <- function(X, Y, ttype) { 156 | 157 | if (is.null(Y)) { 158 | Y <- X[, 2, drop = FALSE] 159 | X <- X[, 1, drop = FALSE] 160 | } 161 | n <- nrow(X) 162 | mu1.hat <- colMeans(X) 163 | mu2.hat <- colMeans(Y) 164 | X_ <- sweep(x = X, MARGIN = 2, STATS = mu1.hat, FUN = "-") 165 | Y_ <- sweep(x = Y, MARGIN = 2, STATS = mu2.hat, FUN = "-") 166 | sig1.hat <- sqrt(colSums(X_^2)/(n - 1)) 167 | sig2.hat <- sqrt(colSums(Y_^2)/(n - 1)) 168 | 169 | if (ttype == 1) { 170 | SR1.hat <- mu1.hat/sig1.hat 171 | SR2.hat <- mu2.hat/sig2.hat 172 | } else { 173 | SR1.hat <- mu1.hat * sig2.hat 174 | SR2.hat <- mu2.hat * sig1.hat 175 | } 176 | diff <- SR1.hat - SR2.hat 177 | return(diff) 178 | } 179 | sharpe.ratio.diff <- compiler::cmpfun(.sharpe.ratio.diff) 180 | 181 | # #' @name .sharpeTestAsymptotic 182 | # #' @title Asymptotic Sharpe test 183 | # #' @importFrom stats pnorm 184 | # #' @import compiler 185 | .sharpeTestAsymptotic <- function(rets, hac, ttype) { 186 | 187 | dsharpe <- sharpe.ratio.diff(rets, Y = NULL, ttype) 188 | se <- se.sharpe.asymptotic(rets, hac, ttype) 189 | tstat <- dsharpe/se 190 | pval <- 2 * stats::pnorm(-abs(tstat)) # asymptotic normal p-value 191 | out <- list(dsharpe = dsharpe, tstat = tstat, se = se, pval = pval) 192 | return(out) 193 | } 194 | sharpeTestAsymptotic <- compiler::cmpfun(.sharpeTestAsymptotic) 195 | 196 | # #' @name .se.sharpe.asymptotic 197 | # #' @title Asymptotic standard error 198 | # #' @importFrom stats cov ar 199 | # #' @import compiler 200 | .se.sharpe.asymptotic <- function(X, hac, ttype) { 201 | 202 | # estimation of (robust) Psi function; see Ledoit Wolf paper 203 | compute.Psi.hat <- function(V.hat, hac) { 204 | 205 | if (hac) { 206 | T <- length(V.hat[, 1]) 207 | alpha.hat <- compute.alpha.hat(V.hat) 208 | S.star <- 2.6614 * (alpha.hat * T)^0.2 209 | Psi.hat <- compute.Gamma.hat(V.hat, 0) 210 | j <- 1 211 | while (j < S.star) { 212 | Gamma.hat <- compute.Gamma.hat(V.hat, j) 213 | Psi.hat <- Psi.hat + kernel.Parzen(j/S.star) * (Gamma.hat + 214 | t(Gamma.hat)) 215 | j <- j + 1 216 | } 217 | Psi.hat <- (T/(T - 4)) * Psi.hat 218 | } else { 219 | Psi.hat <- stats::cov(V.hat) 220 | } 221 | return(Psi.hat) 222 | 223 | } 224 | 225 | # Parzen kernel 226 | kernel.Parzen <- function(x) { 227 | 228 | if (abs(x) <= 0.5) 229 | result <- 1 - 6 * x^2 + 6 * abs(x)^3 else if (abs(x) <= 1) 230 | result <- 2 * (1 - abs(x))^3 else result <- 0 231 | return(result) 232 | 233 | } 234 | 235 | compute.alpha.hat <- function(V.hat) { 236 | 237 | p <- ncol(V.hat) 238 | num <- den <- 0 239 | for (i in 1:p) { 240 | fit <- stats::ar(V.hat[, i], 0, 1, method = "ols") 241 | rho.hat <- as.numeric(fit[2]) 242 | sig.hat <- sqrt(as.numeric(fit[3])) 243 | num <- num + 4 * rho.hat^2 * sig.hat^4/(1 - rho.hat)^8 244 | den <- den + sig.hat^4/(1 - rho.hat)^4 245 | } 246 | return(num/den) 247 | 248 | } 249 | 250 | compute.Gamma.hat <- function(V.hat, j) { 251 | 252 | T <- nrow(V.hat) 253 | p <- ncol(V.hat) 254 | Gamma.hat <- matrix(0, p, p) 255 | if (j >= T) 256 | stop("j must be smaller than the row dimension!") 257 | for (i in ((j + 1):T)) { 258 | Gamma.hat <- Gamma.hat + tcrossprod(V.hat[i, ], V.hat[i - j, 259 | ]) 260 | } 261 | Gamma.hat <- Gamma.hat/T 262 | return(Gamma.hat) 263 | 264 | } 265 | 266 | T <- nrow(X) 267 | 268 | if (ttype == 1) { 269 | mu.hat <- colMeans(X) 270 | gamma.hat <- colMeans(X^2) 271 | gradient <- vector("double", 4) 272 | gradient[1] <- gamma.hat[1]/(gamma.hat[1] - mu.hat[1]^2)^1.5 273 | gradient[2] <- -gamma.hat[2]/(gamma.hat[2] - mu.hat[2]^2)^1.5 274 | gradient[3] <- -0.5 * mu.hat[1]/(gamma.hat[1] - mu.hat[1]^2)^1.5 275 | gradient[4] <- 0.5 * mu.hat[2]/(gamma.hat[2] - mu.hat[2]^2)^1.5 276 | V.hat <- matrix(NA, T, 4) 277 | V.hat[, 1:2] <- sweep(x = X, MARGIN = 2, STATS = mu.hat, FUN = "-") 278 | V.hat[, 3:4] <- sweep(x = X^2, MARGIN = 2, STATS = gamma.hat, FUN = "-") 279 | } else { 280 | m1 <- colMeans(X) 281 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1, FUN = "-") 282 | m2 <- colMeans(X_^2) 283 | g2 <- m2 + m1^2 284 | dm1i <- c(1, 0, 0, 0) 285 | dm1j <- c(0, 0, 1, 0) 286 | dsigi <- 1/(2 * sqrt(m2[1])) * c(-2 * m1[1], 1, 0, 0) 287 | dsigj <- 1/(2 * sqrt(m2[2])) * c(0, 0, -2 * m1[2], 1) 288 | tmp1 <- dm1i * sqrt(m2[2]) + dsigj * m1[1] 289 | tmp2 <- dm1j * sqrt(m2[1]) + dsigi * m1[2] 290 | gradient <- tmp1 - tmp2 291 | V.hat <- matrix(NA, T, 4) 292 | V.hat[, c(1, 3)] <- sweep(x = X, MARGIN = 2, STATS = m1, FUN = "-") 293 | V.hat[, c(2, 4)] <- sweep(x = X^2, MARGIN = 2, STATS = g2, FUN = "-") 294 | } 295 | 296 | Psi.hat <- compute.Psi.hat(V.hat, hac) 297 | se <- as.numeric(sqrt(crossprod(gradient, Psi.hat %*% gradient)/T)) 298 | return(se) 299 | 300 | } 301 | se.sharpe.asymptotic <- compiler::cmpfun(.se.sharpe.asymptotic) 302 | 303 | # #' @name .sharpeTestBootstrap 304 | # #' @import compiler 305 | .sharpeTestBootstrap <- function(rets, bsids, b, ttype, pBoot, d = 0) { 306 | 307 | T <- nrow(rets) 308 | x <- rets[, 1, drop = FALSE] 309 | y <- rets[, 2, drop = FALSE] 310 | dsharpe <- as.numeric(sharpe.ratio.diff(x, y, ttype) - d) 311 | se <- se.sharpe.bootstrap(x, y, b, ttype) 312 | # se = se.sharpe.asymptotic(X = cbind(x, y), hac = TRUE, ttype = ttype) 313 | 314 | # bootstrap indices 315 | nBoot <- ncol(bsids) 316 | bsidx <- 1 + bsids%%T # ensure that the bootstrap indices match the length of the time series 317 | bsX <- matrix(x[bsidx], T, nBoot) 318 | bsY <- matrix(y[bsidx], T, nBoot) 319 | 320 | bsdsharpe <- sharpe.ratio.diff(bsX, bsY, ttype) 321 | bsse <- se.sharpe.bootstrap(bsX, bsY, b, ttype) 322 | tstat <- dsharpe/se 323 | 324 | if (pBoot == 1) { 325 | # first type p-value calculation 326 | bststat <- abs(bsdsharpe - dsharpe)/bsse 327 | pval <- (sum(bststat > abs(tstat)) + 1)/(nBoot + 1) 328 | # pval = sum(bststat > abs(tstat)) / nBoot 329 | } else { 330 | # second type p-value calculation (as in Barras) 331 | bststat <- (bsdsharpe - dsharpe)/bsse 332 | pval <- 2 * min(sum(bststat > tstat) + 1, sum(bststat < tstat) + 333 | 1)/(nBoot + 1) 334 | # pval = 2 * min(sum(bststat > tstat), sum(bststat < tstat)) / nBoot 335 | } 336 | 337 | out <- list(dsharpe = dsharpe, tstat = tstat, se = se, bststat = bststat, 338 | pval = pval) 339 | return(out) 340 | } 341 | sharpeTestBootstrap <- compiler::cmpfun(.sharpeTestBootstrap) 342 | 343 | # #' @name .se.sharpe.bootstrap 344 | # #' @title Bootstrap standard error 345 | # #' @importFrom stats cov 346 | # #' @import compiler 347 | .se.sharpe.bootstrap <- function(X, Y, b, ttype) { 348 | 349 | ## Compute Psi with two approaches: 1) iid bootstrap, 2) circular block 350 | ## bootstrap 351 | compute.Psi.hat <- function(V.hat, b) { 352 | 353 | T <- length(V.hat[, 1]) 354 | if (b == 1) { 355 | # ==> standard estimation 356 | Psi.hat <- stats::cov(V.hat) 357 | } else { 358 | # ==> block estimation 359 | l <- floor(T/b) 360 | Psi.hat <- matrix(0, 4, 4) 361 | for (j in (1:l)) { 362 | zeta <- b^0.5 * colMeans(V.hat[((j - 1) * b + 1):(j * b), 363 | , drop = FALSE]) 364 | Psi.hat <- Psi.hat + tcrossprod(zeta) 365 | } 366 | Psi.hat <- Psi.hat/l 367 | } 368 | return(Psi.hat) 369 | 370 | } 371 | 372 | T <- nrow(X) 373 | N <- ncol(Y) 374 | if (ttype == 1) { 375 | mu1.hat <- colMeans(X) 376 | mu2.hat <- colMeans(Y) 377 | gamma1.hat <- colMeans(X^2) 378 | gamma2.hat <- colMeans(Y^2) 379 | gradient <- array(NA, c(4, 1, N)) 380 | gradient[1, 1, ] <- gamma1.hat/(gamma1.hat - mu1.hat^2)^1.5 381 | gradient[2, 1, ] <- -gamma2.hat/(gamma2.hat - mu2.hat^2)^1.5 382 | gradient[3, 1, ] <- -0.5 * mu1.hat/(gamma1.hat - mu1.hat^2)^1.5 383 | gradient[4, 1, ] <- 0.5 * mu2.hat/(gamma2.hat - mu2.hat^2)^1.5 384 | V.hat <- array(NA, c(T, 4, N)) 385 | V.hat[, 1, ] <- sweep(x = X, MARGIN = 2, STATS = mu1.hat, FUN = "-") 386 | V.hat[, 2, ] <- sweep(x = Y, MARGIN = 2, STATS = mu2.hat, FUN = "-") 387 | V.hat[, 3, ] <- sweep(x = X^2, MARGIN = 2, STATS = gamma1.hat, 388 | FUN = "-") 389 | V.hat[, 4, ] <- sweep(x = Y^2, MARGIN = 2, STATS = gamma2.hat, 390 | FUN = "-") 391 | } else { 392 | m1X <- colMeans(X) 393 | m1Y <- colMeans(Y) 394 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1X, FUN = "-") 395 | Y_ <- sweep(x = Y, MARGIN = 2, STATS = m1Y, FUN = "-") 396 | m2X <- colMeans(X_^2) 397 | m2Y <- colMeans(Y_^2) 398 | g2X <- m2X + m1X^2 399 | g2Y <- m2Y + m1Y^2 400 | 401 | cst1X <- 1/(2 * sqrt(m2X)) 402 | cst1Y <- 1/(2 * sqrt(m2Y)) 403 | 404 | dm1X <- matrix(rep(c(1, 0, 0, 0), N), 4, N, FALSE) 405 | dm1Y <- matrix(rep(c(0, 0, 1, 0), N), 4, N, FALSE) 406 | dsigX <- rbind(-2 * cst1X * m1X, cst1X, 0, 0) 407 | dsigY <- rbind(0, 0, -2 * cst1Y * m1Y, cst1Y) 408 | 409 | # matrix form 410 | m1X_ <- matrix(m1X, nrow = 4, ncol = N, byrow = TRUE) 411 | m1Y_ <- matrix(m1Y, nrow = 4, ncol = N, byrow = TRUE) 412 | m2X_ <- matrix(m2X, nrow = 4, ncol = N, byrow = TRUE) 413 | m2Y_ <- matrix(m2Y, nrow = 4, ncol = N, byrow = TRUE) 414 | 415 | dm1X_ <- matrix(dm1X, nrow = 4, ncol = N, byrow = FALSE) 416 | dm1Y_ <- matrix(dm1Y, nrow = 4, ncol = N, byrow = FALSE) 417 | dsigX_ <- matrix(dsigX, nrow = 4, ncol = N, byrow = FALSE) 418 | dsigY_ <- matrix(dsigY, nrow = 4, ncol = N, byrow = FALSE) 419 | 420 | cst2X_ <- sqrt(m2X_) 421 | cst2Y_ <- sqrt(m2Y_) 422 | 423 | # gradient 424 | tmp1 <- dm1X_ * cst2Y_ + dsigY_ * m1X_ 425 | tmp2 <- dm1Y_ * cst2X_ + dsigX_ * m1Y_ 426 | 427 | # ======= 428 | gradient <- array(NA, c(4, 1, N)) 429 | gradient[1:4, 1, ] <- tmp1 - tmp2 430 | V.hat <- array(NA, c(T, 4, N)) 431 | V.hat[, 1, ] <- sweep(x = X, MARGIN = 2, STATS = m1X, FUN = "-") 432 | V.hat[, 3, ] <- sweep(x = Y, MARGIN = 2, STATS = m1Y, FUN = "-") 433 | V.hat[, 2, ] <- sweep(x = X^2, MARGIN = 2, STATS = g2X, FUN = "-") 434 | V.hat[, 4, ] <- sweep(x = Y^2, MARGIN = 2, STATS = g2Y, FUN = "-") 435 | } 436 | Psi.hat <- array(apply(X = V.hat, MARGIN = 3, FUN = compute.Psi.hat, 437 | b = b), c(4, 4, N)) 438 | se <- vector("double", N) 439 | for (i in 1:N) { 440 | se[i] <- sqrt(crossprod(gradient[, , i], Psi.hat[, , i] %*% gradient[, 441 | , i])/T) 442 | } 443 | return(se) 444 | 445 | } 446 | se.sharpe.bootstrap <- compiler::cmpfun(.se.sharpe.bootstrap) 447 | -------------------------------------------------------------------------------- /R/msharpeTesting.R: -------------------------------------------------------------------------------- 1 | ## Set of R functions for the modified Sharpe ratio testing 2 | 3 | # #' @name .msharpeTesting 4 | # #' @import compiler 5 | .msharpeTesting <- function(x, y, level = 0.9, na.neg = TRUE, control = list()) { 6 | 7 | x <- as.matrix(x) 8 | y <- as.matrix(y) 9 | 10 | # process control parameters 11 | ctr <- processControl(control) 12 | 13 | # check if enough data are available for testing 14 | dxy <- x - y 15 | idx <- (!is.nan(dxy) & !is.na(dxy)) 16 | rets <- cbind(x[idx], y[idx]) 17 | T <- sum(idx) 18 | if (T < ctr$minObs) { 19 | stop("intersection of 'x' and 'y' is shorter than 'minObs'") 20 | } 21 | 22 | # msharpe testing 23 | if (ctr$type == 1) { 24 | # ==> asymptotic approach 25 | tmp <- msharpeTestAsymptotic(rets, level, na.neg, ctr$hac, ctr$ttype) 26 | } else { 27 | # ==> bootstrap approach (iid and circular block bootstrap) 28 | if (ctr$bBoot == 0) { 29 | ctr$bBoot <- msharpeBlockSize(x, y, level, na.neg, ctr) 30 | } 31 | bsids <- bootIndices(T, ctr$nBoot, ctr$bBoot) 32 | tmp <- msharpeTestBootstrap(rets, level, na.neg, bsids, ctr$bBoot, 33 | ctr$ttype, ctr$pBoot) 34 | } 35 | 36 | # info on the funds 37 | info <- infoFund(rets, level = level, na.neg = na.neg) 38 | 39 | ## form output 40 | out <- list(n = T, msharpe = info$msharpe, dmsharpe = -diff(info$msharpe), 41 | tstat = as.vector(tmp$tstat), pval = as.vector(tmp$pval)) 42 | class(out) <- "TESTING" 43 | return(out) 44 | 45 | } 46 | 47 | #' @name msharpeTesting 48 | #' @title Testing the difference of modified Sharpe ratios 49 | #' @description Function which performs the testing of the difference of modified Sharpe 50 | #' ratios. 51 | #' @details The modified Sharpe ratio (Favre and Galeano 2002) is one industry 52 | #' standard for measuring the absolute risk adjusted performance of hedge 53 | #' funds. This function performs the testing of modified Sharpe ratio 54 | #' difference for two funds using a similar approach than Ledoit and Wolf 55 | #' (2002). See also Gregoriou and Gueyie (2003). 56 | #' 57 | #' For the testing, only the intersection of non-\code{NA} observations for the 58 | #' two funds are used. 59 | #' 60 | #' The argument \code{control} is a list that can supply any of the following 61 | #' components: 62 | #' \itemize{ 63 | #' \item \code{'type'} Asymptotic approach (\code{type = 1}) or 64 | #' studentized circular bootstrap approach (\code{type = 2}). Default: 65 | #' \code{type = 1}. 66 | #' \item \code{'ttype'} Test based on ratio (\code{type = 1}) 67 | #' or product (\code{type = 2}). Default: \code{type = 2}. 68 | #' \item \code{'hac'} Heteroscedastic-autocorrelation consistent standard 69 | #' errors. Default: \code{hac = FALSE}. 70 | #' \item \code{'minObs'} Minimum number of concordant observations to compute the ratios. Default: \code{minObs = 71 | #' 10}. 72 | #' \item \code{'nBoot'} Number of bootstrap replications for computing the 73 | #' p-value. Default: \code{nBoot = 499}. 74 | #' \item \code{'bBoot'} Block length in 75 | #' the circular bootstrap. Default: \code{bBoot = 1}, i.e. iid bootstrap. 76 | #' \code{bBoot = 0} uses optimal block-length. 77 | #' \item \code{'pBoot'} Symmetric 78 | #' p-value (\code{pBoot = 1}) or asymmetric p-value (\code{pBoot = 2}). 79 | #' Default: \code{pBoot = 1}. 80 | #' } 81 | #' @param x Vector (of length \eqn{T}) of returns for the first fund. \code{NA} 82 | #' values are allowed. 83 | #' @param y Vector (of length \eqn{T}) of returns for the second fund. \code{NA} 84 | #' values are allowed. 85 | #' @param level Modified Value-at-Risk level. Default: \code{level = 0.90}. 86 | #' @param na.neg A logical value indicating whether \code{NA} values should be 87 | #' returned if a negative modified Value-at-Risk is obtained. Default 88 | #' \code{na.neg = TRUE}. 89 | #' @param control Control parameters (see *Details*). 90 | #' @return A list with the following components:\cr 91 | #' 92 | #' \code{n}: Number of non-\code{NA} concordant observations.\cr 93 | #' 94 | #' \code{msharpe}: Vector (of length 2) of unconditional modified Sharpe 95 | #' ratios.\cr 96 | #' 97 | #' \code{dmsharpe}: Modified Sharpe ratios difference.\cr 98 | #' 99 | #' \code{tstat}: t-stat of modified Sharpe ratios differences.\cr 100 | #' 101 | #' \code{pval}: pvalues of test of modified Sharpe ratios differences. 102 | #' @note Further details on the methodology with an application to the hedge 103 | #' fund industry is given in Ardia and Boudt (2018). 104 | #' 105 | #' Some internal functions where adapted from Michael Wolf MATLAB code. 106 | #' 107 | #' @author David Ardia and Kris Boudt. 108 | #' @seealso \code{\link{msharpe}}, \code{\link{msharpeScreening}} and 109 | #' \code{\link{sharpeTesting}}. 110 | #' @references 111 | #' Ardia, D., Boudt, K. (2015). 112 | #' Testing equality of modified Sharpe ratios. 113 | #' \emph{Finance Research Letters} \bold{13}, pp.97--104. 114 | #' \doi{10.1016/j.frl.2015.02.008} 115 | #' 116 | #' Ardia, D., Boudt, K. (2018). 117 | #' The peer performance ratios of hedge funds. 118 | #' \emph{Journal of Banking and Finance} \bold{87}, pp.351-.368. 119 | #' \doi{10.1016/j.jbankfin.2017.10.014} 120 | #' 121 | #' Barras, L., Scaillet, O., Wermers, R. (2010). 122 | #' False discoveries in mutual fund performance: Measuring luck in estimated alphas. 123 | #' \emph{Journal of Finance} \bold{65}(1), pp.179--216. 124 | #' 125 | #' Favre, L., Galeano, J.A. (2002). 126 | #' Mean-modified Value-at-Risk Optimization with Hedge Funds. 127 | #' \emph{Journal of Alternative Investments} \bold{5}(2), pp.21--25. 128 | #' 129 | #' Gregoriou, G. N., Gueyie, J.-P. (2003). 130 | #' Risk-adjusted performance of funds of hedge funds using a modified Sharpe ratio. 131 | #' \emph{Journal of Wealth Management} \bold{6}(3), pp.77--83. 132 | #' 133 | #' Ledoit, O., Wolf, M. (2008). 134 | #' Robust performance hypothesis testing with the Sharpe ratio. 135 | #' \emph{Journal of Empirical Finance} \bold{15}(5), pp.850--859. 136 | #' 137 | #' Storey, J. (2002). 138 | #' A direct approach to false discovery rates. 139 | #' \emph{Journal of the Royal Statistical Society B} \bold{64}(3), pp.479--498. 140 | #' @keywords htest 141 | #' @examples 142 | #' ## Load the data (randomized data of monthly hedge fund returns) 143 | #' data("hfdata") 144 | #' x = hfdata[,1] 145 | #' y = hfdata[,2] 146 | #' 147 | #' ## Run modified Sharpe testing (asymptotic) 148 | #' ctr = list(type = 1) 149 | #' out = msharpeTesting(x, y, level = 0.95, control = ctr) 150 | #' print(out) 151 | #' 152 | #' ## Run modified Sharpe testing (asymptotic hac) 153 | #' ctr = list(type = 1, hac = TRUE) 154 | #' out = msharpeTesting(x, y, level = 0.95, control = ctr) 155 | #' print(out) 156 | #' 157 | #' ## Run modified Sharpe testing (iid bootstrap) 158 | #' set.seed(1234) 159 | #' ctr = list(type = 2, nBoot = 250) 160 | #' out = msharpeTesting(x, y, level = 0.95, control = ctr) 161 | #' print(out) 162 | #' 163 | #' ## Run modified Sharpe testing (circular bootstrap) 164 | #' set.seed(1234) 165 | #' ctr = list(type = 2, nBoot = 250, bBoot = 5) 166 | #' out = msharpeTesting(x, y, level = 0.95, control = ctr) 167 | #' print(out) 168 | #' @export 169 | #' @import compiler 170 | msharpeTesting <- compiler::cmpfun(.msharpeTesting) 171 | 172 | # #' @name .msharpe.ratio.diff 173 | # #' @title Difference of sharpe ratios 174 | # #' @importFrom stats qnorm 175 | # #' @import compiler 176 | .msharpe.ratio.diff <- function(X, Y = NULL, level, na.neg, ttype) { 177 | if (is.null(Y)) { 178 | Y <- X[, 2, drop = FALSE] 179 | X <- X[, 1, drop = FALSE] 180 | } 181 | m1X <- colMeans(X) 182 | m1Y <- colMeans(Y) 183 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1X, FUN = "-") 184 | Y_ <- sweep(x = Y, MARGIN = 2, STATS = m1Y, FUN = "-") 185 | m2X <- colMeans(X_^2) 186 | m2Y <- colMeans(Y_^2) 187 | m3X <- colMeans(X_^3) 188 | m3Y <- colMeans(Y_^3) 189 | m4X <- colMeans(X_^4) 190 | m4Y <- colMeans(Y_^4) 191 | za <- stats::qnorm(1 - level) 192 | skewX <- m3X/m2X^(3/2) 193 | skewY <- m3Y/m2Y^(3/2) 194 | kurtX <- (m4X/m2X^2) - 3 195 | kurtY <- (m4Y/m2Y^2) - 3 196 | mVaRX <- -m1X + sqrt(m2X) * (-za - (1/6) * (za^2 - 1) * skewX - (1/24) * 197 | (za^3 - 3 * za) * kurtX + (1/36) * (2 * za^3 - 5 * za) * skewX^2) 198 | mVaRY <- -m1Y + sqrt(m2Y) * (-za - (1/6) * (za^2 - 1) * skewY - (1/24) * 199 | (za^3 - 3 * za) * kurtY + (1/36) * (2 * za^3 - 5 * za) * skewY^2) 200 | if (na.neg) { 201 | mVaRX[mVaRX < 0] <- NA 202 | mVaRY[mVaRY < 0] <- NA 203 | } 204 | if (ttype == 1) { 205 | # test based on quotient 206 | mSR1 <- m1X/mVaRX 207 | mSR2 <- m1Y/mVaRY 208 | } else { 209 | # test based on product 210 | mSR1 <- m1X * mVaRY 211 | mSR2 <- m1Y * mVaRX 212 | } 213 | diff <- mSR1 - mSR2 214 | return(diff) 215 | } 216 | msharpe.ratio.diff <- compiler::cmpfun(.msharpe.ratio.diff) 217 | 218 | # #' @name .msharpeTestAsymptotic 219 | # #' @title Asymptotic Sharpe testing 220 | # #' @importFrom stats pnorm 221 | # #' @import compiler 222 | .msharpeTestAsymptotic <- function(rets, level, na.neg, hac, ttype) { 223 | 224 | dmsharpe <- msharpe.ratio.diff(rets, Y = NULL, level, na.neg, ttype) 225 | if (is.na(dmsharpe)) { 226 | out <- list(dmsharpe = NA, tstat = NA, se = NA, pval = NA) 227 | return(out) 228 | } 229 | se <- se.msharpe.asymptotic(rets, level, hac, ttype) 230 | tstat <- dmsharpe/se 231 | pval <- 2 * stats::pnorm(-abs(tstat)) # asymptotic normal p-value 232 | out <- list(dmsharpe = dmsharpe, tstat = tstat, se = se, pval = pval) 233 | return(out) 234 | } 235 | msharpeTestAsymptotic <- compiler::cmpfun(.msharpeTestAsymptotic) 236 | 237 | # #' @name .se.msharpe.asymptotic 238 | # #' @title Asymptotic standard error 239 | # #' @importFrom stats cov ar qnorm 240 | # #' @import compiler 241 | .se.msharpe.asymptotic <- function(X, level, hac, ttype) { 242 | 243 | # estimation of (robust) Psi function; see Ledoit Wolf paper 244 | compute.Psi.hat <- function(V.hat, hac) { 245 | if (hac) { 246 | T <- length(V.hat[, 1]) 247 | alpha.hat <- compute.alpha.hat(V.hat) 248 | S.star <- min(2.6614 * (alpha.hat * T)^0.2, T) # DA fix here to avoid >T values 249 | Psi.hat <- compute.Gamma.hat(V.hat, 0) 250 | j <- 1 251 | while (j < S.star) { 252 | Gamma.hat <- compute.Gamma.hat(V.hat, j) 253 | Psi.hat <- Psi.hat + kernel.Parzen(j/S.star) * (Gamma.hat + 254 | t(Gamma.hat)) 255 | j <- j + 1 256 | } 257 | Psi.hat <- (T/(T - 4)) * Psi.hat 258 | } else { 259 | Psi.hat <- stats::cov(V.hat) 260 | } 261 | return(Psi.hat) 262 | 263 | } 264 | 265 | # Parzen kernel 266 | kernel.Parzen <- function(x) { 267 | if (abs(x) <= 0.5) 268 | result <- 1 - 6 * x^2 + 6 * abs(x)^3 else if (abs(x) <= 1) 269 | result <- 2 * (1 - abs(x))^3 else result <- 0 270 | return(result) 271 | } 272 | 273 | compute.alpha.hat <- function(V.hat) { 274 | p <- ncol(V.hat) 275 | num <- den <- 0 276 | for (i in 1:p) { 277 | fit <- stats::ar(V.hat[, i], 0, 1, method = "ols") 278 | rho.hat <- as.numeric(fit[2]) 279 | sig.hat <- sqrt(as.numeric(fit[3])) 280 | num <- num + 4 * rho.hat^2 * sig.hat^4/(1 - rho.hat)^8 281 | den <- den + sig.hat^4/(1 - rho.hat)^4 282 | } 283 | return(num/den) 284 | 285 | } 286 | 287 | compute.Gamma.hat <- function(V.hat, j) { 288 | T <- nrow(V.hat) 289 | p <- ncol(V.hat) 290 | Gamma.hat <- matrix(0, p, p) 291 | if (j >= T) 292 | stop("j must be smaller than the row dimension!") 293 | for (i in ((j + 1):T)) { 294 | Gamma.hat <- Gamma.hat + tcrossprod(V.hat[i, ], V.hat[i - j, 295 | ]) 296 | } 297 | Gamma.hat <- Gamma.hat/T 298 | return(Gamma.hat) 299 | 300 | } 301 | 302 | T <- nrow(X) 303 | m1 <- colMeans(X) 304 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1, FUN = "-") 305 | m2 <- colMeans(X_^2) 306 | m3 <- colMeans(X_^3) 307 | m4 <- colMeans(X_^4) 308 | g2 <- m2 + m1^2 309 | g3 <- m3 + 3 * m1 * g2 - 2 * m1^3 310 | g4 <- m4 + 4 * m1 * g3 - 6 * m1^2 * g2 + 3 * m1^4 311 | skew <- m3/m2^(3/2) 312 | kurt <- (m4/m2^2) - 3 313 | 314 | # gradient for underlying moments 315 | dm1i <- c(1, 0, 0, 0, 0, 0, 0, 0) 316 | dm1j <- c(0, 0, 0, 0, 1, 0, 0, 0) 317 | dm2i <- c(-2 * m1[1], 1, 0, 0, 0, 0, 0, 0) 318 | dm2j <- c(0, 0, 0, 0, -2 * m1[2], 1, 0, 0) 319 | dm3i <- c(-3 * g2[1] + 6 * m1[1]^2, -3 * m1[1], 1, 0, 0, 0, 0, 0) 320 | dm3j <- c(0, 0, 0, 0, -3 * g2[2] + 6 * m1[2]^2, -3 * m1[2], 1, 0) 321 | dm4i <- c(-4 * g3[1] + 12 * m1[1] * g2[1] - 12 * m1[1]^3, 6 * m1[1]^2, 322 | -4 * m1[1], 1, 0, 0, 0, 0) 323 | dm4j <- c(0, 0, 0, 0, -4 * g3[2] + 12 * m1[2] * g2[2] - 12 * m1[2]^3, 324 | 6 * m1[2]^2, -4 * m1[2], 1) 325 | 326 | dsi <- (m2[1]^(3/2) * dm3i - 1.5 * m3[1] * m2[1]^(1/2) * dm2i)/m2[1]^3 327 | dsj <- (m2[2]^(3/2) * dm3j - 1.5 * m3[2] * m2[2]^(1/2) * dm2j)/m2[2]^3 328 | 329 | dki <- (m2[1]^2 * dm4i - 2 * m4[1] * m2[1] * dm2i)/m2[1]^4 330 | dkj <- (m2[2]^2 * dm4j - 2 * m4[2] * m2[2] * dm2j)/m2[2]^4 331 | 332 | za <- qnorm(1 - level) 333 | 334 | # constants for speedup 335 | cst1i <- 1/(2 * sqrt(m2[1])) 336 | cst1j <- 1/(2 * sqrt(m2[2])) 337 | cst2i <- sqrt(m2[1]) 338 | cst2j <- sqrt(m2[2]) 339 | 340 | # dmVaRi 341 | dmVaRi <- -dm1i - cst1i * dm2i * za 342 | dmVaRi <- dmVaRi - (1/6) * (za^2 - 1) * (cst1i * skew[1] * dm2i + cst2i * 343 | dsi) 344 | dmVaRi <- dmVaRi + (1/36) * (2 * za^3 - 5 * za) * (cst1i * skew[1]^2 * 345 | dm2i + 2 * cst2i * skew[1] * dsi) 346 | dmVaRi <- dmVaRi - (1/24) * (za^3 - 3 * za) * (cst1i * kurt[1] * dm2i + 347 | cst2i * dki) 348 | 349 | # dmVaRj 350 | dmVaRj <- -dm1j - cst1j * dm2j * za 351 | dmVaRj <- dmVaRj - (1/6) * (za^2 - 1) * (cst1j * skew[2] * dm2j + cst2j * 352 | dsj) 353 | dmVaRj <- dmVaRj + (1/36) * (2 * za^3 - 5 * za) * (cst1j * skew[2]^2 * 354 | dm2j + 2 * cst2j * skew[2] * dsj) 355 | dmVaRj <- dmVaRj - (1/24) * (za^3 - 3 * za) * (cst1j * kurt[2] * dm2j + 356 | cst2j * dkj) 357 | 358 | # mVaR 359 | mVaR <- -m1 + sqrt(m2) * (-za - (1/6) * (za^2 - 1) * skew - (1/24) * 360 | (za^3 - 3 * za) * kurt + (1/36) * (2 * za^3 - 5 * za) * skew^2) 361 | 362 | # gradient 363 | if (ttype == 1) { 364 | # test based on quotient 365 | tmp1 <- (mVaR[1] * dm1i - m1[1] * dmVaRi)/mVaR[1]^2 366 | tmp2 <- (mVaR[2] * dm1j - m1[2] * dmVaRj)/mVaR[2]^2 367 | } else { 368 | # test based on product 369 | tmp1 <- (mVaR[2] * dm1i + m1[1] * dmVaRj) 370 | tmp2 <- (mVaR[1] * dm1j + m1[2] * dmVaRi) 371 | } 372 | 373 | gradient <- tmp1 - tmp2 374 | V.hat <- matrix(NA, T, 8) 375 | V.hat[, c(1, 5)] <- sweep(x = X, MARGIN = 2, STATS = m1, FUN = "-") 376 | V.hat[, c(2, 6)] <- sweep(x = X^2, MARGIN = 2, STATS = g2, FUN = "-") 377 | V.hat[, c(3, 7)] <- sweep(x = X^3, MARGIN = 2, STATS = g3, FUN = "-") 378 | V.hat[, c(4, 8)] <- sweep(x = X^4, MARGIN = 2, STATS = g4, FUN = "-") 379 | Psi.hat <- compute.Psi.hat(V.hat, hac) 380 | se <- as.numeric(sqrt(crossprod(gradient, Psi.hat %*% gradient)/T)) 381 | return(se) 382 | } 383 | se.msharpe.asymptotic <- compiler::cmpfun(.se.msharpe.asymptotic) 384 | 385 | # #' @name .msharpeTestBootstrap 386 | # #' @import compiler 387 | .msharpeTestBootstrap <- function(rets, level, na.neg, bsids, b, ttype, 388 | pBoot, d = 0) { 389 | 390 | T <- nrow(rets) 391 | x <- rets[, 1, drop = FALSE] 392 | y <- rets[, 2, drop = FALSE] 393 | 394 | dmsharpe <- as.numeric(msharpe.ratio.diff(x, y, level, na.neg, ttype) - 395 | d) 396 | if (is.na(dmsharpe)) { 397 | out <- list(dmsharpe = NA, tstat = NA, se = NA, bststat = NA, pval = NA) 398 | return(out) 399 | } 400 | se <- se.msharpe.bootstrap(x, y, level, b, ttype) 401 | # se = se.msharpe.asymptotic(X = cbind(x, y), level = level, hac = 402 | # TRUE, ttype = ttype) 403 | 404 | # bootstrap indices 405 | nBoot <- ncol(bsids) 406 | bsidx <- 1 + bsids%%T # ensure that the bootstrap indices match the length of the time series 407 | bsX <- matrix(x[bsidx], T, nBoot) 408 | bsY <- matrix(y[bsidx], T, nBoot) 409 | 410 | bsdmsharpe <- msharpe.ratio.diff(bsX, bsY, level, na.neg = FALSE, ttype) # DA consider negative mVaR as well in the bootstrap 411 | bsse <- se.msharpe.bootstrap(bsX, bsY, level, b, ttype) 412 | tstat <- dmsharpe/se 413 | 414 | if (pBoot == 1) { 415 | # first type p-value calculation 416 | bststat <- abs(bsdmsharpe - dmsharpe)/bsse 417 | pval <- (sum(bststat >= abs(tstat)) + 1)/(nBoot + 1) 418 | # pval = sum(bststat >= abs(tstat)) / nBoot 419 | } else { 420 | # second type p-value calculation (as in Barras) 421 | bststat <- (bsdmsharpe - dmsharpe)/bsse 422 | pval <- 2 * min(sum(bststat > tstat) + 1, sum(bststat < tstat) + 423 | 1)/(nBoot + 1) 424 | # pval = 2 * min(sum(bststat > tstat), sum(bststat < tstat)) / nBoot 425 | } 426 | 427 | out <- list(dmsharpe = dmsharpe, tstat = tstat, se = se, bststat = bststat, 428 | pval = pval) 429 | return(out) 430 | } 431 | msharpeTestBootstrap <- compiler::cmpfun(.msharpeTestBootstrap) 432 | 433 | # #' @name .se.msharpe.bootstrap 434 | # #' @importFrom stats cov qnorm 435 | # #' @import compiler 436 | .se.msharpe.bootstrap <- function(X, Y, level, b, ttype) { 437 | 438 | ## Compute Psi with two approaches: 1) iid bootstrap, 2) circular block 439 | ## bootstrap 440 | compute.Psi.hat <- function(V.hat, b) { 441 | T <- length(V.hat[, 1]) 442 | if (b == 1) { 443 | # ==> standard estimation 444 | Psi.hat <- stats::cov(V.hat) 445 | } else { 446 | # ==> block estimation 447 | l <- floor(T/b) 448 | Psi.hat <- matrix(0, 8, 8) 449 | for (j in (1:l)) { 450 | zeta <- b^0.5 * colMeans(V.hat[((j - 1) * b + 1):(j * b), 451 | , drop = FALSE]) 452 | Psi.hat <- Psi.hat + tcrossprod(zeta) 453 | } 454 | Psi.hat <- Psi.hat/l 455 | } 456 | return(Psi.hat) 457 | } 458 | 459 | T <- nrow(X) 460 | N <- ncol(Y) 461 | m1X <- colMeans(X) 462 | m1Y <- colMeans(Y) 463 | X_ <- sweep(x = X, MARGIN = 2, STATS = m1X, FUN = "-") 464 | Y_ <- sweep(x = Y, MARGIN = 2, STATS = m1Y, FUN = "-") 465 | m2X <- colMeans(X_^2) 466 | m2Y <- colMeans(Y_^2) 467 | m3X <- colMeans(X_^3) 468 | m3Y <- colMeans(Y_^3) 469 | m4X <- colMeans(X_^4) 470 | m4Y <- colMeans(Y_^4) 471 | g2X <- m2X + m1X^2 472 | g2Y <- m2Y + m1Y^2 473 | g3X <- m3X + 3 * m1X * g2X - 2 * m1X^3 474 | g3Y <- m3Y + 3 * m1Y * g2Y - 2 * m1Y^3 475 | g4X <- m4X + 4 * m1X * g3X - 6 * m1X^2 * g2X + 3 * m1X^4 476 | g4Y <- m4Y + 4 * m1Y * g3Y - 6 * m1Y^2 * g2Y + 3 * m1Y^4 477 | 478 | dm1X <- matrix(rep(c(1, 0, 0, 0, 0, 0, 0, 0), N), 8, N, FALSE) 479 | dm1Y <- matrix(rep(c(0, 0, 0, 0, 1, 0, 0, 0), N), 8, N, FALSE) 480 | dm2X <- rbind(-2 * m1X, 1, 0, 0, 0, 0, 0, 0) 481 | dm2Y <- rbind(0, 0, 0, 0, -2 * m1Y, 1, 0, 0) 482 | dm3X <- rbind(-3 * g2X + 6 * m1X^2, -3 * m1X, 1, 0, 0, 0, 0, 0) 483 | dm3Y <- rbind(0, 0, 0, 0, -3 * g2Y + 6 * m1Y^2, -3 * m1Y, 1, 0) 484 | dm4X <- rbind(-4 * g3X + 12 * m1X * g2X - 12 * m1X^3, 6 * m1X^2, -4 * 485 | m1X, 1, 0, 0, 0, 0) 486 | dm4Y <- rbind(0, 0, 0, 0, -4 * g3Y + 12 * m1Y * g2Y - 12 * m1Y^3, 6 * 487 | m1Y^2, -4 * m1Y, 1) 488 | 489 | # matrix form 490 | m1X_ <- matrix(m1X, nrow = 8, ncol = N, byrow = TRUE) 491 | m1Y_ <- matrix(m1Y, nrow = 8, ncol = N, byrow = TRUE) 492 | m2X_ <- matrix(m2X, nrow = 8, ncol = N, byrow = TRUE) 493 | m2Y_ <- matrix(m2Y, nrow = 8, ncol = N, byrow = TRUE) 494 | m3X_ <- matrix(m3X, nrow = 8, ncol = N, byrow = TRUE) 495 | m3Y_ <- matrix(m3Y, nrow = 8, ncol = N, byrow = TRUE) 496 | m4X_ <- matrix(m4X, nrow = 8, ncol = N, byrow = TRUE) 497 | m4Y_ <- matrix(m4Y, nrow = 8, ncol = N, byrow = TRUE) 498 | 499 | dm1X_ <- matrix(dm1X, nrow = 8, ncol = N, byrow = FALSE) 500 | dm1Y_ <- matrix(dm1Y, nrow = 8, ncol = N, byrow = FALSE) 501 | dm2X_ <- matrix(dm2X, nrow = 8, ncol = N, byrow = FALSE) 502 | dm2Y_ <- matrix(dm2Y, nrow = 8, ncol = N, byrow = FALSE) 503 | dm3X_ <- matrix(dm3X, nrow = 8, ncol = N, byrow = FALSE) 504 | dm3Y_ <- matrix(dm3Y, nrow = 8, ncol = N, byrow = FALSE) 505 | dm4X_ <- matrix(dm4X, nrow = 8, ncol = N, byrow = FALSE) 506 | dm4Y_ <- matrix(dm4Y, nrow = 8, ncol = N, byrow = FALSE) 507 | 508 | dsX_ <- (m2X_^(3/2) * dm3X_ - 1.5 * m3X_ * m2X_^(1/2) * dm2X_)/m2X_^3 509 | dsY_ <- (m2Y_^(3/2) * dm3Y_ - 1.5 * m3Y_ * m2Y_^(1/2) * dm2Y_)/m2Y_^3 510 | dkX_ <- (m2X_^2 * dm4X_ - 2 * m4X_ * m2X_ * dm2X_)/m2X_^4 511 | dkY_ <- (m2Y_^2 * dm4Y_ - 2 * m4Y_ * m2Y_ * dm2Y_)/m2Y_^4 512 | 513 | skewX_ <- m3X_/m2X_^(3/2) 514 | skewY_ <- m3Y_/m2Y_^(3/2) 515 | kurtX_ <- (m4X_/m2X_^2) - 3 516 | kurtY_ <- (m4Y_/m2Y_^2) - 3 517 | 518 | za <- qnorm(1 - level) 519 | 520 | # constants for speedup 521 | cst1X_ <- 1/(2 * sqrt(m2X_)) 522 | cst1Y_ <- 1/(2 * sqrt(m2Y_)) 523 | cst2X_ <- sqrt(m2X_) 524 | cst2Y_ <- sqrt(m2Y_) 525 | 526 | dmVaRX_ <- -dm1X_ - za * cst1X_ * dm2X_ 527 | dmVaRX_ <- dmVaRX_ - (1/6) * (za^2 - 1) * (cst1X_ * skewX_ * dm2X_ + 528 | cst2X_ * dsX_) 529 | dmVaRX_ <- dmVaRX_ + (1/36) * (2 * za^3 - 5 * za) * (cst1X_ * skewX_^2 * 530 | dm2X_ + 2 * cst2X_ * skewX_ * dsX_) 531 | dmVaRX_ <- dmVaRX_ - (1/24) * (za^3 - 3 * za) * (cst1X_ * kurtX_ * 532 | dm2X_ + cst2X_ * dkX_) 533 | 534 | # dmVaRY 535 | dmVaRY_ <- -dm1Y_ - za * cst1Y_ * dm2Y_ 536 | dmVaRY_ <- dmVaRY_ - (1/6) * (za^2 - 1) * (cst1Y_ * skewY_ * dm2Y_ + 537 | cst2Y_ * dsY_) 538 | dmVaRY_ <- dmVaRY_ + (1/36) * (2 * za^3 - 5 * za) * (cst1Y_ * skewY_^2 * 539 | dm2Y_ + 2 * cst2Y_ * skewY_ * dsY_) 540 | dmVaRY_ <- dmVaRY_ - (1/24) * (za^3 - 3 * za) * (cst1Y_ * kurtY_ * 541 | dm2Y_ + cst2Y_ * dkY_) 542 | 543 | # mVaR 544 | mVaRX_ <- -m1X_ + sqrt(m2X_) * (-za - (1/6) * (za^2 - 1) * skewX_ - 545 | (1/24) * (za^3 - 3 * za) * kurtX_ + (1/36) * (2 * za^3 - 5 * za) * 546 | skewX_^2) 547 | mVaRY_ <- -m1Y_ + sqrt(m2Y_) * (-za - (1/6) * (za^2 - 1) * skewY_ - 548 | (1/24) * (za^3 - 3 * za) * kurtY_ + (1/36) * (2 * za^3 - 5 * za) * 549 | skewY_^2) 550 | 551 | # gradient 552 | if (ttype == 1) { 553 | # test based on quotient 554 | tmp1 <- (mVaRX_ * dm1X_ - m1X_ * dmVaRX_)/mVaRX_^2 555 | tmp2 <- (mVaRY_ * dm1Y_ - m1Y_ * dmVaRY_)/mVaRY_^2 556 | } else { 557 | # test based on product 558 | tmp1 <- (mVaRY_ * dm1X_ + m1X_ * dmVaRY_) 559 | tmp2 <- (mVaRX_ * dm1Y_ + m1Y_ * dmVaRX_) 560 | } 561 | 562 | # ======= 563 | gradient <- array(NA, c(8, 1, N)) 564 | gradient[1:8, 1, ] <- tmp1 - tmp2 565 | V.hat <- array(NA, c(T, 8, N)) 566 | V.hat[, 1, ] <- sweep(x = X, MARGIN = 2, STATS = m1X, FUN = "-") 567 | V.hat[, 5, ] <- sweep(x = Y, MARGIN = 2, STATS = m1Y, FUN = "-") 568 | V.hat[, 2, ] <- sweep(x = X^2, MARGIN = 2, STATS = g2X, FUN = "-") 569 | V.hat[, 6, ] <- sweep(x = Y^2, MARGIN = 2, STATS = g2Y, FUN = "-") 570 | V.hat[, 3, ] <- sweep(x = X^3, MARGIN = 2, STATS = g3X, FUN = "-") 571 | V.hat[, 7, ] <- sweep(x = Y^3, MARGIN = 2, STATS = g3Y, FUN = "-") 572 | V.hat[, 4, ] <- sweep(x = X^4, MARGIN = 2, STATS = g4X, FUN = "-") 573 | V.hat[, 8, ] <- sweep(x = Y^4, MARGIN = 2, STATS = g4Y, FUN = "-") 574 | Psi.hat <- array(apply(X = V.hat, MARGIN = 3, FUN = compute.Psi.hat, 575 | b = b), c(8, 8, N)) 576 | se <- vector("double", N) 577 | for (i in 1:N) { 578 | se[i] <- sqrt(crossprod(gradient[, , i], Psi.hat[, , i] %*% gradient[, 579 | , i])/T) 580 | } 581 | return(se) 582 | } 583 | se.msharpe.bootstrap <- compiler::cmpfun(.se.msharpe.bootstrap) 584 | --------------------------------------------------------------------------------