# How can I calculate the Maximum Drawdown MDD in python

I need to calculate the a time dynamic Maximum Drawdown in Python. The problem is that e.g.:

( df.CLOSE_SPX.max() - df.CLOSE_SPX.min() ) / df.CLOSE_SPX.max()

can't work since these functions use all data and not e.g. considering the minimum only from a given maximum onwards on the timeline. Does anone know how to implement that in python?

This is a short example of the dataframe used:

              CLOSE_SPX    Close_iBoxx  A_Returns  B_Returns  A_Vola    B_Vola
2014-05-15    1870.85      234.3017    -0.009362   0.003412   0.170535  0.075468
2014-05-16    1877.86      234.0216     0.003747  -0.001195   0.170153  0.075378
2014-05-19    1885.08      233.7717     0.003845  -0.001068   0.170059  0.075384
2014-05-20    1872.83      234.2596    -0.006498   0.002087   0.170135  0.075410
2014-05-21    1888.03      233.9101     0.008116  -0.001492   0.169560  0.075326
2014-05-22    1892.49      233.5429     0.002362  -0.001570   0.169370  0.075341
2014-05-23    1900.53      233.8605     0.004248   0.001360   0.168716  0.075333
2014-05-27    1911.91      234.0368     0.005988   0.000754   0.168797  0.075294
2014-05-28    1909.78      235.4454    -0.001114   0.006019   0.168805  0.075474
2014-05-29    1920.03      235.1813     0.005367  -0.001122   0.168866  0.075451
2014-05-30    1923.57      235.2161     0.001844   0.000148   0.168844  0.075430
2014-06-02    1924.97      233.8868     0.000728  -0.005651   0.168528  0.075641
2014-06-03    1924.24      232.9049    -0.000379  -0.004198   0.167852  0.075267


You can get this using a pandas rolling_max to find the past maximum in a window to calculate the current day's drawdown, then use a rolling_min to determine the maximum drawdown that has been experienced.

Lets say we wanted the moving 1-year (252 trading day) maximum drawdown experienced by a particular symbol. The following should do the trick:

import pandas as pd
import matplotlib.pyplot as pp
import datetime

# Get SPY data for past several years

# We are going to use a trailing 252 trading day window
window = 252

# Calculate the max drawdown in the past window days for each day in the series.
# Use min_periods=1 if you want to let the first 252 days data have an expanding window
Daily_Drawdown = SPY_Dat['Adj Close']/Roll_Max - 1.0

# Next we calculate the minimum (negative) daily drawdown in that window.
# Again, use min_periods=1 if you want to allow the expanding window
Max_Daily_Drawdown = Daily_Drawdown.rolling(window, min_periods=1).min()

# Plot the results
Daily_Drawdown.plot()
Max_Daily_Drawdown.plot()
pp.show()


Which yields (Blue is daily running 252-day drawdown, green is maximum experienced 252-day drawdown in the past year):

• Looks good, but it returns a value error: ValueError Traceback (most recent call last) D:\Python Modules\MDDown.pyx in <module>() 20 21 # Plot the results ---> 22 Daily_Drawdown.plot() 23 Max_Daily_Drawdown.plot() 24 pp.show() Commented Jun 1, 2015 at 19:59
• Looks like there might be a problem with your pandas/matplotlib integration.. check the Max_Daily_Drawdown variable.. it should contain what you need. Commented Jun 1, 2015 at 20:09
• Works, perfect! Thanks a lot, MarkD! I highly appreciate your support! Commented Jun 1, 2015 at 20:44
• This solution isn't exactly what practitioners would call a rolling Max Drawdown because it looks up to window days further back than the window period. Solutions for a strict rolling max drawdown are more difficult. Commented Jun 15, 2021 at 18:12

If you want to consider drawdown from the beginning of the time series rather than from past 252 trading days only, consider using cummax() and cummin()

Roll_Max = SPY_Dat['Adj Close'].cummax()
Daily_Drawdown = SPY_Dat['Adj Close']/Roll_Max - 1.0
Max_Daily_Drawdown = Daily_Drawdown.cummin()


For anyone finding this now pandas has removed pd.rolling_max and min so you have to pass

(series or df).rolling(window, min_periods=None, center=False, win_type=None, on=None, axis=0, closed=None).max()

# We are going to use a trailing 252 trading day window
window = 252

# Calculate the max drawdown in the past window days for each day in the series.
# Use min_periods=1 if you want to let the first 252 days data have an expanding window
Daily_Drawdown = SPY_Dat['Adj Close']/Roll_Max - 1.0

# Next we calculate the minimum (negative) daily drawdown in that window.
# Again, use min_periods=1 if you want to allow the expanding window
Max_Daily_Drawdown = Daily_Drawdown.rolling(window, min_periods=1).min()

# Plot the results
Daily_Drawdown.plot()
Max_Daily_Drawdown.plot()
pp.show()


I recently had a similar issue, but instead of a global MDD, I was required to find the MDD for the interval after each peak. My implementation based on Investopedia description follows bellow.

import pandas as pd

def calc_MDD(networth):
df = pd.Series(networth, name="nw").to_frame()

max_peaks_idx = df.nw.expanding(min_periods=1).apply(lambda x: x.argmax()).fillna(0).astype(int)
df['max_peaks_idx'] = pd.Series(max_peaks_idx).to_frame()

nw_peaks = pd.Series(df.nw.iloc[max_peaks_idx.values].values, index=df.nw.index)

df['dd'] = ((df.nw-nw_peaks)/nw_peaks)
df['mdd'] = df.groupby('max_peaks_idx').dd.apply(lambda x: x.expanding(min_periods=1).apply(lambda y: y.min())).fillna(0)

return df


Here is an sample after running this code:

    nw          max_peaks_idx       dd          mdd
0   10000.000       0           0.000000    0.000000
1   9726.943        0           -0.027306   -0.027306
2   9464.503        0           -0.053550   -0.053550
3   9676.380        0           -0.032362   -0.053550
4   9709.717        0           -0.029028   -0.053550
5   9824.248        0           -0.017575   -0.053550
6   9919.061        0           -0.008094   -0.053550
7   9909.199        0           -0.009080   -0.053550
8   10140.184       8           0.000000    0.000000
9   10088.081       8           -0.005138   -0.005138
10  9970.515        8           -0.016732   -0.016732
11  9972.278        8           -0.016558   -0.016732


And here is an image of the complete applied to the complete dataset.

PS: I could have eliminated the zero values in the dd and mdd columns, but I find it useful that these values help indicate when a new peak was observed in the time-series.

This solution is for ALL data not a specified window period and gives dollar amount rather than a percentage but can easily be adjusted to do that

Lets first look at the non-pandas was to understand the solution:

def mdd(prices: list):
maxDif = 0
start = prices[0]
for i in range(len(prices)):
maxDif = min(maxDif, prices[i]-start)
start = max(prices[i], start)
return maxDif


Here we have a one-pass algorithm to determine the max difference between the high and any low by just updating the start with the max occurrence and calculating the min difference each iteration.

As you can imagine even though it is a one pass algorithm it will still be slow with large data sets so heres an easy pandas one-liner that uses an expanding window

def pdmdd(prices:pd.Series):
return (prices - prices.rolling(len(prices), min_periods=1).max()).min()