5
$\begingroup$

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
$\endgroup$
19
$\begingroup$

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 pandas.io.data as web
import matplotlib.pyplot as pp

# Get SPY data for past several years
SPY_Dat = web.DataReader('SPY', 'yahoo', datetime.date(2007,1,1))

# 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
Roll_Max = pd.rolling_max(SPY_Dat['Adj Close'], window, min_periods=1)
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 = pd.rolling_min(Daily_Drawdown, window, min_periods=1)

# 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): Max Drawdown Result

| improve this answer | |
$\endgroup$
  • $\begingroup$ 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() $\endgroup$ – hb.klein Jun 1 '15 at 19:59
  • $\begingroup$ Looks like there might be a problem with your pandas/matplotlib integration.. check the Max_Daily_Drawdown variable.. it should contain what you need. $\endgroup$ – MarkD Jun 1 '15 at 20:09
  • $\begingroup$ Works, perfect! Thanks a lot, MarkD! I highly appreciate your support! $\endgroup$ – hb.klein Jun 1 '15 at 20:44
9
$\begingroup$

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
Roll_Max = SPY_Dat['Adj Close'].rolling(window, min_periods=1).max()
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()
| improve this answer | |
$\endgroup$
7
$\begingroup$

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()
| improve this answer | |
$\endgroup$
3
$\begingroup$

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. enter image description here

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.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.