Ehlers’s Autocorrelation Periodogram

This post will introduce John Ehlers’s Autocorrelation Periodogram mechanism–a mechanism designed to dynamically find a lookback period. That is, the most common parameter optimized in backtests is the lookback period.

Before beginning this post, I must give credit where it’s due, to one Mr. Fabrizio Maccallini, the head of structured derivatives at Nordea Markets in London. You can find the rest of the repository he did for Dr. John Ehlers’s Cycle Analytics for Traders on his github. I am grateful and honored that such intelligent and experienced individuals are helping to bring some of Dr. Ehlers’s methods into R.

The point of the Ehlers Autocorrelation Periodogram is to dynamically set a period between a minimum and a maximum period length. While I leave the exact explanation of the mechanic to Dr. Ehlers’s book, for all practical intents and purposes, in my opinion, the punchline of this method is to attempt to remove a massive source of overfitting from trading system creation–namely specifying a lookback period.

SMA of 50 days? 100 days? 200 days? Well, this algorithm takes that possibility of overfitting out of your hands. Simply, specify an upper and lower bound for your lookback, and it does the rest. How well it does it is a topic of discussion for those well-versed in the methodologies of electrical engineering (I’m not), so feel free to leave comments that discuss how well the algorithm does its job, and feel free to blog about it as well.

In any case, here’s the original algorithm code, courtesy of Mr. Maccallini:

AGC <- function(loCutoff = 10, hiCutoff = 48, slope = 1.5) {      accSlope = -slope # acceptableSlope = 1.5 dB   ratio = 10 ^ (accSlope / 20)   if ((hiCutoff - loCutoff) > 0)
    factor <-  ratio ^ (2 / (hiCutoff - loCutoff));
  return (factor)
}

autocorrPeriodogram <- function(x, period1 = 10, period2 = 48, avgLength = 3) {
  # high pass filter
  alpha1 <- (cos(sqrt(2) * pi / period2) + sin(sqrt(2) * pi / period2) - 1) / cos(sqrt(2) * pi / period2)
  hp <- (1 - alpha1 / 2) ^ 2 * (x - 2 * lag(x) + lag(x, 2))
  hp <- hp[-c(1, 2)]
  hp <- filter(hp, (1 - alpha1), method = "recursive")
  hp <- c(NA, NA, hp)
  hp <- xts(hp, order.by = index(x))
  # super smoother
  a1 <- exp(-sqrt(2) * pi / period1)
  b1 <- 2 * a1 * cos(sqrt(2) * pi / period1)
  c2 <- b1
  c3 <- -a1 * a1
  c1 <- 1 - c2 - c3
  filt <- c1 * (hp + lag(hp)) / 2
  leadNAs <- sum(is.na(filt))
  filt <- filt[-c(1: leadNAs)]
  filt <- filter(filt, c(c2, c3), method = "recursive")
  filt <- c(rep(NA, leadNAs), filt)
  filt <- xts(filt, order.by = index(x))
  # Pearson correlation for each value of lag
  autocorr <- matrix(0, period2, length(filt))
  for (lag in 2: period2) {
    # Set the average length as M
    if (avgLength == 0) M <- lag
    else M <- avgLength
    autocorr[lag, ] <- runCor(filt, lag(filt, lag), M)
  }
  autocorr[is.na(autocorr)] <- 0
  # Discrete Fourier transform
  # Correlate autocorrelation values with the cosine and sine of each period of interest
  # The sum of the squares of each value represents relative power at each period
  cosinePart <- sinePart <- sqSum <- R <- Pwr <- matrix(0, period2, length(filt))
  for (period in period1: period2) {
    for (N in 2: period2) {
      cosinePart[period, ] = cosinePart[period, ] + autocorr[N, ] * cos(2 * N * pi / period)
      sinePart[period, ] = sinePart[period, ] + autocorr[N, ] * sin(2 * N * pi / period)
    }
    sqSum[period, ] = cosinePart[period, ] ^ 2 + sinePart[period, ] ^ 2
    R[period, ] <- EMA(sqSum[period, ] ^ 2, ratio = 0.2)
  }
  R[is.na(R)] <- 0
  # Normalising Power
  K <- AGC(period1, period2, 1.5)
  maxPwr <- rep(0, length(filt))   for(period in period1: period2) {     for (i in 1: length(filt)) {       if (R[period, i] >= maxPwr[i]) maxPwr[i] <- R[period, i]
      else maxPwr[i] <- K * maxPwr[i]
    }
  }
  for(period in 2: period2) {
    Pwr[period, ] <- R[period, ] / maxPwr
  }
  # Compute the dominant cycle using the Center of Gravity of the spectrum
  Spx <- Sp <- rep(0, length(filter))
  for(period in period1: period2) {
    Spx <- Spx + period * Pwr[period, ] * (Pwr[period, ] >= 0.5)
    Sp <- Sp + Pwr[period, ] * (Pwr[period, ] >= 0.5)
  }
  dominantCycle <- Spx / Sp
  dominantCycle[is.nan(dominantCycle)] <- 0
  dominantCycle <- xts(dominantCycle, order.by=index(x))
  dominantCycle <- dominantCycle[dominantCycle > 0]
  return(dominantCycle)
  #heatmap(Pwr, Rowv = NA, Colv = NA, na.rm = TRUE, labCol = "", add.expr = lines(dominantCycle, col = 'blue'))
}

One thing I do notice is that this code uses a loop that says for(i in 1:length(filt)), which is an O(data points) loop, which I view as the plague in R. While I’ve used Rcpp before, it’s been for only the most basic of loops, so this is definitely a place where the algorithm can stand to be improved with Rcpp due to R’s inherent poor looping.

Those interested in the exact logic of the algorithm will, once again, find it in John Ehlers’s Cycle Analytics For Traders book (see link earlier in the post).

Of course, the first thing to do is to test how well the algorithm does what it purports to do, which is to dictate the lookback period of an algorithm.

Let’s run it on some data.

getSymbols('SPY', from = '1990-01-01')

t1 <- Sys.time()
out <- autocorrPeriodogram(Ad(SPY), period1 = 120, period2 = 252, avgLength = 3)
t2 <- Sys.time() print(t2-t1) 

And the result:

 > t1 <- Sys.time() > out <- autocorrPeriodogram(Ad(SPY), period1 = 120, period2 = 252, avgLength = 3) > t2 <- Sys.time() > print(t2-t1)
Time difference of 33.25429 secs

Now, what does the algorithm-set lookback period look like?

plot(out)

Let’s zoom in on 2001 through 2003, when the markets went through some upheaval.

plot(out['2001::2003']

In this zoomed-in image, we can see that the algorithm’s estimates seem fairly jumpy.

Here’s some code to feed the algorithm’s estimates of n into an indicator to compute an indicator with a dynamic lookback period as set by Ehlers’s autocorrelation periodogram.

acpIndicator <- function(x, minPeriod, maxPeriod, indicatorFun = EMA, ...) {
  acpOut <- autocorrPeriodogram(x = x, period1 = minPeriod, period2 = maxPeriod)
  roundedAcpNs <- round(acpOut, 0) # round to the nearest integer
  uniqueVals <- unique(roundedAcpNs) # unique integer values
  out <- xts(rep(NA, length(roundedAcpNs)), order.by=index(roundedAcpNs))

  for(i in 1:length(uniqueVals)) { # loop through unique values, compute indicator
    tmp <- indicatorFun(x, n = uniqueVals[i], ...)
    out[roundedAcpNs==uniqueVals[i]] <- tmp[roundedAcpNs==uniqueVals[i]]
  }
  return(out)
}

And here is the function applied with an SMA, to tune between 120 and 252 days.

ehlersSMA <- acpIndicator(Ad(SPY), 120, 252, indicatorFun = SMA)

plot(Ad(SPY)['2008::2010'])
lines(ehlersSMA['2008::2010'], col = 'red')

And the result:

As seen, this algorithm is less consistent than I would like, at least when it comes to using a simple moving average.

For now, I’m going to leave this code here, and let people experiment with it. I hope that someone will find that this indicator is helpful to them.

Thanks for reading.

NOTES: I am always interested in networking/meet-ups in the northeast (Philadelphia/NYC). Furthermore, if you believe your firm will benefit from my skills, please do not hesitate to reach out to me. My linkedin profile can be found here.

Lastly, I am volunteering to curate the R section for books on quantocracy. If you have a book about R that can apply to finance, be sure to let me know about it, so that I can review it and possibly recommend it. Thakn you.

Advertisements

A John Ehlers oscillator — Cycle RSI(2)

Since I’ve hit a rut in trend following (how do you quantify rising/falling/flat? What even defines those three terms in precise, machine definition? How do you avoid buying tops while not getting chopped by whipsaws?), I decided to look the other way, with oscillators. Certainly, I’m not ready to give up on Dr. Ehlers just yet. So, in this post, I’ll introduce a recent innovation of the RSI by Dr. John Ehlers.

The indicator is Dr. Ehlers’s modified RSI from Chapter 7 of Cycle Analytics for Traders.

For starters, here’s how the Ehlers RSI is different than the usual ones: it gets filtered with a high-pass filter and then smoothed with a supersmoother filter. While Michael Kapler also touched on this topic a while back, I suppose it can’t hurt if I attempted to touch on it myself.

Here is the high pass filter and the super smoother, from the utility.R file in DSTrading. They’re not exported since as of the moment, they’re simply components of other indicators.

highPassFilter <- function(x) {
  alpha1 <- (cos(.707*2*pi/48)+sin(.707*2*pi/48)-1)/cos(.707*2*pi/48)
  HP <- (1-alpha1/2)*(1-alpha1/2)*(x-2*lag(x)+lag(x,2))
  HP <- HP[-c(1,2)]
  HP <- filter(HP, c(2*(1-alpha1), -1*(1-alpha1)*(1-alpha1)), method="recursive")
  HP <- c(NA, NA, HP)
  HP <- xts(HP, order.by=index(x))
  return(HP)
}

superSmoother <- function(x) {
  a1 <- exp(-1.414*pi/10)
  b1 <- 2*a1*cos(1.414*pi/10)
  c2 <- b1
  c3 <- -a1*a1
  c1 <- 1-c2-c3
  filt <- c1*(x+lag(x))/2
  leadNAs <- sum(is.na(filt))
  filt <- filt[-c(1:leadNAs)]
  filt <- filter(filt, c(c2, c3), method="recursive")
  filt <- c(rep(NA,leadNAs), filt)
  filt <- xts(filt, order.by=index(x))
}

In a nutshell, both of these functions serve to do an exponential smoothing on the data using some statically computed trigonometric quantities, the rationale of which I will simply defer to Dr. Ehlers’s book (link here).

Here’s the modified ehlers RSI, which I call CycleRSI, from the book in which it’s defined:

"CycleRSI" <- function(x, n=20) {
  filt <- superSmoother(highPassFilter(x))
  diffFilt <- diff(filt)
  posDiff <- negDiff <- diffFilt
  posDiff[posDiff < 0] <- 0
  negDiff[negDiff > 0] <- 0
  negDiff <- negDiff*-1
  posSum <- runSum(posDiff, n)
  negSum <- runSum(negDiff, n)
  denom <- posSum+negSum
  rsi <- posSum/denom
  rsi <- superSmoother(rsi)*100
  colnames(rsi) <- "CycleRSI"
  return(rsi)
}

Here’s a picture comparing four separate RSIs.

The first is the RSI featured in this post (cycle RSI) in blue. The next is the basic RSI(2) in red. The one after that is Larry Connors’s Connors RSI , which may be touched on in the future, and the last one, in purple, is the generalized Laguerre RSI, which is yet another Dr. Ehlers creation (which I’ll have to test sometime in the future).

To start things off with the Cycle RSI, I decided to throw a simple strategy around it:

Buy when the CycleRSI(2) crosses under 10 when the close is above the SMA200, which is in the vein of a Larry Connors trading strategy from “Short Term ETF Trading Strategies That Work” (whether they work or not remains debatable), and sell when the CycleRSI(2) crosses above 70, or when the close falls below the SMA200 so that the strategy doesn’t get caught in a runaway downtrend.

Since the strategy comes from an ETF Trading book, I decided to use my old ETF data set, from 2003 through 2010.

Here’s the strategy code, as usual:

require(DSTrading)
require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2010-12-31"
options(width=70)
verbose=TRUE

source("demoData.R")

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "Cycle_RSI_I"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters
nRSI=2
RSIentry=10
RSIexit=70

nSMA=200

period=10
pctATR=.04

#indicators
add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")
add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),
              label="SMA")
add.indicator(strategy.st, name="CycleRSI",
              arguments=list(x=quote(Cl(mktdata)), n=nRSI),
              label="RSI")

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "SMA"), relationship="gt"),
           label="ClGtSMA")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="CycleRSI.RSI", threshold=RSIentry, 
                          relationship="lt", cross=FALSE),
           label="RSIltEntryThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("ClGtSMA", "RSIltEntryThresh"), 
                          cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "SMA"), relationship="lt"),
           label="exitSMA")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="CycleRSI.RSI", threshold=RSIexit,
                          relationship="gt", cross=TRUE),
           label="longExit")

#rules
#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, 
                        ordertype="market", 
                        orderside="long", replace=FALSE, 
                        prefer="Open", osFUN=osDollarATR,
                        tradeSize=tradeSize, pctATR=pctATR, 
                        atrMod="X"), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, 
                        orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, 
                        prefer="Open"), 
         type="exit", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="exitSMA", sigval=TRUE, 
                        orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, 
                        prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

And here are the results:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.846124
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 65.071
> (numTrades <- sum(tStats$Num.Trades))
[1] 2048
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 1.028333

> print(t(durStats))
      [,1]
Min      1
Q1       6
Med      9
Mean    11
Q3      14
Max     43
WMin     1
WQ1      7
WMed     9
WMean   11
WQ3     13
WMax    40
LMin     1
LQ1      6
LMed    11
LMean   12
LQ3     15
LMax    43

> print(mean(as.numeric(as.character(mktExposure$MktExposure))))
[1] 0.2806

> mean(corMeans)
[1] 0.2763

> SharpeRatio.annualized(portfRets)
                                    [,1]
Annualized Sharpe Ratio (Rf=0%) 1.215391
> Return.annualized(portfRets)
                       [,1]
Annualized Return 0.1634448
> maxDrawdown(portfRets)
[1] 0.1694307

Overall, the statistics don’t look bad. However, the 1:1 annualized returns to max drawdown isn’t particularly pleasing, as it means that this strategy can’t be leveraged effectively to continue getting outsized returns in this state. Quite irritating. Here’s the equity curve.

In short, as with other mean reverters, when drawdowns happen, they happen relatively quickly and brutally.

Here’s an individual instrument position chart.

By the looks of things, the strategy does best in a market that grinds upwards, rather than a completely choppy sideways market.

Finally, here’s some code for charting all of the different trades.

agg.chart.ME <- function(Portfolio, Symbols, type=c("MAE", "MFE"), scale=c("cash", "percent", "tick")) {
  type=type[1]
  scale=scale[1]
  trades <- list()
  length(trades) <- length(Symbols)
  for(Symbol in Symbols) {
    trades[[Symbol]] <- pts <- perTradeStats(Portfolio=Portfolio, Symbol=Symbol, includeOpenTrade=FALSE)
  }
  trades <- do.call(rbind, trades)
  trades$Pct.Net.Trading.PL <- 100 * trades$Pct.Net.Trading.PL
  trades$Pct.MAE <- 100 * trades$Pct.MAE
  trades$Pct.MFE <- 100 * trades$Pct.MFE
  profitable <- (trades$Net.Trading.PL > 0)
  switch(scale, cash = {
    .ylab <- "Profit/Loss (cash)"
    if (type == "MAE") {
      .cols <- c("MAE", "Net.Trading.PL")
      .xlab <- "Drawdown (cash)"
      .main <- "Maximum Adverse Excursion (MAE)"
    } else {
      .cols <- c("MFE", "Net.Trading.PL")
      .xlab <- "Run Up (cash)"
      .main <- "Maximum Favourable Excursion (MFE)"
    }
  }, percent = {
    .ylab <- "Profit/Loss (%)"
    if (type == "MAE") {
      .cols <- c("Pct.MAE", "Pct.Net.Trading.PL")
      .xlab <- "Drawdown (%)"
      .main <- "Maximum Adverse Excursion (MAE)"
    } else {
      .cols <- c("Pct.MFE", "Pct.Net.Trading.PL")
      .xlab <- "Run Up (%)"
      .main <- "Maximum Favourable Excursion (MFE)"
    }
  }, tick = {
    .ylab <- "Profit/Loss (ticks)"
    if (type == "MAE") {
      .cols <- c("tick.MAE", "tick.Net.Trading.PL")
      .xlab <- "Drawdown (ticks)"
      .main <- "Maximum Adverse Excursion (MAE)"
    } else {
      .cols <- c("tick.MFE", "tick.Net.Trading.PL")
      .xlab <- "Run Up (ticks)"
      .main <- "Maximum Favourable Excursion (MFE)"
    }
  })
  .main <- paste("All trades", .main)
  plot(abs(trades[, .cols]), type = "n", xlab = .xlab, ylab = .ylab, 
       main = .main)
  grid()
  points(abs(trades[profitable, .cols]), pch = 24, col = "green", 
         bg = "green", cex = 0.6)
  points(abs(trades[!profitable, .cols]), pch = 25, col = "red", 
         bg = "red", cex = 0.6)
  abline(a = 0, b = 1, lty = "dashed", col = "darkgrey")
  legend(x = "bottomright", inset = 0.1, legend = c("Profitable Trade", 
                                                    "Losing Trade"), pch = c(24, 25), col = c("green", "red"), 
         pt.bg = c("green", "red"))
}

And the resulting plot:

One last thing to note…that $50,000 trade in the upper left hand corner? That was a yahoo data issue and is a false print. Beyond that, once again, this seems like standard fare for a mean reverter–when trades go bad, they’re *really* bad, but the puzzle of where to put a stop is a completely separate issue, as it usually means locking in plenty of losses that decrease in magnitude, along with possibly turning winners into losers. On the flip side, here’s the maximum favorable excursion plot.

In short, there are definitely trades that could have been stopped for a profit that turned into losers.

In conclusion, while the initial trading system seems to be a good start, it’s far from complete.

Thanks for reading.

Another Failed Volatility Histeresis: Ehlers’s Own Idea

This week, I attempted to use Ehlers’s own idea from this presentation.

Essentially, the idea is that when an indicator is flat, line crossings can produce whipsaws, so add a fraction of the daily range to the lagged indicator, and see if the non-lagged indicator crosses the threshold. In this case, it’s an exponentially smoothed daily range that’s used to compute the bands. I ran this from 2012 through the present day at the time of this writing (July 14, 2014), as the original link goes through most of the 2000s. (Also, be sure you’re using my most up-to-date IKTrading package, as I updated the quandClean function to deal with some intraday messy data issues that had gone unnoticed before.)

The settings I used were John Ehlers’s original settings — that is, a 20 day analysis period, a 10 day exponential band smoothing (that is, the band is computed as .1*(high-low)+.9*band), entered upon the percent B (that is, the current FRAMA minus the low band over the difference of the bands), and the fraction is 1/10th of the daily range.

Here’s the indicator used:

FRAMAbands <- function(HLC, n=126, FC=1, SC=300, nBands=n/2, bandFrac=10, ...) {
  frama <- FRAMA(HLC, n=n, FC=FC, SC=SC, ...)
  band <- Hi(HLC) - Lo(HLC)
  band <- xts(filter(1/nBands*band, 1-1/nBands, method="recursive"), order.by=index(frama))
  bandUp <- frama$trigger + band/bandFrac
  bandDn <- frama$trigger - band/bandFrac
  pctB <- (frama$FRAMA-bandDn)/(bandUp-bandDn)
  out <- cbind(frama, pctB)
  colnames(out) <- c("FRAMA", "trigger", "pctB")
  return(out)
}

And here’s the strategy code:

source("futuresData.R")

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "FRAMA_BANDS_I"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters
FC = 1
SC = 300
n = 20
triggerLag = 1
nBands = 10
bandFrac=10
entryThreshPctB=1
exitThreshPctB=.5

period=10
pctATR=.06

#indicators
add.indicator(strategy.st, name="FRAMAbands",
              arguments=list(HLC=quote(HLC(mktdata)), FC=FC, SC=SC, 
                             n=n, triggerLag=triggerLag, nBands=nBands,
                             bandFrac=bandFrac),
              label="Fbands")

add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")

#signals
add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="pctB.Fbands", 
                          threshold=entryThreshPctB, 
                          relationship="gt", cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="pctB.Fbands", 
                          threshold=exitThreshPctB, 
                          relationship="lt", cross=TRUE),
           label="longExit")

#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
                        tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

Here are the results:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 0.956477
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 36.39737
> (numTrades <- sum(tStats$Num.Trades))
[1] 1778
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 1.678421

> print(t(durStats))
      [,1]
Min      1
Q1       2
Med      6
Mean     9
Q3      14
Max     65
WMin     1
WQ1      3
WMed    13
WMean   13
WQ3     19
WMax    65
LMin     1
LQ1      2
LMed     4
LMean    6
LQ3      8
LMax    57

mean(corMeans)
[1] 0.08232023

> SharpeRatio.annualized(portfRets)
                                      [,1]
Annualized Sharpe Ratio (Rf=0%) -0.2476826
> Return.annualized(portfRets)
                         [,1]
Annualized Return -0.03485231
> maxDrawdown(portfRets)
[1] 0.2632001

In short, it’s a loser over the past three years. Here’s the equity curve:

Now while it may have worked in the past (or something similar to it, using Ehlers’s filter indicator), it doesn’t seem to do so going forward.

I’ll leave this here for now as a demonstration of how to do Ehlers bands.

Thanks for reading.

Volatility Histeresis: A First Attempt

So the last time that a FRAMA strategy was tried with price crossovers, the problem was that due to counter-trending failures, the filter that was added missed a lot of good trades, and wound up losing a lot of money during flat markets that passed the arbitrary filter.

This trading system tries to rectify those issues by trading a rising FRAMA filtered on a 5-day standard deviation ratio.

The hypothesis is this: the FRAMA rises in legitimately trending markets, and stays flat in choppy markets. Therefore, the ratio of standard deviations (that is, a running standard deviation of the FRAMA over the standard deviation of the market close) should be higher during trending markets, and lower during choppy markets. Additionally, as this ratio bottoms out at zero and usually tops out at 1 (rarely gets higher), it can be used as an indicator across instruments of vastly different properties.

The data that will be used will be the quandl futures data file (without federal funds, coffee, or sugar, because of data issues).

Here’s the data file:

require(IKTrading)


currency('USD')
Sys.setenv(TZ="UTC")


t1 <- Sys.time()
if(!"CME_CL" %in% ls()) {
  #Energies
  CME_CL <- quandClean("CHRIS/CME_CL", start_date=from, end_date=to, verbose=verbose) #Crude
  CME_NG <- quandClean("CHRIS/CME_NG", start_date=from, end_date=to, verbose=verbose) #NatGas
  CME_HO <- quandClean("CHRIS/CME_HO", start_date=from, end_date=to, verbose=verbose) #HeatingOil
  CME_RB <- quandClean("CHRIS/CME_RB", start_date=from, end_date=to, verbose=verbose) #Gasoline
  ICE_B <- quandClean("CHRIS/ICE_B", start_date=from, end_date=to, verbose=verbose) #Brent
  ICE_G <- quandClean("CHRIS/ICE_G", start_date=from, end_date=to, verbose=verbose) #Gasoil
  
  #Grains
  CME_C <- quandClean("CHRIS/CME_C", start_date=from, end_date=to, verbose=verbose) #Chicago Corn
  CME_S <- quandClean("CHRIS/CME_S", start_date=from, end_date=to, verbose=verbose) #Chicago Soybeans
  CME_W <- quandClean("CHRIS/CME_W", start_date=from, end_date=to, verbose=verbose) #Chicago Wheat
  CME_SM <- quandClean("CHRIS/CME_SM", start_date=from, end_date=to, verbose=verbose) #Chicago Soybean Meal
  CME_KW <- quandClean("CHRIS/CME_KW", start_date=from, end_date=to, verbose=verbose) #Kansas City Wheat
  CME_BO <- quandClean("CHRIS/CME_BO", start_date=from, end_date=to, verbose=verbose) #Chicago Soybean Oil
  
  #Softs
  #ICE_SB <- quandClean("CHRIS/ICE_SB", start_date=from, end_date=to, verbose=verbose) #Sugar
  #Sugar 2007-03-26 is wrong
  #ICE_KC <- quandClean("CHRIS/ICE_KC", start_date=from, end_date=to, verbose=verbose) #Coffee
  #Coffee January of 08 is FUBAR'd
  ICE_CC <- quandClean("CHRIS/ICE_CC", start_date=from, end_date=to, verbose=verbose) #Cocoa
  ICE_CT <- quandClean("CHRIS/ICE_CT", start_date=from, end_date=to, verbose=verbose) #Cotton
  
  #Other Ags
  CME_LC <- quandClean("CHRIS/CME_LC", start_date=from, end_date=to, verbose=verbose) #Live Cattle
  CME_LN <- quandClean("CHRIS/CME_LN", start_date=from, end_date=to, verbose=verbose) #Lean Hogs
  
  #Precious Metals
  CME_GC <- quandClean("CHRIS/CME_GC", start_date=from, end_date=to, verbose=verbose) #Gold
  CME_SI <- quandClean("CHRIS/CME_SI", start_date=from, end_date=to, verbose=verbose) #Silver
  CME_PL <- quandClean("CHRIS/CME_PL", start_date=from, end_date=to, verbose=verbose) #Platinum
  CME_PA <- quandClean("CHRIS/CME_PA", start_date=from, end_date=to, verbose=verbose) #Palladium
  
  #Base
  CME_HG <- quandClean("CHRIS/CME_HG", start_date=from, end_date=to, verbose=verbose) #Copper
  
  #Currencies
  CME_AD <- quandClean("CHRIS/CME_AD", start_date=from, end_date=to, verbose=verbose) #Ozzie
  CME_CD <- quandClean("CHRIS/CME_CD", start_date=from, end_date=to, verbose=verbose) #Loonie
  CME_SF <- quandClean("CHRIS/CME_SF", start_date=from, end_date=to, verbose=verbose) #Franc
  CME_EC <- quandClean("CHRIS/CME_EC", start_date=from, end_date=to, verbose=verbose) #Euro
  CME_BP <- quandClean("CHRIS/CME_BP", start_date=from, end_date=to, verbose=verbose) #Cable
  CME_JY <- quandClean("CHRIS/CME_JY", start_date=from, end_date=to, verbose=verbose) #Yen
  CME_NE <- quandClean("CHRIS/CME_NE", start_date=from, end_date=to, verbose=verbose) #Kiwi
  
  #Equities
  CME_ES <- quandClean("CHRIS/CME_ES", start_date=from, end_date=to, verbose=verbose) #Emini
  CME_MD <- quandClean("CHRIS/CME_MD", start_date=from, end_date=to, verbose=verbose) #Midcap 400
  CME_NQ <- quandClean("CHRIS/CME_NQ", start_date=from, end_date=to, verbose=verbose) #Nasdaq 100
  CME_TF <- quandClean("CHRIS/CME_TF", start_date=from, end_date=to, verbose=verbose) #Russell Smallcap
  CME_NK <- quandClean("CHRIS/CME_NK", start_date=from, end_date=to, verbose=verbose) #Nikkei
  
  #Dollar Index and Bonds/Rates
  ICE_DX  <- quandClean("CHRIS/CME_DX", start_date=from, end_date=to, verbose=verbose) #Dixie
  #CME_FF  <- quandClean("CHRIS/CME_FF", start_date=from, end_date=to, verbose=verbose) #30-day fed funds
  CME_ED  <- quandClean("CHRIS/CME_ED", start_date=from, end_date=to, verbose=verbose) #3 Mo. Eurodollar/TED Spread
  CME_FV  <- quandClean("CHRIS/CME_FV", start_date=from, end_date=to, verbose=verbose) #Five Year TNote
  CME_TY  <- quandClean("CHRIS/CME_TY", start_date=from, end_date=to, verbose=verbose) #Ten Year Note
  CME_US  <- quandClean("CHRIS/CME_US", start_date=from, end_date=to, verbose=verbose) #30 year bond
}

CMEinsts <- c("CL", "NG", "HO", "RB", "C", "S", "W", "SM", "KW", "BO", "LC", "LN", "GC", "SI", "PL", 
              "PA", "HG", "AD", "CD", "SF", "EC", "BP", "JY", "NE", "ES", "MD", "NQ", "TF", "NK", #"FF",
              "ED", "FV", "TY", "US")

ICEinsts <- c("B", "G", #"SB", #"KC", 
              "CC", "CT", "DX")
CME <- paste("CME", CMEinsts, sep="_")
ICE <- paste("ICE", ICEinsts, sep="_")
symbols <- c(CME, ICE)
stock(symbols, currency="USD", multiplier=1)
t2 <- Sys.time()
print(t2-t1)

Here’s the strategy:

require(DSTrading)
require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2000-03-01"
to="2011-12-31"
options(width=70)
verose=TRUE

FRAMAsdr <- function(HLC, n, FC, SC, nSD, ...) {
  frama <- FRAMA(HLC, n=n, FC=FC, SC=SC, ...)
  sdr <- runSD(frama$FRAMA, n=nSD)/runSD(Cl(HLC), n=nSD)
  sdr[sdr > 2]  <- 2
  out <- cbind(FRAMA=frama$FRAMA, trigger=frama$trigger, sdr=sdr)
  out
}

source("futuresData.R")

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "FRAMA_SDR_I"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters
FC = 1
SC = 300
n = 126
triggerLag = 1
nSD = 5
sdThresh <- .3

period=10
pctATR=.02

#indicators
add.indicator(strategy.st, name="FRAMAsdr",
              arguments=list(HLC=quote(HLC(mktdata)), FC=FC, SC=SC, 
                             n=n, triggerLag=triggerLag, nSD=nSD),
              label="SDR")

add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="gt"),
           label="FRAMAup")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="sdr.SDR", threshold=sdThresh, 
                          relationship="gt",cross=FALSE),
           label="SDRgtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("FRAMAup", "SDRgtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="lt"),
           label="FRAMAdnExit")

#add.signal(strategy.st, name="sigThreshold",
#           arguments=list(column="sdr.SDR", threshold=sdThresh, relationship="lt", cross=TRUE),
#           label="SDRexit")

#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
                        tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="FRAMAdnExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)

#add.rule(strategy.st, name="ruleSignal", 
#         arguments=list(sigcol="SDRexit", sigval=TRUE, orderqty="all", ordertype="market", 
#                        orderside="long", replace=FALSE, prefer="Open"), 
#         type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

Notice that the exit due to the volatility filter had to have been commented out (as it caused the strategy to lose all its edge). In any case, the FRAMA is the usual 126 day FRAMA, and the running standard deviation is 5 days, in order to try and reduce lag. The standard deviation ratio threshold will be .2 or higher. Here are the results:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.297276
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 39.08526
> (numTrades <- sum(tStats$Num.Trades))
[1] 5186
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 2.065526

In other words, typical trend follower results. 40/60 wrong to right, with a 2:1 win to loss ratio. Far from spectacular.

Duration statistics:

print(t(durStats))
      [,1]
Min      1
Q1       2
Med      5
Mean    11
Q3      11
Max    158
WMin     1
WQ1      5
WMed    10
WMean   18
WQ3     21
WMax   158
LMin     1
LQ1      1
LMed     3
LMean    6
LQ3      6
LMax    93

In short, winners last longer than losers, which makes sense given that there are a lot of whipsaws, and that this is a trend-following strategy.

Market exposure:

> print(mean(as.numeric(as.character(mktExposure$MktExposure))))
[1] 0.3820789

38%. So how did it perform?

Like this. Not particularly great, considering it’s a 60% gain over 11 years. Here are the particular statistics:

> SharpeRatio.annualized(portfRets)
                                     [,1]
Annualized Sharpe Ratio (Rf=0%) 0.8124137
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.04229355
> maxDrawdown(portfRets)
[1] 0.07784351

In other words, about 10 basis points of returns per percent of market exposure, or a 10% annualized return. The problem being? The drawdown is much higher than the annualized return, meaning that leverage will only make things worse. Basically, for the low return on exposure and high drawdown to annualized return, this strategy is a failure. While the steadily ascending equity curve is good, it is meaningless when the worst losses take more than a year to recover from.

In any case, here’s a look at some individual instruments.

Here’s the equity curve for the E-minis.

So first off, we can see one little feature of this strategy–due to the entry and exit not being symmetric (that is, it takes two conditions to enter–a rising FRAMA and a standard deviation ratio above .2–and only exits on one of them (falling FRAMA), price action that exhibits a steady grind upwards, due to the rapid change in ATR (it’s a 10-day figure) can actually slightly pyramid from time to time. This is a good feature in my opinion, since it can add onto a winning position. However, in times of extreme volatility, when even an adaptive indicator can get bounced around chasing “mini-trends”, we can see losses pile on.

Next, let’s look at a much worse situation. Here’s the equity curve for the Eurodollar/TED spread.

In this case, it’s clearly visible that the strategy has countertrend issues, as well as the fact that the 5-day standard deviation ratio can be relatively myopic when it comes to instruments that have protracted periods of complete inactivity–that is, the market is not even choppy so much as just still.

I’ll leave this here, and move onto other attempts at getting around this sort of roadblock next.

Thanks for reading.

FRAMA Part V: Wrap-Up on Confirmatory Indicator/Test Sample

So, it is possible to create a trading system that can correctly isolate severe and protracted downturns, without taking (too many) false signals.

Here are the rules:

126 day FRAMA, FC=4, SC=300 (we’re still modifying the original ETFHQ strategy).
A running median (somewhere between 150 days and 252 days–seemingly, all these configurations work).

Both the FRAMA and the confirmatory median must be moving in the same direction (up for a long trade, down for a short trade–the median rising is the new rule here), and the price must cross the FRAMA in that direction to enter into a trade, while exiting when the price crosses below the FRAMA. Presented as a set of quantstrat rules, it gets rather lengthy, since a rule needs to specify the three setup conditions, a rule to bind them together, and an exit rule (5 rules each side).

The strategy works on both long and short ends, though the short version seems more of an insurance strategy than anything else. Here’s the equity curve for a 150 day median:

Basically, it makes money in terrible periods, but gives some of it back during just about any other time. It’s there just to put it out there as something that can finally try and isolate the truly despicable conditions and give you a pop in those times. Other than that? Using it would depend on how often someone believes those sorts of drawdown conditions would occur–that is, a descending adaptive indicator, a descending 7-12 month median.

Here are the trade and portfolio statistics:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.169916
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 33.475
> (numTrades <- sum(tStats$Num.Trades))
[1] 667
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 2.631724

                                      [,1]
Annualized Sharpe Ratio (Rf=0%) 0.07606299
> Return.annualized(portfRets)
                         [,1]
Annualized Return 0.004247807
> maxDrawdown(portfRets)
[1] 0.09845553

In other words, it’s absolutely not a standalone strategy, but more of a little something to give a long-only strategy a boost during bad times. It’s certainly not as spectacular as it gets. For instance, here’s the equity curve for XLK in late 2008-2009. Mainly, the problem with the ETFHQ strategy (I’m still on that, yes) is that it does not at all take into account the magnitude of the direction of the indicator. This means that in a reverting market, this strategy has a potential to lose a great deal of money unnecessarily.

Basically, this strategy is highly conservative, meaning that it has a tendency to miss good trades, take unnecessary ones, and is generally flawed because it has no way of really estimating the slope of the FRAMA.

As the possible solution to this involves a strategy by John Ehlers, I think I’ll leave this strategy here for now.

So, to send off this original ETFHQ price cross strategy off, I’ll test it out of sample using a 200-day median, using both long and short sides (from 2010-03-01 to get the 200 day median burned in, to the current date as of the time of this writing, 2014-06-20).

Here are the trade stats and portfolio stats:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.195693
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 36.20733
> (numTrades <- sum(tStats$Num.Trades))
[1] 1407
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 2.263333

> SharpeRatio.annualized(portfRets)
                                     [,1]
Annualized Sharpe Ratio (Rf=0%) 0.3290298
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.02467234
> maxDrawdown(portfRets)
[1] 0.1354166

With the corresponding equity curve:

In short, definitely not good. Why?

Here’s a good symptom as to why:

This is the out-of-sample equity curve of SHY–that is, the ETF of short term bonds. The trend had ended, but the trading system didn’t pick up on that.

In this case, you can see that the magnitude of the trend makes no difference to the strategy–which is a major problem. Although the counter-trend trading was eliminated, forcing action was not, and trying to remain loyal to the price crossing the indicator strategy while sticking to more conventional methods (a confirming indicator) turns out to be flawed. Here is another side symptom of a flawed system:

In this instance, using such a conservative confirmatory indicator for the short trade and simply using that same indicator for the long side indicates that there may very well have been overfitting on the system. On a more general note, however, this picture makes one wonder whether a confirmatory indicator was even necessary. For instance, there were certainly protracted periods during which there was a long trend that were cut off due to the running median being slightly negative. There were both long and short opportunities missed.

In my opinion, I think this puts the kibosh on something as ham-handed as a long-running confirmatory indicator. Why? Because I think that it over-corrects for a flawed order logic system that doesn’t take into account the magnitude of the slope of the indicator. Obviously, trading in a countertrend (descending indicator) is a terrible idea. But what about a slight change of directional sign as part of a greater counter-trend? Suddenly, a robust, deliberately lagging confirmatory indicator no longer seems like such a bad idea. However, as you can see, the downside of a lagging indicator is that it may very well lag your primary indicator in a good portion of cases. And it does nothing to eliminate sideways trading.

Surely, a more elegant solution exists that attempts to quantify the fact that sometimes, the smooth-yet-adaptive FRAMA can trend rapidly (and such trades should be taken posthaste), and can also go flat. Ultimately, I think that while the indicator settings from ETFHQ have some merit, the simplistic order logic on its own can certainly hurt–and coupled with an order-sizing function that downsizes orders in times of trending while magnifying them in times of calm (a side-effect of ATR, which was created to equalize risk across instruments, but with the unintended consequence of very much not equalizing risk across market conditions) can cause problems.

The next strategy will attempt to rectify these issues.

Thanks for reading.

FRAMA Part IV: Continuing the Long/Short Filter Search

This post examines an n-day median filter for two desirable properties: robustness to outliers and an inherent trend-confirming lag. While this is an incomplete filter (or maybe even inferior), it offers some key insights into improving the trading system.

The strategy will be thus:

First and foremost, this will be a short-only strategy, due to the long bias within the sample period, so the stress-test of the system will be to attempt to capture the non-dominant trend (and only when appropriate).

Here’s the strategy: we will continue to use the same 126 day FRAMA with the fast constant set at 4, and a slow constant at 300 (that is, it can oscillate anywhere between an EMA4 and EMA300). We will only enter into a short position when this indicator is descending, below the 126-day median of the price action, and when the price action is lower than this indicator (usually this means a cross, not in all cases though). We will exit when the price action rises back above the indicator.

Here’s the strategy in R code:

require(DSTrading)
require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2010-12-31"
options(width=70)

#to rerun the strategy, rerun everything below this line
source("demoData.R") #contains all of the data-related boilerplate.

#trade sizing and initial equity settings
tradeSize <- 10000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "FRAMA_III"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters

FC=4
SC=300
n=126
triggerLag=1

period=10
pctATR=.02

#indicators 

add.indicator(strategy.st, name="FRAMA",
              arguments=list(HLC=quote(HLC(mktdata)), n=n, 
                             SC=SC, FC=FC, triggerLag=triggerLag),
              label="primary")

add.indicator(strategy.st, name="runMedian",
              arguments=list(x=quote(Cl(mktdata)), n=n),
              label="confirmatory")

add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")

# #long signals
# 
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("FRAMA.primary", "X1.confirmatory"), 
#                           relationship="gte"),
#            label="FRAMAgteMedian")
# 
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("FRAMA.primary", "trigger.primary"), 
#                           relationship="gte"),
#            label="FRAMArising")
# 
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("Close", "FRAMA.primary"), 
#                           relationship="gte"),
#            label="ClGtFRAMA")
# 
# add.signal(strategy.st, name="sigAND",
#            arguments=list(columns=c("FRAMAgteMedian", 
#                                     "FRAMArising", "ClGtFRAMA"), 
#                           cross=TRUE),
#            label="longEntry")
# 
# add.signal(strategy.st, name="sigCrossover",
#            arguments=list(columns=c("Close", "FRAMA.primary"), 
#                           relationship="lt"),
#            label="longExit")
# 
# #long rules
# 
# add.rule(strategy.st, name="ruleSignal", 
#          arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
#                         orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
#                         tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), 
#          type="enter", path.dep=TRUE)
# 
# add.rule(strategy.st, name="ruleSignal", 
#          arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
#                         orderside="long", replace=FALSE, prefer="Open"), 
#          type="exit", path.dep=TRUE)

#short signals

add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("FRAMA.primary", "X1.confirmatory"), 
                          relationship="lt"),
           label="FRAMAltMedian")

add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("FRAMA.primary", "trigger.primary"), 
                          relationship="lt"),
           label="FRAMAfalling")

add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "FRAMA.primary"), 
                          relationship="lt"),
           label="ClLtFRAMA")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("FRAMAltMedian", 
                                    "FRAMAfalling", "ClLtFRAMA"), 
                          cross=TRUE),
           label="shortEntry")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "FRAMA.primary"), 
                          relationship="gt"),
           label="shortExit")

#short rules

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open", osFUN=osDollarATR,
                        tradeSize=-tradeSize, pctATR=pctATR, atrMod="X"), 
         type="enter", path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)



#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)


#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

The results aren’t pretty, meaning that the filter is still incomplete. Here are the trade stats:

                        EFA      EPP      EWA      EWC      EWG
Num.Txns              90.00    68.00    85.00    66.00    90.00
Num.Trades            44.00    34.00    41.00    33.00    45.00
Net.Trading.PL     -2030.68   -25.54 -1485.82 -2283.03  -356.83
Avg.Trade.PL         -46.15    -0.75   -36.24   -69.18    -7.93
Med.Trade.PL        -103.99   -44.95   -77.27  -100.40   -69.91
Largest.Winner      1238.56  1656.56  1106.59  2195.51  3197.29
Largest.Loser       -661.04  -786.27  -548.06  -823.55  -783.65
Gross.Profits       4336.92  4455.45  3246.48  3566.35  5948.76
Gross.Losses       -6367.61 -4480.99 -4732.30 -5849.38 -6305.59
Std.Dev.Trade.PL     364.75   419.42   288.36   487.05   557.98
Percent.Positive      29.55    32.35    43.90    24.24    37.78
Percent.Negative      70.45    67.65    56.10    75.76    62.22
Profit.Factor          0.68     0.99     0.69     0.61     0.94
Avg.Win.Trade        333.61   405.04   180.36   445.79   349.93
Med.Win.Trade         57.09   238.60    66.34   124.31   101.88
Avg.Losing.Trade    -205.41  -194.83  -205.75  -233.98  -225.20
Med.Losing.Trade    -156.80  -122.45  -170.49  -166.84  -184.70
Avg.Daily.PL         -46.15    -0.75   -36.24   -69.18    -7.93
Med.Daily.PL        -103.99   -44.95   -77.27  -100.40   -69.91
Std.Dev.Daily.PL     364.75   419.42   288.36   487.05   557.98
Ann.Sharpe            -2.01    -0.03    -2.00    -2.25    -0.23
Max.Drawdown       -5089.55 -3095.58 -3609.64 -4915.76 -4222.60
Profit.To.Max.Draw    -0.40    -0.01    -0.41    -0.46    -0.08
Avg.WinLoss.Ratio      1.62     2.08     0.88     1.91     1.55
Med.WinLoss.Ratio      0.36     1.95     0.39     0.75     0.55
Max.Equity           146.74  1473.19   834.54    78.43  1467.12
Min.Equity         -4942.81 -2562.72 -2775.10 -4837.32 -3960.49
End.Equity         -2030.68   -25.54 -1485.82 -2283.03  -356.83

                        EWH      EWJ      EWS      EWT      EWU
Num.Txns              74.00   102.00    72.00    66.00    72.00
Num.Trades            36.00    51.00    35.00    33.00    36.00
Net.Trading.PL       596.16 -1493.76   982.93  1354.40   439.00
Avg.Trade.PL          16.56   -29.29    28.08    41.04    12.19
Med.Trade.PL         -54.45   -89.63   -52.85   -40.22   -56.50
Largest.Winner      3436.43  1076.25  1165.10  1980.68  1680.33
Largest.Loser       -544.78  -781.15  -429.64  -441.47  -468.28
Gross.Profits       4519.53  5681.81  4763.28  4317.26  5105.73
Gross.Losses       -3923.37 -7175.57 -3780.35 -2962.87 -4666.73
Std.Dev.Trade.PL     610.29   368.61   353.59   408.77   415.23
Percent.Positive      38.89    37.25    37.14    42.42    33.33
Percent.Negative      61.11    62.75    62.86    57.58    66.67
Profit.Factor          1.15     0.79     1.26     1.46     1.09
Avg.Win.Trade        322.82   299.04   366.41   308.38   425.48
Med.Win.Trade         79.88    99.19   267.75   115.47   399.34
Avg.Losing.Trade    -178.33  -224.24  -171.83  -155.94  -194.45
Med.Losing.Trade    -137.71  -175.69  -141.06   -95.88  -180.13
Avg.Daily.PL          16.56   -29.29    28.08    41.04    12.19
Med.Daily.PL         -54.45   -89.63   -52.85   -40.22   -56.50
Std.Dev.Daily.PL     610.29   368.61   353.59   408.77   415.23
Ann.Sharpe             0.43    -1.26     1.26     1.59     0.47
Max.Drawdown       -2390.89 -2994.16 -1689.63 -2113.93 -3192.52
Profit.To.Max.Draw     0.25    -0.50     0.58     0.64     0.14
Avg.WinLoss.Ratio      1.81     1.33     2.13     1.98     2.19
Med.WinLoss.Ratio      0.58     0.56     1.90     1.20     2.22
Max.Equity          2534.61  1380.90  1938.04  2065.70  1845.45
Min.Equity         -2131.17 -2540.03 -1265.23 -1501.15 -3192.52
End.Equity           596.16 -1493.76   982.93  1354.40   439.00

                        EWY      EWZ      EZU      IEF      IGE
Num.Txns              68.00    80.00    96.00    63.00    56.00
Num.Trades            34.00    40.00    48.00    32.00    28.00
Net.Trading.PL      1359.59 -2763.77  -178.24 -5286.17  -588.44
Avg.Trade.PL          39.99   -69.09    -3.71  -165.19   -21.02
Med.Trade.PL          19.52  -103.71   -73.53  -253.57   -87.68
Largest.Winner      1799.34  2495.03  1423.73   908.54  2146.42
Largest.Loser       -467.07  -496.73  -847.67  -758.78  -466.57
Gross.Profits       4729.27  2790.33  5960.68  2309.18  2757.36
Gross.Losses       -3369.68 -5554.10 -6138.92 -7595.36 -3345.80
Std.Dev.Trade.PL     414.55   440.16   402.00   349.52   456.89
Percent.Positive      55.88    15.00    33.33    25.00    25.00
Percent.Negative      44.12    85.00    66.67    75.00    75.00
Profit.Factor          1.40     0.50     0.97     0.30     0.82
Avg.Win.Trade        248.91   465.05   372.54   288.65   393.91
Med.Win.Trade         58.75    43.02    67.59   156.68    43.03
Avg.Losing.Trade    -224.65  -163.36  -191.84  -316.47  -159.32
Med.Losing.Trade    -217.23  -110.98  -139.29  -284.87  -115.98
Avg.Daily.PL          39.99   -69.09    -3.71  -192.64   -21.02
Med.Daily.PL          19.52  -103.71   -73.53  -260.53   -87.68
Std.Dev.Daily.PL     414.55   440.16   402.00   318.33   456.89
Ann.Sharpe             1.53    -2.49    -0.15    -9.61    -0.73
Max.Drawdown       -2237.74 -3903.71 -3510.08 -6682.82 -2836.80
Profit.To.Max.Draw     0.61    -0.71    -0.05    -0.79    -0.21
Avg.WinLoss.Ratio      1.11     2.85     1.94     0.91     2.47
Med.WinLoss.Ratio      0.27     0.39     0.49     0.55     0.37
Max.Equity          3532.28   836.88  1270.40   669.24   709.44
Min.Equity          -790.83 -3066.84 -3222.22 -6013.57 -2127.36
End.Equity          1359.59 -2763.77  -178.24 -5286.17  -588.44

                        IYR      IYZ      LQD      RWR      SHY
Num.Txns              96.00   108.00    63.00    98.00    51.00
Num.Trades            48.00    54.00    31.00    49.00    25.00
Net.Trading.PL     -3444.89 -2032.70  1532.27 -3740.29 -4049.16
Avg.Trade.PL         -71.77   -37.64    49.43   -76.33  -161.97
Med.Trade.PL        -129.99   -83.00   -84.44  -114.84  -141.20
Largest.Winner      1714.13  2673.04  2693.04  1455.78    86.02
Largest.Loser       -745.50  -463.08  -480.73  -578.29  -644.17
Gross.Profits       4652.33  4978.26  5114.62  3534.95   365.46
Gross.Losses       -8097.22 -7010.97 -3582.35 -7275.24 -4414.63
Std.Dev.Trade.PL     405.93   479.46   604.30   341.37   195.72
Percent.Positive      22.92    22.22    35.48    16.33    28.00
Percent.Negative      77.08    77.78    64.52    83.67    72.00
Profit.Factor          0.57     0.71     1.43     0.49     0.08
Avg.Win.Trade        422.94   414.86   464.97   441.87    52.21
Med.Win.Trade        110.81    29.28   139.76   188.82    44.33
Avg.Losing.Trade    -218.84  -166.93  -179.12  -177.44  -245.26
Med.Losing.Trade    -182.73  -134.02  -129.79  -138.78  -232.61
Avg.Daily.PL         -71.77   -37.64    45.47   -76.33  -162.83
Med.Daily.PL        -129.99   -83.00   -86.18  -114.84  -144.12
Std.Dev.Daily.PL     405.93   479.46   614.22   341.37   199.88
Ann.Sharpe            -2.81    -1.25     1.18    -3.55   -12.93
Max.Drawdown       -3857.85 -5575.45 -2876.51 -4695.60 -4049.16
Profit.To.Max.Draw    -0.89    -0.36     0.53    -0.80    -1.00
Avg.WinLoss.Ratio      1.93     2.49     2.60     2.49     0.21
Med.WinLoss.Ratio      0.61     0.22     1.08     1.36     0.19
Max.Equity           260.07   118.92  3375.06   302.96     0.00
Min.Equity         -3597.77 -5456.52 -2138.62 -4392.65 -4049.16
End.Equity         -3444.89 -2032.70  1532.27 -3740.29 -4049.16

                        TLT      XLB      XLE      XLF      XLI
Num.Txns              85.00   104.00    50.00   120.00    92.00
Num.Trades            43.00    51.00    25.00    60.00    46.00
Net.Trading.PL     -4037.97 -5591.16  -308.15 -3036.79 -2136.85
Avg.Trade.PL         -93.91  -109.63   -12.33   -50.61   -46.45
Med.Trade.PL        -133.03  -138.47   -96.47   -79.48  -108.98
Largest.Winner      1425.91  1831.45  1828.51  1058.03  1218.87
Largest.Loser       -543.28  -707.20  -430.13  -711.69  -632.77
Gross.Profits       3355.40  3130.31  2472.08  5282.27  4597.82
Gross.Losses       -7393.37 -8721.48 -2780.24 -8319.05 -6734.68
Std.Dev.Trade.PL     338.88   345.20   420.71   309.84   342.80
Percent.Positive      25.58    25.49    20.00    30.00    23.91
Percent.Negative      74.42    74.51    80.00    70.00    76.09
Profit.Factor          0.45     0.36     0.89     0.63     0.68
Avg.Win.Trade        305.04   240.79   494.42   293.46   417.98
Med.Win.Trade        168.50    83.98    33.87   135.46   294.74
Avg.Losing.Trade    -231.04  -229.51  -139.01  -198.07  -192.42
Med.Losing.Trade    -197.38  -207.82  -120.99  -171.67  -149.06
Avg.Daily.PL        -101.44  -109.63   -12.33   -50.61   -46.45
Med.Daily.PL        -140.48  -138.47   -96.47   -79.48  -108.98
Std.Dev.Daily.PL     339.33   345.20   420.71   309.84   342.80
Ann.Sharpe            -4.75    -5.04    -0.47    -2.59    -2.15
Max.Drawdown       -4926.34 -6711.79 -1938.05 -3451.10 -4068.90
Profit.To.Max.Draw    -0.82    -0.83    -0.16    -0.88    -0.53
Avg.WinLoss.Ratio      1.32     1.05     3.56     1.48     2.17
Med.WinLoss.Ratio      0.85     0.40     0.28     0.79     1.98
Max.Equity           459.78     0.00  1298.51   414.31     0.00
Min.Equity         -4466.56 -6711.79 -1329.01 -3036.79 -4068.90
End.Equity         -4037.97 -5591.16  -308.15 -3036.79 -2136.85

                        XLK      XLP      XLU      XLV      XLY
Num.Txns              86.00    92.00    82.00    94.00    82.00
Num.Trades            43.00    45.00    40.00    47.00    40.00
Net.Trading.PL     -1205.62 -4427.34 -3490.76 -4291.56  -230.80
Avg.Trade.PL         -28.04   -98.39   -87.27   -91.31    -5.77
Med.Trade.PL        -101.30  -153.01   -93.87   -98.69  -140.01
Largest.Winner      2403.16  1008.09  1805.03   842.35  2090.68
Largest.Loser       -806.29  -460.10  -462.68  -554.57  -698.45
Gross.Profits       4984.72  2839.42  2493.63  2959.31  6253.33
Gross.Losses       -6190.34 -7266.76 -5984.39 -7250.87 -6484.14
Std.Dev.Trade.PL     464.22   294.41   350.37   280.87   495.21
Percent.Positive      30.23    15.56    20.00    19.15    30.00
Percent.Negative      69.77    84.44    80.00    80.85    70.00
Profit.Factor          0.81     0.39     0.42     0.41     0.96
Avg.Win.Trade        383.44   405.63   311.70   328.81   521.11
Med.Win.Trade        191.31   116.12    61.87   266.16   307.13
Avg.Losing.Trade    -206.34  -191.23  -187.01  -190.81  -231.58
Med.Losing.Trade    -188.49  -191.04  -156.33  -161.51  -171.21
Avg.Daily.PL         -28.04   -98.39   -87.27   -91.31    -5.77
Med.Daily.PL        -101.30  -153.01   -93.87   -98.69  -140.01
Std.Dev.Daily.PL     464.22   294.41   350.37   280.87   495.21
Ann.Sharpe            -0.96    -5.30    -3.95    -5.16    -0.18
Max.Drawdown       -3448.99 -5384.93 -3540.13 -5186.60 -3964.07
Profit.To.Max.Draw    -0.35    -0.82    -0.99    -0.83    -0.06
Avg.WinLoss.Ratio      1.86     2.12     1.67     1.72     2.25
Med.WinLoss.Ratio      1.01     0.61     0.40     1.65     1.79
Max.Equity           646.11   651.59     0.00   895.04  2960.96
Min.Equity         -3003.57 -4733.34 -3540.13 -4291.56 -1003.11
End.Equity         -1205.62 -4427.34 -3490.76 -4291.56  -230.80

At this point, for the sake of brevity, I’ll leave off the equity curves and portfolio statistics (they’ll obviously be bad). However, let’s look at some images of what exactly is going on with individual trades.

Here is the full-backtest equity curve and corresponding indicators for XLP. The FRAMA is in purple, with the 126-day median in orange, along with the 10-day ATR (lagged by a day) on the bottom.

And here we can immediately see certain properties:

1) ATR order-sizing is not a be-all, end-all type of order. It was created for one purpose, which is to equalize risk across instruments (the original idea of which, I defer to Andreas Clenow’s article). However, that is only a base from which to begin, using other scaled order-sizing procedures which can attempt to quantify the confidence in any particular trade. As it currently stands, for short strategies in equities, the best opportunities happen in the depths of rapid falling price action, during which ATR will rise. One may consider augmenting the ATR order sizing function in order to accomplish this task (or merely apply leverage at the proper time, through modifying the pctATR parameter).

2) While the running median certainly has value as a filter to keep out obviously brainless trades (E.G. in the middle of an uptrend), once the FRAMA crosses the median, anything can happen, as the only logic is that the current FRAMA is just slightly lower than the previous day’s. This may mean that the running median itself is still rising, or that the FRAMA is effectively flat, and what is being traded on is purely noise. And furthermore, with ATR order sizing amplifying the consequences of that noise, this edge case can have disastrous consequences on an equity curve.

Here’s a zoom in on 2005, where we see a pretty severe drawdown (chart time series recolored for clarity).

As can be seen, even though the FRAMA seems to be slightly rising, a price crossing when the FRAMA is lower than the previous day by even an invisibly small amount (compare the purple–the FRAMA, to the red–the same quantity lagged a day) is enough to trigger a trade that will buy a sizable number of shares, even when the volatility is too small to justify such a trade. Essentially, most of the losses in this trading system arise as a result of trading during these flat periods during which the system attempts to force action.

This pattern repeats itself. Here is the equity curve for XLB.

Again, aside from maybe a bad trade in the end thanks to any trade being taken once all three conditions line up (decreasing FRAMA, FRAMA lower than median, price lower than FRAMA) too late due to a flat FRAMA/median relationship, most of the losers seem to be trades made during very flat and calm market action, even when the running median may be going in the opposite direction of the FRAMA, during which the ATR order-sizing function tried to force action. A second filter that serves to catch these edge-case situations (or maybe a filter that replaces the running median entirely) will be investigated in the future.

So, to recap this post:

The running median filter is an intrinsically lagging but robust indicator, chosen deliberately for these two properties. It is able to filter out trades that obviously go against the trend. However, due to some edge cases, there were still a great deal of losses that were incurred, which drown out the one good shorting opportunity over this sample period. This is an issue that needs addressing.

Thanks for reading.

FRAMA Part III: Avoiding Countertrend Trading — A First Attempt

This post will begin to experiment with long-term directional detection using relationships between two FRAMA indicators. By observing the relationship between two differently parametrized FRAMAs and the relationship by virtue of the ATR, it will be possible to avoid counter-trend trading on both sides. We will see this example later:

As with TVI, when the signals and rules were swapped for the short end, the equity curve was an unmitigated disaster. Unlike the flat-during-bull-runs-and-permanently-popping-up equity curve of ETFHQ, this equity curve was a disaster. For those that read the final TVI post, the equity curve looked almost identical to that–just a monotonous drawdown until the crisis, at which point the gains aren’t made up, and then the losses continue. In short, there’s no need to go into depth of those statistics.

As the link to ETFHQ suggests, we will use a longer-term FRAMA (the n=252, FC=40, SC=252 version). The market will be in an uptrend when the fast FRAMA (the FRAMA from the previous post) is above this slower FRAMA, and vice versa. Furthermore, in order to avoid some whipsaws, the fast FRAMA will have to be ascending (or descending, during a downtrend), and the entry signal will be when the price crosses over (under) the faster FRAMA, with the exit being the reverse.

In the interest of brevity, since the sample period was an uptrend, then a great deal of strategies will look good on the upside. The question is whether or not the strategy does well on the short side, as the litmus test in testing a confirming indicator is whether or not it can create a positive expectation in a strategy that is counter-trend to the dominant trend in the sample data. As this is a replication of an implied idea by ETFHQ (rather than my own particular idea), let’s look at the code for the strategy. In this instance, both the long and short end of this symmetric strategy are included, and in RStudio, commenting or uncommenting one half or the other is as simple as highlight+ctrl+shift+C.

Here’s the code.

require(DSTrading)
require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to="2010-12-31"
options(width=70)

#to rerun the strategy, rerun everything below this line
source("demoData.R") #contains all of the data-related boilerplate.

#trade sizing and initial equity settings
tradeSize <- 10000
initEq <- tradeSize*length(symbols)

strategy.st <- portfolio.st <- account.st <- "FRAMA_II"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters

FCfast=4
SCfast=300
nFast=126
fastTriggerLag=1

FCslow=40
SCslow=252
nSlow=252
slowTriggerLag=1

period=10
pctATR=.02

#indicators
#Have to add in this function first, since the word 'slow' gets picked up
#by the HLC function as "low", which causes issues.
add.indicator(strategy.st, name="lagATR", 
              arguments=list(HLC=quote(HLC(mktdata)), n=period), 
              label="atrX")


add.indicator(strategy.st, name="FRAMA", 
              arguments=list(HLC=quote(HLC(mktdata)), n=nFast, FC=FCfast, 
                             SC=SCfast, triggerLag=fastTriggerLag),
              label="fast")


add.indicator(strategy.st, name="FRAMA", 
              arguments=list(HLC=quote(HLC(mktdata)), n=nSlow, FC=FCslow, 
                             SC=SCslow, triggerLag=slowTriggerLag),
              label="slow")

# #long signals
# 
# #condition 1: our fast FRAMA is above our slow FRAMA
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("FRAMA.fast", "FRAMA.slow"), relationship="gt"),
#            label="fastFRAMAaboveSlow")
# 
# #condition 2: our fast FRAMA is rising
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("FRAMA.fast", "trigger.fast"), relationship="gt"),
#            label="fastFRAMArising")
# 
# #setup: price crosses above the fast FRAMA
# add.signal(strategy.st, name="sigComparison",
#            arguments=list(columns=c("Close", "FRAMA.fast"), relationship="gte"),
#            label="CloseGteFastFRAMA")
# 
# #wrap our conditions and our setup into one entry signal
# add.signal(strategy.st, name="sigAND",
#            arguments=list(columns=c("fastFRAMAaboveSlow", "fastFRAMArising", "CloseGteFastFRAMA"), cross=TRUE),
#            label="longEntry")
# 
# #our old exit signal
# add.signal(strategy.st, name="sigCrossover",
#            arguments=list(columns=c("Close", "FRAMA.fast"), relationship="lt"),
#            label="longExit")

# #long rules

# add.rule(strategy.st, name="ruleSignal", 
#          arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market", 
#                         orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
#                         tradeSize=tradeSize, pctATR=pctATR, atrMod="X"), 
#          type="enter", path.dep=TRUE)
# 
# add.rule(strategy.st, name="ruleSignal", 
#          arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market", 
#                         orderside="long", replace=FALSE, prefer="Open"), 
#          type="exit", path.dep=TRUE)



#short signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("FRAMA.fast", "FRAMA.slow"), relationship="lt"),
           label="fastFRAMAbelowSlow")

#condition 2: our fast FRAMA is falling
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("FRAMA.fast", "trigger.fast"), relationship="lt"),
           label="fastFRAMAfalling")


#setup: price crosses below the fast FRAMA
add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "FRAMA.fast"), relationship="lt"),
           label="CloseLtFastFRAMA")

#wrap our conditions and our setup into one entry signal
add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("fastFRAMAbelowSlow", "fastFRAMAfalling", "CloseLtFastFRAMA"), cross=TRUE),
           label="shortEntry")

#our old exit signal
add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "FRAMA.fast"), relationship="gt"),
           label="shortExit")

#short rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open", osFUN=osDollarATR,
                        tradeSize=-tradeSize, pctATR=pctATR, atrMod="X"), 
         type="enter", path.dep=TRUE)
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all", ordertype="market", 
                        orderside="short", replace=FALSE, prefer="Open"), 
         type="exit", path.dep=TRUE)


#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)


#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)

First, a quick little side-note: since many indicators look for the word “low”, my usage of the label “slow” would cause a bug if I added the lagATR indicator after that point. So try to avoid labels such as “high”, “low”, “open”, and “close” in your indicators, or at least declare all indicators that would look for the word “low” before declaring the indicator you label in part as “slow”, if you must go this route. Low is probably the easiest one for which to overlook this, since “slow” has so many applications to confirmatory indicators. (EG: SMA fast vs. SMA slow, etc.)

Again, to reiterate, the system will take a long position when price crosses over (under) a rising (falling) fast FRAMA that’s higher (lower) than the slow FRAMA, and exit that position when the price crosses back under (over) the fast FRAMA. The cross must happen when the other two conditions are intact, as opposed to a trade being entered when all three conditions come together, which may be in the middle of a trend.

As the majority of the sample data was in an uptrend (and the fact that the Andreas Clenow-inspired ATR order-sizing function pays enormous dividends in protecting for a limited time in a counter-trend), I decided to put the (slightly modified–with the one condition of rising in an uptrend or falling in a downtrend) system to the test by testing it on the non-dominant trend in the sample–that is, to see if the system can stay out at all points aside from the crisis.

Here are the (not-so-flattering) results:

Trade statistics:

                        EFA      EPP      EWA      EWC      EWG
Num.Txns              88.00    80.00    84.00    60.00    70.00
Num.Trades            43.00    40.00    40.00    30.00    35.00
Net.Trading.PL     -1393.99 -1585.91   981.89   875.47  -810.83
Avg.Trade.PL         -32.42   -39.65    24.55    29.18   -23.17
Med.Trade.PL        -106.64   -70.14     1.79   -74.83   -46.21
Largest.Winner      1238.56   775.14  1106.59  2195.51   449.23
Largest.Loser       -661.04  -466.95  -552.10  -565.80  -547.09
Gross.Profits       4211.42  3301.63  3677.16  3856.57  2719.33
Gross.Losses       -5605.42 -4887.54 -2695.27 -2981.10 -3530.16
Std.Dev.Trade.PL     344.67   284.29   263.92   462.93   226.75
Percent.Positive      32.56    30.00    50.00    26.67    48.57
Percent.Negative      67.44    70.00    50.00    73.33    51.43
Profit.Factor          0.75     0.68     1.36     1.29     0.77
Avg.Win.Trade        300.82   275.14   183.86   482.07   159.96
Med.Win.Trade         73.89   172.89    56.79   245.71   121.09
Avg.Losing.Trade    -193.29  -174.55  -134.76  -135.50  -196.12
Med.Losing.Trade    -156.80  -141.04   -88.56  -108.19  -187.47
Avg.Daily.PL         -33.19   -39.65    24.55    29.18   -23.17
Med.Daily.PL        -103.99   -70.14     1.79   -74.83   -46.21
Std.Dev.Daily.PL     337.33   284.29   263.92   462.93   226.75
Ann.Sharpe            -1.56    -2.21     1.48     1.00    -1.62
Max.Drawdown       -4696.67 -3090.61 -2193.20 -2032.07 -1990.83
Profit.To.Max.Draw    -0.30    -0.51     0.45     0.43    -0.41
Avg.WinLoss.Ratio      1.56     1.58     1.36     3.56     0.82
Med.WinLoss.Ratio      0.47     1.23     0.64     2.27     0.65
Max.Equity           560.75   355.13  2236.74  1567.71  1030.36
Min.Equity         -4135.91 -2881.48 -1702.76 -2019.64  -960.47
End.Equity         -1393.99 -1585.91   981.89   875.47  -810.83

                        EWH      EWJ      EWS      EWT      EWU
Num.Txns              48.00   102.00    74.00    56.00    82.00
Num.Trades            23.00    51.00    35.00    27.00    41.00
Net.Trading.PL      -420.23  -951.54  1424.73   292.07 -1756.36
Avg.Trade.PL         -18.27   -18.66    40.71    10.82   -42.84
Med.Trade.PL         -42.63   -54.18   -15.44     3.83   -46.42
Largest.Winner       309.93  1704.54  1165.10   437.42   664.06
Largest.Loser       -341.09  -460.39  -424.29  -367.22  -367.18
Gross.Profits        996.32  5137.50  4424.92  2072.64  2461.62
Gross.Losses       -1416.55 -6089.05 -3000.19 -1780.57 -4217.98
Std.Dev.Trade.PL     135.68   358.27   328.50   180.94   227.57
Percent.Positive      39.13    39.22    42.86    51.85    34.15
Percent.Negative      60.87    60.78    57.14    48.15    65.85
Profit.Factor          0.70     0.84     1.47     1.16     0.58
Avg.Win.Trade        110.70   256.88   294.99   148.05   175.83
Med.Win.Trade         91.25    80.40    84.30   100.73    66.50
Avg.Losing.Trade    -101.18  -196.42  -150.01  -136.97  -156.22
Med.Losing.Trade     -92.50  -173.06  -141.06  -144.50  -146.70
Avg.Daily.PL         -18.27   -18.66    40.71    10.82   -42.84
Med.Daily.PL         -42.63   -54.18   -15.44     3.83   -46.42
Std.Dev.Daily.PL     135.68   358.27   328.50   180.94   227.57
Ann.Sharpe            -2.14    -0.83     1.97     0.95    -2.99
Max.Drawdown       -1330.17 -3187.00 -1855.03 -1440.36 -3674.50
Profit.To.Max.Draw    -0.32    -0.30     0.77     0.20    -0.48
Avg.WinLoss.Ratio      1.09     1.31     1.97     1.08     1.13
Med.WinLoss.Ratio      0.99     0.46     0.60     0.70     0.45
Max.Equity           791.29  2235.46  2100.37   919.22   280.39
Min.Equity          -974.85 -2116.00 -1230.08  -885.33 -3394.12
End.Equity          -420.23  -951.54  1424.73   292.07 -1756.36

                        EWY      EWZ      EZU      IEF      IGE
Num.Txns              82.00    58.00    90.00    47.00    96.00
Num.Trades            41.00    29.00    45.00    24.00    48.00
Net.Trading.PL      2644.53   434.29 -1639.77 -1071.52 -1826.08
Avg.Trade.PL          64.50    14.98   -36.44   -44.65   -38.04
Med.Trade.PL         -36.18   -89.69   -70.13   -56.73   -79.30
Largest.Winner      2447.28  2495.03  1222.50   908.54  2146.42
Largest.Loser       -392.38  -382.20  -455.60  -717.52  -297.67
Gross.Profits       5519.58  3649.15  3231.22  2332.00  3162.60
Gross.Losses       -2875.04 -3214.86 -4870.99 -3403.52 -4988.68
Std.Dev.Trade.PL     441.24   521.49   282.42   339.58   349.80
Percent.Positive      48.78    24.14    37.78    41.67    20.83
Percent.Negative      51.22    75.86    62.22    58.33    79.17
Profit.Factor          1.92     1.14     0.66     0.69     0.63
Avg.Win.Trade        275.98   521.31   190.07   233.20   316.26
Med.Win.Trade        119.21    91.01    54.82    81.98    82.87
Avg.Losing.Trade    -136.91  -146.13  -173.96  -243.11  -131.28
Med.Losing.Trade     -97.92  -109.64  -155.51  -230.79  -113.58
Avg.Daily.PL          64.50    14.98   -36.44   -74.70   -38.04
Med.Daily.PL         -36.18   -89.69   -70.13   -71.76   -79.30
Std.Dev.Daily.PL     441.24   521.49   282.42   312.88   349.80
Ann.Sharpe             2.32     0.46    -2.05    -3.79    -1.73
Max.Drawdown       -1779.21 -3253.19 -3402.61 -3204.56 -3455.82
Profit.To.Max.Draw     1.49     0.13    -0.48    -0.33    -0.53
Avg.WinLoss.Ratio      2.02     3.57     1.09     0.96     2.41
Med.WinLoss.Ratio      1.22     0.83     0.35     0.36     0.73
Max.Equity          3319.81  2235.92   291.74  1170.92   255.57
Min.Equity         -1779.21 -1280.22 -3110.88 -2033.64 -3200.25
End.Equity          2644.53   434.29 -1639.77 -1071.52 -1826.08

                        IYR      IYZ      LQD      RWR      SHY
Num.Txns             106.00   108.00    43.00   114.00    33.00
Num.Trades            53.00    54.00    21.00    56.00    17.00
Net.Trading.PL     -3809.10 -3010.91  1863.94 -3690.62 -3715.43
Avg.Trade.PL         -71.87   -55.76    88.76   -65.90  -218.55
Med.Trade.PL        -107.50   -94.91   -14.30   -95.48  -165.13
Largest.Winner      1714.13  2673.04  1618.71  1455.78    23.93
Largest.Loser       -745.50  -463.08  -236.13  -476.51  -870.37
Gross.Profits       3465.76  3941.01  3050.21  2877.22    44.39
Gross.Losses       -7274.86 -6951.92 -1186.26 -6567.84 -3759.82
Std.Dev.Trade.PL     316.10   412.75   387.67   256.76   226.14
Percent.Positive      22.64    22.22    47.62    19.64    11.76
Percent.Negative      77.36    77.78    52.38    80.36    88.24
Profit.Factor          0.48     0.57     2.57     0.44     0.01
Avg.Win.Trade        288.81   328.42   305.02   261.57    22.19
Med.Win.Trade        125.96    52.77   178.87   144.25    22.19
Avg.Losing.Trade    -177.44  -165.52  -107.84  -145.95  -250.65
Med.Losing.Trade    -151.10  -150.12   -97.91  -134.83  -210.93
Avg.Daily.PL         -71.87   -55.76    80.29   -65.90  -223.39
Med.Daily.PL        -107.50   -94.91   -33.90   -95.48  -188.03
Std.Dev.Daily.PL     316.10   412.75   395.74   256.76   232.64
Ann.Sharpe            -3.61    -2.14     3.22    -4.07   -15.24
Max.Drawdown       -4518.57 -4628.68 -1075.56 -4511.52 -4429.88
Profit.To.Max.Draw    -0.84    -0.65     1.73    -0.82    -0.84
Avg.WinLoss.Ratio      1.63     1.98     2.83     1.79     0.09
Med.WinLoss.Ratio      0.83     0.35     1.83     1.07     0.11
Max.Equity           709.48   561.64  2649.32   820.90   714.45
Min.Equity         -3809.10 -4067.04  -318.88 -3690.62 -3715.43
End.Equity         -3809.10 -3010.91  1863.94 -3690.62 -3715.43

                        TLT      XLB      XLE      XLF      XLI
Num.Txns              73.00    72.00    82.00   104.00   106.00
Num.Trades            37.00    36.00    41.00    52.00    53.00
Net.Trading.PL     -2881.18    75.64  -738.57  -705.52 -1281.19
Avg.Trade.PL         -77.87     2.10   -18.01   -13.57   -24.17
Med.Trade.PL        -147.94   -45.01   -94.63   -71.06   -77.50
Largest.Winner      1425.91  1831.45  2087.67  1058.03  1218.87
Largest.Loser       -486.72  -423.07  -299.82  -711.69  -480.88
Gross.Profits       3086.09  3723.24  3173.04  5277.71  4948.54
Gross.Losses       -5967.27 -3647.61 -3911.61 -5983.23 -6229.73
Std.Dev.Trade.PL     338.67   369.57   371.02   313.49   307.17
Percent.Positive      24.32    36.11    14.63    34.62    30.19
Percent.Negative      75.68    63.89    85.37    65.38    69.81
Profit.Factor          0.52     1.02     0.81     0.88     0.79
Avg.Win.Trade        342.90   286.40   528.84   293.21   309.28
Med.Win.Trade        151.67   158.38   237.83   139.29   204.28
Avg.Losing.Trade    -213.12  -158.59  -111.76  -175.98  -168.37
Med.Losing.Trade    -195.66  -128.42   -96.70  -144.43  -147.64
Avg.Daily.PL         -86.21     2.10   -18.01   -13.57   -24.17
Med.Daily.PL        -149.61   -45.01   -94.63   -71.06   -77.50
Std.Dev.Daily.PL     339.60   369.57   371.02   313.49   307.17
Ann.Sharpe            -4.03     0.09    -0.77    -0.69    -1.25
Max.Drawdown       -3946.88 -2772.07 -2742.16 -2243.85 -2727.83
Profit.To.Max.Draw    -0.73     0.03    -0.27    -0.31    -0.47
Avg.WinLoss.Ratio      1.61     1.81     4.73     1.67     1.84
Med.WinLoss.Ratio      0.78     1.23     2.46     0.96     1.38
Max.Equity           139.90  1411.59   335.20  1066.45   848.83
Min.Equity         -3806.97 -1978.82 -2742.16 -1573.88 -2028.21
End.Equity         -2881.18    75.64  -738.57  -705.52 -1281.19

                        XLK      XLP      XLU      XLV      XLY
Num.Txns              94.00    84.00    86.00    62.00    66.00
Num.Trades            47.00    41.00    42.00    31.00    33.00
Net.Trading.PL     -1651.16 -3264.51 -4665.83 -2093.02   507.06
Avg.Trade.PL         -35.13   -79.62  -111.09   -67.52    15.37
Med.Trade.PL         -99.55  -129.52   -90.84   -80.14  -100.97
Largest.Winner      2403.16  1008.09   174.64  1447.68  2090.68
Largest.Loser       -526.85  -460.10  -419.92  -533.05  -352.17
Gross.Profits       4484.79  2660.58   600.44  2419.62  4101.12
Gross.Losses       -6135.95 -5925.09 -5266.27 -4512.64 -3594.06
Std.Dev.Trade.PL     419.01   287.43   134.66   364.01   430.90
Percent.Positive      31.91    19.51    21.43    16.13    30.30
Percent.Negative      68.09    80.49    78.57    83.87    69.70
Profit.Factor          0.73     0.45     0.11     0.54     1.14
Avg.Win.Trade        298.99   332.57    66.72   483.92   410.11
Med.Win.Trade        106.63    84.26    46.23    86.14   202.57
Avg.Losing.Trade    -191.75  -179.55  -159.58  -173.56  -156.26
Med.Losing.Trade    -168.10  -161.95  -152.33  -128.20  -151.78
Avg.Daily.PL         -35.13   -79.62  -111.09   -67.52    15.37
Med.Daily.PL         -99.55  -129.52   -90.84   -80.14  -100.97
Std.Dev.Daily.PL     419.01   287.43   134.66   364.01   430.90
Ann.Sharpe            -1.33    -4.40   -13.10    -2.94     0.57
Max.Drawdown       -4435.53 -5189.24 -4665.83 -3779.96 -2264.20
Profit.To.Max.Draw    -0.37    -0.63    -1.00    -0.55     0.22
Avg.WinLoss.Ratio      1.56     1.85     0.42     2.79     2.62
Med.WinLoss.Ratio      0.63     0.52     0.30     0.67     1.33
Max.Equity           861.83  1156.76     0.00  1686.94  2771.26
Min.Equity         -3573.71 -4032.47 -4665.83 -2093.02  -613.72
End.Equity         -1651.16 -3264.51 -4665.83 -2093.02   507.06
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 0.7443694
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 31.708
> (numTrades <- sum(tStats$Num.Trades))
[1] 1166
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
[1] 1.824333

In other words, we can already see that the proposed confirmatory indicator is a dud. To display the raw instrument daily stats at this point would also be uninteresting, so we’ll move past that.

Duration statistics:

durStats <- durationStatistics(Portfolio=portfolio.st, Symbols=sort(symbols))
print(t(durStats))
      EFA EPP EWA EWC EWG EWH EWJ EWS EWT EWU EWY EWZ EZU IEF IGE IYR
Min     1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
Q1      2   1   1   4   1   2   1   3   1   1   1   1   2   2   1   1
Med     4   4   4   8   5   4   4   5   5   4   3   5   4   5   5   3
Mean   10   8   9  13  10   7   9  11  13   8  10  11   8  10   9   7
Q3      7   8   7  16  10   6   8  10  10  10   7   8   7   8   8   7
Max    83  72  79  74  77  55  98  76  79  72 105  71  83  66  90  59
WMin    1   1   1   7   1   1   1   1   1   1   1   1   1   1   6   1
WQ1     3   2   1  14   1   3   1   4   2   1   1   4   2   7   8   6
WMed   10   6   6  23   5   6   2  10  10   4   3   8   4   8  18   9
WMean  21  14  13  26  13  11  13  18  22  13  16  24  11  20  26  15
WQ3    26  14   9  29  13   7   9  26  21  15  14  41   7  39  30  17
WMax   83  72  79  74  77  55  98  76  79  72 105  71  83  66  90  59
LMin    1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
LQ1     2   1   1   4   2   2   2   3   1   2   1   1   2   1   1   1
LMed    4   3   4   6   4   3   4   4   4   4   1   4   5   2   3   3
LMean   4   5   6   9   6   4   6   5   4   6   3   7   6   4   4   4
LQ3     6   6   6  12   8   6   8   5   5   7   3   7   7   6   6   5
LMax   21  23  22  33  21  14  25  32   8  29  12  68  26  10  14  36

      IYZ LQD RWR SHY TLT XLB XLE XLF XLI XLK XLP XLU XLV XLY
Min     1   1   1   1   1   1   1   1   1   1   1   1   1   1
Q1      1   1   1   4   2   2   1   2   2   2   2   2   2   2
Med     5   3   3   6   4   6   4   5   4   3   4   4   5   5
Mean    9   9   6   9   8  12  10  13   9   9   8   6   8  14
Q3      7   5   6  12   7  14   7  18  11  10   7   6   8  11
Max   105  91  67  29  55  82  97  91  55  67  67  34  35 100
WMin    1   1   1   3   1   2   7   1   1   1   1   1   3   2
WQ1     3   3   8   4   2   7   9   3   3   2   4   1   7   7
WMed    8   5  10   6   6  17  22  20  10  15  15   4  17  26
WMean  21  17  18   6  18  23  40  26  17  20  22   9  19  35
WQ3    28  18  24   7  32  22  69  34  26  26  32  13  35  56
WMax  105  91  67   8  55  82  97  91  55  67  67  34  35 100
LMin    1   1   1   1   1   1   1   1   1   1   1   1   1   1
LQ1     1   1   1   4   2   2   1   2   2   1   1   2   1   1
LMed    4   1   2   6   4   3   3   4   4   3   3   4   4   4
LMean   5   2   3   9   5   6   5   6   6   4   4   4   6   4
LQ3     7   3   5  12   7   8   6   8  10   5   6   5   7   6
LMax   29   7  15  29  19  29  40  21  19  21  20  16  30  13

Basically, we can see that there are some really long trades that win, but between this table and the previous trade stats output, that is more than swamped by the legions of losers. Here is the market exposure:


#market exposure
tmp <- list()
length(tmp) <- length(symbols)
for(i in 1:nrow(dStats)) {
  totalDays <- nrow(get(rownames(dStats)[i]))
  mktExposure <- dStats$Total.Days[i]/totalDays
  tmp[[i]] <- c(rownames(dStats)[i], round(mktExposure, 3))
}
mktExposure <- data.frame(do.call(rbind, tmp))
colnames(mktExposure) <- c("Symbol","MktExposure")
print(mktExposure)

   Symbol MktExposure
1     EFA       0.168
2     EPP       0.125
3     EWA       0.149
4     EWC        0.15
5     EWG       0.133
6     EWH       0.063
7     EWJ       0.176
8     EWS       0.145
9     EWT       0.133
10    EWU       0.135
11    EWY       0.152
12    EWZ       0.126
13    EZU       0.147
14    IEF       0.102
15    IGE       0.168
16    IYR       0.152
17    IYZ       0.186
18    LQD       0.083
19    RWR       0.149
20    SHY       0.052
21    TLT       0.126
22    XLB       0.168
23    XLE        0.16
24    XLF       0.249
25    XLI       0.196
26    XLK       0.168
27    XLP       0.129
28    XLU       0.101
29    XLV         0.1
30    XLY       0.168

In other words, even though the market exposure is rather small, the system still manages to hemorrhage a great deal during those small exposures, which does not sing many praises for the proposed system.

Here is the code for a cash sharpe and the equity curve comparisons:

#portfolio cash PL
portString <- paste0("portfolio.", portfolio.st)
portPL <- .blotter[[portString]]$summary$Net.Trading.PL

#Cash Sharpe
(SharpeRatio.annualized(portPL, geometric=FALSE))

#Portfolio comparisons to SPY
instRets <- PortfReturns(account.st)

#Correlations
instCors <- cor(instRets)
diag(instRets) <- NA
corMeans <- rowMeans(instCors, na.rm=TRUE)
names(corMeans) <- gsub(".DailyEndEq", "", names(corMeans))
print(round(corMeans,3))
mean(corMeans)

portfRets <- xts(rowMeans(instRets)*ncol(instRets), order.by=index(instRets))
portfRets <- portfRets[!is.na(portfRets)]
cumPortfRets <- cumprod(1+portfRets)
firstNonZeroDay <- as.character(index(portfRets)[min(which(portfRets!=0))])
getSymbols("SPY", from=firstNonZeroDay, to=to)
SPYrets <- diff(log(Cl(SPY)))[-1]
cumSPYrets <- cumprod(1+SPYrets)
comparison <- cbind(cumPortfRets, cumSPYrets)
colnames(comparison)  <- c("strategy", "SPY")
chart.TimeSeries(comparison, legend.loc = "topleft",
                 colors=c("green","red"))

Which gives us the following results:

(SharpeRatio.annualized(portPL, geometric=FALSE))
                                Net.Trading.PL
Annualized Sharpe Ratio (Rf=0%)     -0.2687879

In short, the idea of using a “slower” FRAMA does not seem to hold much water. And here are the portfolio statistics to confirm it:

> SharpeRatio.annualized(portfRets)
                                      [,1]
Annualized Sharpe Ratio (Rf=0%) -0.2950648
> Return.annualized(portfRets)
                         [,1]
Annualized Return -0.01560975
> maxDrawdown(portfRets)
[1] 0.1551006

But why?

For that, we’ll look at a picture of the equity curve of an individual instrument, complete with overlaid indicators.

chart.Posn(portfolio.st, "XLF")
tmp <- FRAMA(HLC(XLF), n=nFast, FC=FCfast, SC=SCfast, triggerLag=fastTriggerLag)
add_TA(tmp$FRAMA, on=1, col="purple", lwd=3)
add_TA(tmp$trigger, on=1, col="blue", lwd=0.5)
tmp2 <- FRAMA(HLC(XLF), n=nSlow, FC=FCslow, SC=SCslow, triggerLag=slowTriggerLag)
add_TA(tmp2$FRAMA, on=1, col="orange", lwd=3)
tmp2 <- lagATR(HLC=HLC(XLF), n=period)
add_TA(tmp2$atr, col="purple", lwd=2)

Which produces the following plot:

The primary indicator is in purple, while the confirmatory indicator is in orange. And now we see the reason why: because although the FRAMA (n=252, FC=40, SC=252) is a seemingly fine parametrization in and of itself, as a “big-picture/long-term-trend/greater smoothing” indicator, it does not seem like the best choice, at least in the conventional sense as using something such as the SMA200, ROC200 > 0, or RS Rank (see this post from SystemTraderSuccess).

Why? Because from my intuition, adaptive moving average indicators all aim to do the same thing–they aim to be a more accurate way of aggregating lots of data in order to tell you what is happening to as close as current time as they can get. That is, if you look at the presentation by Dr. John Ehlers (see this link), you’ll notice how similar all of the indicators are. All of them effectively aim to maximize near-term smoothness and eliminate as much lag as possible. That is, if you’re looking to make short-term momentum trades that last five days, if your indicator has a five-day lag (EG a 10-day running median), well, your indicator isn’t of much use in that case, because by the time you receive the signal, the opportunity is over!

However, while eliminating lag is usually desirable, in one case, it isn’t. To go off on a tangent, the Japanese trading system called Ichimoku Kinko Hyo (which may be investigated in the future), created by Goichi Hosoda, deliberately makes use of lagging current price action to create a cloud. That is, if you want a confirmatory indicator, you want something robust (especially to the heightened volatility during corrections, bear markets, downtrends, etc.), and something that *has* a bit of lag to it, to confirm the relationship between the more up-to-date indicator (E.G. an adaptive moving average, a short-term oscillator such as RSI2, etc.), and the overarching, long-term trend.

The failure to do so in this case results in problematic counter-trend trades before the financial crisis. While the trading during the financial crisis had a very choppy equity curve during the height of the crisis itself, this was for an individual instrument, and note, that by the end of the crisis, the strategy had indeed made money. The greater problem was that due to the similarities in kind of the confirmatory indicator with the one used for entries and exits, then occasionally, the confirmatory indicator would overtake the indicator it was supposed to confirm, even in a sideways or upwards market, which resulted in several disastrous trades.

And while the indicator used for entries and exits should be as up-to-date as possible so as to get in and out in as timely a fashion as possible, a confirmatory indicator, first and foremost, should not reverse the entire system’s understanding of the market mode on a whim, and secondly, should try to be more backward looking, so as to better do its job of confirmation. Thus, in my opinion, the recommendation of this “slower” FRAMA to be used as a confirmatory indicator by ETFHQ was rather ill-advised. Thus, the investigation will continue into finding a more suitable confirmatory indicator.

Thanks for reading.