The Continuing Search For Robust Momentum Indicators: the Fractal Adaptive Moving Average
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Following from the last post and setting aside the not-working-as-advertised Trend Vigor indicator, we will turn our attention to the world of adaptive moving averages. In this case, I will be working with the FRAMA–the FRactal Adaptive Moving Average. The reason I am starting off with this one is that according to ETFHQ in this post, FRAMA is an indicator that seems to have very strong performance, even using what definitely seems to be a very simple strategy (long if the price crosses over the indicator, exit vice versa), which would most likely leave one open to whipsaws.
But before then, I’d like to make an introduction to the FRAMA, by linking to the original Dr. John Ehlers paper, here.
While I won’t attempt to give a better formal explanation than the man that created the indicator (which is why the paper is there), the way I intuitively think about the FRAMA (or the adaptive moving average family of indicators, found here) is that they are improvements of the exponential moving average that attempt to smooth the indicator during cyclical market periods to avoid whipsaws, and to have a faster response during periods of strong trends, so as to minimize the damage done due to an ending trend.
The FRAMA itself compares two periods of n/2 days (the last n/2 days, and the last n/2 days before those last n/2 days) to the total period (n days). Intuitively, if there is a straight trend upwards, then the expression (log(N1+N2)-log(N3))/log(2), where N1 is the difference of highest high and lowest low over the last n/2 days and N2 is identical except for the previous n/2 days before the last n/2 days, and N3 is the same quantity over all n days, will be equal to zero, and thus, the exponent of that would be equal to 1, which is analogous to an EMA of 1 day. Similarly, when there is a great deal of congestion, then the expression log(N1+N2) will be greater than log(N3), and so the exponent (that is, the fractal dimension) of the exponent would be closer to 2 (or greater, since I implemented the modified FRAMA).
Let’s look at the code:
"FRAMA" <- function(HLC, n=20, FC=1, SC=200, triggerLag=1, ...) { price <- ehlersPriceMethod(HLC, ...) if (n%%2==1) n=n-1 #n must be even N3 <- (runMax(Hi(HLC), n)-runMin(Lo(HLC), n))/n N1 <- (runMax(Hi(HLC), n/2)-runMin(Lo(HLC), n/2))/(n/2) lagSeries <- lag(HLC, n/2) N2 <- (runMax(Hi(lagSeries), n/2)-runMin(Lo(lagSeries), n/2))/(n/2) dimen <- (log(N1+N2)-log(N3))/log(2) w <- log(2/(SC+1)) oldAlpha <- exp(w*(dimen-1)) oldN <- (2-oldAlpha)/oldAlpha newN <- ((SC-FC)*(oldN-1)/(SC-1))+FC alpha <- 2/(newN+1) alpha[which(alpha > 1)] <- 1 alpha[which(alpha < w)] <- w alphaComplement <- 1-alpha initializationIndex <- index(alpha[is.na(alpha)]) alpha[is.na(alpha)] <- 1; alphaComplement[is.na(alphaComplement)] <- 0 FRAMA <- rep(0, length(price)) FRAMA[1] <- price[1] FRAMA <- computeFRAMA(alpha, alphaComplement, FRAMA, price) FRAMA <- xts(FRAMA, order.by=index(price)) FRAMA[initializationIndex] <- alpha[initializationIndex] <- NA trigger <- lag(FRAMA, triggerLag) out <- cbind(FRAMA=FRAMA, trigger=trigger) return(out) } NumericVector computeFRAMA(NumericVector alpha, NumericVector alphaComplement, NumericVector FRAMA, NumericVector price) { int n = price.size(); for(int i=1; i<n; i++) { FRAMA[i] = alpha[i]*price[i] + alphaComplement[i]*FRAMA[i-1]; } return FRAMA; }
Essentially, from the second chunk of code, this is an advanced form of the exponential moving average that takes into account the amount of movement over a greater time period relative to the swing at two finer intervals in the two halves of that period. The methodology for the modified FRAMA is thanks to ETFHQ (once again), found here.
And while words can make for a bit of explanation, in this case, a picture (or several) is worth far more. Here is some code I wrote to plot an EMA, and three separate FRAMA computations (the default John Ehlers settings, the best ETFHQ settings, and the slower ETFHQ settings) on XLB from 2003 through 2010 (yes, the same XLB from our Trend Vigor backtest, because it was the go-to instrument for all our individual equity curves).
from="2003-01-01" to="2010-12-31" source("demoData.R") tmp <- FRAMA(HLC(XLB)) tmp2 <- FRAMA(HLC(XLB), FC=4, n=126, SC=300) tmp3 <- FRAMA(HLC(XLB), FC=40, n=252, SC=252) ema <- EMA(x=Cl(XLB),n=126) myTheme<-chart_theme() myTheme$col$dn.col<-'gray2' myTheme$col$dn.border <-'gray2' myTheme$col$up.border <-'gray2' chart_Series(XLB, TA="add_TA(tmp$FRAMA, col='blue', on=1, lwd=3); add_TA(tmp2$FRAMA, col='green', on=1, lwd=3); add_TA(tmp3$FRAMA, col='red', on=1, lwd=3); add_TA(ema$EMA, col='orange', on=1, lwd=3)", theme=myTheme) legend(x=5, y=40, legend=c("FRAMA FC=1, n=20, SC=200", "FRAMA FC=4, n=126, SC=300", "FRAMA FC=40, n=252, SC=252", "EMA n=126"), fill=c("blue", "green", "red", "orange"), bty="n")
This produces the following plot:
From this perspective, the improvements are clear. Essentially, the long-term FRAMA (FC 40, n 252, SC 252) possesses much of the smoothness of the 126 day EMA, while being far more responsive to the turns in price action to keep open equity at the end of a trend. The two faster FRAMAs, on the other hand, hug the price action more closely, yet still retain a degree of smoothness.
Here’s the code to zoom in on 2007-2008.
chart_Series(XLB, TA="add_TA(tmp$FRAMA, col='blue', on=1, lwd=3); add_TA(tmp2$FRAMA, col='green', on=1, lwd=3); add_TA(tmp3$FRAMA, col='red', on=1, lwd=3); add_TA(ema$EMA, col='orange', on=1, lwd=3)", theme=myTheme, subset="2007::2008") legend(x=5, y=30, legend=c("FRAMA FC=1, n=20, SC=200", "FRAMA FC=4, n=126, SC=300", "FRAMA FC=40, n=252, SC=252", "EMA n=126"), fill=c("blue", "green", "red", "orange"), bty="n")
And, the corresponding plot.
Here, we can see some more properties. While the default John Ehlers settings (blue) seemingly tracks price action very closely, the indicator usually finds itself right in the middle of the price action, but still has the occasional trend following property when price action breaks through it at the start of the financial crisis. In other words, it seems that it can hurt you both as a trend follower (whipsaws), and as a mean reverting indicator (as seen when XLB starts falling in the crisis), so this gives rise to the idea that an indicator can track the price too well.
On the other hand, the 126 day FRAMA (the ETFHQ settings, in green) seems to look like a dynamic support and resistance indicator that the talking heads go on and on about (yet give very little advice on how to actually objectively compute), in that the price action seems to touch it every so often, but not oscillate around it. It breaks in one direction and manages to stay in that direction, until it breaks in the other direction, and sustain a move to that direction. This seems like a foundation of a future trading strategy.
Finally, the 252 day FRAMA (the ETFHQ settings for the long-term FRAMA indicator, in red) looks like a confirmatory indicator or filter.
Notice that by comparison, the 126 day EMA seems to lag as much if not more than the 252 day FRAMA, and from this vantage point, it seems that the results are not as good for the same amount of data processed.
Overall, it seems that by trading off smoothness and responsiveness, one can see the foundations of a possible system.
The potential trading systems here will be explored in the future.
Thanks for reading.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.