Skip to content
All library documents

Computing Maximum Drawdown from a Return Series

Article Quant Q&A · Author: unter_983

Summary

The document considers whether maximum drawdown can be calculated directly from periodic percentage returns. It explains that adding returns cumulatively is generally not the appropriate way to build a portfolio value path when returns compound. Instead, returns are compounded to produce cumulative performance, which can be represented as a net asset value series; drawdown is then measured as the percentage decline from the running high-water mark.

The answers also highlight a boundary convention: whether the series includes an initial portfolio value can change the result. One response notes that a common performance package starts from a base value of 100 and gives a modified approach to match that convention. The examples are concise and assume returns are represented in a compatible decimal format and handled as a pandas series; they do not address missing data, fees, or the choice of sampling frequency.

Key ideas

  • Periodic returns should be compounded to construct a portfolio value path.
  • Drawdown at each point is the decline in value relative to the prior running high.
  • Maximum drawdown is the most negative point in that drawdown series.
  • Including an initial value, such as a base NAV of 100, can affect the computed result.

Tags

Full text
# Implementation of Maximum Drawdown in python working directly with returns


# Implementation of Maximum Drawdown in python working directly with returns












I have a strategy on a stock (such as Buy and Hold) on which I have to calculate the maximum drawdown. The problem is that I'm working on returns expressed in percentages, so I do not have the time series of prices but the one of returns obtained at each step. So I wrote this code:

```
def MDD(returns):
 rend_cum=returns.cumsum()
 rend_max=pd.Series(rend_cum).cummax()
 drawdown=rend_cum-rend_max
 MDD=max(abs(drawdown))

 return(MDD)
```

Is it correct?

## Answer by amdopt (score 5, accepted)

https://quant.stackexchange.com/a/57708

You are missing a few things. The function below assumes that `returns` is either a pandas series or a column of a pandas dataframe. Try this:

```
def MDD(returns):
 
    cum_rets = (1 + returns).cumprod() - 1
    nav = ((1 + cum_rets) * 100).fillna(100)
    hwm = nav.cummax()
    dd = nav / hwm - 1

    return min(dd)
```

## Answer by tom1919 (score 3)

https://quant.stackexchange.com/a/74134

The empyrical package has an efficient function for max drawdown. Here's an example:

```
import empyrical as ep

returns = np.array([-0.02089651,
                    -0.023142165,
                    0.016320209,
                    0.009323824,
                    -0.048883758,
                    -0.003912041,
                    0.005281875,
                    0.029637668,
                    -0.012299053,
                    0.040005685])

max_dd = ep.max_drawdown(returns)
```

The function that @amdopt provided in an answer to your question will sometimes produce a different answer than the empyrical function because empyrical uses a starting value of 100. This modified version would provide the equivalent results:

```
def MDD(returns):
    cum_rets = (1 + returns).cumprod() - 1
    nav = ((1 + cum_rets) * 100).fillna(100)
    nav = pd.Series([100]).append(nav) # start at 100
    hwm = nav.cummax()
    dd = nav / hwm - 1
    return min(dd)
```

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.