Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Useful Functions

Authors
Affiliations
University of Delaware
Zhejiang Financial College

Find the maximum drawdown

PerformanceAnalytics provides several functions to calculate the drawdowns. What I offer here is an alternative to PerformanceAnalytics::table.Drawdowns. The benefits of my solution are:

  • It’s 1.89x faster than table.Drawdowns.

  • It doesn’t require the user to convert the input into a zoo or xts object, which is convenient if you’re using data.table.

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)

The drawdowns Function

The drawdowns below is a faster alternative to PerformanceAnalytics.

Inputs:

  • ret: a vector of returns

  • date: a vector of dates

  • top: the number of largest drawdowns to be returned

Output: A data.table with the following columns:

  • start: the start date of the drawdown

  • trough: the date of the trough

  • end: the last day of the drawdown (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)
Loading...

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')]
Loading...