Comparing ATR order sizing to max dollar order sizing

First off, it has come to my attention that some readers have trouble getting some of my demos to work because there may be different versions of TTR in use. If ever your demo doesn’t work, the first thing I would immediately recommend you do is this:

Only run the code through the add.indicator logic. And then, rather than adding the signals and rules, run the following code:

test <- applyIndicators(strategy.st, mktdata=OHLC(XLB))
head(test)

That should show you the exact column names of your indicators, and you can adjust your inputs accordingly.While one of my first posts introduced the ATR order-sizing function, I recently received a suggestion to test it in the context of whether or not it actually normalized risk across instruments. To keep things simple, my strategy is as plain vanilla as strategies come — RSI2 20/80 filtered on SMA200.

Here’s the code for the ATR order sizing version, for completeness’s sake.

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

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

source("demoData.R")

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

strategy.st <- portfolio.st <- account.st <- "DollarVsATRos"
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
pctATR=.02
period=10

nRSI <- 2
buyThresh <- 20
sellThresh <- 80
nSMA <- 200

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

add.indicator(strategy.st, name="RSI",
              arguments=list(price=quote(Cl(mktdata)), n=nRSI),
              label="rsi")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),
              label="sma")

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

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

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

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

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

#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="filterExit", 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 some of the usual analytics, which don’t interest me in and of themselves as this strategy is rather throwaway, but to compare them to what happens when I use the max dollar order sizing function in a moment:

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

> SharpeRatio.annualized(portfRets)
                                     [,1]
Annualized Sharpe Ratio (Rf=0%) 0.9783541
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.07369592
> maxDrawdown(portfRets)
[1] 0.08405041

> round(apply.yearly(dailyRetComparison, Return.cumulative),3)
           strategy    SPY
2003-12-31    0.052  0.066
2004-12-31    0.074  0.079
2005-12-30    0.045  0.025
2006-12-29    0.182  0.132
2007-12-31    0.117  0.019
2008-12-31   -0.010 -0.433
2009-12-31    0.130  0.192
2010-12-31   -0.005  0.110
2011-12-30    0.069 -0.028
2012-12-31    0.087  0.126
> round(apply.yearly(dailyRetComparison, SharpeRatio.annualized),3)
           strategy    SPY
2003-12-31    1.867  3.641
2004-12-31    1.020  0.706
2005-12-30    0.625  0.238
2006-12-29    2.394  1.312
2007-12-31    1.105  0.123
2008-12-31   -0.376 -1.050
2009-12-31    1.752  0.719
2010-12-31   -0.051  0.614
2011-12-30    0.859 -0.122
2012-12-31    1.201  0.990
> round(apply.yearly(dailyRetComparison, maxDrawdown),3)
           strategy   SPY
2003-12-31    0.018 0.025
2004-12-31    0.065 0.085
2005-12-30    0.053 0.074
2006-12-29    0.074 0.077
2007-12-31    0.066 0.102
2008-12-31    0.032 0.520
2009-12-31    0.045 0.280
2010-12-31    0.084 0.167
2011-12-30    0.053 0.207
2012-12-31    0.050 0.099

Now here’s a new bit of analytics–comparing annualized standard deviations between securities:

> sdQuantile <- quantile(sapply(instRets, sd.annualized))
> sdQuantile
         0%         25%         50%         75%        100% 
0.004048235 0.004349390 0.004476377 0.004748530 0.005557765 
> (extremeRatio <- sdQuantile[5]/sdQuantile[1]-1)
    100% 
0.372886 
> (boxBorderRatio <- sdQuantile[4]/sdQuantile[2]-1)
      75% 
0.0917693 

In short, because the instrument returns are computed as a function of only the initial account equity (quantstrat doesn’t know that I’m “allocating” a notional cash amount to each separate ETF–because I’m really not–I just treat it as one pile of cash that I mentally think of as being divided “equally” between all 30 ETFs), that means that the returns per instrument also have already implicitly factored in the weighting scheme from the order sizing function. In this case, the most volatile instrument is about 37% more volatile than the least — and since I’m dealing with indices of small nations along with short-term treasury bills in ETF form, I’d say that’s impressive.

More impressive, in my opinion, is that the difference in volatility between the 25th and 75th percentile is about 9%. It means that our ATR order sizing seems to be doing its job.Here’s the raw computations in terms of annualized volatility:

> sapply(instRets, sd.annualized)
EFA.DailyEndEq EPP.DailyEndEq EWA.DailyEndEq EWC.DailyEndEq 
   0.004787248    0.005557765    0.004897699    0.004305728 
EWG.DailyEndEq EWH.DailyEndEq EWJ.DailyEndEq EWS.DailyEndEq 
   0.004806879    0.004782505    0.004460708    0.004618460 
EWT.DailyEndEq EWU.DailyEndEq EWY.DailyEndEq EWZ.DailyEndEq 
   0.004417686    0.004655716    0.004888876    0.004858743 
EZU.DailyEndEq IEF.DailyEndEq IGE.DailyEndEq IYR.DailyEndEq 
   0.004631333    0.004779468    0.004617250    0.004359273 
IYZ.DailyEndEq LQD.DailyEndEq RWR.DailyEndEq SHY.DailyEndEq 
   0.004346095    0.004101408    0.004388131    0.004585389 
TLT.DailyEndEq XLB.DailyEndEq XLE.DailyEndEq XLF.DailyEndEq 
   0.004392335    0.004319708    0.004515228    0.004426415 
XLI.DailyEndEq XLK.DailyEndEq XLP.DailyEndEq XLU.DailyEndEq 
   0.004129331    0.004492046    0.004369804    0.004048235 
XLV.DailyEndEq XLY.DailyEndEq 
   0.004148445    0.004203503 

And here’s a histogram of those same calculations:

In this case, the reason that the extreme computation gives us a 37% greater result is that one security, EPP (pacific ex-Japan, which for all intents and purposes is emerging markets) is simply out there a bit. The rest just seem very clumped up.

Now let’s remove the ATR order sizing and replace it with a simple osMaxDollar rule, that simply will keep a position topped off at a notional dollar value. In short, aside from a few possible one-way position rebalancing transactions (E.G. with the ATR order sizing rule, ATR may have gone up whereas total value of a position may have gone down, which may trigger the osMaxDollar rule but not the osDollarATR rule on a second RSI cross) Here’s the new entry rule, with the ATR commented out:

# 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="longEntry", sigval=TRUE, ordertype="market", 
                        orderside="long", replace=FALSE, prefer="Open", osFUN=osMaxDollar,
                        tradeSize=tradeSize, maxSize=tradeSize), 
         type="enter", path.dep=TRUE)

Let’s look at the corresponding statistical results:

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

> SharpeRatio.annualized(portfRets)
                                     [,1]
Annualized Sharpe Ratio (Rf=0%) 0.8529713
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.04857159
> maxDrawdown(portfRets)
[1] 0.06682969
> 
> dailyRetComparison <- cbind(portfRets, SPYrets)
> colnames(dailyRetComparison)  <- c("strategy", "SPY")
> round(apply.yearly(dailyRetComparison, Return.cumulative),3)
           strategy    SPY
2003-12-31    0.034  0.066
2004-12-31    0.055  0.079
2005-12-30    0.047  0.025
2006-12-29    0.090  0.132
2007-12-31    0.065  0.019
2008-12-31   -0.023 -0.433
2009-12-31    0.141  0.192
2010-12-31   -0.010  0.110
2011-12-30    0.038 -0.028
2012-12-31    0.052  0.126
> round(apply.yearly(dailyRetComparison, SharpeRatio.annualized),3)
           strategy    SPY
2003-12-31    1.639  3.641
2004-12-31    1.116  0.706
2005-12-30    0.985  0.238
2006-12-29    1.755  1.312
2007-12-31    0.785  0.123
2008-12-31   -0.856 -1.050
2009-12-31    1.774  0.719
2010-12-31   -0.134  0.614
2011-12-30    0.686 -0.122
2012-12-31    1.182  0.990
> round(apply.yearly(dailyRetComparison, maxDrawdown),3)
           strategy   SPY
2003-12-31    0.015 0.025
2004-12-31    0.035 0.085
2005-12-30    0.033 0.074
2006-12-29    0.058 0.077
2007-12-31    0.058 0.102
2008-12-31    0.036 0.520
2009-12-31    0.043 0.280
2010-12-31    0.062 0.167
2011-12-30    0.038 0.207
2012-12-31    0.035 0.099

And now for the kicker–to see just how much riskier using a naive order-sizing method that doesn’t take into account the different idiosyncratic of a security is:

> sdQuantile <- quantile(sapply(instRets, sd.annualized))
> sdQuantile
          0%          25%          50%          75%         100% 
0.0002952884 0.0026934043 0.0032690492 0.0037727970 0.0061480828 
> (extremeRatio <- sdQuantile[5]/sdQuantile[1]-1)
   100% 
19.8206 
> (boxBorderRatio <- sdQuantile[4]/sdQuantile[2]-1)
     75% 
0.400754 
> hist(sapply(instRets, sd.annualized))

In short, the ratio between the riskiest and least riskiest asset rises from less than 40% to 1900%. But in case, that’s too much of an outlier (E.G. dealing with treasury bill/note/bond ETFs vs. pacific ex-Japan aka emerging Asia), the difference between the third and first quartiles in terms of volatility ratio has jumped from 9% to 40%.

Here’s the corresponding histogram:As can be seen, a visibly higher variance in variances–in other words, a second moment on the second moment–meaning that to not use an order-sizing function that takes into account individual security risk therefore introduces unnecessary kurtosis and heavier tails into the risk/reward ratio, and due to this unnecessary excess risk, performance suffers measurably.Here are the individual security annualized standard deviations for the max dollar order sizing method:

> sapply(instRets, sd.annualized)
EFA.DailyEndEq EPP.DailyEndEq EWA.DailyEndEq EWC.DailyEndEq 
  0.0029895232   0.0037767697   0.0040222015   0.0036137500 
EWG.DailyEndEq EWH.DailyEndEq EWJ.DailyEndEq EWS.DailyEndEq 
  0.0037097070   0.0039615376   0.0030398638   0.0037608791 
EWT.DailyEndEq EWU.DailyEndEq EWY.DailyEndEq EWZ.DailyEndEq 
  0.0041140227   0.0032204771   0.0047719772   0.0061480828 
EZU.DailyEndEq IEF.DailyEndEq IGE.DailyEndEq IYR.DailyEndEq 
  0.0033176214   0.0013059712   0.0041621776   0.0033752435 
IYZ.DailyEndEq LQD.DailyEndEq RWR.DailyEndEq SHY.DailyEndEq 
  0.0026899679   0.0011777797   0.0034789117   0.0002952884 
TLT.DailyEndEq XLB.DailyEndEq XLE.DailyEndEq XLF.DailyEndEq 
  0.0024854557   0.0034895815   0.0043568967   0.0029546665 
XLI.DailyEndEq XLK.DailyEndEq XLP.DailyEndEq XLU.DailyEndEq 
  0.0027963302   0.0028882028   0.0021212224   0.0025802850 
XLV.DailyEndEq XLY.DailyEndEq 
  0.0020399289   0.0027037138

Is ATR order sizing the absolute best order-sizing methodology? Most certainly not.In fact, in the PortfolioAnalytics package (quantstrat’s syntax was modeled from this), there are ways to explicitly penalize the higher order moments and co-moments. However, in this case, ATR order sizing works as a simple yet somewhat effective demonstrator of risk-adjusted order-sizing, while implicitly combating some of the risks in not paying attention to the higher moments of the distributions of returns, and also still remaining fairly close to the shore in terms of ease of explanation to those without heavy quantitative backgrounds. This facilitates marketing to large asset managers that may otherwise be hesitant in investing with a more complex strategy that they may not so easily understand.

Thanks for reading.

VCI — The Value Charts Indicator

So recently, I was made known of the Value Charts Indicator , which was supposed to be some form of alternative to the RSI. I decided to investigate it, and see if it’s worth using.

Before diving into a strategy, here’s how the indicator works:

"VCI" <- function(OHLC, nLookback=40, nRange=8, pctRank=FALSE) {
  if(nLookback > 7) {
    varA <- runMax(Hi(OHLC), nRange) - runMin(Lo(OHLC), nRange)
    varB <- lag(varA, nRange+1)
    varC <- lag(varA, nRange*2)
    varD <- lag(varA, nRange*3)
    varE <- lag(varA, nRange*4)
    LRange <- (varA+varB+varC+varD+varE)/25    
  }
  if(nLookback <=7) {
    absDiff <- abs(diff(Cl(OHLC)))
    dailyRange <- Hi(OHLC) - Lo(OHLC)
    tmp <- cbind(absDiff, dailyRange)
    maxTmp <- pmax(tmp)
    LRange <- SMA(maxTmp, 5)*.16
  }
  hilo <- (Hi(OHLC)+Lo(OHLC))/2
  VO <- (Op(OHLC)-SMA(hilo, nLookback))/LRange
  VH <- (Hi(OHLC)-SMA(hilo, nLookback))/LRange
  VL <- (Lo(OHLC)-SMA(hilo, nLookback))/LRange
  VC <- (Cl(OHLC)-SMA(hilo, nLookback))/LRange
  out <- cbind(VO=VO, VH=VH, VL=VL, VC=VC)
  colnames(out) <- c("VO", "VH", "VL", "VC")
  return(out)
}

Long story short, if the lookback period is 8 bars or more, it is something akin to an average of various five lagged ranges, over five times the specified range. That is, define your first range computation as the difference between the highest high and lowest low, and then average that with that same computation lagged by nRange+1 bars, nRange*2 bars, and so on. At a shorter frame than 8 bars (that is, a special case), the computation is a moving average of the daily maximum between the daily range and the close-to-close range (E.G. with a high of 4 and low of 2, with close of 3 and previous close of 2, that daily value will be equal to 4-2=2), and then take a 5 period SMA of that, and multiply by .16. Although the initial indicator had the range dependent on the lookback period, I chose to decouple it for greater flexibility to the user.

This range calculation is then used as a denominator of a computation that is the difference of the current price minus the SMA value of the average of an (H+L)/2 price proxy. In short, it’s a variation on a theme of the classical z-score from statistics. In other words, (X-Xbar)/(normalizing value).

This z-score is computed for all four price strands.

In my current implementation, I have not yet implemented the functionality for zero-movement bars (though that can be done by request) if anyone sees value with this indicator.

To put this indicator through its paces, I threw about as plain-standard-vanilla strategy around it. The strategy activates upon the close price greater than SMA200 (the “conventional wisdom”), and buys when the indicator crosses under -2 and exits above 2, using a lookback period of 10 days, with a range period of 2 days (the settings the indicator creator(s) had in mind were that -4/+4 was relatively oversold/overbought, with -8/+8 being extremely so). The idea here was to get a bunch of relatively short-term trades going, and use the advantage of large numbers to see how well this indicator performs.

Here’s the strategy code:

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

initDate="1990-01-01"
from="2003-01-01"
to=as.character(Sys.Date())
options(width=70)

source("demoData.R")

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

strategy.st <- portfolio.st <- account.st <- "VCI_test"
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
pctATR=.02
period=10

nRange=2
nLookback=10
pctRank=FALSE

buyThresh=-2
sellThresh=2

nSMA=200

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

add.indicator(strategy.st, name="VCI",
              arguments=list(OHLC=quote(OHLC(mktdata)), nLookback=nLookback,
                             nRange=nRange, pctRank=pctRank),
              label="vci")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),
              label="sma")

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

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="VC.vci", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="VCIltThresh")

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

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="VC.vci", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

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

#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="filterExit", 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.684617
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 69.466
> (numTrades <- sum(tStats$Num.Trades))
[1] 2801
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio[tStats$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 0.753

> print(t(durStats))
      [,1]
Min      1
Q1       5
Med      9
Mean    11
Q3      14
Max     57
WMin     1
WQ1      5
WMed     8
WMean    9
WQ3     12
WMax    41
LMin     1
LQ1      5
LMed    15
LMean   15
LQ3     22
LMax    57

> SharpeRatio.annualized(portfRets)
                                     [,1]
Annualized Sharpe Ratio (Rf=0%) 0.8951308
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.06821319
> maxDrawdown(portfRets)
[1] 0.108064

> round(apply.yearly(dailyRetComparison, Return.cumulative),3)
           strategy    SPY
2003-12-31    0.058  0.066
2004-12-31    0.056  0.079
2005-12-30    0.034  0.025
2006-12-29    0.148  0.132
2007-12-31    0.094  0.019
2008-12-31   -0.022 -0.433
2009-12-31    0.149  0.192
2010-12-31   -0.055  0.110
2011-12-30    0.072 -0.028
2012-12-31    0.072  0.126
2013-12-31    0.057  0.289
2014-08-22    0.143  0.075
> round(apply.yearly(dailyRetComparison, SharpeRatio.annualized),3)
           strategy    SPY
2003-12-31    2.379  3.641
2004-12-31    0.751  0.706
2005-12-30    0.476  0.238
2006-12-29    2.083  1.312
2007-12-31    0.909  0.123
2008-12-31   -0.943 -1.050
2009-12-31    2.023  0.719
2010-12-31   -0.548  0.614
2011-12-30    0.854 -0.122
2012-12-31    1.015  0.990
2013-12-31    0.655  2.594
2014-08-22    2.869  1.137
> round(apply.yearly(dailyRetComparison, maxDrawdown),3)
           strategy   SPY
2003-12-31    0.014 0.025
2004-12-31    0.079 0.085
2005-12-30    0.058 0.074
2006-12-29    0.068 0.077
2007-12-31    0.073 0.102
2008-12-31    0.029 0.520
2009-12-31    0.041 0.280
2010-12-31    0.108 0.167
2011-12-30    0.052 0.207
2012-12-31    0.043 0.099
2013-12-31    0.072 0.062
2014-08-22    0.047 0.058

In short, it has the statistical profile of a standard mean-reverting strategy–lots of winners, losers slightly larger than winners, losers last longer in the market than winners as well. In terms of Sharpe Ratio, it’s solid but not exactly stellar. Overall, the strategy generally sports much better risk control than the raw SPY, but the annualized return to drawdown ratio isn’t quite up to the same level as some strategies tested on this blog in the past.

This is the equity curve comparison.

The equity profile seems to be pretty standard fare–winners happen over time, but a drawdown can wipe some of them (but not all) pretty quickly, as the system continues to make new equity highs. Solid, but not stellar.

Here’s an example of the equity curve of an individual instrument (the usual XLB):

Something to note is that the indicator is fairly choppy, and does best in a strong uptrend, when terms like oversold, pullback, and so on, are actually that, as opposed to a change in trend, or a protracted cyclic downtrend in a sideways market.

Here’s a picture of the strategy on XLB in 2012.

As you can see, the indicator at the 2-bar range isn’t exactly the smoothest, but with proper position-sizing rules (I use position sizing based on a 10-day ATR), the disadvantage of chopping across a threshold can be largely mitigated.

OVerall, while this indicator doesn’t seem to be much better than the more conventional RSIs, it nevertheless seems to be an alternative, and for those that want to use it, it’s now in my IKTrading package.

Thanks for reading.

A Hammer Trading System — Demonstrating Custom Indicator-Based Limit Orders in Quantstrat

So several weeks ago, I decided to listen on a webinar (and myself will be giving one on using quantstrat on Sep. 3 for Big Mike’s Trading, see link). Among some of those talks was a trading system called the “Trend Turn Trade Take Profit” system. This is his system:

Define an uptrend as an SMA10 above an SMA30.
Define a pullback as an SMA5 below an SMA10.

Define a hammer as a candle with an upper shadow less than 20% of the lower shadow, and a body less than 50% of the lower shadow. Enter on the high of the hammer, with the stop loss set at the low of the hammer and an additional one third of the range. The take profit target is 1.5 to 1.7 times the distance between the entry and the stop price.

Additionally (not tested here) was the bullish engulfing pattern, which is a two-bar pattern with the conditions of a down day followed by an up day on which the open of the up day was less than the close of the down day, and the close of the up day was higher than the previous day’s open, with the stop set to the low of the pattern, and the profit target in the same place.

This system was advertised to be correct about 70% of the time, with trades whose wins were 1.6 times as much as the losses, so I decided to investigate it.

The upside to this post, in addition to investigating someone else’s system, is that it will allow me to demonstrate how to create more nuanced orders with quantstrat. The best selling point for quantstrat, in my opinion, is that it provides a framework to do just about anything you want, provided you know how to do it (not trivial). In any case, the salient thing to take from this strategy is that it’s possible to create some interesting custom orders with some nuanced syntax.

Here’s the syntax for this strategy:

hammer <- function(OHLC, profMargin=1.5) {
  dailyMax <- pmax(Op(OHLC), Cl(OHLC))
  dailyMin <- pmin(Op(OHLC), Cl(OHLC))
  upShadow <- Hi(OHLC) - dailyMax
  dnShadow <- dailyMin - Lo(OHLC)
  body <- dailyMax-dailyMin
  hammerDay <- dnShadow/body > 2 & dnShadow/upShadow > 5
  hammers <- OHLC[hammerDay==1,]
  hammers$stopLoss <- 4/3*Lo(hammers)-1/3*Hi(hammers)
  hammers$takeProfit <- Hi(hammers) + (Hi(hammers)-hammers$stopLoss)*profMargin
  hammers <- cbind(hammerDay, hammers$stopLoss, hammers$takeProfit)
  hammers$stopLoss <- na.locf(hammers$stopLoss)
  hammers$takeProfit <- na.locf(hammers$takeProfit)
  colnames(hammers) <- c("hammer", "SL", "TP")
  return(hammers)
}

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

initDate="1990-01-01"
from="2003-01-01"
to=as.character(Sys.Date())
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 <- "Hammer_4TP"
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
nSMA1=10
nSMA2=30
nSMA3=5
profMargin=1.5

period=10
pctATR=.1


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

add.indicator(strategy.st, name="hammer",
              arguments=list(OHLC=quote(OHLC(mktdata)), 
                             profMargin=profMargin),
              label="hammer")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), 
                             n=nSMA1),
              label="sma1")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), 
                             n=nSMA2),
              label="sma2")

add.indicator(strategy.st, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), 
                             n=nSMA3),
              label="sma3")
#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("SMA.sma1", "SMA.sma2"), 
                          relationship="gt"),
           label="upTrend")

add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("SMA.sma3", "SMA.sma1"), 
                          relationship="lt"),
           label="pullback")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="hammer.hammer", threshold=.5, 
                          relationship="gt", cross=TRUE),
           label="hammerDay")

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

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("SMA.sma1", "SMA.sma2"), 
                          relationship="lt"),
           label="SMAexit")
#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        osFUN=osDollarATR,
                        tradeSize=tradeSize, 
                        prefer="High",
                        pctATR=pctATR,
                        atrMod="X",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="hammerEntry")
 
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$SL.hammer[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="hammerEntry",
         label="stopLossLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="limit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$TP.hammer[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="hammerEntry",
         label="takeProfitLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal",
         arguments=list(sigcol="SMAexit",
                        sigval=TRUE,
                        ordertype="market",
                        orderside="long",
                        replace=TRUE,
                        orderqty='all',
                        prefer='Open',
                        orderset='orders'
                        ),
         type='exit',
         label='SMAexitLong',
         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)

I added one additional rule to the strategy in that if the trend reverses (SMA10 < SMA30), to get out of the trade.

First off, let's take a closer look at the entry and exit rules.

#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        osFUN=osDollarATR,
                        tradeSize=tradeSize, 
                        prefer="High",
                        pctATR=pctATR,
                        atrMod="X",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="hammerEntry")
 
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$SL.hammer[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="hammerEntry",
         label="stopLossLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="limit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$TP.hammer[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="hammerEntry",
         label="takeProfitLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal",
         arguments=list(sigcol="SMAexit",
                        sigval=TRUE,
                        ordertype="market",
                        orderside="long",
                        replace=TRUE,
                        orderqty='all',
                        prefer='Open',
                        orderset='orders'
                        ),
         type='exit',
         label='SMAexitLong',
         path.dep=TRUE)

The rules used here use a few new concepts that I haven't used in previous blog posts. First off, the argument of orderset puts all the orders within one order set as a one-canceling-the-other mechanism. Next, the order.price syntax works similarly to the market data syntax on specifying indicators — EG add.indicator(strategy.st, name=”SMA”, arguments=list(x=quote(Cl(mktdata)), etc…), except this time, it specifies a certain column in the market data (which is, in fact, what Cl(mktdata) does, or HLC(mktdata), and so on), but also, the [timestamp] syntax is necessary so it knows what specific quantity in time is being referred to.

For take-profit orders, as you want to sell above the market, or buy below the market, the correct type of order (that is, the ordertype argument) is a limit order. With stop-losses or trailing stops (not shown here), since you want to sell below the market or buy above the market, the correct ordertype is a stoplimit order.

Finally, the rule I added (the SMA exit) actually improves the strategy's performance (I wanted to give this system the benefit of the doubt).

Here are the results, with the strategy leveraged up to .1 pctATR (the usual strategies I test range between .02 and .04):

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

print(t(durStats))
      [,1]
Min      1
Q1       1
Med      4
Mean     5
Q3       7
Max     56
WMin     1
WQ1      2
WMed     4
WMean    6
WQ3      7
WMax    56
LMin     1
LQ1      1
LMed     3
LMean    5
LQ3      6
LMax    42

> print(mktExposure)
   Symbol MktExposure
1     EFA       0.023
2     EPP       0.019
3     EWA       0.026
4     EWC       0.015
5     EWG       0.019
6     EWH       0.023
7     EWJ       0.017
8     EWS       0.024
9     EWT       0.022
10    EWU       0.025
11    EWY        0.02
12    EWZ       0.019
13    EZU       0.023
14    IEF        0.01
15    IGE       0.022
16    IYR        0.02
17    IYZ       0.024
18    LQD       0.022
19    RWR       0.023
20    SHY       0.017
21    TLT       0.007
22    XLB       0.016
23    XLE       0.021
24    XLF       0.012
25    XLI       0.022
26    XLK       0.019
27    XLP       0.023
28    XLU       0.022
29    XLV        0.02
30    XLY       0.018
> print(mean(as.numeric(as.character(mktExposure$MktExposure))))
[1] 0.01976667

> SharpeRatio.annualized(portfRets)
                                    [,1]
Annualized Sharpe Ratio (Rf=0%) 1.027048
> Return.annualized(portfRets)
                        [,1]
Annualized Return 0.06408888
> maxDrawdown(portfRets)
[1] 0.09036151

> round(apply.yearly(dailyRetComparison, Return.cumulative),3)
           strategy    SPY
2003-12-31    0.179  0.369
2004-12-31    0.075  0.079
2005-12-30   -0.036  0.025
2006-12-29    0.143  0.132
2007-12-31    0.121  0.019
2008-12-31   -0.042 -0.433
2009-12-31    0.066  0.192
2010-12-31    0.135  0.110
2011-12-30    0.057 -0.028
2012-12-31    0.039  0.126
2013-12-31   -0.023  0.289
2014-08-06    0.048  0.036
> round(apply.yearly(dailyRetComparison, SharpeRatio.annualized),3)
           strategy    SPY
2003-12-31    2.971  3.100
2004-12-31    1.039  0.706
2005-12-30   -0.774  0.238
2006-12-29    2.355  1.312
2007-12-31    2.024  0.123
2008-12-31   -0.925 -1.050
2009-12-31    1.026  0.719
2010-12-31    2.504  0.614
2011-12-30    0.644 -0.122
2012-12-31    0.640  0.990
2013-12-31   -0.520  2.594
2014-08-06    1.171  0.586
> round(apply.yearly(dailyRetComparison, maxDrawdown),3)
           strategy   SPY
2003-12-31    0.030 0.056
2004-12-31    0.058 0.085
2005-12-30    0.046 0.074
2006-12-29    0.035 0.077
2007-12-31    0.039 0.102
2008-12-31    0.061 0.520
2009-12-31    0.044 0.280
2010-12-31    0.029 0.167
2011-12-30    0.069 0.207
2012-12-31    0.057 0.099
2013-12-31    0.071 0.062
2014-08-06    0.032 0.058

In short, looking at the trade stats, this system is…far from what was advertised. In fact, here's the equity curve.

Anything but spectacular the past several years, which is why I suppose it was free to give it away in a webinar. Overall, however, the past several years have just seen the S&P just continue to catch up to this strategy. At the end of the day, it’s a highly unimpressive system in my opinion, and I won’t be exploring the other aspects of it further. However, as an exercise in showing some nuanced features of quantstrat, I think this was a worthwhile endeavor.

Thanks for reading.

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.

FRAMA Part II: Replicating A Simple Strategy

This post will begin the investigation into FRAMA strategies, with the aim of ultimately finding a FRAMA trading strategy with less market exposure, fewer whipsaw trades, and fewer counter-trend trades. This post will also introduce new analytics regarding trade duration.

To begin the investigation into developing strategies based on the previously-introduced FRAMA, I’m going to replicate the simple strategy from ETFHQ — use a 126 day FRAMA with a fast constant of 4 (that is, an EMA that goes as fast as a 4-day EMA), and all the way up to a slow constant of 300. For my ATR order-sizing, which, once again, was inspired by Andreas Clenow in the post on leverage being pointless, I’m going to use 2 percent of notional capital, with a 10 day ATR for my order sizing (ATR 20 and 30 display slightly weaker results, but nevertheless, are very close in performance).

Once again, let’s start by looking at the strategy, using our same 30 instruments as with our TVI demos (I thought about testing on mutual funds, but due to the obnoxious fees that mutual funds charge for trying to trade with them, I feel that I’d have to employ too much magical thinking to neglect their obscene trading transaction costs):

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

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_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=4
SC=300
n=126
triggerLag=1
pctATR=.02
period=10

#indicators

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

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


#signals

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "FRAMA.frama"), relationship="gte"),
           label="longEntry")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "FRAMA.frama"), relationship="lt"),
           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)

It’s a fairly simple strategy–buy the next day’s open when the price crosses above the indicator, and vice versa. In other words, it’s about as simple a strategy as you can get as its sole purpose was to demonstrate the effectiveness of the indicator. And while someone interested can peruse through ETFHQ to find all of the indicator relative tests, the general gist is that adaptive moving averages work very well, and the FRAMA (the fractal adaptive moving average) works slightly better than the rest. Ultimately though, if one believes ETFHQ’s analysis (and I do), then even one good trend-following indicator will be sufficient.

Here are the trade statistics:

                         EFA       EPP       EWA      EWC       EWG
Num.Txns              289.00    245.00    281.00   219.00    248.00
Num.Trades            145.00    123.00    140.00   109.00    121.00
Net.Trading.PL       9233.11  17020.32  14917.84 14298.85  13603.78
Avg.Trade.PL           63.68    138.38    106.56   131.18    112.43
Med.Trade.PL          -46.29    -26.79    -24.81   -21.06    -28.14
Largest.Winner       2454.58   4499.10   3301.82  4031.95   3582.43
Largest.Loser        -523.15   -847.71   -607.41  -613.76   -531.39
Gross.Profits       23874.49  29202.05  28060.29 23541.10  24820.41
Gross.Losses       -14641.38 -12181.73 -13142.45 -9242.26 -11216.63
Std.Dev.Trade.PL      420.81    624.11    518.71   565.75    565.31
Percent.Positive       42.07     46.34     46.43    46.79     46.28
Percent.Negative       57.93     53.66     53.57    53.21     53.72
Profit.Factor           1.63      2.40      2.14     2.55      2.21
Avg.Win.Trade         391.39    512.32    431.70   461.59    443.22
Med.Win.Trade         199.18    238.18    220.98   236.81    179.68
Avg.Losing.Trade     -174.30   -184.57   -175.23  -159.35   -172.56
Med.Losing.Trade     -152.31   -121.13   -159.74  -133.38   -128.70
Avg.Daily.PL           63.07    137.42    105.72   128.59    112.43
Med.Daily.PL          -49.26    -29.12    -27.68   -22.15    -28.14
Std.Dev.Daily.PL      422.22    626.59    520.49   567.74    565.31
Ann.Sharpe              2.37      3.48      3.22     3.60      3.16
Max.Drawdown        -2338.49  -1945.85  -3026.93 -2001.41  -2047.83
Profit.To.Max.Draw      3.95      8.75      4.93     7.14      6.64
Avg.WinLoss.Ratio       2.25      2.78      2.46     2.90      2.57
Med.WinLoss.Ratio       1.31      1.97      1.38     1.78      1.40
Max.Equity           9902.70  17058.92  14996.93 14298.85  14604.43
Min.Equity           -136.30   -189.12   -159.59  -233.07   -315.54
End.Equity           9233.11  17020.32  14917.84 14298.85  13603.78

                         EWH       EWJ       EWS      EWT      EWU
Num.Txns              271.00    243.00    275.00   199.00   227.00
Num.Trades            135.00    119.00    133.00    96.00   111.00
Net.Trading.PL      11548.79   5965.14  12676.51 12221.36 12044.63
Avg.Trade.PL           85.55     50.13     95.31   127.31   108.51
Med.Trade.PL          -27.05    -26.14    -12.78    14.04    -7.08
Largest.Winner       2668.92   1872.67   2773.40  2749.00  2521.64
Largest.Loser        -572.88   -521.96   -593.95  -537.06  -603.96
Gross.Profits       23173.00  17577.09  23801.66 19413.97 21927.72
Gross.Losses       -11624.21 -11611.96 -11125.16 -7192.61 -9883.09
Std.Dev.Trade.PL      501.77    395.06    470.11   506.25   512.20
Percent.Positive       44.44     43.70     46.62    53.12    49.55
Percent.Negative       55.56     56.30     53.38    46.88    50.45
Profit.Factor           1.99      1.51      2.14     2.70     2.22
Avg.Win.Trade         386.22    338.02    383.90   380.67   398.69
Med.Win.Trade         125.17    140.16    212.49   172.84   195.04
Avg.Losing.Trade     -154.99   -173.31   -156.69  -159.84  -176.48
Med.Losing.Trade     -111.58   -146.64   -115.28  -138.75  -158.23
Avg.Daily.PL           86.78     38.81     94.45   108.42   108.20
Med.Daily.PL          -25.57    -32.55    -17.73    12.24    -7.80
Std.Dev.Daily.PL      503.45    376.87    471.79   473.73   514.53
Ann.Sharpe              2.74      1.63      3.18     3.63     3.34
Max.Drawdown        -2298.32  -3445.56  -2017.06 -2764.34 -2071.61
Profit.To.Max.Draw      5.02      1.73      6.28     4.42     5.81
Avg.WinLoss.Ratio       2.49      1.95      2.45     2.38     2.26
Med.WinLoss.Ratio       1.12      0.96      1.84     1.25     1.23
Max.Equity          12708.66   6336.41  13177.76 12221.36 12640.57
Min.Equity           -272.68   -307.36   -331.04     0.00   -57.47
End.Equity          11548.79   5965.14  12676.51 12221.36 12044.63

                         EWY       EWZ       EZU      IEF       IGE
Num.Txns              261.00    265.00    270.00   216.00    249.00
Num.Trades            130.00    133.00    134.00   107.00    125.00
Net.Trading.PL      10513.27  14496.80  11233.90 13370.63  12428.65
Avg.Trade.PL           80.87    109.00     83.84   124.96     99.43
Med.Trade.PL           21.32    -42.35    -34.59   -41.49    -93.07
Largest.Winner       1550.67   2633.38   3163.76  2799.11   3710.24
Largest.Loser        -614.98   -682.75   -553.54  -542.02   -576.53
Gross.Profits       23615.06  28466.25  25227.96 22877.10  26829.04
Gross.Losses       -13101.78 -13969.45 -13994.06 -9506.47 -14400.39
Std.Dev.Trade.PL      417.96    548.34    538.74   546.44    660.65
Percent.Positive       50.77     43.61     42.54    42.06     32.80
Percent.Negative       49.23     56.39     57.46    57.94     67.20
Profit.Factor           1.80      2.04      1.80     2.41      1.86
Avg.Win.Trade         357.80    490.80    442.60   508.38    654.37
Med.Win.Trade         207.28    210.90    194.52   221.16    367.42
Avg.Losing.Trade     -204.72   -186.26   -181.74  -153.33   -171.43
Med.Losing.Trade     -187.43   -163.60   -144.77  -129.05   -154.06
Avg.Daily.PL           77.86    105.49     83.84   124.96     77.08
Med.Daily.PL           19.08    -44.57    -34.59   -41.49    -93.37
Std.Dev.Daily.PL      418.17    548.93    538.74   546.44    614.05
Ann.Sharpe              2.96      3.05      2.47     3.63      1.99
Max.Drawdown        -2462.61  -2188.41  -2310.88 -2650.59  -3045.12
Profit.To.Max.Draw      4.27      6.62      4.86     5.04      4.08
Avg.WinLoss.Ratio       1.75      2.64      2.44     3.32      3.82
Med.WinLoss.Ratio       1.11      1.29      1.34     1.71      2.38
Max.Equity          10513.27  14760.11  12629.07 14339.37  12428.65
Min.Equity           -339.01   -214.04   -203.63  -278.08   -268.60
End.Equity          10513.27  14496.80  11233.90 13370.63  12428.65

                         IYR       IYZ      LQD       RWR       SHY
Num.Txns              253.00    257.00   222.00    267.00    300.00
Num.Trades            125.00    127.00   108.00    133.00    143.00
Net.Trading.PL       9702.92   8599.09 13277.24  12090.68  15021.96
Avg.Trade.PL           77.62     67.71   122.94     90.91    105.05
Med.Trade.PL          -60.97    -47.00   -17.31    -63.38    -41.73
Largest.Winner       2602.17   2511.55  2526.23   2810.42   1987.06
Largest.Loser        -864.63   -619.97  -339.49   -758.94   -971.00
Gross.Profits       22264.43  20356.47 20777.57  25087.09  30006.10
Gross.Losses       -12561.51 -11757.38 -7500.32 -12996.41 -14984.14
Std.Dev.Trade.PL      488.33    426.35   486.90    528.23    499.11
Percent.Positive       36.80     43.31    46.30     39.10     44.06
Percent.Negative       63.20     56.69    53.70     60.90     55.94
Profit.Factor           1.77      1.73     2.77      1.93      2.00
Avg.Win.Trade         484.01    370.12   415.55    482.44    476.29
Med.Win.Trade         313.81    182.80   139.12    228.24    229.73
Avg.Losing.Trade     -159.01   -163.30  -129.32   -160.45   -187.30
Med.Losing.Trade     -126.56   -112.84  -103.66   -147.59   -145.26
Avg.Daily.PL           75.21     66.83   122.94     88.59    105.05
Med.Daily.PL          -61.69    -50.50   -17.31    -65.60    -41.73
Std.Dev.Daily.PL      489.56    427.93   486.90    529.57    499.11
Ann.Sharpe              2.44      2.48     4.01      2.66      3.34
Max.Drawdown        -5488.96  -2253.39 -1261.10  -5062.88  -2852.91
Profit.To.Max.Draw      1.77      3.82    10.53      2.39      5.27
Avg.WinLoss.Ratio       3.04      2.27     3.21      3.01      2.54
Med.WinLoss.Ratio       2.48      1.62     1.34      1.55      1.58
Max.Equity          13313.44   9225.39 14084.98  15192.88  16570.24
Min.Equity           -152.91   -522.27  -347.48   -394.76  -1036.81
End.Equity           9702.92   8599.09 13277.24  12090.68  15021.96

                         TLT       XLB       XLE       XLF       XLI
Num.Txns              238.00    247.00    229.00    245.00    261.00
Num.Trades            118.00    122.00    115.00    121.00    130.00
Net.Trading.PL       7117.62   6227.48  10335.40     -8.45   6351.34
Avg.Trade.PL           60.32     51.04     89.87     -0.07     48.86
Med.Trade.PL          -71.33    -62.80    -65.87    -56.76    -45.18
Largest.Winner       2548.77   2083.24   2407.81   1895.31   1571.81
Largest.Loser        -509.75   -544.43   -484.90   -626.99   -465.20
Gross.Profits       20099.36  19500.08  21827.84  12869.00  17364.07
Gross.Losses       -12981.73 -13272.60 -11492.44 -12877.45 -11012.73
Std.Dev.Trade.PL      506.64    421.98    566.48    345.43    379.26
Percent.Positive       31.36     36.89     34.78     35.54     41.54
Percent.Negative       68.64     63.11     65.22     64.46     58.46
Profit.Factor           1.55      1.47      1.90      1.00      1.58
Avg.Win.Trade         543.23    433.34    545.70    299.28    321.56
Med.Win.Trade         230.04    248.59    241.31    153.68    129.61
Avg.Losing.Trade     -160.27   -172.37   -153.23   -165.10   -144.90
Med.Losing.Trade     -130.27   -159.19   -148.44   -134.74   -131.29
Avg.Daily.PL           60.32     47.96     68.35     -8.00     34.56
Med.Daily.PL          -71.33    -65.49    -66.25    -58.07    -46.28
Std.Dev.Daily.PL      506.64    422.35    519.59    335.64    343.76
Ann.Sharpe              1.89      1.80      2.09     -0.38      1.60
Max.Drawdown        -4938.54  -3692.51  -2780.65  -5704.14  -3013.95
Profit.To.Max.Draw      1.44      1.69      3.72      0.00      2.11
Avg.WinLoss.Ratio       3.39      2.51      3.56      1.81      2.22
Med.WinLoss.Ratio       1.77      1.56      1.63      1.14      0.99
Max.Equity           9318.18   6780.72  10335.40   4191.68   6357.94
Min.Equity           -693.51   -213.25   -557.67  -1512.46   -795.23
End.Equity           7117.62   6227.48  10335.40     -8.45   6351.34

                         XLK       XLP       XLU       XLV       XLY
Num.Txns              254.00    280.00    241.00    218.00    220.00
Num.Trades            127.00    137.00    121.00    107.00    109.00
Net.Trading.PL       2940.29   2543.61   7904.42   3189.25   6727.03
Avg.Trade.PL           23.15     18.57     65.33     29.81     61.72
Med.Trade.PL          -72.35    -85.92    -38.84    -88.65    -71.80
Largest.Winner       2050.23   1782.84   1240.83   1927.28   2667.81
Largest.Loser        -749.16   -679.39   -598.65   -662.12   -559.61
Gross.Profits       16321.02  17361.88  18269.97  14178.93  18237.47
Gross.Losses       -13380.73 -14818.27 -10365.55 -10989.67 -11510.44
Std.Dev.Trade.PL      389.23    363.63    363.21    418.10    525.74
Percent.Positive       35.43     32.12     43.80     28.97     39.45
Percent.Negative       64.57     67.88     56.20     71.03     60.55
Profit.Factor           1.22      1.17      1.76      1.29      1.58
Avg.Win.Trade         362.69    394.59    344.72    457.38    424.13
Med.Win.Trade         158.36    236.63    180.99    238.62    159.38
Avg.Losing.Trade     -163.18   -159.34   -152.43   -144.60   -174.40
Med.Losing.Trade     -135.65   -131.38   -124.59   -122.90   -146.41
Avg.Daily.PL           23.15     18.57     63.70     29.81     61.72
Med.Daily.PL          -72.35    -85.92    -41.32    -88.65    -71.80
Std.Dev.Daily.PL      389.23    363.63    364.29    418.10    525.74
Ann.Sharpe              0.94      0.81      2.78      1.13      1.86
Max.Drawdown        -3074.10  -4531.59  -2545.84  -4623.78  -4041.34
Profit.To.Max.Draw      0.96      0.56      3.10      0.69      1.66
Avg.WinLoss.Ratio       2.22      2.48      2.26      3.16      2.43
Med.WinLoss.Ratio       1.17      1.80      1.45      1.94      1.09
Max.Equity           2981.10   2993.23   8632.88   3902.68   6838.89
Min.Equity          -1716.68  -1538.36   -165.00  -1213.50   -310.63
End.Equity           2940.29   2543.61   7904.42   3189.25   6727.03

And the aggregate trade statistics:

> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
[1] 1.828178
> (aggCorrect <- mean(tStats$Percent.Positive))
[1] 41.55233
> (numTrades <- sum(tStats$Num.Trades))
[1] 3704
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
[1] 2.619

Far from spectacular. Less than 50% hit rate, the profit factor definitely indicates that there is massive room for improvement as well.

Here are the daily statistics:

                         EFA       EPP       EWA       EWC       EWG
Total.Net.Profit     9233.11  17020.32  14917.84  14298.85  13603.78
Total.Days           1280.00   1330.00   1330.00   1296.00   1264.00
Winning.Days          690.00    729.00    725.00    719.00    702.00
Losing.Days           590.00    601.00    605.00    577.00    562.00
Avg.Day.PL              7.21     12.80     11.22     11.03     10.76
Med.Day.PL             12.85     16.84     17.06     18.59     21.33
Largest.Winner        725.53    521.47    428.25    648.73    510.14
Largest.Loser        -973.81  -1162.04   -722.38   -505.11   -841.33
Gross.Profits       72427.12  85143.30  80490.48  70817.88  75036.35
Gross.Losses       -63194.01 -68122.98 -65572.64 -56519.03 -61432.57
Std.Dev.Daily.PL      139.51    153.35    140.28    125.46    141.11
Percent.Positive       53.91     54.81     54.51     55.48     55.54
Percent.Negative       46.09     45.19     45.49     44.52     44.46
Profit.Factor           1.15      1.25      1.23      1.25      1.22
Avg.Win.Day           104.97    116.79    111.02     98.49    106.89
Med.Win.Day            83.48     95.05     90.88     83.65     84.31
Avg.Losing.Day       -107.11   -113.35   -108.38    -97.95   -109.31
Med.Losing.Day        -81.61    -83.94    -84.18    -72.76    -84.31
Avg.Daily.PL            7.21     12.80     11.22     11.03     10.76
Med.Daily.PL           12.85     16.84     17.06     18.59     21.33
Std.Dev.Daily.PL.1    139.51    153.35    140.28    125.46    141.11
Ann.Sharpe              0.82      1.32      1.27      1.40      1.21
Max.Drawdown        -2338.49  -1945.85  -3026.93  -2001.41  -2047.83
Profit.To.Max.Draw      3.95      8.75      4.93      7.14      6.64
Avg.WinLoss.Ratio       0.98      1.03      1.02      1.01      0.98
Med.WinLoss.Ratio       1.02      1.13      1.08      1.15      1.00
Max.Equity           9902.70  17058.92  14996.93  14298.85  14604.43
Min.Equity           -136.30   -189.12   -159.59   -233.07   -315.54
End.Equity           9233.11  17020.32  14917.84  14298.85  13603.78

                         EWH       EWJ       EWS       EWT       EWU
Total.Net.Profit    11548.79   5965.14  12676.51  12221.36  12044.63
Total.Days           1221.00   1130.00   1300.00   1138.00   1252.00
Winning.Days          644.00    588.00    712.00    610.00    689.00
Losing.Days           577.00    542.00    588.00    528.00    563.00
Avg.Day.PL              9.46      5.28      9.75     10.74      9.62
Med.Day.PL             11.07     13.74     17.01     18.92     18.71
Largest.Winner        456.34    640.14    578.24    780.22    596.56
Largest.Loser        -495.32   -496.23  -1058.32   -835.42   -812.47
Gross.Profits       71689.04  67809.75  70413.47  71565.95  71295.50
Gross.Losses       -60140.25 -61844.61 -57736.96 -59344.59 -59250.87
Std.Dev.Daily.PL      138.92    148.13    130.19    150.04    132.82
Percent.Positive       52.74     52.04     54.77     53.60     55.03
Percent.Negative       47.26     47.96     45.23     46.40     44.97
Profit.Factor           1.19      1.10      1.22      1.21      1.20
Avg.Win.Day           111.32    115.32     98.90    117.32    103.48
Med.Win.Day            89.52     94.38     79.32     95.13     87.31
Avg.Losing.Day       -104.23   -114.10    -98.19   -112.40   -105.24
Med.Losing.Day        -81.34    -92.34    -75.96    -85.69    -82.84
Avg.Daily.PL            9.46      5.28      9.75     10.74      9.62
Med.Daily.PL           11.07     13.74     17.01     18.92     18.71
Std.Dev.Daily.PL.1    138.92    148.13    130.19    150.04    132.82
Ann.Sharpe              1.08      0.57      1.19      1.14      1.15
Max.Drawdown        -2298.32  -3445.56  -2017.06  -2764.34  -2071.61
Profit.To.Max.Draw      5.02      1.73      6.28      4.42      5.81
Avg.WinLoss.Ratio       1.07      1.01      1.01      1.04      0.98
Med.WinLoss.Ratio       1.10      1.02      1.04      1.11      1.05
Max.Equity          12708.66   6336.41  13177.76  12221.36  12640.57
Min.Equity           -272.68   -307.36   -331.04      0.00    -57.47
End.Equity          11548.79   5965.14  12676.51  12221.36  12044.63

                         EWY       EWZ       EZU       IEF       IGE
Total.Net.Profit    10513.27  14496.80  11233.90  13370.63  12428.65
Total.Days           1297.00   1351.00   1282.00   1186.00   1362.00
Winning.Days          714.00    743.00    705.00    628.00    730.00
Losing.Days           583.00    608.00    577.00    558.00    632.00
Avg.Day.PL              8.11     10.73      8.76     11.27      9.13
Med.Day.PL             15.39     16.83     16.09     13.04     12.81
Largest.Winner        461.13    661.59    726.99    660.63    666.40
Largest.Loser        -819.21   -837.40  -1254.82   -555.37   -643.72
Gross.Profits       77069.26  77329.78  74286.39  79557.53  81524.20
Gross.Losses       -66555.99 -62832.98 -63052.49 -66186.90 -69095.55
Std.Dev.Daily.PL      146.05    135.63    142.76    158.58    142.55
Percent.Positive       55.05     55.00     54.99     52.95     53.60
Percent.Negative       44.95     45.00     45.01     47.05     46.40
Profit.Factor           1.16      1.23      1.18      1.20      1.18
Avg.Win.Day           107.94    104.08    105.37    126.68    111.68
Med.Win.Day            88.89     81.86     84.23     99.02     92.92
Avg.Losing.Day       -114.16   -103.34   -109.28   -118.61   -109.33
Med.Losing.Day        -80.09    -78.39    -82.94    -94.81    -90.22
Avg.Daily.PL            8.11     10.73      8.76     11.27      9.13
Med.Daily.PL           15.39     16.83     16.09     13.04     12.81
Std.Dev.Daily.PL.1    146.05    135.63    142.76    158.58    142.55
Ann.Sharpe              0.88      1.26      0.97      1.13      1.02
Max.Drawdown        -2462.61  -2188.41  -2310.88  -2650.59  -3045.12
Profit.To.Max.Draw      4.27      6.62      4.86      5.04      4.08
Avg.WinLoss.Ratio       0.95      1.01      0.96      1.07      1.02
Med.WinLoss.Ratio       1.11      1.04      1.02      1.04      1.03
Max.Equity          10513.27  14760.11  12629.07  14339.37  12428.65
Min.Equity           -339.01   -214.04   -203.63   -278.08   -268.60
End.Equity          10513.27  14496.80  11233.90  13370.63  12428.65

                         IYR       IYZ       LQD       RWR       SHY
Total.Net.Profit     9702.92   8599.09  13277.24  12090.68  15021.96
Total.Days           1272.00   1232.00   1231.00   1306.00   1350.00
Winning.Days          689.00    656.00    687.00    708.00    738.00
Losing.Days           583.00    576.00    544.00    598.00    612.00
Avg.Day.PL              7.63      6.98     10.79      9.26     11.13
Med.Day.PL             12.50     11.72     14.81     11.46     20.75
Largest.Winner        550.83    601.69   1475.28    648.22    591.25
Largest.Loser        -749.58   -803.69   -724.79   -960.12   -988.98
Gross.Profits       68544.80  62158.11  60933.53  74573.82  80464.45
Gross.Losses       -58841.88 -53559.03 -47656.28 -62483.14 -65442.49
Std.Dev.Daily.PL      131.85    123.46    119.48    139.10    140.36
Percent.Positive       54.17     53.25     55.81     54.21     54.67
Percent.Negative       45.83     46.75     44.19     45.79     45.33
Profit.Factor           1.16      1.16      1.28      1.19      1.23
Avg.Win.Day            99.48     94.75     88.70    105.33    109.03
Med.Win.Day            82.65     72.40     70.23     83.85     88.76
Avg.Losing.Day       -100.93    -92.98    -87.60   -104.49   -106.93
Med.Losing.Day        -72.35    -70.97    -69.84    -78.11    -88.06
Avg.Daily.PL            7.63      6.98     10.79      9.26     11.13
Med.Daily.PL           12.50     11.72     14.81     11.46     20.75
Std.Dev.Daily.PL.1    131.85    123.46    119.48    139.10    140.36
Ann.Sharpe              0.92      0.90      1.43      1.06      1.26
Max.Drawdown        -5488.96  -2253.39  -1261.10  -5062.88  -2852.91
Profit.To.Max.Draw      1.77      3.82     10.53      2.39      5.27
Avg.WinLoss.Ratio       0.99      1.02      1.01      1.01      1.02
Med.WinLoss.Ratio       1.14      1.02      1.01      1.07      1.01
Max.Equity          13313.44   9225.39  14084.98  15192.88  16570.24
Min.Equity           -152.91   -522.27   -347.48   -394.76  -1036.81
End.Equity           9702.92   8599.09  13277.24  12090.68  15021.96

                         TLT       XLB       XLE       XLF       XLI
Total.Net.Profit     7117.62   6227.48  10335.40     -8.45   6351.34
Total.Days           1126.00   1252.00   1336.00   1208.00   1291.00
Winning.Days          576.00    679.00    732.00    626.00    694.00
Losing.Days           550.00    573.00    604.00    582.00    597.00
Avg.Day.PL              6.32      4.97      7.74     -0.01      4.92
Med.Day.PL              7.01     12.34     15.29      6.50     11.06
Largest.Winner        706.10    586.14    525.30    425.95    479.01
Largest.Loser        -609.88   -815.74   -501.16   -446.61   -384.75
Gross.Profits       70845.43  64778.65  73739.30  55919.96  61941.72
Gross.Losses       -63727.80 -58551.17 -63403.90 -55928.41 -55590.37
Std.Dev.Daily.PL      158.57    130.49    131.97    122.51    116.99
Percent.Positive       51.15     54.23     54.79     51.82     53.76
Percent.Negative       48.85     45.77     45.21     48.18     46.24
Profit.Factor           1.11      1.11      1.16      1.00      1.11
Avg.Win.Day           123.00     95.40    100.74     89.33     89.25
Med.Win.Day            93.07     72.97     79.85     66.21     73.41
Avg.Losing.Day       -115.87   -102.18   -104.97    -96.10    -93.12
Med.Losing.Day        -89.17    -77.13    -79.13    -69.41    -72.87
Avg.Daily.PL            6.32      4.97      7.74     -0.01      4.92
Med.Daily.PL            7.01     12.34     15.29      6.50     11.06
Std.Dev.Daily.PL.1    158.57    130.49    131.97    122.51    116.99
Ann.Sharpe              0.63      0.61      0.93      0.00      0.67
Max.Drawdown        -4938.54  -3692.51  -2780.65  -5704.14  -3013.95
Profit.To.Max.Draw      1.44      1.69      3.72      0.00      2.11
Avg.WinLoss.Ratio       1.06      0.93      0.96      0.93      0.96
Med.WinLoss.Ratio       1.04      0.95      1.01      0.95      1.01
Max.Equity           9318.18   6780.72  10335.40   4191.68   6357.94
Min.Equity           -693.51   -213.25   -557.67  -1512.46   -795.23
End.Equity           7117.62   6227.48  10335.40     -8.45   6351.34

                         XLK       XLP       XLU       XLV       XLY
Total.Net.Profit     2940.29   2543.61   7904.42   3189.25   6727.03
Total.Days           1210.00   1326.00   1322.00   1153.00   1179.00
Winning.Days          664.00    704.00    710.00    593.00    611.00
Losing.Days           546.00    622.00    612.00    560.00    568.00
Avg.Day.PL              2.43      1.92      5.98      2.77      5.71
Med.Day.PL             14.64     11.03     12.12      5.31      8.48
Largest.Winner        437.09    537.52    546.32    453.96    450.42
Largest.Loser        -765.80   -750.98   -855.10   -726.49   -447.69
Gross.Profits       57610.32  62334.98  61795.49  53165.92  58852.52
Gross.Losses       -54670.02 -59791.38 -53891.07 -49976.67 -52125.49
Std.Dev.Daily.PL      122.10    118.49    115.44    117.37    120.29
Percent.Positive       54.88     53.09     53.71     51.43     51.82
Percent.Negative       45.12     46.91     46.29     48.57     48.18
Profit.Factor           1.05      1.04      1.15      1.06      1.13
Avg.Win.Day            86.76     88.54     87.04     89.66     96.32
Med.Win.Day            71.15     73.84     70.10     72.61     78.75
Avg.Losing.Day       -100.13    -96.13    -88.06    -89.24    -91.77
Med.Losing.Day        -72.57    -74.82    -65.52    -71.56    -70.88
Avg.Daily.PL            2.43      1.92      5.98      2.77      5.71
Med.Daily.PL           14.64     11.03     12.12      5.31      8.48
Std.Dev.Daily.PL.1    122.10    118.49    115.44    117.37    120.29
Ann.Sharpe              0.32      0.26      0.82      0.37      0.75
Max.Drawdown        -3074.10  -4531.59  -2545.84  -4623.78  -4041.34
Profit.To.Max.Draw      0.96      0.56      3.10      0.69      1.66
Avg.WinLoss.Ratio       0.87      0.92      0.99      1.00      1.05
Med.WinLoss.Ratio       0.98      0.99      1.07      1.01      1.11
Max.Equity           2981.10   2993.23   8632.88   3902.68   6838.89
Min.Equity          -1716.68  -1538.36   -165.00  -1213.50   -310.63
End.Equity           2940.29   2543.61   7904.42   3189.25   6727.03

Now, I’d like to introduce some new analytics found in my IKTrading package–namely, trade duration statistics. This is a simple little function that breaks down the trades by duration, in aggregate, winners, and losers, using the usual five-number summary and the mean time. It’s programmed under the assumption that the units are days. Once you start drilling into more frequent trading, one would likely need a bit more refined analysis. However, as most freely available data occurs at the daily frequency, this function should be sufficient for most analyses.

Here is the input and output for this strategy:

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   2   2   3   2   2   2   2   2   2   2   3   2   2   2   3
Med     6   6   5   7   6   4   5   6   6   6   6   6   6   4   6   6
Mean   12  14  13  16  14  12  12  13  16  15  13  13  12  15  14  13
Q3     14  15  14  17  13  12  11  14  18  14  15  14  13  17  14  12
Max   119 132 135 122 126 185 130 139 106 132 105  95 131 144 125 128
WMin    1   1   1   1   1   1   1   1   1   1   1   1   1   1   3   1
WQ1     9   7   3   8   3   2   2   6   3   6   5   7   5   6   9   7
WMed   14  14  14  18  12  11   8  13  14  14  13  14  13  17  19  15
WMean  22  25  22  28  24  21  20  23  24  27  21  24  23  28  30  28
WQ3    20  28  26  34  25  20  25  23  38  28  27  37  22  38  42  33
WMax  119 132 135 122 126 185 130 139 106 132 105  95 131 144 125 128
LMin    1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
LQ1     1   1   1   2   1   2   2   1   1   1   1   1   1   1   1   2
LMed    3   4   3   4   3   3   3   3   3   3   3   3   3   3   3   4
LMean   4   5   5   6   5   5   6   5   7   4   5   5   5   5   6   5
LQ3     5   7   6   7   7   6   8   6   6   6   6   6   6   5   6   6
LMax   30  22  22  34  28  30  25  41  64  17  28  43  22  40  62  40

      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      3   1   2   2   2   3   2   2   2   2   2   3   2   3
Med     6   4   5   6   5   7   6   5   7   6   4   7   5   8
Mean   13  15  13  12  12  14  15  13  12  13  13  15  14  15
Q3     13  14  12  14  12  15  16  15  13  13  13  14  13  14
Max   115 151 128 109  90 105 113 130  99 116 117 121 132 123
WMin    1   1   1   1   1   6   1   1   1   1   1   1   1   1
WQ1     6   3   7   6   9  12   9   9   6   6   5   7   9  11
WMed   13  13  14  14  21  16  19  17  12  14  17  16  15  14
WMean  23  27  27  22  29  28  31  27  21  27  29  27  35  28
WQ3    34  28  27  28  43  34  48  32  20  38  45  40  44  28
WMax  115 151 128 109  90 105 113 130  99 116 117 121 132 123
LMin    1   1   1   1   1   1   1   1   1   1   1   1   1   1
LQ1     2   1   1   2   1   1   1   2   1   1   1   1   1   2
LMed    4   2   3   4   3   3   3   3   4   4   4   4   4   4
LMean   5   5   4   5   5   6   6   6   7   5   6   5   6   6
LQ3     6   5   6   6   6   7   7   7   8   7   7   7   7   8
LMax   33  25  32  42  31  36  38  41  36  19  41  39  69  35

From top to bottom in this transposed table (or left to right in the original), we have aggregate trade duration statistics, the same statistics on winners, and finally, losers. This paints a picture of this current strategy as having a profile of a classic trend follower: let your winners run, and cut your losses. Or, to set it to a higher standard, the occasional winner at the price of plenty of small whipsaws, with the occasional long-duration loser. Note that this loser may not have been a loser the entire time–it could very well have been a trade that was going sideways for a long time and finally sunk into negative territory near the end, but it seems that every instrument has had at least one somewhat long-running trade that wound up losing at least a penny.

On the winning side of the trade, the winning trades stay in the market for long berths, with the best ones riding waves that last for around half a year. Ultimately, this is the appeal of trend following–the idea of just getting in once, and just having the market pay you. The goal of investigating the FRAMA (and potentially other trend indicators), is to try and locate those long profit waves while avoiding the whipsaws, countertrends, and so on.

One other interesting piece of analytics I’m incorporating into this demo is the idea of market exposure–or what percentage of the time that the strategy is actually *in* the market. Here is the code and the results:

#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)

Essentially, this little piece of code takes advantage of the daily statistics output to compute market exposure. Here’s the output:

   Symbol MktExposure
1     EFA       0.635
2     EPP        0.66
3     EWA        0.66
4     EWC       0.643
5     EWG       0.627
6     EWH       0.606
7     EWJ       0.561
8     EWS       0.645
9     EWT       0.565
10    EWU       0.621
11    EWY       0.644
12    EWZ        0.67
13    EZU       0.636
14    IEF       0.589
15    IGE       0.676
16    IYR       0.631
17    IYZ       0.611
18    LQD       0.611
19    RWR       0.648
20    SHY        0.67
21    TLT       0.559
22    XLB       0.621
23    XLE       0.663
24    XLF         0.6
25    XLI       0.641
26    XLK         0.6
27    XLP       0.658
28    XLU       0.656
29    XLV       0.572
30    XLY       0.585

So basically, around 60-66% of the time spent in the market, most of which are short, sporadic, losing trades, with the occasional long winner.

Here’s what the equity curve looks like:

As can be seen during the crisis, this baseline strategy is taking lots of trades…for no reason at all.

And here are the three aggregate portfolio statistics:

> SharpeRatio.annualized(portfRets)
                                    [,1]
Annualized Sharpe Ratio (Rf=0%) 1.334861
> Return.annualized(portfRets)
                       [,1]
Annualized Return 0.1288973
> maxDrawdown(portfRets)
[1] 0.1562275

The largest drawdown occurs during the crisis, but beyond that, the annualized returns are solid. What I find most impressive is that the annualized Sharpe Ratio over the course of this backtest, even with what seems to be a period of drawdowns that can be removed with what seems to be relative ease (most market timing trend-followers seem to do a decent job of avoiding most of the brunt of the crisis).

And finally, to demonstrate the indicator and investigate areas for improvement, here’s the equity curve of XLF (not XLB this time), since XLF lost eight dollars over the course of the backtest.

One advantage that I think the FRAMA has over the Trend Vigor is that as it is an indicator that’s superimposed directly on the price action, it’s easier to understand visually. (Also, the math is definitely more intuitive.) As we can see from the XLF equity curve, the base strategy presented by ETFHQ could certainly use a confirmatory, slower-moving indicator to keep out counter-trend trading. After all, while the tiny little whipsaw trades are undoubtedly a nuisance, correcting counter-trend trading is a lower-hanging fruit, and would seem to be more profitable in addressing.

Thanks for reading.