I recently presented a paper by Andrew Lo, Harry Mamaysky, and Jiang Wang called "Foundations of Technical Analysis: Computational Algorithms, Statistical Inference, and Empirical Implementation (2000)" at Quantopian's journal club. In the paper, the authors utilize non-parametric kernel regression to smooth a stock's daily price time series to a point where the local minima and maxima that a human technical analyst would find relevant can be separated from noisier short-term price fluctuations. The authors then search these denoised local minima and maxima for a few of the patterns commonly pursued by technical analysts. Once they've identified occurrences of particular patterns, the authors test their predictive power by observing the subsequent forward return on the stock.

It is interesting to note that since this paper was written in 2000 and all the data used in my implementation is from 2003-2016, my results can be considered to be "out of sample" with respect to the authors' findings.

In our application, a kernel regression estimates some function $m(x)$ given (time, price) observations $(x_1, P_1) ... (x_t, P_t)$ where $t = 1...T$. The value of $m(x)$ is computed by taking weighted average of the prices in the timeseries, where the weight assigned to observation $P_t$ is determinded by its distance from x. This distance weight is described by a "kernel" fuction $w(x)$. The authors use a gausian kernel with a manually tuned bandwidth parameter.

$$m(x) = \frac{1}{T}\sum_{t=1}^{T}w_t(x)P_t$$

In [327]:

prices_=msft_prices.copy()prices_.index=linspace(1.,len(prices_),len(prices_))# I've chosen bandwith parameters manually. This is something that could be improved.kr=KernelReg([prices_.values],[prices_.index.values],var_type='c',bw=[1.8,1])f=kr.fit([prices_.index.values])smooth_prices=pd.Series(data=f[0],index=msft_prices.index)

From this smoothed price timeseries we can extract local minima and maxima (orange points in the plots below.)

The author use the minima and maxima from the smoothed timeseries to identify true local minima and maxima in the original timeseres by taking the maximum/minimum price within a t-1, t+1 window around the smooth timeseries maxima/minima (purple points in the plots below). We use these minima and maxima from the original price data to look for the given technical patterns.

You can see in the plots below, that finding minima and maxima using the kernel regression allows us to skip over minima and maxima that are too local.

# Let's throw what we have so far into a function:deffind_max_min(prices):prices_=prices.copy()prices_.index=linspace(1.,len(prices_),len(prices_))kr=KernelReg([prices_.values],[prices_.index.values],var_type='c',bw=[1.8,1])f=kr.fit([prices_.index.values])smooth_prices=pd.Series(data=f[0],index=prices.index)local_max=argrelextrema(smooth_prices.values,np.greater)[0]local_min=argrelextrema(smooth_prices.values,np.less)[0]price_local_max_dt=[]foriinlocal_max:if(i>1)and(i<len(prices)-1):price_local_max_dt.append(prices.iloc[i-2:i+2].argmax())price_local_min_dt=[]foriinlocal_min:if(i>1)and(i<len(prices)-1):price_local_min_dt.append(prices.iloc[i-2:i+2].argmin())prices.name='price'maxima=pd.DataFrame(prices.loc[price_local_max_dt])minima=pd.DataFrame(prices.loc[price_local_min_dt])max_min=pd.concat([maxima,minima]).sort_index()max_min.index.name='date'max_min=max_min.reset_index()max_min=max_min[~max_min.date.duplicated()]p=prices.reset_index()max_min['day_num']=p[p['index'].isin(max_min.date)].index.valuesmax_min=max_min.set_index('day_num').pricereturnmax_min