inst/doc/SharpeR.R

## ----'preamble', include=FALSE, warning=FALSE, message=FALSE----
library(knitr)

# set the knitr options ... for everyone!
# if you unset this, then vignette build bonks. oh, joy.
#opts_knit$set(progress=TRUE)
opts_knit$set(eval.after='fig.cap')
# for a package vignette, you do want to echo.
# opts_chunk$set(echo=FALSE,warning=FALSE,message=FALSE)
opts_chunk$set(warning=FALSE,message=FALSE)
#opts_chunk$set(results="asis")
opts_chunk$set(cache=TRUE,cache.path="cache/SharpeR")

#opts_chunk$set(fig.path="figure/",dev=c("pdf","cairo_ps"))
opts_chunk$set(fig.path="figure/SharpeR",dev=c("pdf"))
opts_chunk$set(fig.width=5,fig.height=4,dpi=64)

# doing this means that png files are made of figures;
# the savings is small, and it looks like shit:
#opts_chunk$set(fig.path="figure/",dev=c("png","pdf","cairo_ps"))
#opts_chunk$set(fig.width=4,fig.height=4)
# for figures? this is sweave-specific?
#opts_knit$set(eps=TRUE)

# this would be for figures:
#opts_chunk$set(out.width='.8\\textwidth')
# for text wrapping:
options(width=64,digits=2)
opts_chunk$set(size="small")
opts_chunk$set(tidy=TRUE,tidy.opts=list(width.cutoff=50,keep.blank.line=TRUE))

compile.time <- Sys.time()

# from the environment

# only recompute if FORCE_RECOMPUTE=True w/out case match.
FORCE_RECOMPUTE <- 
	(toupper(Sys.getenv('FORCE_RECOMPUTE',unset='False')) == "TRUE")

# compiler flags!

# not used yet
LONG.FORM <- FALSE

library(quantmod)
options("getSymbols.warning4.0"=FALSE)

## ----'babysteps'----------------------------------------------
library(SharpeR)
# suppose you computed the Sharpe of your strategy to
# be 1.3 / sqrt(yr), based on 1200 daily observations.
# store them as follows:
my.sr <- sr(sr=1.3,df=1200-1,ope=252,epoch="yr")
print(my.sr)
# multiple strategies can be tracked as well.
# one can attach names to them.
srstats <- c(0.5,1.2,0.6)
dim(srstats) <- c(3,1)
rownames(srstats) <- c("strat. A","strat. B","benchmark")
my.sr <- sr(srstats,df=1200-1,ope=252,epoch="yr")
print(my.sr)

## ----'showoff'------------------------------------------------
set.seed(as.integer(charToRaw("set the seed")))
# Sharpe's 'model': just given a bunch of returns.
returns <- rnorm(253*8,mean=3e-4,sd=1e-2)
asr <- as.sr(returns,ope=253,epoch="yr")
print(asr)
# a data.frame with a single strategy
asr <- as.sr(data.frame(my.strategy=returns),ope=253,epoch="yr")
print(asr)

## ----'more_data_frame'----------------------------------------
# a data.frame with multiple strategies
asr <- as.sr(data.frame(strat1=rnorm(253*8),strat2=rnorm(253*8,mean=4e-4,sd=1e-2)),
	ope=253,epoch="yr")
print(asr)

## ----'stock_loading',eval=FALSE,echo=TRUE---------------------
#  require(quantmod)
#  # get price data, compute log returns on adjusted closes
#  get.ret <- function(sym,warnings=FALSE,...) {
#  	# getSymbols.yahoo will barf sometimes; do a trycatch
#    trynum <- 0
#  	while (!exists("OHCLV") && (trynum < 7)) {
#  		trynum <- trynum + 1
#  		try(OHLCV <- getSymbols(sym,auto.assign=FALSE,warnings=warnings,...),silent=TRUE)
#    }
#  	adj.names <- paste(c(sym,"Adjusted"),collapse=".",sep="")
#  	if (adj.names %in% colnames(OHLCV)) {
#  		adj.close <- OHLCV[,adj.names]
#  	} else {
#  		# for DJIA from FRED, say.
#  		adj.close <- OHLCV[,sym]
#  	}
#  	rm(OHLCV)
#  	# rename it
#  	colnames(adj.close) <- c(sym)
#  	adj.close <- adj.close[!is.na(adj.close)]
#  	lrets <- diff(log(adj.close))
#  	#chop first
#  	lrets[-1,]
#  }
#  get.rets <- function(syms,...) { some.rets <- do.call("cbind",lapply(syms,get.ret,...)) }

## ----'stock_loading_sneaky',eval=TRUE,echo=FALSE--------------
# sleight of hand to load precomputed data instead.
get.rets <- function(syms,from='2003-01-01',to='2013-01-01',...) {
	fname <- system.file('extdata','ret_data.rda',package='SharpeR')
	if (fname == "") {
		fname <- 'ret_data.rda'
	}
	# poofs all.ret here
	load(fname)
	sub.data <- all.ret[paste(from,to,sep="::"),colnames(all.ret) %in% syms]
	return(sub.data)
}

## ----'helper_function'----------------------------------------
require(quantmod)
# quantmod::periodReturn does not deal properly with multiple
# columns, and the straightforward apply(mtms,2,periodReturn) barfs
my.periodReturn <- function(mtms,...) {
	per.rets <- do.call(cbind,lapply(mtms,
		function(x) {
			retv <- periodReturn(x,...)
			colnames(retv) <- colnames(x)
			return(retv) 
		}))
}
# convert log return to mtm, ignoring NA
lr2mtm <- function(x,...) {
	x[is.na(x)] = 0
	exp(cumsum(x))
}

## ----'some_stocks'--------------------------------------------
some.rets <- get.rets(c("IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01")
print(as.sr(some.rets))

## ----'reannualize'--------------------------------------------
yearly <- as.sr(some.rets[,"XOM"])
monthly <- reannualize(yearly,new.ope=21,new.epoch="mo.")
print(yearly)
# significance should be the same, but units changed.
print(monthly)

## ----'AAPL'---------------------------------------------------
# get the returns (see above for the function)
aapl.rets <- get.rets(c("AAPL","SPY"),from="2003-01-01",to="2013-01-01")
# make them monthly:
mo.rets <- my.periodReturn(lr2mtm(aapl.rets),period='monthly',type='arithmetic')
rm(aapl.rets)  # cleanup
# look at both of them together:
both.sr <- as.sr(mo.rets)
print(both.sr)
# confindence intervals on the Sharpe:
print(confint(both.sr))
# perform a CAPM attribution, using SPY as 'the market'
linmod <- lm(AAPL ~ SPY,data=mo.rets)
# convert attribution model to Sharpe
CAPM.sr <- as.sr(linmod,ope=both.sr$ope,epoch="yr")
# statistical significance does not change (though note the sr summary
# prints a 1-sided p-value)
print(summary(linmod))
print(CAPM.sr)
# the confidence intervals tell the same story, but in different units:
print(confint(linmod,'(Intercept)'))
print(confint(CAPM.sr))

## ----'SPDRcheck'----------------------------------------------
# get the sector 'spiders'
secto.rets <- get.rets(c("XLY","XLE","XLP","XLF","XLV","XLI","XLB","XLK","XLU"),
	from="2003-01-01",to="2013-01-01")
# make them monthly:
mo.rets <- my.periodReturn(lr2mtm(secto.rets),period='monthly',type='arithmetic')
# one-sample test on utilities:
XLU.monthly <- mo.rets[,"XLU"]
print(sr_test(XLU.monthly),alternative="two.sided")

# test for equality of Sharpe among the different spiders
print(sr_equality_test(as.matrix(secto.rets)))
# perform a paired two-sample test via sr_test:
XLF.monthly <- mo.rets[,"XLF"]
print(sr_test(x=XLU.monthly,y=XLF.monthly,ope=12,paired=TRUE))

## ----'sropt_basics'-------------------------------------------
set.seed(as.integer(charToRaw("7bf4b86a-1834-4b58-9eff-6c7dec724fec")))
# from a matrix object:
ope <- 253
n.stok <- 7
n.yr <- 8
# somewhat unrealistic: independent returns.
rand.rets <- matrix(rnorm(n.yr * ope * n.stok),ncol=n.stok)
asro <- as.sropt(rand.rets,ope=ope)
rm(rand.rets)
print(asro)
# under the alternative, when the mean is nonzero
rand.rets <- matrix(rnorm(n.yr * ope * n.stok,mean=6e-4,sd=1e-2),ncol=n.stok)
asro <- as.sropt(rand.rets,ope=ope)
rm(rand.rets)
print(asro)
# from an xts object
some.rets <- get.rets(c("IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01")
asro <- as.sropt(some.rets)
print(asro)

## ----'sropt_estim'--------------------------------------------
# confidence intervals:
print(confint(asro,level.lo=0.05,level.hi=1))
# estimation
print(inference(asro,type="KRS"))
print(inference(asro,type="MLE"))

## ----'MLE_rule'-----------------------------------------------
ope <- 253
zeta.s <- 0.8
n.check <- 1000
df1 <- 10
df2 <- 6 * ope
rvs <- rsropt(n.check,df1,df2,zeta.s,ope,drag=0)
roll.own <- sropt(z.s=rvs,df1,df2,drag=0,ope=ope,epoch="yr")
MLEs <- inference(roll.own,type="MLE")
zerMLE <- MLEs <= 0
crit.value <- 0.5 * (max(rvs[zerMLE])^2 + min(rvs[!zerMLE])^2)
aspect.ratio <- df1 / (df2 / ope)
cat(sprintf("empirical cutoff for zero MLE is %2.2f yr^{-1}\n", crit.value))
cat(sprintf("the aspect ratio is %2.2f yr^{-1}\n",aspect.ratio))

## ----'estimate_overfit',fig.cap=paste("Q-Q plot of",n.sim,"achieved optimal \\txtSR values from brute force search over both windows of a Moving Average Crossover under the null of driftless log returns with zero autocorrelation versus the approximation by a 2-parameter optimal \\txtSR distribution is shown.")----
require(TTR)
# brute force search two window MAC
brute.force <- function(lrets,rrets=exp(lrets)-1,win1,win2=win1) {
	mtms <- c(1,exp(cumsum(lrets)))  # prepend a 1.
  # do all the SMAs;
  SMA1 <- sapply(win1,function(n) { SMA(mtms,n=n) }) 
  symmetric <- missing(win2)
  if (!symmetric)
  	SMA2 <- sapply(win2,function(n) { SMA(mtms,n=n) }) 

  mwin <- max(c(win1,win2))
  zeds <- matrix(NaN,nrow=length(win1),ncol=length(win2))
	upb <- if (symmetric) length(win1) - 1 else length(win1)
	# 2FIX: vectorize this!
	for (iidx in 1:upb) {
		SM1 <- SMA1[,iidx]
		lob <- if (symmetric) iidx + 1 else 1
		for (jidx in lob:length(win2)) {
			SM2 <- if (symmetric) SMA1[,jidx] else SMA2[,jidx]
			trades <- sign(SM1 - SM2)
			dum.bt <- trades[mwin:(length(trades)-1)] * rrets[mwin:length(rrets)]  # braindead backtest.
			mysr <- as.sr(dum.bt)
			zeds[iidx,jidx] <- mysr$sr
			# abuse symmetry of arithmetic returns
			if (symmetric) zeds[jidx,iidx] <- - zeds[iidx,jidx]  
		}
	}
	retv <- max(zeds,na.rm=TRUE) 
	return(retv)
}
# simulate one.
sim.one <- function(nbt,win1,...) {
	lrets <- rnorm(nbt+max(win1),sd=0.01)
	retv <- brute.force(lrets,win1=win1,...)
	return(retv)
}
# set everything up
set.seed(as.integer(charToRaw("e23769f4-94f8-4c36-bca1-28c48c49b4fb")))
ope <- 253
n.yr <- 4
n.obs <- ceiling(ope * n.yr)
LONG.FORM <- FALSE
n.sim <- if (LONG.FORM) 2048 else 1024
win1 <- if (LONG.FORM) c(2,4,8,16,32,64,128,256) else c(4,16,64,256)

# run them
system.time(max.zeds <- replicate(n.sim,sim.one(n.obs,win1)))
# qqplot;
qqplot(qsropt(ppoints(length(max.zeds)),df1=2,df2=n.obs),max.zeds,
			 xlab = "Theoretical Approximate Quantiles", ylab = "Sample Quantiles")
qqline(max.zeds,datax=FALSE,distribution = function(p) { qsropt(p,df1=2,df2=n.obs) },
			 col=2)

## ----'now_on_spy'---------------------------------------------
# is MAC on SPY significant?
SPY.lret <- get.rets(c('SPY'),from="2003-01-01",to="2013-01-01")
# oops! there might be NAs in there!
mysr <- as.sr(SPY.lret,na.rm=TRUE)  # just to get the ope
print(mysr)
# get rid of NAs
SPY.lret[is.na(SPY.lret)] <- 0
# try a whole lot of windows:
win1 <- seq(4,204,by=10)
zeds <- brute.force(SPY.lret,win1=win1)
SPY.MAC.asro <- sropt(z.s=zeds,df1=2,df2=length(SPY.lret) - max(win1),ope=mysr$ope)
print(SPY.MAC.asro)
print(inference(SPY.MAC.asro,type="KRS"))

## ----'del_sropt_basics'---------------------------------------
set.seed(as.integer(charToRaw("364e72ab-1570-43bf-a1c6-ee7481e1c631")))
# from a matrix object:
ope <- 253
n.stok <- 7
n.yr <- 8
# somewhat unrealistic: independent returns, under the null
rand.rets <- matrix(rnorm(n.yr * ope * n.stok),ncol=n.stok)
# the hedge constraint: hedge out the first stock.
G <- diag(n.stok)[1,]
asro <- as.del_sropt(rand.rets,G,ope=ope)
print(asro)
# hedge out the first two 
G <- diag(n.stok)[1:2,]
asro <- as.del_sropt(rand.rets,G,ope=ope)
print(asro)
# under the alternative, when the mean is nonzero
rand.rets <- matrix(rnorm(n.yr * ope * n.stok,mean=6e-4,sd=1e-2),ncol=n.stok)
G <- diag(n.stok)[1,]
asro <- as.del_sropt(rand.rets,G,ope=ope)
print(asro)

## ----'del_sropt_hedging'--------------------------------------
# from an xts object
some.rets <- get.rets(c("SPY","IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01")
# without the hedge, allowing SPY position
asro <- as.sropt(some.rets)
print(asro)
# hedge out SPY!
G <- diag(dim(some.rets)[2])[1,]
asro.hej <- as.del_sropt(some.rets,G)
print(asro.hej)

## ----'del_sropt_estim'----------------------------------------
# confidence intervals:
print(confint(asro,level.lo=0.05,level.hi=1))
# estimation
print(inference(asro,type="KRS"))
print(inference(asro,type="MLE"))

## ----'sr_eq_test_normal',fig.cap=paste("P-values from \\Rfunction{sr\\_equality\\_test} on",n.sim,"multiple realizations of independent, normally distributed returns series are Q-Q plotted against uniformity.")----
# function for Q-Q plot against uniformity
qqunif <- function(x,xlab="Theoretical Quantiles under Uniformity",
									 ylab=NULL,...) {
	if (is.null(ylab))
		ylab=paste("Sample Quantiles (",deparse(substitute(x)),")",sep="")
	qqplot(qunif(ppoints(length(x))),x,xlab=xlab,ylab=ylab,...)
	abline(0,1,col='red')
}

# under normality.
LONG.FORM <- FALSE
n.ro <- if (LONG.FORM) 253*4 else 253*2
n.co <- if (LONG.FORM) 20 else 4
n.sim <- if (LONG.FORM) 1024 else 512
set.seed(as.integer(charToRaw("e3709e11-37e0-449b-bcdf-9271fb1666e5")))
afoo <- replicate(n.sim,sr_equality_test(matrix(rnorm(n.ro*n.co),ncol=n.co),type="F"))
sr_eq.pvals <- unlist(afoo["p.value",])
qqunif(sr_eq.pvals)

## ----'sr_eq_test_hetero',fig.cap=paste("P-values from \\Rfunction{sr\\_equality\\_test} on",n.sim,"multiple realizations of independent, \\tlaw{}-distributed returns series are Q-Q plotted against uniformity.")----
# try heteroskedasticity?
set.seed(as.integer(charToRaw("81c97c5e-7b21-4672-8140-bd01d98d1d2e")))
afoo <- replicate(n.sim,sr_equality_test(matrix(rt(n.ro*n.co,df=4),ncol=n.co),type="F"))
sr_eq.pvals <- unlist(afoo["p.value",])
qqunif(sr_eq.pvals)

## ----'sr_eq_test_correlated',fig.cap=paste("P-values from \\Rfunction{sr\\_equality\\_test} on",n.sim,"multiple realizations of correlated, normally-distributed returns series are Q-Q plotted against uniformity.")----
# try correlated returns
n.fact <- max(2,n.co - 5)
gen.ret <- function(n1,n2,f=max(2,n2-2),fuzz=0.1) {
	A <- matrix(rnorm(n1*f),nrow=n1)
	B <- matrix(rnorm(f*n2),nrow=f)
	C <- sqrt(1-fuzz^2) * A %*% B + fuzz * matrix(rnorm(n1*n2),nrow=n1)
}
set.seed(as.integer(charToRaw("e4d61c2c-efb3-4cba-9a6e-5f5276ce2ded")))
afoo <- replicate(n.sim,sr_equality_test(gen.ret(n.ro,n.co,n.fact),type="F"))
sr_eq.pvals <- unlist(afoo["p.value",])
qqunif(sr_eq.pvals)

## ----'sr_eq_test_mtime_pre',echo=FALSE------------------------
n.co <- if (LONG.FORM) 100 else 50 
n.sim <- if (LONG.FORM) 1024 else 128

## ----'sr_eq_test_mtime',fig.cap=paste("P-values from \\Rfunction{sr\\_equality\\_test} on",n.sim,"multiple realizations of",n.co,"market timing strategies' returns series are Q-Q plotted against uniformity. Nominal coverage is not maintained: the test is far too liberal in this case.")----
n.co <- if (LONG.FORM) 100 else 50 
n.sim <- if (LONG.FORM) 1024 else 128
SPY.lret <- get.rets(c('SPY'),from="2003-01-01",to="2013-01-01")
SPY.wk.rret <- my.periodReturn(lr2mtm(SPY.lret),period='weekly',
	type='arithmetic')
gen.tim <- function(n2) {
	mkt.timing.signal <- sign(rnorm(n2*length(SPY.wk.rret)))
	mkt.ret <- matrix(rep(SPY.wk.rret,n2) * mkt.timing.signal,ncol=n2)
}
set.seed(as.integer(charToRaw("447cfe85-b612-4b14-bd01-404e6e99aca4")))
system.time(afoo <- replicate(n.sim,sr_equality_test(gen.tim(n.co),type="F")))
sr_eq.pvals <- unlist(afoo["p.value",])
qqunif(sr_eq.pvals)

## ----'sr_fancy_eq'--------------------------------------------
# get returns
some.rets <- as.matrix(get.rets(c("IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01"))
# using the default vcov
test.vanilla <- sr_equality_test(some.rets,type="F")
print(test.vanilla)
if (require(sandwich)) {
	# and a fancy one:
	test.HAC <- sr_equality_test(some.rets,type="F",vcov.func=vcovHAC)
	print(test.HAC)
}

## ----'sr_vcov'------------------------------------------------
# get returns
some.rets <- as.matrix(get.rets(c("IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01"))
ope <- 252
vanilla.Sig <- sr_vcov(some.rets,ope=ope)
print(vanilla.Sig)
if (require(sandwich)) {
	HC.Sig <- sr_vcov(some.rets,vcov=vcovHC,ope=ope)
	print(HC.Sig$Ohat)
}
if (require(sandwich)) {
	HAC.Sig <- sr_vcov(some.rets,vcov=vcovHAC,ope=ope)
	print(HAC.Sig$Ohat)
}

## ----'marko_vcov'---------------------------------------------
# get returns
some.rets <- get.rets(c("IBM","AAPL","XOM"),
	from="2007-01-01",to="2013-01-01")

ism.wald <- function(X,vcov.func=vcov) {
	# negating returns is idiomatic to get + Markowitz
	ism <- ism_vcov(- as.matrix(X),vcov.func=vcov.func)
	ism.mu <- ism$mu[1:ism$p]
	ism.Sg <- ism$Ohat[1:ism$p,1:ism$p]
	retval <- ism.mu / sqrt(diag(ism.Sg))
	dim(retval) <- c(ism$p,1)
	rownames(retval) <- rownames(ism$mu)[1:ism$p]
	return(retval)
}

wald.stats <- ism.wald(some.rets)
print(t(wald.stats))

if (require(sandwich)) {
	wald.stats <- ism.wald(some.rets,vcov.func=sandwich::vcovHAC)
	print(t(wald.stats))
}

Try the SharpeR package in your browser

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

SharpeR documentation built on Aug. 18, 2021, 5:08 p.m.