January cohort is now open: How to secure your spot:

How to compute drawdown on an investment

PQN #006 Visualize the trend with pandas rolling statistics

How to compute drawdown on an investment

In today’s issue, I’m going to show you how to compute the drawdown of the SPY ETF with Python.

Drawdown is the maximum decline from peak to trough during a specific period before a new peak is reached. Every trading strategy experiences drawdowns. Computing it helps you compare the relative riskiness between assets or strategies.

Unfortunately, most people don’t consider drawdown when managing their investments. Or if they do, struggle to compute it.

Today I’m going to walk you through it step by step.

Step 1: Get the data

I start by importing the libraries I need.

import yfinance as yf
import numpy as np

Then I get data and compute the simple returns.

data = yf.download("SPY", start="2020-01-01", end="2022-07-31")
returns = data["Adj Close"].pct_change()

I use yfinance to get stock data – in this case, SPY. Drawdown is usually computed with the returns of a portfolio. I want to keep it simple so I use the S&P500 ETF.

Step 2: Create the drawdown function

Drawdown is computed with 4 lines of code.

When computing returns, the first value is turned into np.nan. I replace it with a 0.0 to compute cumulative returns. Then I create a cumulative return series which is the cumulative product of 1 plus the return. Next, I use NumPy’s accumulate function. Accumulate tracks the running maximum value which is perfect for keeping tabs on the peak return.

Finally, I compute the percentage difference between the cumulative and peak returns.

Here’s the code:

def drawdown(returns):
    """Determines the drawdown
    returns : pd.Series
        Daily returns of an asset, noncumulative
    drawdown : pd.Series

    # replace the first nan value with 0.0
    returns.fillna(0.0, inplace=True)

    # create cumulative returns
    cumulative = (returns + 1).cumprod()

    # np.maximum.accumulate takes the running max value
    # of the input series. in this case, it will maintain
    # the running maximum value. this is the running
    # maximum return
    running_max = np.maximum.accumulate(cumulative)

    # compute the change between the cumulative return
    # and the running maximum return
    return (cumulative - running_max) / running_max

And the plot.

drawdown(returns).plot(kind="area", color="salmon", alpha=0.5)
PQN #007: How to compute drawdown on an investment

This chart shows SPY dropping 33.7% from its peak to trough return in 2020.

Step 3: Create a max drawdown function

Next, I use the drawdown to compute a max drawdown chart.

The max drawdown differs from the drawdown by tracking the maximum drawdown of a 30-day rolling window of returns. To use rolling statistics, check out this issue of The PyQuant Newsletter.

Here’s the code.

def max_drawdown(returns):
    """ Determines the maximum drawdown
    returns : pd.Series
        Daily returns of an asset, noncumulative
    max_drawdown : float

    return np.min(drawdown(returns))

max_drawdown applies the drawdown function to 30 days of returns and figures out the smallest (most negative) value that occurs over those 30 days. Then it moves forward one day, computes it again, until the end of the series.

Here’s the plot.

returns.rolling(30).apply(max_drawdown).plot(kind="area", color="salmon", alpha=0.5)
PQN #007: How to compute drawdown on an investment

Drawdown and max drawdown focus on capital preservation. It’s a useful indicator of the riskiness of a stock, portfolio, or strategy. Use it to help manage your risk.