Computing Maximum Drawdown from a Return Series
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.