Monday, February 19, 2018

S&P 500 Drawdowns (%) and Recovery (Days)

This is the first article to kick start this blog, which is intended to serve as a repository of my market studies and to promote the exchange of opinion from friends with an interest in economics. The studies will seek to model the following: relationship between various asset classes over long time horizons, detecting early warning signs of an impeding recession and position risk management. The data will primarily come from the St. Louis Fed website, Yahoo Finance and measuringworth.com.


The exercise below addresses position risk management by studying and modeling the relationship between Drawdown Depth (%) & Length (days) for the S&P 500 index. The data is not adjusted for dividends or inflation - so the model may underestimate the recovery period for very large drawdowns. For the purpose of this exercise "Close" prices are used because in real trading entering in the middle of a trading session increases the likelihood of overpaying due to intra-day volatility.

It is assumed no leverage is used when trading. For example, at the time of this writing you could buy an E-Mini S&P 500 futures contract worth  $136,611 with only a $5,800 margin. Since each point is equal to $50, theoretically, it will only take a 116-point decline (or a 4.2% drawdown) to completely empty your account (in reality your broker will liquidate your position ahead of time when the margin call isn't met).

The two primary forces driving asset returns are growth & inflation.

# Getting data. Only "Close" prices are used. Quantmod package.
getSymbols('^GSPC', src = 'yahoo', from = "1960-01-01")
sp <- GSPC
sp$Open <- GSPC$GSPC.Open
sp$High <- GSPC$GSPC.High
sp$Low <- GSPC$GSPC.Low
sp$Close <- GSPC$GSPC.Close
sp$GSPC.Adjusted=NULL
sp$GSPC.Volume=NULL
sp$GSPC.Open=NULL
sp$GSPC.High=NULL
sp$GSPC.Low=NULL
sp$GSPC.Close=NULL

#Percent change of returns.
sp$Change_pct <- (sp$Close-lag(sp$Close,k=1))/(lag(sp$Close,k=1))
sp$Change_pct["1960-01-04"]<- 0 #No NAs allowed

#Calculating drawdowns (Performance Analytics package)
Drawdown_Tbl <- table.Drawdowns(sp$Change_pct, top = 9999, digits = 4) #Table of drawdowns
Draw <- Drawdown_Tbl[,c(4,5)] #Depth and Length columns
Draw$Depth <- Draw$Depth*(-1) #Transform negative values to positive in order to do log transformations
stat.desc(Draw)

#Fitting right model to the data.
Days <- Draw$Length
Percent <- Draw$Depth

scatterplot(Days ~ Percent)
plot(Days ~ Percent) #Plot data
lines(lowess(Days ~ Percent)) #Plot lowess function to get an idea on the best fit

mod1 <- lm(Days ~ Percent) #Linear fit model
mod2 <- lm(Days ~ Percent + I(Percent^2)) #Quadratic fit. R^2 is higher than mod1, residuals are not perfect but good enough for this application.

lines(Percent, predict(mod1), col=1) #Plot model on data
lines(Percent, predict(mod2), col=2) #Plot model on data

#Console output

> summary(mod2)

Call:
lm(formula = Days ~ Percent + I(Percent^2))

Residuals:
    Min      1Q  Median      3Q     Max
-504.56   -2.10   -0.20    0.95  508.07

Coefficients:
                     Estimate      Std. Error t value Pr(>|t|) 
(Intercept)     1.431          2.721        0.526    0.599  #Low confidence, exclude from model
Percent          471.977      93.759      5.034    7.09e-07 ***
I(Percent^2)  4997.371    220.148     22.700  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 46.2 on 429 degrees of freedom
Multiple R-squared:  0.9112, Adjusted R-squared:  0.9108
F-statistic:  2202 on 2 and 429 DF,  p-value: < 2.2e-16

-------------------------------------------------------------------------------------

#Model: 
The data displays traits consistent with heteroskedasticity; however, a higher order model function will not be used to avoid the pitfalls of over-fitting. In addition, since this model is non-linear, we cannot rely on p-values without looking at the Std. Error values.

Y = Days until break-even
X = Percent drawdown from previous high

Y = 4997.371(X)^2 + 471.977(X) 









#Distribution of previous drawdowns 
The model predicts how many days must pass until the previous high is reached. However, in day-to-day trading we don't know what the market bottom will be. For example, a 5% fall from the previous high could be where the market turns direction, or it could be a pit-stop where the price pauses for some time and then continues its descent to 10%, 15% and more.

We can't predict the market bottom but we can look at their normal distribution so as to get an idea.

Draw0 <- Draw[which(Draw$Depth >= .05),] #Looking at distribution of drawdowns over 5%
boxplot(Draw0$Depth, main = "Distribution of Drawdowns (over 5%)", ylab = "Percent Fall from High")
points(mean(Draw0$Depth), col = "red", cex = 1.3) #Red dot is the mean

boxplot(Draw$Depth, main = "Distribution of Drawdowns", ylab = "Percent Fall from High")
points(mean(Draw$Depth), col = "red", cex = 1.3) #Red dot is the mean

#Console Output:
> summary(Draw0) #Summary of drawdowns over 5%

     Depth            Length   
 Min.   :0.0500   Min.   :   8.0
 1st Qu.:0.0623   1st Qu.:  40.0
 Median :0.0786   Median :  62.0  
 Mean   :0.1322   Mean   : 225.1  
 3rd Qu.:0.1356   3rd Qu.: 160.0
 Max.   :0.5678   Max.   :1898.0

> summary(Draw) #Summary of all drawdowns

     Depth              Length     
 Min.   :0.000100   Min.   :   2.00
 1st Qu.:0.002475   1st Qu.:   2.00
 Median :0.006700   Median :   4.00  
 Mean   :0.024674   Mean   :  32.63
 3rd Qu.:0.022925   3rd Qu.:  13.00
 Max.   :0.567800   Max.   :1898.00
>


# Interpretation of distributions
A casual interpretation is that if the price falls 5% from the previous high then it is likely to continue falling even further and reach a bottom of 7.86% - which is the median. The mean is 13.22%.

However, looking at all the existing drawdown distributions the median is 0.67% and the mean is 2.5%. This means that the vast majority of drawdowns are short-lived and in a bull market it may be advantageous to enter the market immediately after a correction of 0.67%.



In the section below we look at the drawdown risk if we enter a position at a random point in time with no regards to economic conditions. It is assumed that position is opened at "Close" price and the 3-month window is calculated starting on the following day.

# Probability of encountering a drawdown 3 months into the future.
draw3 <- rollapply(lag(sp$Low,k=-1), 63, min, align = "left") #3-month absolute minimum, starting date is following day
sp$Draw_3mo <- round((draw3-sp$Close)/sp$Close,3)
Days_No_Drawdown <-sp[which(sp$Draw_3mo>0),]
length(Days_No_Drawdown)/length(sp)

Conclusion: Only 1.07% of the days experienced no drawdown so we can be confident in assuming that some drawdown will be encountered into the future after opening a position. Note that the probability of encountering drawdowns goes higher in Bear markets and is lower in Bull markets.

# If/when a drawdown is encountered in the next 3 months, what should we expect it to be.
Days_Yes_Drawdown <-sp[which(sp$Draw_3mo<0),]
summary(Days_Yes_Drawdown$Draw_3mo)
stat.desc(Days_Yes_Drawdown$Draw_3mo)

> stat.desc(Days_Yes_Drawdown$Draw_3mo)
                  Draw_3mo
nbr.val       1.410500e+04
nbr.null      0.000000e+00
nbr.na        0.000000e+00
min          -4.300000e-01
max          -1.000000e-03
range         4.290000e-01
sum          -7.847150e+02
median       -3.900000e-02
mean         -5.563382e-02
SE.mean       4.764645e-04
CI.mean.0.95  9.339335e-04
var           3.202095e-03
std.dev       5.658706e-02
coef.var     -1.017134e+00

Conclusion: Median is 3.9%,  Mean is 5.56%,  Standard Deviation is 5.66%, Range is -0.1% to -43%. In good economic times the drawdowns should be smaller than average and the opposite should be expected in periods of high market volatility; therefore, the data should be interpreted within the context of market conditions.


No comments:

Post a Comment