4. Useful Functions#
4.1. Find the maximum drawdown#
PerformanceAnalytics
provides several functions to calculate the drawdowns. What I offered here is an alternative to PerformanceAnalytics::table.Drawdowns
. The benefits of my solutoin are:
It’s 1.89x faster than
table.Drawdowns
.It doesn’t require the user to convert the input into a
zoo
orxts
object, which is convenient if you’re usingdata.table
.
4.1.1. Create a Sample Data#
The only inputs of our drawdowns
function are 1) a return vector ret
and 2) a date vector date
. Note they should belong to the same stock.
Let’s generate these two variables.
set.seed(42)
# Generate 100 returns with mean 0 and standard deviation 0.05
ret = rnorm(100, mean=0, sd=0.05)
# Generate 100 dates starting from 2010/1/1
date = seq(as.Date("2010/1/1"), by="day", length.out=100)
4.1.2. The drawdowns
Function#
The drawdowns
below is a faster alternative to PerformanceAnalytics
.
Inputs:
ret
: a vector of returnsdate
: a vector of datestop
: the number of largest drawdowns to be returned
Output:
A data.table
with the following columns:
start
: the start date of the drawdowntrough
: the date of the troughend
: the last day of the darwdown (if new watermark hasn’t been reached yet, it’s the last day in the sample).depth
: the drawdown (percentage).
library(data.table)
drawdowns <- function(ret, date, top=5) {
# This function calculates the drawdowns for a given return series
# Input:
# ret: a vector of returns
# date: a vector of dates
# top: the number of largest drawdowns to be returned
input = data.table(date, ret)[order(date)]
# a drawdown is defined whenever the cumulative return is below the previous peak
drawdowns_raw = input[, {
ret_cum=cumprod(1 + ret)
ret_cummax=cummax(c(1, ret_cum))[-1]
drawdowns=ret_cum/ret_cummax - 1
list(date, ret, ret_cum, ret_cummax, drawdowns)
}]
# print(drawdowns_raw)
# find the largest drawdowns
drawdowns = drawdowns_raw[, ':='(grp = rleid(drawdowns<0))
][drawdowns<0,
.(start=date[1], trough=date[which.min(drawdowns)],
end=date[.N], depth=drawdowns[which.min(drawdowns)]),
keyby=.(grp)
][order(depth)]
# return the top drawdowns
top = fifelse(top > nrow(drawdowns), nrow(drawdowns), top)
drawdowns = drawdowns[1:top][, ':='(grp=NULL)]
return(drawdowns[])
}
drawdowns(ret, date, top=2)
start | trough | end | depth |
---|---|---|---|
<date> | <date> | <date> | <dbl> |
2010-01-13 | 2010-02-16 | 2010-04-10 | -0.49650974 |
2010-01-02 | 2010-01-02 | 2010-01-03 | -0.02823491 |
If we use PerformanceAnalytics::table.Drawdowns
, the code and result are offered below. As you can see, the result is the same.
suppressMessages({library(PerformanceAnalytics)})
inputs = zoo(ret, order.by=date)
table.Drawdowns(inputs, top=2)[, c('From', 'Trough', 'Depth')]
From | Trough | Depth |
---|---|---|
<date> | <date> | <dbl> |
2010-01-13 | 2010-02-16 | -0.4965 |
2010-01-02 | 2010-01-02 | -0.0282 |