Backtesting Classical Technical Patterns
In the last post, Classical Technical Patterns, I discussed the algorithm and pattern definitions presented in the Foundations of Technical Analysis by A. Lo, H. Mamaysky, J. Wang (2000) paper. Today, I want to check how different patterns performed historically using SPY.
I will follow the rolling window procedure discussed on pages 14-15 of the paper. Let’s begin by loading the historical data for the SPY and running a rolling window pattern search algorithm.
############################################################################### # Load Systematic Investor Toolbox (SIT) ############################################################################### con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb')) source(con) close(con) #***************************************************************** # Load historical data #****************************************************************** load.packages('quantmod') ticker = 'SPY' data = getSymbols(ticker, src = 'yahoo', from = '1970-01-01', auto.assign = F) data = adjustOHLC(data, use.Adjusted=T) #***************************************************************** # Search for all patterns over a rolling window #****************************************************************** load.packages('sm') history = as.vector(coredata(Cl(data))) window.L = 35 window.d = 3 window.len = window.L + window.d patterns = pattern.db() found.patterns = c() for(t in window.len : (len(history)-1)) { ret = history[(t+1)]/history[t]-1 sample = history[(t - window.len + 1):t] obj = find.extrema( sample ) if(len(obj$data.extrema.loc) > 0) { out = find.patterns(obj, patterns = patterns, silent=F, plot=F) if(len(out)>0) found.patterns = rbind(found.patterns,cbind(t,out,t-window.len+out, ret)) } if( t %% 10 == 0) cat(t, 'out of', len(history), '\n') } colnames(found.patterns) = spl('t,start,end,tstart,tend,ret')
There are many patterns that are found multiple times. Let’s remove the entries that refer to the same pattern and keep only the first occurrence.
#***************************************************************** # Clean found patterns #****************************************************************** # remove patterns that finished after window.L found.patterns = found.patterns[found.patterns[,'end'] <= window.L,] # remove the patterns found multiple times, only keep first one pattern.names = unique(rownames(found.patterns)) all.patterns = c() for(name in pattern.names) { index = which(rownames(found.patterns) == name) temp = NA * found.patterns[index,] i.count = 0 i.start = 1 while(i.start < len(index)) { i.count = i.count + 1 temp[i.count,] = found.patterns[index[i.start],] subindex = which(found.patterns[index,'tstart'] > temp[i.count,'tend']) if(len(subindex) > 0) { i.start = subindex[1] } else break } all.patterns = rbind(all.patterns, temp[1:i.count,]) }
Now we can visualize the performance of each pattern using the charts from my presentation about Seasonality Analysis and Pattern Matching at the R/Finance conference.
#***************************************************************** # Plot #****************************************************************** # Frequency for each Pattern frequency = tapply(rep(1,nrow(all.patterns)), rownames(all.patterns), sum) layout(1) barplot.with.labels(frequency/100, 'Frequency for each Pattern') # Summary for each Pattern all.patterns[,'ret'] = history[(all.patterns[,'t']+20)] / history[all.patterns[,'t']] - 1 data_list = tapply(all.patterns[,'ret'], rownames(all.patterns), list) group.seasonality(data_list, '20 days after Pattern') # Details for BBOT pattern layout(1) name = 'BBOT' index = which(rownames(all.patterns) == name) time.seasonality(data, all.patterns[index,'t'], 20, name)
The Broadening bottoms (BBOT) and Rectangle tops (RTOP) worked historically well for SPY.
To view the complete source code for this example, please have a look at the bt.patterns.test() function in rfinance2012.r at github.
Thanks for the interesting post. Have you had the chance to look at applying some kind of technical filters to the patterns eg HS where last extremum is above 50day and 200day moving averages. That might help to find better signals…