{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Useful Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Find the maximum drawdown" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`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:\n", "\n", "- It's **1.89x** faster than `table.Drawdowns`.\n", "\n", "- 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`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create a Sample Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "Let's generate these two variables." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [], "source": [ "set.seed(42)\n", "\n", "# Generate 100 returns with mean 0 and standard deviation 0.05\n", "ret = rnorm(100, mean=0, sd=0.05)\n", "\n", "# Generate 100 dates starting from 2010/1/1\n", "date = seq(as.Date(\"2010/1/1\"), by=\"day\", length.out=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The `drawdowns` Function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `drawdowns` below is a faster alternative to `PerformanceAnalytics`.\n", "\n", "**Inputs:**\n", "- `ret`: a vector of returns\n", "- `date`: a vector of dates\n", "- `top`: the number of largest drawdowns to be returned\n", "\n", "**Output:**\n", "A `data.table` with the following columns:\n", "- `start`: the start date of the drawdown\n", "- `trough`: the date of the trough\n", "- `end`: the last day of the darwdown (if new watermark hasn't been reached yet, it's the last day in the sample).\n", "- `depth`: the drawdown (percentage)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [ { "data": { "text/html": [ "<table class=\"dataframe\">\n", "<caption>A data.table: 2 × 4</caption>\n", "<thead>\n", "\t<tr><th scope=col>start</th><th scope=col>trough</th><th scope=col>end</th><th scope=col>depth</th></tr>\n", "\t<tr><th scope=col><date></th><th scope=col><date></th><th scope=col><date></th><th scope=col><dbl></th></tr>\n", "</thead>\n", "<tbody>\n", "\t<tr><td>2010-01-13</td><td>2010-02-16</td><td>2010-04-10</td><td>-0.49650974</td></tr>\n", "\t<tr><td>2010-01-02</td><td>2010-01-02</td><td>2010-01-03</td><td>-0.02823491</td></tr>\n", "</tbody>\n", "</table>\n" ], "text/latex": [ "A data.table: 2 × 4\n", "\\begin{tabular}{llll}\n", " start & trough & end & depth\\\\\n", " <date> & <date> & <date> & <dbl>\\\\\n", "\\hline\n", "\t 2010-01-13 & 2010-02-16 & 2010-04-10 & -0.49650974\\\\\n", "\t 2010-01-02 & 2010-01-02 & 2010-01-03 & -0.02823491\\\\\n", "\\end{tabular}\n" ], "text/markdown": [ "\n", "A data.table: 2 × 4\n", "\n", "| start <date> | trough <date> | end <date> | depth <dbl> |\n", "|---|---|---|---|\n", "| 2010-01-13 | 2010-02-16 | 2010-04-10 | -0.49650974 |\n", "| 2010-01-02 | 2010-01-02 | 2010-01-03 | -0.02823491 |\n", "\n" ], "text/plain": [ " start trough end depth \n", "1 2010-01-13 2010-02-16 2010-04-10 -0.49650974\n", "2 2010-01-02 2010-01-02 2010-01-03 -0.02823491" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "library(data.table)\n", "\n", "drawdowns <- function(ret, date, top=5) {\n", " # This function calculates the drawdowns for a given return series\n", " # Input:\n", " # ret: a vector of returns\n", " # date: a vector of dates\n", " # top: the number of largest drawdowns to be returned\n", "\n", " input = data.table(date, ret)[order(date)]\n", "\n", " # a drawdown is defined whenever the cumulative return is below the previous peak\n", " drawdowns_raw = input[, {\n", " ret_cum=cumprod(1 + ret)\n", " ret_cummax=cummax(c(1, ret_cum))[-1]\n", " drawdowns=ret_cum/ret_cummax - 1\n", " list(date, ret, ret_cum, ret_cummax, drawdowns)\n", " }]\n", "\n", " # print(drawdowns_raw)\n", "\n", " # find the largest drawdowns\n", " drawdowns = drawdowns_raw[, ':='(grp = rleid(drawdowns<0))\n", " ][drawdowns<0, \n", " .(start=date[1], trough=date[which.min(drawdowns)],\n", " end=date[.N], depth=drawdowns[which.min(drawdowns)]),\n", " keyby=.(grp)\n", " ][order(depth)]\n", "\n", " # return the top drawdowns\n", " top = fifelse(top > nrow(drawdowns), nrow(drawdowns), top)\n", " drawdowns = drawdowns[1:top][, ':='(grp=NULL)]\n", " \n", " return(drawdowns[])\n", "}\n", "\n", "drawdowns(ret, date, top=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we use `PerformanceAnalytics::table.Drawdowns`, the code and result are offered below. As you can see, the result is the same." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "vscode": { "languageId": "r" } }, "outputs": [ { "data": { "text/html": [ "<table class=\"dataframe\">\n", "<caption>A data.frame: 2 × 3</caption>\n", "<thead>\n", "\t<tr><th scope=col>From</th><th scope=col>Trough</th><th scope=col>Depth</th></tr>\n", "\t<tr><th scope=col><date></th><th scope=col><date></th><th scope=col><dbl></th></tr>\n", "</thead>\n", "<tbody>\n", "\t<tr><td>2010-01-13</td><td>2010-02-16</td><td>-0.4965</td></tr>\n", "\t<tr><td>2010-01-02</td><td>2010-01-02</td><td>-0.0282</td></tr>\n", "</tbody>\n", "</table>\n" ], "text/latex": [ "A data.frame: 2 × 3\n", "\\begin{tabular}{lll}\n", " From & Trough & Depth\\\\\n", " <date> & <date> & <dbl>\\\\\n", "\\hline\n", "\t 2010-01-13 & 2010-02-16 & -0.4965\\\\\n", "\t 2010-01-02 & 2010-01-02 & -0.0282\\\\\n", "\\end{tabular}\n" ], "text/markdown": [ "\n", "A data.frame: 2 × 3\n", "\n", "| From <date> | Trough <date> | Depth <dbl> |\n", "|---|---|---|\n", "| 2010-01-13 | 2010-02-16 | -0.4965 |\n", "| 2010-01-02 | 2010-01-02 | -0.0282 |\n", "\n" ], "text/plain": [ " From Trough Depth \n", "1 2010-01-13 2010-02-16 -0.4965\n", "2 2010-01-02 2010-01-02 -0.0282" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "suppressMessages({library(PerformanceAnalytics)})\n", "\n", "inputs = zoo(ret, order.by=date)\n", "table.Drawdowns(inputs, top=2)[, c('From', 'Trough', 'Depth')]" ] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "4.3.2" } }, "nbformat": 4, "nbformat_minor": 2 }