Momentum Traders

We all hear that many institutions look at the momentum strategy where you go long when the 50 day moving average is above the 200 day average. As you will see below from 1993 this has been a pretty effective strategy using daily data applied to the S&P 500. For your information the strategy is still long despite the recent volatility.

Here is a chart showing the moving averages, the yellow is the 50 day and the red is the 200 day.

Rplot07

 

Here are the backtest results:

2015-01-23_1313 Rplot043

For those wanting to see the R code generating these charts and stats here it is:

require(quantmod)
require(PerformanceAnalytics)
 
#get the data and fill out the MA
getSymbols('SPY', from='1950-01-01')
SPY$ma200 <- SMA(Cl(SPY), 200)
SPY$ma50 <- SMA(Cl(SPY), 50)
 
#lets look at it from 1990 to 2015
spy <- SPY['1990/2015']
 
#our baseline, unfiltered results
ret <- ROC(Cl(spy)) 
 
#our comparision, filtered result
ma_sig <- Lag(ifelse(SPY$ma50 > SPY$ma200, 1, 0))
ma_ret <- ROC(Cl(spy)) * ma_sig
 
golden<- cbind(ma_ret,ret)
colnames(golden) = c('GoldCross','Buy&Hold')
 
#Plot to visually see the actual moving averages
chartSeries(spy, 
            type = "line",
            name = "Moving Average : Golden Cross",
            TA= c(addSMA(50, col = 'yellow'), addSMA(200)))
 
# lets see what the latest signals are 1 being a buy signal
tail(ma_sig)
 
maxDrawdown(golden)
table.AnnualizedReturns(golden, Rf= 0.02/252)
charts.PerformanceSummary(golden, Rf = 0.02, main="Golden Cross",geometric=FALSE)

Created by Pretty R at inside-R.org

Volatility Clustering

In case you thought volatility is isolated, the charts below give you an idea of how volatility leads to more volatility – duh! Knowing this doesn’t license you to print money, life would be way too boring if that was the case. The problem is not knowing that volatility cluster it is that we have no idea how long we will stay in a low or high volatility environment and its amplitude. In essence the billion dollar question is identifying when an environment regime shifts.

There is a ton of research in this area, and I have to confess I am attracted to the hidden Markov chain process in the shorter time horizons. Later in the year I hope to share some of my research into this subject. Let’s get back to the clustering:

In the top chart I look at the S&P500 on a rolling 100 days with 2% or more moves, and the bottom chart shows the Shanghai Stock Index which took a 7% bath yesterday, and with GDP numbers coming out later today promises to provide further fireworks. What I can say about the comparative 2 charts is that China is starting to look like its entering a more volatile period ahead of the US.

For those interested in the R code I have included it as well. (Hat tip to John Hussman for the graphic idea, I wish my code were more elegant, but I think it does the job, ignore the errors about my labelling).

SP500

SP500

Shanghai Stock Index

Shanghai Stock Index

require(quantmod)
require(PerformanceAnalytics)
 
#get the data
getSymbols('^SSEC', from='1990-01-01')
 
#lets look at it from 1990 to 2015
#spy <- SPY['1990/2015']
Shang<- SSEC['1990/2015']
 
#our baseline, unfiltered results
ret <- ROC(Cl(Shang)) 
 
#our comparision, filtered result
filter.d <- Lag(ifelse(ret < -0.02, 1, 0))
drops<- rollapply(filter.d==1,100,sum)
filter.g <- Lag(ifelse(ret < 0.02, 1, 0))
gain<- rollapply(filter.g==1,100,sum)
 
plot(drops, main = "Drop and Gain Clustering", sub = "sum of 2% movements over 100 prior days", ylab ="drops")
par(new=T)
plot(gain, main = "Drop and Gain Clustering", labels = FALSE, col = "red")
axis(side =4)
mtext("gains", side = 4)

Created by Pretty R at inside-R.org

DOW SIGNALS

There are a number of sites that market their signals as great indicators for outperforming the market, let me highlight how careful one needs to be when looking at backtests.

In the strategy I am about to show you we create a ratio between the S&P 500 and the Dow and we trigger a signal to buy the market when the current ratio is above the rolling mean ratio. I will include the code at the bottom for those who want to understand the details, but the table and chart will illustrate my point. A further important point that I must emphasize and will continue to highlight is that I look at risk-adjusted returns as my proxy for out-performance.

So lets begin:

Rplot06 2015-01-15_1532

Yes Sir you beauty, we have here a 24yr backtest where our model handily outperforms a buy and hold with a Sharpe Ratio of 0.32 vs 0.22. So should we bet the farm on this baby? Not so fast I say, lets look at this strategy over some more data, in the table below we look at performance from 1950 a lengthy 64yrs.

2015-01-15_1537

What we see here, is underperformance; so it is very important when considering a model to ensure that the starting date isn’t cherry picked. In this illustration there are very few parameters, and we only tweaked the date. Many people pushing automated models love to “curve-fit” parameters to satisfy the backtest with no basis of reality.

Here is the R code for those that are interested:

require(quantmod)
require(PerformanceAnalytics)
 
getSymbols("^GSPC", from= "1900-01-01")
sp500.weekly <- GSPC[endpoints(GSPC, "weeks"),6]
sp500rets<- ROC(sp500.weekly, type = "discrete", n = 1)
 
DJ<- read.csv('http://www.quandl.com/api/v1/datasets/BCB/UDJIAD1.csv?&auth_token=kvYEqCqKCTyL4anWz5Zv&trim_start=1896-07-14&trim_end=2015-05-12&sort_order=desc', colClasses=c('Date'='Date'))
date<- DJ$Date
values<- DJ[,2]
DJ_xts<- as.xts(values, order.by = as.Date(date, "%d/%m/%Y"))
dj.weekly <- DJ_xts[endpoints(DJ_xts, "weeks"),1]
djrets<- ROC(dj.weekly, type = "discrete", n = 1)
 
data<- merge(sp500.weekly,dj.weekly)
data.sub = data['1950-02-05::']
ratio<- data.sub[,1]/data.sub[,2]
ave.ratio<- rollapply(ratio,20,mean)
lead.lag<- ifelse(ratio >= ave.ratio, "Lead", "Lag")
 
# filtered results investing in S&P500 with the signal
ma_sig <- Lag(ifelse(lead.lag=="Lead", 1, 0))
ma_ret <- sp500rets * ma_sig
 
dowtimer<- cbind(ma_ret,sp500rets) # or
 
colnames(dowtimer) = c('SPX/DJI-Timer','Buy&Hold')
 
maxDrawdown(dowtimer)
table.AnnualizedReturns(dowtimer, Rf= 0.04/52)
charts.PerformanceSummary(dowtimer, Rf = 0.04/52, main="SPX/DJI Timer",geometric=FALSE)

Created by Pretty R at inside-R.org

Global Stock Index Watchlist

I am really pretty chuffed with my effort, I spent many hours today working on this R code. I haven’t commented it like I should because it is not yet a complete product. For those who don’t know, R is an open source statistical mathematical programming language, that has taken the data analytic world by storm. I am a novice at writing scripts but enjoy learning; it is my latest hobby.

Here is the output and code which any of you can run on your own PC, the data is from Yahoo. I notice Russia is missing so I must make an effort to include for my next post.

Global Stock Index Watchlist % change

require(quantmod)
require(PerformanceAnalytics)
require(gridExtra)
 
 
# select ticker symbols and time frame from Yahoo World Indexes
index <- c("^AORD",  "^SSEC",	"^HSI",	"^BSESN",	"^JKSE",	"^KLSE",	"^N225",	"^NZ50",	"^STI",	"^KS11",	"^TWII",	"^GSPTSE",	"^GSPC",	"^ATX",	"^BFX",	"^FCHI",	"^GDAXI",	"^SSMI",	"^FTSE",	"GD.AT
")
date_begin <- as.Date("2009-03-01")
date_end <- as.Date("2014-12-31")
 
tickers <- getSymbols(index, from=date_begin, to=date_end, auto.assign=TRUE)
 
dataset <- Ad(get(tickers[1]))
for (i in 2:length(tickers)) { dataset <- merge(dataset, Ad(get(tickers[i]))) }
 
# handle NA values 
data <- na.locf(dataset) # last observation carried forward to get rid of the NA's
data <- na.omit(data) # omit values with NA values
 
# daily returns
daily <- dailyReturn(data[,1])
for (i in 2:ncol(data)) 
    { daily <- merge(daily, dailyReturn(data[,i]))  
    } 
colnames(daily) <- index
day<- tail(daily,n=1)
 
# weekly returns
weekly <- weeklyReturn(data[,1])
for (i in 2:ncol(data)) 
    { weekly <- merge(weekly, weeklyReturn(data[,i]))  
    } 
colnames(weekly) <- index
week<- tail(weekly,n=1)
 
# monthly returns
monthly <- monthlyReturn(data[,1])
for (i in 2:ncol(data)) 
    { monthly <- merge(monthly, monthlyReturn(data[,i]))  
    } 
colnames(monthly) <- index
month<- tail(monthly,n=1)
 
# quarterly returns
quarterly <- quarterlyReturn(data[,1])
for (i in 2:ncol(data)) 
    { quarterly <- merge(quarterly, quarterlyReturn(data[,i]))  
    } 
colnames(quarterly) <- index
quarter<- tail(quarterly,n=1)
 
# Annual returns
annual <- annualReturn(data[,1])
for (i in 2:ncol(data)) 
    { annual <- merge(annual, annualReturn(data[,i]))  
    } 
colnames(annual) <- index
year<- tail(annual,n=1)
 
summary<- rbind(day,week,month,quarter,year)
colnames(summary) <- c("Aussie", "China", "Hong Kong", "India", "Indonesia", "Malaysia", "Japan", "New Zealand", "Singapore", "Korea", "Taiwan", "Canada", "S&P500", "Austria", "Belgium", "France", "Germany", "Swiss", "UK", "Greece")
 
 
# transpose the data
global<- t(summary)
colnames(global)<- c('Daily','Weekly','Monthly','Quarterly','Annual')
global<- as.data.frame(global) 
 
is.num<- sapply(global, is.numeric)
global[is.num]<- lapply(global[is.num], round,4)
global<- global*100
 
print(global)
grid.newpage(recording = FALSE)
grid.table(global)

Created by Pretty R at inside-R.org

GDP threshold Strategy

As a continuation of my analysis on the relationship between GDP and the markets, it is clear to see that if you place a simple threshold (the quarterly annualized GDP growth needs to be above zero) to your buy and hold then you significantly improve a vanilla buy and hold strategy. See results and code below. Sharpe Ratio improved from 0.49 to 0.61

Rplot Rplot02

 

library(quantmod)
library(Quandl)
library(PerformanceAnalytics)
library(gridExtra)
 
#Getting data from Quandl
GDP<- Quandl("FRED/GDP", trim_start="1947-01-01", trim_end="2014-07-01", transformation="rdiff", authcode="kvYEqCqKCTyL4anWz5Zv", type = "xts")
SP500<- Quandl("YAHOO/INDEX_GSPC", trim_start="1950-01-03", trim_end="2014-11-21", authcode="kvYEqCqKCTyL4anWz5Zv", type = "xts")
 
#need to make SP500 data quartetly using the quantmod function.
SP.q<- quarterlyReturn(SP500[,6], subset=NULL, type='arithmetic', leading=FALSE)
 
#cleaning up and aligning the data frame, I know this is fuzzy logic I am a novice coder
df1<- merge(GDP,SP.q)
df2<- merge(lag(df1[,1], k=1),df1)
m<- merge(df2[,1],df2[,3])
mt<- na.omit(m)
mb<- lag(mt[,2],k=1)
mc<- lag(mt[,2],k=2)
mt<- cbind(mt,mb,mc)
mt<- na.omit(mt)
colnames(mt) <- c("GDP", "S&P", "S&P -1", "S&P -2")
head(mt)
 
# Signal
gdp_sig <- ifelse(mt[,1] > 0, 1, 0)  ## I took out the lag function because of the built in lag in the DF
gdp_ret <- mt[,3] * gdp_sig
 
golden<- cbind(gdp_ret,mt[,3])
colnames(golden) = c('GDPtrigger','Buy&Hold')
 
t<- table.AnnualizedReturns(golden, Rf= 0)
grid.newpage(recording = FALSE)
grid.table(t)
 
charts.PerformanceSummary(golden, Rf = 0, main="GDP",geometric=FALSE)

Created by Pretty R at inside-R.org

GDP correlation to S&P500

I have been wanting to do this analysis for some time, and I always seem to be interrupted. I want to stress that I still need to validate my findings but they kind of gel with my intuition. [I have included my R code below]

GDP S&P S&P -1 S&P -2
GDP correlation 100% 2.67% 17.43% 24.04%
Adj. R Square -0.30% 2.65% 5.41%

Ok so what do we have here. Every time we get a GDP release, ignore the revisions and all the noise around the announcement for this exercise, people straight away extrapolate its effect on the stock market, i.e. a high GDP growth rate will correlate with a high quarterly performance . In our study we simply want to know what the correlation coefficient is of the S&P500 and the quarterly GDP growth figure. As you can see it is very low there is a 2.67% correlation (one of my questions to investigate is the GDP data is annualized quarterly data, whereas the S&P500 is simply quarterly so this could be a factor. The adjusted R2 is totally insignificant.

What is noticeable and quite significant is if you lag the performance of the S&P500 by 1 and 2 quarters you get a statistically significant relationship. So a 2 quarter lag is even better than 1, which in my mind makes sense, that the benefits to the market filter through slowly. These are rough workings and will require more input from me.
Postscript: just been thinking about it in the steamshower. GDP numbers are released roughly a quarter after the fact so the observation that the same quarters have no correlation actually makes sense. Something else to add is that I am going to do the study on raw quarterly data as well to see if this increases the correlation and R2 I think it will

library(quantmod)
library(Quandl)
 
#Getting data from Quandl
GDP<- Quandl("FRED/GDP", trim_start="1947-01-01", trim_end="2014-07-01", transformation="rdiff", authcode="kvYEqCqKCTyL4anWz5Zv", type = "xts")
SP500<- Quandl("YAHOO/INDEX_GSPC", trim_start="1950-01-03", trim_end="2014-11-21", authcode="kvYEqCqKCTyL4anWz5Zv", type = "xts")
 
#need to make SP500 data quartetly using the quantmod function.
SP.q<- quarterlyReturn(SP500[,6], subset=NULL, type='arithmetic', leading=FALSE)
 
#cleaning up and aligning the data frame, I know this is fuzzy logic I am a novice coder
df1<- merge(GDP,SP.q)
df2<- merge(lag(df1[,1], k=1),df1)
m<- merge(df2[,1],df2[,3])
mt<- na.omit(m)
mb<- lag(mt[,2],k=1)
mc<- lag(mt[,2],k=2)
mt<- cbind(mt,mb,mc)
mt<- na.omit(mt)
colnames(mt) <- c("GDP", "S&P", "S&P -1", "S&P -2")
head(mt)
 
#regression analysis
regression<- lm(mt[,1] ~ mt[,2])
 
#lagging S&P500 by 1 quarter
regression1<- lm(mt[,1] ~ mt[,3])
 
#lagging S&P500 by 2 quarter
regression2<- lm(mt[,1] ~ mt[,4])
 
#statistical analysis
cor(mt)*100
summary(regression)$adj.r.squared *100
summary(regression1)$adj.r.squared *100
summary(regression2)$adj.r.squared *100
 
#plotting regressions
plot(regression)
plot(regression1)
plot(regression2)

Created by Pretty R at inside-R.org

 

My Shiller Model

I had a quick look at my trusted Shiller Model which actually went to cash mid October this year. It continues to suggest holding onto cash. I attach the r code I used to build it, and I can apologize in advance for my sloppy coding, but it does most of the job. I plan on cleaning the code up in the near future.

Rplot05 Rplot04

library(quantmod)
library(PerformanceAnalytics)
library(gridExtra)
 
# I am pulling the data from Quandl and the MULTPL dataset. 
multpl<- read.csv('https://www.quandl.com/api/v1/datasets/MULTPL/SHILLER_PE_RATIO_MONTH.csv?trim_start=1881-01-01&trim_end=2014-12-17&auth_token=CqKCTyL4anWz5Zv', colClasses=c('Date'='Date'))
snp<- read.csv('https://www.quandl.com/api/v1/datasets/MULTPL/SP500_REAL_PRICE_MONTH.csv?trim_start=1871-01-01&trim_end=2014-12-17&auth_token=CqKCTyL4anWz5Zv', colClasses=c('Date'='Date'))
 
date<- snp$Date
values<- snp[,2]
snp.obj<- as.xts(values, order.by = as.Date(date, "%d/%m/%Y"))
snprets<- ROC(snp.obj, type = "discrete", n = 1)
 
date<- multpl$Date
values<- multpl[,2]
PE.obj<- as.xts(values, order.by = as.Date(date, "%d/%m/%Y"))
 
Shiller<- merge(snp.obj,PE.obj, snprets)
Shiller.sub = Shiller['1900-01-01::']
colnames(Shiller.sub) = c('S&P500','Shiller PE','S&P500 returns')
 
mean<- rollapply(PE.obj,48,mean)
sdsig<- rollapply(PE.obj,48,sd) + mean
over<- Lag(ifelse(PE.obj> sdsig,1,0))
 
pe_ret <- snprets * over
PEtimer<- cbind(pe_ret,snprets) 
colnames(PEtimer) = c('PE-Timer','Buy&Hold')
 
maxDrawdown(PEtimer)
grid.newpage(recording = FALSE)
grid.table(tail(over))
tail(over)
grid.newpage(recording = FALSE)
grid.table(table.AnnualizedReturns(PEtimer, Rf=0))
table.AnnualizedReturns(PEtimer, Rf= 0)
charts.PerformanceSummary(PEtimer, Rf = 0, main="Shiller PE Timer",geometric=FALSE)

Created by Pretty R at inside-R.org