# 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() Jun 1 '15 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. Jun 1 '15 at 20:09
• Works, perfect! Thanks a lot, MarkD! I highly appreciate your support! Jun 1 '15 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. Jun 15 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.