Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
This post will introduce John Ehlerss Autocorrelation Periodogram mechanisma mechanism designed to
dynamically find a lookback period. That is, the most common parameter optimized in backtests is the
lookback period.
Before beginning this post, I must give credit where its due, to one Mr. Fabrizio Maccallini, the head of
structured derivatives at Nordea Markets in London. You can find the rest of the repository he did for Dr.
John Ehlerss Cycle Analytics for Traders on his github. I am grateful and honored that such intelligent and
experienced individuals are helping to bring some of Dr. Ehlerss methods into R.
The point of the Ehlers Autocorrelation Periodogram is to dynamically set a period between a minimum and
a maximum period length. While I leave the exact explanation of the mechanic to Dr. Ehlerss book, for all
practical intents and purposes, in my opinion, the punchline of this method is to attempt to remove a massive
source of overfitting from trading system creationnamely specifying a lookback period.
SMA of 50 days? 100 days? 200 days? Well, this algorithm takes that possibility of overfitting out of your
hands. Simply, specify an upper and lower bound for your lookback, and it does the rest. How well it does it
is a topic of discussion for those well-versed in the methodologies of electrical engineering (Im not), so feel
free to leave comments that discuss how well the algorithm does its job, and feel free to blog about it as
well.
In any case, heres the original algorithm code, courtesy of Mr. Maccallini:
1 AGC <- function(loCutoff = 10, hiCutoff = 48, slope = 1.5) { accSlope = -slope #
acceptableSlope = 1.5 dB ratio = 10 ^ (accSlope / 20) if ((hiCutoff - loCutoff) >
2
0)
3 factor <- ratio ^ (2 / (hiCutoff - loCutoff));
4 return (factor)
5 }
6
7 autocorrPeriodogram <- function(x, period1 = 10, period2 = 48, avgLength = 3) {
# high pass filter
8 alpha1 <- (cos(sqrt(2) * pi / period2) + sin(sqrt(2) * pi / period2) - 1) /
9 cos(sqrt(2) * pi / period2)
1 hp <- (1 - alpha1 / 2) ^ 2 * (x - 2 * lag(x) + lag(x, 2))
0 hp <- hp[-c(1, 2)]
11 hp <- filter(hp, (1 - alpha1), method = "recursive")
hp <- c(NA, NA, hp)
1 hp <- xts(hp, order.by = index(x))
2 # super smoother
1 a1 <- exp(-sqrt(2) * pi / period1)
3 b1 <- 2 * a1 * cos(sqrt(2) * pi / period1)
c2 <- b1
1 c3 <- -a1 * a1
4 c1 <- 1 - c2 - c3
1 filt <- c1 * (hp + lag(hp)) / 2
5 leadNAs <- sum(is.na(filt))
filt <- filt[-c(1: leadNAs)]
1
filt <- filter(filt, c(c2, c3), method = "recursive")
6 filt <- c(rep(NA, leadNAs), filt)
1 filt <- xts(filt, order.by = index(x))
7 # Pearson correlation for each value of lag
1 autocorr <- matrix(0, period2, length(filt))
for (lag in 2: period2) {
8 # Set the average length as M
1 if (avgLength == 0) M <- lag
9 else M <- avgLength
2 autocorr[lag, ] <- runCor(filt, lag(filt, lag), M)
0 }
autocorr[is.na(autocorr)] <- 0
2 # Discrete Fourier transform
1 # Correlate autocorrelation values with the cosine and sine of each period of
2 interest
2 # The sum of the squares of each value represents relative power at each period
cosinePart <- sinePart <- sqSum <- R <- Pwr <- matrix(0, period2, length(filt))
2 for (period in period1: period2) {
3 for (N in 2: period2) {
2 cosinePart[period, ] = cosinePart[period, ] + autocorr[N, ] * cos(2 * N * pi /
4 period)
2 sinePart[period, ] = sinePart[period, ] + autocorr[N, ] * sin(2 * N * pi /
period)
5 }
2 sqSum[period, ] = cosinePart[period, ] ^ 2 + sinePart[period, ] ^ 2
6 R[period, ] <- EMA(sqSum[period, ] ^ 2, ratio = 0.2)
2 }
7 R[is.na(R)] <- 0
# Normalising Power
2 K <- AGC(period1, period2, 1.5)
8 maxPwr <- rep(0, length(filt)) for(period in period1: period2) { for (i in 1:
2 length(filt)) { if (R[period, i] >= maxPwr[i]) maxPwr[i] <- R[period, i]
9 else maxPwr[i] <- K * maxPwr[i]
}
3 }
0 for(period in 2: period2) {
3 Pwr[period, ] <- R[period, ] / maxPwr
1 }
3 # Compute the dominant cycle using the Center of Gravity of the spectrum
Spx <- Sp <- rep(0, length(filter))
2 for(period in period1: period2) {
3 Spx <- Spx + period * Pwr[period, ] * (Pwr[period, ] >= 0.5)
3 Sp <- Sp + Pwr[period, ] * (Pwr[period, ] >= 0.5)
3 }
dominantCycle <- Spx / Sp
4
dominantCycle[is.nan(dominantCycle)] <- 0
3 dominantCycle <- xts(dominantCycle, order.by=index(x))
5 dominantCycle <- dominantCycle[dominantCycle > 0]
3 return(dominantCycle)
6 #heatmap(Pwr, Rowv = NA, Colv = NA, na.rm = TRUE, labCol = "", add.expr =
lines(dominantCycle, col = 'blue'))
3 }
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
One thing I do notice is that this code uses a loop that says for(i in 1:length(filt)), which is an O(data points)
loop, which I view as the plague in R. While Ive used Rcpp before, its been for only the most basic of
loops, so this is definitely a place where the algorithm can stand to be improved with Rcpp due to Rs
inherent poor looping.
Those interested in the exact logic of the algorithm will, once again, find it in John Ehlerss Cycle Analytics
For Traders book (see link earlier in the post).
Of course, the first thing to do is to test how well the algorithm does what it purports to do, which is to
dictate the lookback period of an algorithm.
1>avgLength
t1 <- Sys.time() > out <- autocorrPeriodogram(Ad(SPY), period1 = 120, period2 = 252,
= 3) > t2 <- Sys.time() > print(t2-t1)
2Time difference of 33.25429 secs
1 plot(out)
Lets zoom in on 2001 through 2003, when the markets went through some upheaval.
1 plot(out['2001::2003']
In this zoomed-in image, we can see that the algorithms estimates seem fairly jumpy.
Heres some code to feed the algorithms estimates of n into an indicator to compute an indicator with a
dynamic lookback period as set by Ehlerss autocorrelation periodogram.
1
2 acpIndicator <- function(x, minPeriod, maxPeriod, indicatorFun = EMA, ...) {
acpOut <- autocorrPeriodogram(x = x, period1 = minPeriod, period2 = maxPeriod)
3 roundedAcpNs <- round(acpOut, 0) # round to the nearest integer
4 uniqueVals <- unique(roundedAcpNs) # unique integer values
5 out <- xts(rep(NA, length(roundedAcpNs)), order.by=index(roundedAcpNs))
6
7 for(i in 1:length(uniqueVals)) { # loop through unique values, compute indicator
8 tmp <- indicatorFun(x, n = uniqueVals[i], ...)
out[roundedAcpNs==uniqueVals[i]] <- tmp[roundedAcpNs==uniqueVals[i]]
9 }
10 return(out)
11 }
12
And here is the function applied with an SMA, to tune between 120 and 252 days.
For now, Im going to leave this code here, and let people experiment with it. I hope that someone will find
that this indicator is helpful to them.
Lastly, I am volunteering to curate the R section for books on quantocracy. If you have a book about R that
can apply to finance, be sure to let me know about it, so that I can review it and possibly recommend it.
Thakn you.
The indicator is Dr. Ehlerss modified RSI from Chapter 7 of Cycle Analytics for Traders.
For starters, heres 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 cant 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. Theyre not
exported since as of the moment, theyre simply components of other indicators.
1
2
3 highPassFilter <- function(x) {
alpha1 <- (cos(.707*2*pi/48)+sin(.707*2*pi/48)-1)/cos(.707*2*pi/48)
4 HP <- (1-alpha1/2)*(1-alpha1/2)*(x-2*lag(x)+lag(x,2))
5 HP <- HP[-c(1,2)]
6 HP <- filter(HP, c(2*(1-alpha1), -1*(1-alpha1)*(1-alpha1)), method="recursive")
7 HP <- c(NA, NA, HP)
8 HP <- xts(HP, order.by=index(x))
return(HP)
9 }
10
11 superSmoother <- function(x) {
12 a1 <- exp(-1.414*pi/10)
13 b1 <- 2*a1*cos(1.414*pi/10)
14 c2 <- b1
c3 <- -a1*a1
15 c1 <- 1-c2-c3
16 filt <- c1*(x+lag(x))/2
17 leadNAs <- sum(is.na(filt))
18 filt <- filt[-c(1:leadNAs)]
filt <- filter(filt, c(c2, c3), method="recursive")
19 filt <- c(rep(NA,leadNAs), filt)
20 filt <- xts(filt, order.by=index(x))
21 }
22
23
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. Ehlerss book (link
here).
Heres the modified ehlers RSI, which I call CycleRSI, from the book in which its defined:
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 Connorss 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 Ill 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 doesnt 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.
1 require(DSTrading)
require(IKTrading)
2 require(quantstrat)
3 require(PerformanceAnalytics)
4
5 initDate="1990-01-01"
from="2003-01-01"
6
to="2010-12-31"
7 options(width=70)
8 verbose=TRUE
9
10 source("demoData.R")
11
12 #trade sizing and initial equity settings
tradeSize <- 100000
13 initEq <- tradeSize*length(symbols)
14
15 strategy.st <- portfolio.st <- account.st <- "Cycle_RSI_I"
16 rm.strat(portfolio.st)
17 rm.strat(strategy.st)
18 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
19 currency='USD',initEq=initEq)
20 initOrders(portfolio.st, initDate=initDate)
21 strategy(strategy.st, store=TRUE)
22
23 #parameters
nRSI=2
24 RSIentry=10
25 RSIexit=70
26
27 nSMA=200
28
29 period=10
30 pctATR=.04
31
#indicators
32 add.indicator(strategy.st, name="lagATR",
33 arguments=list(HLC=quote(HLC(mktdata)), n=period),
34 label="atrX")
35 add.indicator(strategy.st, name="SMA",
36 arguments=list(x=quote(Cl(mktdata)), n=nSMA),
label="SMA")
37 add.indicator(strategy.st, name="CycleRSI",
38 arguments=list(x=quote(Cl(mktdata)), n=nRSI),
39 label="RSI")
40
41 #signals
add.signal(strategy.st, name="sigComparison",
42 arguments=list(columns=c("Close", "SMA"), relationship="gt"),
43 label="ClGtSMA")
44
45 add.signal(strategy.st, name="sigThreshold",
46 arguments=list(column="CycleRSI.RSI", threshold=RSIentry,
47 relationship="lt", cross=FALSE),
label="RSIltEntryThresh")
48
49 add.signal(strategy.st, name="sigAND",
50 arguments=list(columns=c("ClGtSMA", "RSIltEntryThresh"),
51 cross=TRUE),
52 label="longEntry")
53
add.signal(strategy.st, name="sigCrossover",
54
arguments=list(columns=c("Close", "SMA"), relationship="lt"),
55 label="exitSMA")
56
57 add.signal(strategy.st, name="sigThreshold",
58 arguments=list(column="CycleRSI.RSI", threshold=RSIexit,
59 relationship="gt", cross=TRUE),
label="longExit")
60
61
62
63
64
65
66
67
68
69
70
71
72 #rules
73 #rules
74 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="longEntry", sigval=TRUE,
75 ordertype="market",
76 orderside="long", replace=FALSE,
77 prefer="Open", osFUN=osDollarATR,
78 tradeSize=tradeSize, pctATR=pctATR,
79 atrMod="X"),
type="enter", path.dep=TRUE)
80
81 add.rule(strategy.st, name="ruleSignal",
82 arguments=list(sigcol="longExit", sigval=TRUE,
83 orderqty="all", ordertype="market",
84 orderside="long", replace=FALSE,
85 prefer="Open"),
type="exit", path.dep=TRUE)
86
87 add.rule(strategy.st, name="ruleSignal",
88 arguments=list(sigcol="exitSMA", sigval=TRUE,
89 orderqty="all", ordertype="market",
90 orderside="long", replace=FALSE,
prefer="Open"),
91 type="exit", path.dep=TRUE)
92
93 #apply strategy
94 t1 <- Sys.time()
95 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
96 t2 <- Sys.time()
print(t2-t1)
97
98 #set up analytics
99 updatePortf(portfolio.st)
10 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
0 updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)
10
1
10
2
10
3
10
4
10
5
10
6
And here are the results:
Overall, the statistics dont look bad. However, the 1:1 annualized returns to max drawdown isnt
particularly pleasing, as it means that this strategy cant be leveraged effectively to continue getting outsized
returns in this state. Quite irritating. Heres the equity curve.
In short, as with other mean reverters, when drawdowns happen, they happen relatively quickly and brutally.
Heres 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, heres some code for charting all of the different trades.
In conclusion, while the initial trading system seems to be a good start, its far from complete.
This week, I attempted to use Ehlerss 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, its an exponentially smoothed daily range thats 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 youre 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 Ehlerss 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.
1 source("futuresData.R")
2
3 #trade sizing and initial equity settings
tradeSize <- 100000
4 initEq <- tradeSize*length(symbols)
5
6 strategy.st <- portfolio.st <- account.st <- "FRAMA_BANDS_I"
7 rm.strat(portfolio.st)
8 rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
9 initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
1 currency='USD',initEq=initEq)
0 initOrders(portfolio.st, initDate=initDate)
11strategy(strategy.st, store=TRUE)
1
2 #parameters
FC = 1
1 SC = 300
3 n = 20
1 triggerLag = 1
4 nBands = 10
1 bandFrac=10
entryThreshPctB=1
5 exitThreshPctB=.5
1
6 period=10
1 pctATR=.06
7
1 #indicators
add.indicator(strategy.st, name="FRAMAbands",
8 arguments=list(HLC=quote(HLC(mktdata)), FC=FC, SC=SC,
1 n=n, triggerLag=triggerLag, nBands=nBands,
9 bandFrac=bandFrac),
2 label="Fbands")
0
2 add.indicator(strategy.st, name="lagATR",
arguments=list(HLC=quote(HLC(mktdata)), n=period),
1 label="atrX")
2
2 #signals
2 add.signal(strategy.st, name="sigThreshold",
3 arguments=list(column="pctB.Fbands",
threshold=entryThreshPctB,
2 relationship="gt", cross=TRUE),
4 label="longEntry")
2
5 add.signal(strategy.st, name="sigThreshold",
2 arguments=list(column="pctB.Fbands",
threshold=exitThreshPctB,
6
relationship="lt", cross=TRUE),
2 label="longExit")
7
2 #rules
8 add.rule(strategy.st, name="ruleSignal",
2 arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
orderside="long", replace=FALSE, prefer="Open",
9 osFUN=osDollarATR,
3 tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
0 type="enter", path.dep=TRUE)
3
1 add.rule(strategy.st, name="ruleSignal",
3 arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
ordertype="market",
2 orderside="long", replace=FALSE, prefer="Open"),
3 type="exit", path.dep=TRUE)
3
3 #apply strategy
4 t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
3 t2 <- Sys.time()
5 print(t2-t1)
3
6 #set up analytics
3 updatePortf(portfolio.st)
7 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
3 updateEndEq(account.st)
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
3
7
4
In short, its a loser over the past three years. Heres the equity curve:
Now while it may have worked in the past (or something similar to it, using Ehlerss filter indicator), it
doesnt seem to do so going forward.
Ill leave this here for now as a demonstration of how to do Ehlers bands.
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).
1 require(IKTrading)
2
3
4 currency('USD')
Sys.setenv(TZ="UTC")
5
6
7 t1 <- Sys.time()
8 if(!"CME_CL" %in% ls()) {
9 #Energies
1 CME_CL <- quandClean("CHRIS/CME_CL", start_date=from, end_date=to, verbose=verbose)
#Crude
0 CME_NG <- quandClean("CHRIS/CME_NG", start_date=from, end_date=to, verbose=verbose)
11#NatGas
1 CME_HO <- quandClean("CHRIS/CME_HO", start_date=from, end_date=to, verbose=verbose)
2 #HeatingOil
1 CME_RB <- quandClean("CHRIS/CME_RB", start_date=from, end_date=to, verbose=verbose)
#Gasoline
3 ICE_B <- quandClean("CHRIS/ICE_B", start_date=from, end_date=to, verbose=verbose)
1 #Brent
4 ICE_G <- quandClean("CHRIS/ICE_G", start_date=from, end_date=to, verbose=verbose)
1 #Gasoil
5
#Grains
1
CME_C <- quandClean("CHRIS/CME_C", start_date=from, end_date=to, verbose=verbose)
6 #Chicago Corn
1 CME_S <- quandClean("CHRIS/CME_S", start_date=from, end_date=to, verbose=verbose)
7 #Chicago Soybeans
1 CME_W <- quandClean("CHRIS/CME_W", start_date=from, end_date=to, verbose=verbose)
#Chicago Wheat
8 CME_SM <- quandClean("CHRIS/CME_SM", start_date=from, end_date=to, verbose=verbose)
1 #Chicago Soybean Meal
9 CME_KW <- quandClean("CHRIS/CME_KW", start_date=from, end_date=to, verbose=verbose)
2 #Kansas City Wheat
0 CME_BO <- quandClean("CHRIS/CME_BO", start_date=from, end_date=to, verbose=verbose)
#Chicago Soybean Oil
2
1 #Softs
2 #ICE_SB <- quandClean("CHRIS/ICE_SB", start_date=from, end_date=to,
2 verbose=verbose) #Sugar
2 #Sugar 2007-03-26 is wrong
#ICE_KC <- quandClean("CHRIS/ICE_KC", start_date=from, end_date=to,
3
verbose=verbose) #Coffee
2 #Coffee January of 08 is FUBAR'd
4 ICE_CC <- quandClean("CHRIS/ICE_CC", start_date=from, end_date=to, verbose=verbose)
#Cocoa
2
ICE_CT <- quandClean("CHRIS/ICE_CT", start_date=from, end_date=to, verbose=verbose)
5 #Cotton
2
6 #Other Ags
2 CME_LC <- quandClean("CHRIS/CME_LC", start_date=from, end_date=to, verbose=verbose)
7 #Live Cattle
CME_LN <- quandClean("CHRIS/CME_LN", start_date=from, end_date=to, verbose=verbose)
2 #Lean Hogs
8
2 #Precious Metals
9 CME_GC <- quandClean("CHRIS/CME_GC", start_date=from, end_date=to, verbose=verbose)
3 #Gold
0 CME_SI <- quandClean("CHRIS/CME_SI", start_date=from, end_date=to, verbose=verbose)
#Silver
3 CME_PL <- quandClean("CHRIS/CME_PL", start_date=from, end_date=to, verbose=verbose)
1 #Platinum
3 CME_PA <- quandClean("CHRIS/CME_PA", start_date=from, end_date=to, verbose=verbose)
2 #Palladium
3
3 #Base
CME_HG <- quandClean("CHRIS/CME_HG", start_date=from, end_date=to, verbose=verbose)
3 #Copper
4
3 #Currencies
5 CME_AD <- quandClean("CHRIS/CME_AD", start_date=from, end_date=to, verbose=verbose)
3 #Ozzie
6 CME_CD <- quandClean("CHRIS/CME_CD", start_date=from, end_date=to, verbose=verbose)
#Loonie
3 CME_SF <- quandClean("CHRIS/CME_SF", start_date=from, end_date=to, verbose=verbose)
7 #Franc
3 CME_EC <- quandClean("CHRIS/CME_EC", start_date=from, end_date=to, verbose=verbose)
8 #Euro
CME_BP <- quandClean("CHRIS/CME_BP", start_date=from, end_date=to, verbose=verbose)
3 #Cable
9 CME_JY <- quandClean("CHRIS/CME_JY", start_date=from, end_date=to, verbose=verbose)
4 #Yen
0 CME_NE <- quandClean("CHRIS/CME_NE", start_date=from, end_date=to, verbose=verbose)
4 #Kiwi
1
#Equities
4 CME_ES <- quandClean("CHRIS/CME_ES", start_date=from, end_date=to, verbose=verbose)
2 #Emini
4 CME_MD <- quandClean("CHRIS/CME_MD", start_date=from, end_date=to, verbose=verbose)
3 #Midcap 400
4 CME_NQ <- quandClean("CHRIS/CME_NQ", start_date=from, end_date=to, verbose=verbose)
#Nasdaq 100
4 CME_TF <- quandClean("CHRIS/CME_TF", start_date=from, end_date=to, verbose=verbose)
4 #Russell Smallcap
5 CME_NK <- quandClean("CHRIS/CME_NK", start_date=from, end_date=to, verbose=verbose)
4 #Nikkei
6
#Dollar Index and Bonds/Rates
4
ICE_DX <- quandClean("CHRIS/CME_DX", start_date=from, end_date=to,
7 verbose=verbose) #Dixie
4 #CME_FF <- quandClean("CHRIS/CME_FF", start_date=from, end_date=to,
8 verbose=verbose) #30-day fed funds
4 CME_ED <- quandClean("CHRIS/CME_ED", start_date=from, end_date=to,
verbose=verbose) #3 Mo. Eurodollar/TED Spread
9 CME_FV <- quandClean("CHRIS/CME_FV", start_date=from, end_date=to,
5 verbose=verbose) #Five Year TNote
0 CME_TY <- quandClean("CHRIS/CME_TY", start_date=from, end_date=to,
5 verbose=verbose) #Ten Year Note
1 CME_US <- quandClean("CHRIS/CME_US", start_date=from, end_date=to,
verbose=verbose) #30 year bond
5 }
2
CMEinsts <- c("CL", "NG", "HO", "RB", "C", "S", "W", "SM", "KW", "BO", "LC", "LN",
5
"GC", "SI", "PL",
3 "PA", "HG", "AD", "CD", "SF", "EC", "BP", "JY", "NE", "ES", "MD", "NQ",
5 "TF", "NK", #"FF",
4 "ED", "FV", "TY", "US")
5
5 ICEinsts <- c("B", "G", #"SB", #"KC",
"CC", "CT", "DX")
5 CME <- paste("CME", CMEinsts, sep="_")
6 ICE <- paste("ICE", ICEinsts, sep="_")
5 symbols <- c(CME, ICE)
7 stock(symbols, currency="USD", multiplier=1)
5 t2 <- Sys.time()
print(t2-t1)
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
3
7
4
7
5
7
6
7
7
7
8
7
9
8
0
8
1
8
2
8
3
1 require(DSTrading)
require(IKTrading)
2
require(quantstrat)
3 require(PerformanceAnalytics)
4
5 initDate="1990-01-01"
6 from="2000-03-01"
7 to="2011-12-31"
options(width=70)
8 verose=TRUE
9
10 FRAMAsdr <- function(HLC, n, FC, SC, nSD, ...) {
11 frama <- FRAMA(HLC, n=n, FC=FC, SC=SC, ...)
12 sdr <- runSD(frama$FRAMA, n=nSD)/runSD(Cl(HLC), n=nSD)
13 sdr[sdr > 2] <- 2
out <- cbind(FRAMA=frama$FRAMA, trigger=frama$trigger, sdr=sdr)
14 out
15 }
16
17 source("futuresData.R")
18
19 #trade sizing and initial equity settings
tradeSize <- 100000
20 initEq <- tradeSize*length(symbols)
21
22 strategy.st <- portfolio.st <- account.st <- "FRAMA_SDR_I"
23 rm.strat(portfolio.st)
24 rm.strat(strategy.st)
25 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
26 currency='USD',initEq=initEq)
27 initOrders(portfolio.st, initDate=initDate)
28 strategy(strategy.st, store=TRUE)
29
30 #parameters
31 FC = 1
SC = 300
32 n = 126
33 triggerLag = 1
34 nSD = 5
35 sdThresh <- .3
36
period=10
37 pctATR=.02
38
39 #indicators
40 add.indicator(strategy.st, name="FRAMAsdr",
41 arguments=list(HLC=quote(HLC(mktdata)), FC=FC, SC=SC,
42 n=n, triggerLag=triggerLag, nSD=nSD),
label="SDR")
43
44 add.indicator(strategy.st, name="lagATR",
arguments=list(HLC=quote(HLC(mktdata)), n=period),
45
label="atrX")
46
47 #signals
48 add.signal(strategy.st, name="sigComparison",
49 arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="gt"),
50 label="FRAMAup")
51
add.signal(strategy.st, name="sigThreshold",
52 arguments=list(column="sdr.SDR", threshold=sdThresh,
53 relationship="gt",cross=FALSE),
54 label="SDRgtThresh")
55
56 add.signal(strategy.st, name="sigAND",
57 arguments=list(columns=c("FRAMAup", "SDRgtThresh"), cross=TRUE),
label="longEntry")
58
59 add.signal(strategy.st, name="sigCrossover",
60 arguments=list(columns=c("FRAMA.SDR", "trigger.SDR"), relationship="lt"),
61 label="FRAMAdnExit")
62
63 #add.signal(strategy.st, name="sigThreshold",
# arguments=list(column="sdr.SDR", threshold=sdThresh, relationship="lt",
64 cross=TRUE),
65 # label="SDRexit")
66
67 #rules
68 add.rule(strategy.st, name="ruleSignal",
69 arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
orderside="long", replace=FALSE, prefer="Open",
70 osFUN=osDollarATR,
71 tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
72 type="enter", path.dep=TRUE)
73
74 add.rule(strategy.st, name="ruleSignal",
75 arguments=list(sigcol="FRAMAdnExit", sigval=TRUE, orderqty="all",
ordertype="market",
76 orderside="long", replace=FALSE, prefer="Open"),
77 type="exit", path.dep=TRUE)
78
79 #add.rule(strategy.st, name="ruleSignal",
80 # arguments=list(sigcol="SDRexit", sigval=TRUE, orderqty="all",
ordertype="market",
81 # orderside="long", replace=FALSE, prefer="Open"),
82 # type="exit", path.dep=TRUE)
83
84 #apply strategy
85 t1 <- Sys.time()
86 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
87 print(t2-t1)
88
89 #set up analytics
90 updatePortf(portfolio.st)
91 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
92
updateEndEq(account.st)
93
94
95
96
97
98
99
10
0
10
1
10
2
10
3
10
4
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:
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:
1
2
print(t(durStats))
3 [,1]
4 Min 1
5 Q1 2
6 Med 5
7 Mean 11
Q3 11
8 Max 158
9 WMin 1
10 WQ1 5
11 WMed 10
WMean 18
12
WQ3 21
13 WMax 158
14 LMin 1
15 LQ1 1
16 LMed 3
LMean 6
17 LQ3 6
18 LMax 93
19
20
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:
1 > print(mean(as.numeric(as.character(mktExposure$MktExposure))))
2 [1] 0.3820789
Like this. Not particularly great, considering its a 60% gain over 11 years. Here are the particular statistics:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.8124137
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.04229355
6 > maxDrawdown(portfRets)
7 [1] 0.07784351
8
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.
Next, lets look at a much worse situation. Heres the equity curve for the Eurodollar/TED spread.
In this case, its 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 inactivitythat is, the market is not even choppy so much as just still.
Ill leave this here, and move onto other attempts at getting around this sort of roadblock next.
So, it is possible to create a trading system that can correctly isolate severe and protracted downturns,
without taking (too many) false signals.
126 day FRAMA, FC=4, SC=300 (were still modifying the original ETFHQ strategy).
A running median (somewhere between 150 days and 252 daysseemingly, 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 tradethe 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. Heres 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. Its
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 occurthat is, a descending adaptive indicator, a descending 7-12
month median.
In other words, its absolutely not a standalone strategy, but more of a little something to give a long-only
strategy a boost during bad times. Its certainly not as spectacular as it gets. For instance, heres the equity
curve for XLK in late 2008-2009. Mainly, the problem with the ETFHQ strategy (Im 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 Ill leave this strategy here for
now.
So, to send off this original ETFHQ price cross strategy off, Ill 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).
In this case, you can see that the magnitude of the trend makes no difference to the strategywhich 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 doesnt 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 hurtand 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.
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.
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).
Heres 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.
1 require(DSTrading)
require(IKTrading)
2
require(quantstrat)
3 require(PerformanceAnalytics)
4
5 initDate="1990-01-01"
6 from="2003-01-01"
7 to="2010-12-31"
options(width=70)
8
9 #to rerun the strategy, rerun everything below this line
10 source("demoData.R") #contains all of the data-related boilerplate.
11
12 #trade sizing and initial equity settings
13 tradeSize <- 10000
14 initEq <- tradeSize*length(symbols)
15
strategy.st <- portfolio.st <- account.st <- "FRAMA_III"
16 rm.strat(portfolio.st)
17 rm.strat(strategy.st)
18 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
19 initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
currency='USD',initEq=initEq)
20 initOrders(portfolio.st, initDate=initDate)
21 strategy(strategy.st, store=TRUE)
22
23 #parameters
24
25 FC=4
26 SC=300
n=126
27 triggerLag=1
28
29 period=10
30 pctATR=.02
31
32 #indicators
33
add.indicator(strategy.st, name="FRAMA",
34
arguments=list(HLC=quote(HLC(mktdata)), n=n,
35 SC=SC, FC=FC, triggerLag=triggerLag),
36 label="primary")
37
38 add.indicator(strategy.st, name="runMedian",
39 arguments=list(x=quote(Cl(mktdata)), n=n),
label="confirmatory")
40
41 add.indicator(strategy.st, name="lagATR",
42 arguments=list(HLC=quote(HLC(mktdata)), n=period),
43 label="atrX")
44
45 # #long signals
46 #
# add.signal(strategy.st, name="sigComparison",
47 # arguments=list(columns=c("FRAMA.primary", "X1.confirmatory"),
48 # relationship="gte"),
49 # label="FRAMAgteMedian")
50 #
# add.signal(strategy.st, name="sigComparison",
51 # arguments=list(columns=c("FRAMA.primary", "trigger.primary"),
52 # relationship="gte"),
53 # label="FRAMArising")
54 #
55 # add.signal(strategy.st, name="sigComparison",
# arguments=list(columns=c("Close", "FRAMA.primary"),
56 # relationship="gte"),
57 # label="ClGtFRAMA")
58 #
59 # add.signal(strategy.st, name="sigAND",
60 # arguments=list(columns=c("FRAMAgteMedian",
# "FRAMArising", "ClGtFRAMA"),
61 # cross=TRUE),
62 # label="longEntry")
63 #
64 # add.signal(strategy.st, name="sigCrossover",
# arguments=list(columns=c("Close", "FRAMA.primary"),
65 # relationship="lt"),
66 # label="longExit")
67 #
68 # #long rules
69 #
# add.rule(strategy.st, name="ruleSignal",
70 # arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
71 # orderside="long", replace=FALSE, prefer="Open",
72 osFUN=osDollarATR,
73 # tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
# type="enter", path.dep=TRUE)
74
#
75 # add.rule(strategy.st, name="ruleSignal",
76 # arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
77 ordertype="market",
78 # orderside="long", replace=FALSE, prefer="Open"),
# type="exit", path.dep=TRUE)
79
80 #short signals
81
82 add.signal(strategy.st, name="sigComparison",
83 arguments=list(columns=c("FRAMA.primary", "X1.confirmatory"),
84 relationship="lt"),
85 label="FRAMAltMedian")
86
add.signal(strategy.st, name="sigComparison",
87 arguments=list(columns=c("FRAMA.primary", "trigger.primary"),
relationship="lt"),
88
label="FRAMAfalling")
89
90 add.signal(strategy.st, name="sigComparison",
91 arguments=list(columns=c("Close", "FRAMA.primary"),
92 relationship="lt"),
93 label="ClLtFRAMA")
94
add.signal(strategy.st, name="sigAND",
95 arguments=list(columns=c("FRAMAltMedian",
96 "FRAMAfalling", "ClLtFRAMA"),
97 cross=TRUE),
98 label="shortEntry")
99
10 add.signal(strategy.st, name="sigCrossover",
arguments=list(columns=c("Close", "FRAMA.primary"),
0 relationship="gt"),
10 label="shortExit")
1
10 #short rules
2
10 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market",
3 orderside="short", replace=FALSE, prefer="Open",
10 osFUN=osDollarATR,
4 tradeSize=-tradeSize, pctATR=pctATR, atrMod="X"),
10 type="enter", path.dep=TRUE)
5
10 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all",
6 ordertype="market",
10 orderside="short", replace=FALSE, prefer="Open"),
7 type="exit", path.dep=TRUE)
10
8
10
9 #apply strategy
110t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
111 t2 <- Sys.time()
112print(t2-t1)
113
114
115#set up analytics
116updatePortf(portfolio.st)
117dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
118updateEndEq(account.st)
119
12
0
12
1
12
2
12
3
12
4
12
5
12
6
12
7
12
8
12
9
13
0
13
1
13
2
13
3
13
4
13
5
13
6
13
7
13
8
13
9
14
0
14
1
14
2
14
3
14
4
14
5
14
6
The results arent pretty, meaning that the filter is still incomplete. Here are the trade stats:
At this point, for the sake of brevity, Ill leave off the equity curves and portfolio statistics (theyll obviously
be bad). However, lets 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.
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 Clenows 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 days. 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.
Heres 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 purplethe FRAMA, to the
redthe 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.
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.
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 thatjust a monotonous drawdown until the crisis, at which point the gains arent made up, and
then the losses continue. In short, theres 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), lets 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.
1 require(DSTrading)
require(IKTrading)
2 require(quantstrat)
3 require(PerformanceAnalytics)
4
5 initDate="1990-01-01"
6 from="2003-01-01"
to="2010-12-31"
7
options(width=70)
8
9 #to rerun the strategy, rerun everything below this line
10 source("demoData.R") #contains all of the data-related boilerplate.
11
12 #trade sizing and initial equity settings
13 tradeSize <- 10000
initEq <- tradeSize*length(symbols)
14
15 strategy.st <- portfolio.st <- account.st <- "FRAMA_II"
16 rm.strat(portfolio.st)
17 rm.strat(strategy.st)
18 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
19 initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
currency='USD',initEq=initEq)
20 initOrders(portfolio.st, initDate=initDate)
21 strategy(strategy.st, store=TRUE)
22
23 #parameters
24
25 FCfast=4
SCfast=300
26 nFast=126
27 fastTriggerLag=1
28
29 FCslow=40
30 SCslow=252
31 nSlow=252
slowTriggerLag=1
32
33 period=10
34 pctATR=.02
35
36 #indicators
37 #Have to add in this function first, since the word 'slow' gets picked up
38 #by the HLC function as "low", which causes issues.
add.indicator(strategy.st, name="lagATR",
39 arguments=list(HLC=quote(HLC(mktdata)), n=period),
40 label="atrX")
41
42
43 add.indicator(strategy.st, name="FRAMA",
44 arguments=list(HLC=quote(HLC(mktdata)), n=nFast, FC=FCfast,
SC=SCfast, triggerLag=fastTriggerLag),
45 label="fast")
46
47
48 add.indicator(strategy.st, name="FRAMA",
49 arguments=list(HLC=quote(HLC(mktdata)), n=nSlow, FC=FCslow,
50 SC=SCslow, triggerLag=slowTriggerLag),
51 label="slow")
52
# #long signals
53 #
54 # #condition 1: our fast FRAMA is above our slow FRAMA
55 # add.signal(strategy.st, name="sigComparison",
56 # arguments=list(columns=c("FRAMA.fast", "FRAMA.slow"),
relationship="gt"),
57
# label="fastFRAMAaboveSlow")
58 #
59 # #condition 2: our fast FRAMA is rising
60 # add.signal(strategy.st, name="sigComparison",
# arguments=list(columns=c("FRAMA.fast", "trigger.fast"),
61 relationship="gt"),
# label="fastFRAMArising")
62
#
63 # #setup: price crosses above the fast FRAMA
64 # add.signal(strategy.st, name="sigComparison",
65 # arguments=list(columns=c("Close", "FRAMA.fast"), relationship="gte"),
66 # label="CloseGteFastFRAMA")
#
67 # #wrap our conditions and our setup into one entry signal
68 # add.signal(strategy.st, name="sigAND",
69 # arguments=list(columns=c("fastFRAMAaboveSlow", "fastFRAMArising",
70 "CloseGteFastFRAMA"), cross=TRUE),
71 # label="longEntry")
#
72 # #our old exit signal
73 # add.signal(strategy.st, name="sigCrossover",
74 # arguments=list(columns=c("Close", "FRAMA.fast"), relationship="lt"),
75 # label="longExit")
76
# #long rules
77
78 # add.rule(strategy.st, name="ruleSignal",
79 # arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
80 # orderside="long", replace=FALSE, prefer="Open",
81 osFUN=osDollarATR,
82 # tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
# type="enter", path.dep=TRUE)
83 #
84 # add.rule(strategy.st, name="ruleSignal",
85 # arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
86 ordertype="market",
87 # orderside="long", replace=FALSE, prefer="Open"),
# type="exit", path.dep=TRUE)
88
89
90
91 #short signals
92 add.signal(strategy.st, name="sigComparison",
93 arguments=list(columns=c("FRAMA.fast", "FRAMA.slow"), relationship="lt"),
94 label="fastFRAMAbelowSlow")
95
#condition 2: our fast FRAMA is falling
96 add.signal(strategy.st, name="sigComparison",
97 arguments=list(columns=c("FRAMA.fast", "trigger.fast"),
98 relationship="lt"),
99 label="fastFRAMAfalling")
10
0
10 #setup: price crosses below the fast FRAMA
add.signal(strategy.st, name="sigCrossover",
1 arguments=list(columns=c("Close", "FRAMA.fast"), relationship="lt"),
10 label="CloseLtFastFRAMA")
2
10 #wrap our conditions and our setup into one entry signal
3 add.signal(strategy.st, name="sigAND",
arguments=list(columns=c("fastFRAMAbelowSlow", "fastFRAMAfalling",
10
"CloseLtFastFRAMA"), cross=TRUE),
4 label="shortEntry")
10
5 #our old exit signal
10 add.signal(strategy.st, name="sigCrossover",
6 arguments=list(columns=c("Close", "FRAMA.fast"), relationship="gt"),
label="shortExit")
10
7 #short rules
10 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market",
8
orderside="short", replace=FALSE, prefer="Open",
10 osFUN=osDollarATR,
9 tradeSize=-tradeSize, pctATR=pctATR, atrMod="X"),
110 type="enter", path.dep=TRUE)
111 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all",
112ordertype="market",
113 orderside="short", replace=FALSE, prefer="Open"),
114 type="exit", path.dep=TRUE)
115
116
117#apply strategy
118t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
119t2 <- Sys.time()
12 print(t2-t1)
0
12
1 #set up analytics
12 updatePortf(portfolio.st)
2 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
updateAcct(portfolio.st,dateRange)
12 updateEndEq(account.st)
3
12
4
12
5
12
6
12
7
12
8
12
9
13
0
13
1
13
2
13
3
13
4
13
5
13
6
13
7
13
8
13
9
14
0
14
1
14
2
14
3
14
4
14
5
14
6
14
7
14
8
14
9
15
0
15
1
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 thats 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 modifiedwith 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 samplethat is, to see if the system can stay out at
all points aside from the crisis.
Trade statistics:
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 well move past that.
Duration statistics:
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:
1 #market exposure
tmp <- list()
2
length(tmp) <- length(symbols)
3 for(i in 1:nrow(dStats)) {
4 totalDays <- nrow(get(rownames(dStats)[i]))
5 mktExposure <- dStats$Total.Days[i]/totalDays
6 tmp[[i]] <- c(rownames(dStats)[i], round(mktExposure, 3))
}
7 mktExposure <- data.frame(do.call(rbind, tmp))
8 colnames(mktExposure) <- c("Symbol","MktExposure")
9 print(mktExposure)
10
11 Symbol MktExposure
12 1 EFA 0.168
2 EPP 0.125
13 3 EWA 0.149
14 4 EWC 0.15
15 5 EWG 0.133
16 6 EWH 0.063
7 EWJ 0.176
17 8 EWS 0.145
18 9 EWT 0.133
19 10 EWU 0.135
20 11 EWY 0.152
21 12 EWZ 0.126
13 EZU 0.147
22 14 IEF 0.102
23 15 IGE 0.168
24 16 IYR 0.152
25 17 IYZ 0.186
26 18 LQD 0.083
19 RWR 0.149
27 20 SHY 0.052
28 21 TLT 0.126
29 22 XLB 0.168
30 23 XLE 0.16
24 XLF 0.249
31 25 XLI 0.196
32
33
34
35
36 26 XLK 0.168
27 XLP 0.129
37 28 XLU 0.101
38 29 XLV 0.1
39 30 XLY 0.168
40
41
42
43
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:
1
2
3 #portfolio cash PL
4 portString <- paste0("portfolio.", portfolio.st)
5 portPL <- .blotter[[portString]]$summary$Net.Trading.PL
6
#Cash Sharpe
7 (SharpeRatio.annualized(portPL, geometric=FALSE))
8
9 #Portfolio comparisons to SPY
10 instRets <- PortfReturns(account.st)
11
12 #Correlations
13 instCors <- cor(instRets)
diag(instRets) <- NA
14 corMeans <- rowMeans(instCors, na.rm=TRUE)
15 names(corMeans) <- gsub(".DailyEndEq", "", names(corMeans))
16 print(round(corMeans,3))
17 mean(corMeans)
18
portfRets <- xts(rowMeans(instRets)*ncol(instRets), order.by=index(instRets))
19 portfRets <- portfRets[!is.na(portfRets)]
20 cumPortfRets <- cumprod(1+portfRets)
21 firstNonZeroDay <- as.character(index(portfRets)[min(which(portfRets!=0))])
22 getSymbols("SPY", from=firstNonZeroDay, to=to)
23 SPYrets <- diff(log(Cl(SPY)))[-1]
cumSPYrets <- cumprod(1+SPYrets)
24 comparison <- cbind(cumPortfRets, cumSPYrets)
25 colnames(comparison) <- c("strategy", "SPY")
26 chart.TimeSeries(comparison, legend.loc = "topleft",
27 colors=c("green","red"))
28
29
1 (SharpeRatio.annualized(portPL, geometric=FALSE))
2 Net.Trading.PL
3 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:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) -0.2950648
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return -0.01560975
6 > maxDrawdown(portfRets)
7 [1] 0.1551006
8
But why?
For that, well look at a picture of the equity curve of an individual instrument, complete with overlaid
indicators.
1
chart.Posn(portfolio.st, "XLF")
2 tmp <- FRAMA(HLC(XLF), n=nFast, FC=FCfast, SC=SCfast, triggerLag=fastTriggerLag)
3 add_TA(tmp$FRAMA, on=1, col="purple", lwd=3)
4 add_TA(tmp$trigger, on=1, col="blue", lwd=0.5)
5 tmp2 <- FRAMA(HLC(XLF), n=nSlow, FC=FCslow, SC=SCslow, triggerLag=slowTriggerLag)
6 add_TA(tmp2$FRAMA, on=1, col="orange", lwd=3)
tmp2 <- lagATR(HLC=HLC(XLF), n=period)
7 add_TA(tmp2$atr, col="purple", lwd=2)
8
Why? Because from my intuition, adaptive moving average indicators all aim to do the same thingthey 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), youll
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 youre 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 isnt 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 isnt. 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
systems 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.
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, Im 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, Im
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, lets 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 Id have to employ too much magical thinking to neglect their obscene trading
transaction costs):
1 require(DSTrading)
2 require(IKTrading)
require(quantstrat)
3
4 initDate="1990-01-01"
5 from="2003-01-01"
6 to="2010-12-31"
7 options(width=70)
8
#to rerun the strategy, rerun everything below this line
9 source("demoData.R") #contains all of the data-related boilerplate.
1
0 #trade sizing and initial equity settings
11tradeSize <- 10000
1 initEq <- tradeSize*length(symbols)
2
1 strategy.st <- portfolio.st <- account.st <- "FRAMA_I"
rm.strat(portfolio.st)
3 rm.strat(strategy.st)
1 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
4 initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
1 currency='USD',initEq=initEq)
5 initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)
1
6 #Parameters
1
7 FC=4
1 SC=300
8 n=126
triggerLag=1
1 pctATR=.02
9 period=10
2
0 #indicators
2
1 add.indicator(strategy.st, name="FRAMA",
arguments=list(HLC=quote(HLC(mktdata)),n=n,
2
FC=FC, SC=SC, triggerLag=triggerLag),
2 label="frama")
2
3 add.indicator(strategy.st, name="lagATR",
2 arguments=list(HLC=quote(HLC(mktdata)), n=period),
4 label="atrX")
2
5
#signals
2
6 add.signal(strategy.st, name="sigCrossover",
2 arguments=list(columns=c("Close", "FRAMA.frama"), relationship="gte"),
7 label="longEntry")
2
8 add.signal(strategy.st, name="sigCrossover",
2 arguments=list(columns=c("Close", "FRAMA.frama"), relationship="lt"),
label="longExit")
9
3 #rules
0
3 add.rule(strategy.st, name="ruleSignal",
1 arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
3 orderside="long", replace=FALSE, prefer="Open",
osFUN=osDollarATR,
2 tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
3 type="enter", path.dep=TRUE)
3
3 add.rule(strategy.st, name="ruleSignal",
4 arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
3 ordertype="market",
orderside="long", replace=FALSE, prefer="Open"),
5 type="exit", path.dep=TRUE)
3
6 #apply strategy
3 t1 <- Sys.time()
7 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
3 t2 <- Sys.time()
print(t2-t1)
8
3
9 #set up analytics
4 updatePortf(portfolio.st)
0 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
4 updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
3
7
4
7
5
7
6
7
7
7
8
7
9
8
0
Its a fairly simple strategybuy the next days open when the price crosses above the indicator, and vice
versa. In other words, its 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
ETFHQs analysis (and I do), then even one good trend-following indicator will be sufficient.
1
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
2 [1] 1.828178
3 > (aggCorrect <- mean(tStats$Percent.Positive))
4 [1] 41.55233
5 > (numTrades <- sum(tStats$Num.Trades))
6 [1] 3704
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
7 [1] 2.619
8
Far from spectacular. Less than 50% hit rate, the profit factor definitely indicates that there is massive room
for improvement as well.
Now, Id like to introduce some new analytics found in my IKTrading packagenamely, 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. Its 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.
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 timeit 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 followingthe 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 Im incorporating into this demo is the idea of market exposureor
what percentage of the time that the strategy is actually *in* the market. Here is the code and the results:
1
2 #market exposure
tmp <- list()
3 length(tmp) <- length(symbols)
4 for(i in 1:nrow(dStats)) {
5 totalDays <- nrow(get(rownames(dStats)[i]))
6 mktExposure <- dStats$Total.Days[i]/totalDays
7 tmp[[i]] <- c(rownames(dStats)[i], round(mktExposure, 3))
}
8 mktExposure <- data.frame(do.call(rbind, tmp))
9 colnames(mktExposure) <- c("Symbol","MktExposure")
10 print(mktExposure)
11
Essentially, this little piece of code takes advantage of the daily statistics output to compute market
exposure. Heres the output:
1 Symbol MktExposure
1 EFA 0.635
2
2 EPP 0.66
3 3 EWA 0.66
4 4 EWC 0.643
5 5 EWG 0.627
6 6 EWH 0.606
7 EWJ 0.561
7 8 EWS 0.645
8 9 EWT 0.565
9 10 EWU 0.621
10 11 EWY 0.644
11 12 EWZ 0.67
13 EZU 0.636
12
13
14
15 14 IEF 0.589
15 IGE 0.676
16
16 IYR 0.631
17 17 IYZ 0.611
18 18 LQD 0.611
19 19 RWR 0.648
20 20 SHY 0.67
21 TLT 0.559
21 22 XLB 0.621
22 23 XLE 0.663
23 24 XLF 0.6
24 25 XLI 0.641
25 26 XLK 0.6
27 XLP 0.658
26 28 XLU 0.656
27 29 XLV 0.572
28 30 XLY 0.585
29
30
31
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.
As can be seen during the crisis, this baseline strategy is taking lots of tradesfor no reason at all.
1 > SharpeRatio.annualized(portfRets)
[,1]
2
Annualized Sharpe Ratio (Rf=0%) 1.334861
3
4 > Return.annualized(portfRets)
5 [,1]
6 Annualized Return 0.1288973
7 > maxDrawdown(portfRets)
[1] 0.1562275
8
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, heres 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 thats
superimposed directly on the price action, its 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.
Following from the last post and setting aside the not-working-as-advertised Trend Vigor indicator, we will
turn our attention to the world of adaptive moving averages. In this case, I will be working with the
FRAMAthe FRactal Adaptive Moving Average. The reason I am starting off with this one is that according
to ETFHQ in this post, FRAMA is an indicator that seems to have very strong performance, even using what
definitely seems to be a very simple strategy (long if the price crosses over the indicator, exit vice versa),
which would most likely leave one open to whipsaws.
But before then, Id like to make an introduction to the FRAMA, by linking to the original Dr. John Ehlers
paper, here.
While I wont attempt to give a better formal explanation than the man that created the indicator (which is
why the paper is there), the way I intuitively think about the FRAMA (or the adaptive moving average
family of indicators, found here) is that they are improvements of the exponential moving average that
attempt to smooth the indicator during cyclical market periods to avoid whipsaws, and to have a faster
response during periods of strong trends, so as to minimize the damage done due to an ending trend.
The FRAMA itself compares two periods of n/2 days (the last n/2 days, and the last n/2 days before those
last n/2 days) to the total period (n days). Intuitively, if there is a straight trend upwards, then the expression
(log(N1+N2)-log(N3))/log(2), where N1 is the difference of highest high and lowest low over the last n/2
days and N2 is identical except for the previous n/2 days before the last n/2 days, and N3 is the same
quantity over all n days, will be equal to zero, and thus, the exponent of that would be equal to 1, which is
analogous to an EMA of 1 day. Similarly, when there is a great deal of congestion, then the expression
log(N1+N2) will be greater than log(N3), and so the exponent (that is, the fractal dimension) of the exponent
would be closer to 2 (or greater, since I implemented the modified FRAMA).
1
2
3 "FRAMA" <- function(HLC, n=20, FC=1, SC=200, triggerLag=1, ...) {
4 price <- ehlersPriceMethod(HLC, ...)
5 if (n%%2==1) n=n-1 #n must be even
6 N3 <- (runMax(Hi(HLC), n)-runMin(Lo(HLC), n))/n
N1 <- (runMax(Hi(HLC), n/2)-runMin(Lo(HLC), n/2))/(n/2)
7 lagSeries <- lag(HLC, n/2)
8 N2 <- (runMax(Hi(lagSeries), n/2)-runMin(Lo(lagSeries), n/2))/(n/2)
9 dimen <- (log(N1+N2)-log(N3))/log(2)
10 w <- log(2/(SC+1))
11 oldAlpha <- exp(w*(dimen-1))
oldN <- (2-oldAlpha)/oldAlpha
12 newN <- ((SC-FC)*(oldN-1)/(SC-1))+FC
13 alpha <- 2/(newN+1)
14 alpha[which(alpha > 1)] <- 1
15 alpha[which(alpha < w)] <- w
alphaComplement <- 1-alpha
16 initializationIndex <- index(alpha[is.na(alpha)])
17 alpha[is.na(alpha)] <- 1; alphaComplement[is.na(alphaComplement)] <- 0
18 FRAMA <- rep(0, length(price))
19 FRAMA[1] <- price[1]
20 FRAMA <- computeFRAMA(alpha, alphaComplement, FRAMA, price)
FRAMA <- xts(FRAMA, order.by=index(price))
21 FRAMA[initializationIndex] <- alpha[initializationIndex] <- NA
22 trigger <- lag(FRAMA, triggerLag)
23 out <- cbind(FRAMA=FRAMA, trigger=trigger)
24 return(out)
25 }
26
27
NumericVector computeFRAMA(NumericVector alpha, NumericVector alphaComplement,
NumericVector FRAMA, NumericVector price) {
int n = price.size();
for(int i=1; i<n; i++) {
FRAMA[i] = alpha[i]*price[i] + alphaComplement[i]*FRAMA[i-1];
}
return FRAMA;
}
Essentially, from the second chunk of code, this is an advanced form of the exponential moving average that
takes into account the amount of movement over a greater time period relative to the swing at two finer
intervals in the two halves of that period. The methodology for the modified FRAMA is thanks to ETFHQ
(once again), found here.
And while words can make for a bit of explanation, in this case, a picture (or several) is worth far more.
Here is some code I wrote to plot an EMA, and three separate FRAMA computations (the default John
Ehlers settings, the best ETFHQ settings, and the slower ETFHQ settings) on XLB from 2003 through 2010
(yes, the same XLB from our Trend Vigor backtest, because it was the go-to instrument for all our individual
equity curves).
1
2
3 from="2003-01-01"
to="2010-12-31"
4 source("demoData.R")
5
6 tmp <- FRAMA(HLC(XLB))
7 tmp2 <- FRAMA(HLC(XLB), FC=4, n=126, SC=300)
8 tmp3 <- FRAMA(HLC(XLB), FC=40, n=252, SC=252)
9 ema <- EMA(x=Cl(XLB),n=126)
10
myTheme<-chart_theme()
11 myTheme$col$dn.col<-'gray2'
12 myTheme$col$dn.border <-'gray2'
13 myTheme$col$up.border <-'gray2'
14
15 chart_Series(XLB,
TA="add_TA(tmp$FRAMA, col='blue', on=1, lwd=3);
16
add_TA(tmp2$FRAMA, col='green', on=1, lwd=3);
17 add_TA(tmp3$FRAMA, col='red', on=1, lwd=3);
18 add_TA(ema$EMA, col='orange', on=1, lwd=3)",
19 theme=myTheme)
20
21 legend(x=5, y=40, legend=c("FRAMA FC=1, n=20, SC=200",
"FRAMA FC=4, n=126, SC=300",
22 "FRAMA FC=40, n=252, SC=252",
23 "EMA n=126"),
24 fill=c("blue", "green", "red", "orange"), bty="n")
25
26
1
2 chart_Series(XLB,
TA="add_TA(tmp$FRAMA, col='blue', on=1, lwd=3);
3 add_TA(tmp2$FRAMA, col='green', on=1, lwd=3);
4 add_TA(tmp3$FRAMA, col='red', on=1, lwd=3);
5 add_TA(ema$EMA, col='orange', on=1, lwd=3)",
6 theme=myTheme, subset="2007::2008")
7
8 legend(x=5, y=30, legend=c("FRAMA FC=1, n=20, SC=200",
"FRAMA FC=4, n=126, SC=300",
9 "FRAMA FC=40, n=252, SC=252",
10 "EMA n=126"),
11 fill=c("blue", "green", "red", "orange"), bty="n")
12
On the other hand, the 126 day FRAMA (the ETFHQ settings, in green) seems to look like a dynamic
support and resistance indicator that the talking heads go on and on about (yet give very little advice on how
to actually objectively compute), in that the price action seems to touch it every so often, but not oscillate
around it. It breaks in one direction and manages to stay in that direction, until it breaks in the other
direction, and sustain a move to that direction. This seems like a foundation of a future trading strategy.
Finally, the 252 day FRAMA (the ETFHQ settings for the long-term FRAMA indicator, in red) looks like a
confirmatory indicator or filter.
Notice that by comparison, the 126 day EMA seems to lag as much if not more than the 252 day FRAMA,
and from this vantage point, it seems that the results are not as good for the same amount of data processed.
Overall, it seems that by trading off smoothness and responsiveness, one can see the foundations of a
possible system.
While Trend Vigor has potential on the long end (as seen in part III of this investigation here), as Andreas
Clenow has stated in his article Trend Following Does Not Work On Stocks, the short side of trend
following gets killed in equities. And, since by far and away most of the securities in this backtest were
equity-based ETFs (most of the ones available before 2003 were equity index ETFs), the results can
basically be summed up as buying insurance for Black Swans. On the plus side, Trend Vigor at very high
period settings (longer than 120) is able to capture some of the upside from being short in the depths of the
financial crisis, but unfortunately gives most of it back.
To begin, heres the code for this strategy, starting off at the previous settings of a period of 20 and delta 0
with ATR order sizing. The demo data has slightly changed in that the initialization, from, and to parameters
are now found in the demo scripts, rather than the file itself, which, as a result, is no longer a standalone file.
1 require(DSTrading)
require(IKTrading)
2
require(quantstrat)
3
4 initDate="1990-01-01"
5 from="2003-01-01"
6 to="2010-12-31"
7
8 #to rerun the strategy, rerun everything below this line
source("demoData.R") #contains all of the data-related boilerplate.
9
1 #trade sizing and initial equity settings
0 tradeSize <- -10000
initEq <- -tradeSize*length(symbols)
11
1 strategy.st <- portfolio.st <- account.st <- "TVI_short"
2 rm.strat(portfolio.st)
1 rm.strat(strategy.st)
3 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
1 currency='USD',initEq=initEq)
4 initOrders(portfolio.st, initDate=initDate)
1 strategy(strategy.st, store=TRUE)
5
1 #parameters (trigger lag unchanged, defaulted at 1)
6 delta=0
period=20
1 pctATR=.02 #control risk with this parameter
7
1 #indicators
8 add.indicator(strategy.st, name="TVI", arguments=list(x=quote(Cl(mktdata)),
1 period=period, delta=delta), label="TVI")
9 add.indicator(strategy.st,
n=period), label="atrX")
name="lagATR", arguments=list(HLC=quote(HLC(mktdata)),
2
0 #signals
2 add.signal(strategy.st, name="sigThreshold",
1 arguments=list(threshold=-1, column="vigor.TVI", relationship="lte",
2 cross=FALSE),
label="TVIltThresh")
2 add.signal(strategy.st, name="sigComparison",
2 arguments=list(columns=c("vigor.TVI","trigger.TVI"), relationship="lt"),
3 label="TVIltLag")
2 add.signal(strategy.st, name="sigAND",
4 arguments=list(columns=c("TVIltThresh","TVIltLag"), cross=TRUE),
label="shortEntry")
2 add.signal(strategy.st, name="sigCrossover",
5 arguments=list(columns=c("vigor.TVI","trigger.TVI"), relationship="gt"),
2 label="shortExit")
6
2 #rules
7 add.rule(strategy.st, name="ruleSignal",
arguments=list(sigcol="shortEntry", sigval=TRUE, ordertype="market",
2 orderside="short", replace=FALSE, prefer="Open",
8 osFUN=osDollarATR,
2 tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
9 type="enter", path.dep=TRUE)
add.rule(strategy.st, name="ruleSignal",
3 arguments=list(sigcol="shortExit", sigval=TRUE, orderqty="all",
0 ordertype="market",
3 orderside="short", replace=FALSE, prefer="Open"),
1 type="exit", path.dep=TRUE)
3
2
3 #apply strategy
t1 <- Sys.time()
3 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
3 t2 <- Sys.time()
4 print(t2-t1)
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
So, already, we can see that its pretty much a bloodbathnamely because throughout 2003 through 2010, the
general trend was upwards, and also, because of the use of constant ATR order sizing, when the moves are
largest (during the heightened volatility), our position size is smallest, relative to it. Which isnt to say that
this is a particularly bad idea considering that a short position can get whipsawed badly, but that such a
short-term detection system has no sense of the overall larger trend.
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) -0.3647753
4 > Return.annualized(portfRets)
5 [,1]
6 Annualized Return -0.04075671
> maxDrawdown(portfRets)
7 [1] 0.3464871
8 >
9
In other words, this strategy simply costs you an annualized 4% a year, just to get the pop-up during the
financial crisis, and then proceed to give that all back to the market anyway. In other words, its a really silly
form of insurance, especially considering that due to the heightened volatility in times of extreme crisis, the
ATR order sizing function *already* functions as a massive hedge. For the sake of completeness, heres the
equity curve of this strategy.
If the other extreme is triedthat is, a more conservative delta parameter (.1), and a much larger period
(greater than 120 days, to attempt to implement this more as a filter, in this case, 140), these are the results:
1
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
2 [1] 1.157702
3 > (numTrades <- sum(tStats$Num.Trades))
4 [1] 77
5 > (aggCorrect <- sum(tStats$Num.Trades*tStats$Percent.Positive/100)/numTrades)
6 [1] 0.4155857
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
7 [1] NaN
8
The NaN means that one or more of the instruments had so few trades that there were no losers (most likely
1 or 2 trades that just so happened to get lucky), E.G. XLU had 1 trade.
Again, nothing worth writing home over. Even after I tried several different settings (for instance, if the delta
parameter is too high, most of the move to the downside in the crisis will be missed, and any profit will get
consumed by the recovery, and then some), when the best result barely breaks even in a once-in-a-lifetime
crisis, and so many other configurations either bite too easily on minor dips in an uptrend, or the converse,
are too afraid to enter even in a once-in-a-lifetime opportunity, its best to not even try to fit this square peg
into a round hole, and just not even think about trying Trend Vigor on the short end in equities.
Basically, enter late, exit late (meaning theres a lot of lag to this computation) on the filter end, and bite too
easily on the short-term trading end.
In other words, as a standalone indicator, Id be careful with it. Originally, this indicator was meant to be a
predictor of trending vs. cycling. And essentially, at least when it comes to equities, it fails miserably, taking
many trades it shouldnt in an environment that steadily grinds upwards. This makes me believe that in a
trend that grinds downwards, that a long strategy would be similarly disastrous. In short, in my opinion, the
Trend Vigor indicator fails its original stated purpose, which is to partition markets into
trending/cycling/trending downwards.
This all in mind, lets see what happened to our holdout datathat is, from 2011 onwards using the long-only
ATR position-sizing strategy through 2011 to present day (that is, the current date as of the time of this
writing, which is June 9th, 2014, or 2014-06-09).
Overall, it still was profitable, but definitely not impressively so. For the first time, the 50% mark got
broken, which essentially contradicted the uniqueness of the indicator to begin with.
Basically, it seems that while the domestic side of equities seemed to do fairly well, the international side of
the trade really hurt. For once, the perils of diversification are apparent. Here are the portfolio statistics:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.4074399
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.04448638
6
> maxDrawdown(portfRets)
7 [1] 0.1439702
8
So, ho-hum annualized returns, but the Sharpe Ratio at this point is far from stellar, and the drawdowns are
definitely disappointing. Here is the resultant equity curve comparison.
In other words, it kept pace with the S&P 500 up until the beginning of 2013 (when in fact, their
performance was just about equal), with less violent (but longer-lasting) drawdowns. Unfortunately, starting
in mid-2013, the S&P 500 roared away, while the strategy just seemed to be flat, at the new equity high. This
would probably anger quite a few investors. Lets check out an equity curve from a losing security and see
what happened.
So does this mean that there was absolutely nothing to take away from this?
Well, no. Trend Vigor is something that can be added to a repertoire of indicators. Unlike the usual SMA and
EMA fare, the advantage that Trend Vigor has is that over longer time horizons, assuming gradual trends (as
opposed to the steep drops in things such as equities), is that it still can get things right more often than not,
and when it does, the average win to loss profile is usually impressive. However, a marginal improvement
over the basics isnt what trend vigor was originally advertised to be, so much as a reliable market mode
indicator. And in a trending environment, even your basic SMA crossovers will make money. In an
oscillating environment, even the most basic RSI plug-and-chug will make money. The key, of course, is to
try and find an indicator that will tell you what to deploy when. And for that purpose, unlike what was
advertised in the original John Ehlers presentation, Trend Vigor, as far as this investigation has led me, is
*not* that.
To continue the investigation of Trend Vigor (see part 1 here, and part 2 here), lets examine the importance
of position sizing. Also, this post will show the way to obtain this equity curve:
Before starting this post, I would like to defer to an authority in terms of the importance of position sizing.
That would be one Andreas Clenow, author of an extremely highly-rated book about trend-following. Heres
his post: Why Leverage Is Pointless
Now before starting the main thrust of this post, Id like to make a quick comparison regarding the 30 ETFs
I selected for this demonamely, their inter-instrument correlations.
1
2
tmp <- list()
3 length(tmp) <- length(symbols)
4 for (i in 1:length(symbols)) {
5 tmp[[i]] <-Cl(get(symbols[i]))
6 }
tmp <- do.call(cbind, tmp)
7 baseCors <- cor(tmp)
8 diag(baseCors) <- NA
9 instrumentAverageBaseCors <- rowMeans(baseCors, na.rm=TRUE)
1 names(instrumentAverageBaseCors) <- gsub(".Close", "",
0 names(instrumentAverageBaseCors))
instrumentAverageBaseCors
11(grandMeanBaseCors <- mean(instrumentAverageBaseCors))
1
2
1
2
3
> instrumentAverageBaseCors
4 XLB XLE XLF XLP XLI XLU XLV
5 0.8208166 0.7872692 0.2157401 0.7618719 0.7882733 0.8049700 0.7487639
6 XLK XLY RWR EWJ EWG EWU EWC
7 0.7785739 0.5912142 0.7055041 0.6665144 0.8112501 0.7735207 0.8159072
EWY EWA EWH EWS IYZ EZU IYR
8 0.8231610 0.8234274 0.8022086 0.7971305 0.6875318 0.7669643 0.6894201
9 EWT EWZ EFA IGE EPP LQD SHY
1 0.7746172 0.7227011 0.8047205 0.8034852 0.8274662 0.5552580 0.4339562
0 IEF TLT
11 0.3954464 0.3863955
> (grandMeanBaseCors <- mean(instrumentAverageBaseCors))
1 [1] 0.7054693
2
1
3
In other words, pretty ugly. All sorts of ugly, but I suppose this is what happens when limiting the choices to
ETFs in inception since 2003. So remember that number. A correlation north of .70 on average among all the
instruments.
In the previous post, we saw that some highly successful instruments, when analyzed at their own scale,
such as SHY (the short term bonds ETF), had spectacular performances. However, in the grand scheme of
things, due to identical notional risk allocation, the fact that SHY itself just didnt move as much as, say, the
SPDR sector ETFs meant that its performance could not make as much of an impact in the portfolio
performance. This implication meant that despite having 30 instruments in the portfolio, not only were the
base instruments highly correlated (quandl futures data algorithm coming sometime in the future!), but
beyond that, the performance was dominated by the most volatile among them. This is clearly undesirable,
and with most trading information in the public domain looking at a trading system as just a set of
indicators, signals, and rules on one instrument at a time, there is very little emphasis placed on relative
position sizing, with the occasional thoughtful writer saying my order size is based on dividing some
notional amount by some ATR, and leaving it at that.
How important is that one little detail? As Andreas Clenow has pointed out in his post, it is extremely
important, and in many cases, far more important than a couple of simplistic rules.
The approach I take is rooted in futures trading. In futures trading, due to the discontinuous nature of
contracts, in order to formulate a backtest, quants/engineers/traders/whoever have to create a continuous
contract, going back in time, and have to find some way to smooth over those price discontinuities between
the two contracts they roll between at any point in time. More often than not, these changes add up quickly,
and so, any computation that was sensitive to the addition of a scalar quantity was instantly off the table (this
means that anything depending on percentage changes in price? Gone). One solution to this is the Average
True Range, which not only succeeds in this regard, but also isnt penalized by directional volatility, like
standard deviation in a trend (consider a monotonically rising pricea moving average across that time
period would constantly lag the current quantity, and a standard deviation would consistently punish that
divergence). Furthermore, if I have not yet stated it already, the ATR has a very important interpretation for
position sizing:
What does this mean? It means that rather that rather than ordering a notional quantity of the security, the
trading system can instead order a specific level of *risk*, regardless of the notional price. This allows a
trading system simulation to *force* equal risk contributions from all securities involved in the system. My
implementation of this idea (found in my IKTrading package, see my about page for my github link) grew
into a little bit of a Swiss army knife to include maximum position sizing, and a rebalancing feature.
To get it out of the way, the lagATR function exists to, as its name says, lag the ATR computation, in order to
prevent look-ahead bias. After all, you cannot buy the open using an ATR value computed at the close of that
same day.
Now, moving onto the actual osDollarATR function, the way it works is like this: it has the user specify a
notional trade size (tradeSize), that is, a notional dollar amount, and then the risk level the user would like
on that notional dollar amount (pctATR). And heres where it gets funby multiplying the notional dollar
amount by the percentage ATR, it allows the strategy to basically buy units of ATR. Beyond this, for
strategies that scale in, pyramid, dollar-cost-average, or otherwise stack up positions, there is functionality to
cap the risk, along with a feature that would check if the maximum risk level had been breached, and to pare
down (not featured in this demonstration). Essentially, inherent in this conversion from dollars to ATR is an
implicit variable leverage factor. That is, consider a security A that had an ATR for a given period of $2, that
was priced for $50. Now consider another security B that was priced at $100 with an ATR for the same
period of $1. If you wished to risk a notional 2% of $10,000 (that is, $200 worth of ATR), youd only need
100 units of security A, essentially staying 50% in cash for that transaction, while for security B, youd
actually leverage 2:1 in order to purchase 200 units, even if you lack the notional capital. Obviously, since
this is a computer, the function is assuming that you can actually *obtain* the proper leverage to properly
position-size. But beyond that, this function inherently embodies the idea of showing why leverage is
pointless.
The strategy is identical to the previous posts, using the same TVI(20,0) indicator (or if you want, (20,0,1),
if you take into account the triggerLag parameter), with one key difference: rather than using the equal
dollar weight order-sizing function, I will instead use this new ATR order sizing function.
1 require(DSTrading)
require(IKTrading)
2 require(quantstrat)
3
4 initDate="1990-01-01"
from="2003-01-01"
5 to="2010-12-31"
6
7 #to rerun the strategy, rerun everything below this line
8 source("demoData.R") #contains all of the data-related boilerplate.
9
1 #trade sizing and initial equity settings
tradeSize <- 10000
0 initEq <- tradeSize*length(symbols)
11
1 strategy.st <- portfolio.st <- account.st <- "TVI_osATR"
2 rm.strat(portfolio.st)
1 rm.strat(strategy.st)
3 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
1 currency='USD',initEq=initEq)
4 initOrders(portfolio.st, initDate=initDate)
1 strategy(strategy.st, store=TRUE)
5
1 #parameters (trigger lag unchanged, defaulted at 1)
6 delta=0
period=20
1 pctATR=.02 #control risk with this parameter
7
1 #indicators
8 add.indicator(strategy.st, name="TVI", arguments=list(x=quote(Cl(mktdata)),
1 period=period, delta=delta), label="TVI")
add.indicator(strategy.st, name="lagATR", arguments=list(HLC=quote(HLC(mktdata)),
9 n=period), label="atrX")
2
0 #signals
2 add.signal(strategy.st, name="sigThreshold",
1 arguments=list(threshold=1, column="vigor.TVI", relationship="gte",
2 cross=FALSE),
label="TVIgtThresh")
2 add.signal(strategy.st, name="sigComparison",
2 arguments=list(columns=c("vigor.TVI","trigger.TVI"), relationship="gt"),
3 label="TVIgtLag")
2 add.signal(strategy.st, name="sigAND",
arguments=list(columns=c("TVIgtThresh","TVIgtLag"), cross=TRUE),
4 label="longEntry")
2 add.signal(strategy.st, name="sigCrossover",
5 arguments=list(columns=c("vigor.TVI","trigger.TVI"), relationship="lt"),
2 label="longExit")
6
2 #rules
add.rule(strategy.st, name="ruleSignal",
7 arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
2 orderside="long", replace=FALSE, prefer="Open",
8 osFUN=osDollarATR,
2 tradeSize=tradeSize, pctATR=pctATR, atrMod="X"),
type="enter", path.dep=TRUE)
9
add.rule(strategy.st, name="ruleSignal",
3 arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
0 ordertype="market",
3 orderside="long", replace=FALSE, prefer="Open"),
1 type="exit", path.dep=TRUE)
3
2
3 #apply strategy
t1 <- Sys.time()
3 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
3 t2 <- Sys.time()
print(t2-t1)
4
3
5
#set up analytics
3 updatePortf(portfolio.st)
6 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
3 updateAcct(portfolio.st,dateRange)
7 updateEndEq(account.st)
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
Notice the addition of the new indicator (using an ATR period tied to the TVI period of 20), and the
modified entry rule. Also, a small detail is the atrMod string modifier. This modifies the string atr, by
appending the modifier to the end of it. While it looks superfluous at first, the reason for this addition is in
case the user wishes to make use of multiple atr indicators, and thereby allowing the order sizing function to
locate the appropriate column. Also note that the label on the indicator has to be the term atr and then the
modifier. If this seems slightly kludge-y, Im open for any suggestions.
One other thing to notethe computation for the ATR order stream should have valid values before the first
order signal fires, or it will not work. That is, consider a system that, for arguments sake, has a 20-day
period to compute its indicator (such as TVI(20,0)), but a 100-day ATR computation. At the beginning of the
data, it could very well be the case that there is not yet a valid value for the ATR computation, and therefore,
a valid order quantity cannot be returned.
So, those two notes about using this function dealt with, lets move onto the results.
1
#tradeStats
2 tStats <- tradeStats(Portfolios = portfolio.st, use="trades", inclZeroDays=FALSE)
3 tStats[,4:ncol(tStats)] <- round(tStats[,4:ncol(tStats)], 2)
4 print(data.frame(t(tStats[,-c(1,2)])))
5 (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
(aggCorrect <- mean(tStats$Percent.Positive))
6
(numTrades <- sum(tStats$Num.Trades))
7 (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
8
1 EFA EPP EWA EWC EWG
Num.Txns 63.00 63.00 57.00 67.00 59.00
2
Num.Trades 32.00 32.00 29.00 34.00 30.00
3 Net.Trading.PL 15389.57 15578.88 16204.18 11846.58 12646.07
4 Avg.Trade.PL 480.92 486.84 558.76 348.43 421.54
5 Med.Trade.PL 185.67 179.37 275.26 -13.31 -15.89
6 Largest.Winner 2440.60 3542.54 3609.82 3040.75 2875.15
Largest.Loser -840.30 -1190.45 -937.03 -1199.78 -912.56
7 Gross.Profits 20142.82 20037.94 20434.89 19120.45 18349.74
8 Gross.Losses -4753.25 -4459.05 -4230.71 -7273.88 -5703.66
9 Std.Dev.Trade.PL 953.83 1055.36 1191.82 1022.49 1097.44
10 Percent.Positive 65.62 59.38 58.62 50.00 43.33
Percent.Negative 34.38 40.62 41.38 50.00 56.67
11 Profit.Factor 4.24 4.49 4.83 2.63 3.22
Avg.Win.Trade 959.18 1054.63 1202.05 1124.73 1411.52
12
Med.Win.Trade 902.74 722.86 592.03 1075.71 1042.79
13 Avg.Losing.Trade -432.11 -343.00 -352.56 -427.88 -335.51
14 Med.Losing.Trade -450.96 -231.60 -284.15 -377.76 -346.90
15 Avg.Daily.PL 489.72 495.27 565.90 314.01 438.00
16 Med.Daily.PL 163.09 133.15 243.61 -56.13 -14.28
Std.Dev.Daily.PL 968.28 1071.71 1213.06 1018.14 1113.09
17 Ann.Sharpe 8.03 7.34 7.41 4.90 6.25
18 Max.Drawdown -2338.43 -2430.09 -2168.66 -2982.50 -2904.73
19 Profit.To.Max.Draw 6.58 6.41 7.47 3.97 4.35
20 Avg.WinLoss.Ratio 2.22 3.07 3.41 2.63 4.21
21 Med.WinLoss.Ratio 2.00 3.12 2.08 2.85 3.01
Max.Equity 15968.41 16789.50 17435.72 13048.24 13773.99
22 Min.Equity -17.93 -188.46 0.00 -393.45 -100.42
23 End.Equity 15389.57 15578.88 16204.18 11846.58 12646.07
24
25 EWH EWJ EWS EWT EWU
26 Num.Txns 57.00 69.00 53.00 61.00 55.00
Num.Trades 29.00 34.00 27.00 31.00 27.00
27 Net.Trading.PL 12085.74 7457.81 13356.23 6906.24 7492.71
28 Avg.Trade.PL 416.75 219.35 494.68 222.78 277.51
29 Med.Trade.PL 192.70 -34.71 425.41 -52.39 87.70
30 Largest.Winner 2924.25 2586.20 3268.76 1965.93 2715.28
31 Largest.Loser -1552.50 -854.08 -631.60 -1052.89 -999.50
Gross.Profits 16111.39 12289.56 15881.04 13110.95 12459.02
32 Gross.Losses -4025.66 -4831.75 -2524.81 -6204.71 -4966.31
33 Std.Dev.Trade.PL 925.26 736.30 848.37 883.21 872.02
34 Percent.Positive 68.97 47.06 70.37 48.39 59.26
35 Percent.Negative 31.03 52.94 29.63 51.61 40.74
36 Profit.Factor 4.00 2.54 6.29 2.11 2.51
Avg.Win.Trade 805.57 768.10 835.84 874.06 778.69
37 Med.Win.Trade 465.57 571.09 576.83 517.53 542.01
38 Avg.Losing.Trade -447.30 -268.43 -315.60 -387.79 -451.48
39 Med.Losing.Trade -413.37 -206.93 -338.84 -322.41 -433.08
40 Avg.Daily.PL 434.15 169.87 504.61 129.14 284.11
Med.Daily.PL 201.67 -36.02 430.95 -54.67 72.31
41 Std.Dev.Daily.PL 937.40 687.94 863.57 725.09 888.60
42 Ann.Sharpe 7.35 3.92 9.28 2.83 5.08
43 Max.Drawdown -2229.93 -4036.69 -2021.12 -3147.85 -2599.57
44 Profit.To.Max.Draw 5.42 1.85 6.61 2.19 2.88
45 Avg.WinLoss.Ratio 1.80 2.86 2.65 2.25 1.72
Med.WinLoss.Ratio 1.13 2.76 1.70 1.61 1.25
46 Max.Equity 13043.23 8395.92 14415.11 6906.24 9160.15
47 Min.Equity -33.87 -274.69 -699.37 -788.28 -327.38
48 End.Equity 12085.74 7457.81 13356.23 6906.24 7492.71
49
50 EWY EWZ EZU IEF IGE
Num.Txns 63.00 65.00 57.00 62.00 67.00
51
Num.Trades 32.00 33.00 29.00 31.00 34.00
52 Net.Trading.PL 9736.49 16814.33 13135.27 17932.39 13435.41
53 Avg.Trade.PL 304.27 509.53 452.94 578.46 395.16
54 Med.Trade.PL 61.08 295.06 94.54 362.74 32.80
55 Largest.Winner 2278.76 2821.49 2805.66 4412.27 2955.08
Largest.Loser -915.90 -1170.96 -1222.74 -836.09 -763.29
56 Gross.Profits 14597.54 22199.95 18539.53 21651.92 18691.32
57 Gross.Losses -4861.05 -5385.61 -5404.25 -3719.52 -5255.91
58 Std.Dev.Trade.PL 815.28 1006.45 1097.26 1097.78 984.69
59 Percent.Positive 53.12 57.58 55.17 70.97 52.94
60 Percent.Negative 46.88 42.42 44.83 29.03 47.06
Profit.Factor 3.00 4.12 3.43 5.82 3.56
61 Avg.Win.Trade 858.68 1168.42 1158.72 984.18 1038.41
62 Med.Win.Trade 617.31 1071.08 1068.20 552.55 708.61
63 Avg.Losing.Trade -324.07 -384.69 -415.71 -413.28 -328.49
64 Med.Losing.Trade -293.26 -391.20 -339.56 -420.83 -303.28
Avg.Daily.PL 296.04 510.77 465.74 578.46 324.22
65 Med.Daily.PL 8.30 249.20 89.51 362.74 17.63
66 Std.Dev.Daily.PL 827.41 1022.53 1115.19 1097.78 907.45
67 Ann.Sharpe 5.68 7.93 6.63 8.36 5.67
Max.Drawdown -2689.16 -2599.61 -2764.56 -1475.57 -2439.52
68 Profit.To.Max.Draw 3.62 6.47 4.75 12.15 5.51
69 Avg.WinLoss.Ratio 2.65 3.04 2.79 2.38 3.16
70 Med.WinLoss.Ratio 2.10 2.74 3.15 1.31 2.34
71 Max.Equity 9736.49 17637.00 14909.94 18695.22 13435.41
Min.Equity -392.89 0.00 0.00 -127.86 -654.86
72 End.Equity 9736.49 16814.33 13135.27 17932.39 13435.41
73
74 IYR IYZ LQD RWR SHY
75 Num.Txns 57.00 67.00 60.00 57.00 54.00
76 Num.Trades 29.00 34.00 30.00 29.00 25.00
77 Net.Trading.PL 13555.67 7434.57 12091.15 13301.24 31955.85
Avg.Trade.PL 467.44 218.66 403.04 458.66 1278.23
78 Med.Trade.PL 137.55 -65.13 133.08 142.52 261.88
79 Largest.Winner 6726.10 1967.48 2512.92 2396.20 13432.83
80 Largest.Loser -511.83 -931.17 -1902.27 -664.22 -887.59
81 Gross.Profits 17122.17 13327.47 16376.88 17323.42 34748.07
82 Gross.Losses
Std.Dev.Trade.PL
-3566.50 -5892.89 -4285.72 -4022.18 -2792.21
1379.47 754.07 947.51 914.56 2874.15
83 Percent.Positive 62.07 44.12 66.67 55.17 64.00
84 Percent.Negative 37.93 55.88 33.33 44.83 36.00
85 Profit.Factor 4.80 2.26 3.82 4.31 12.44
86 Avg.Win.Trade 951.23 888.50 818.84 1082.71 2171.75
Med.Win.Trade 338.57 542.36 623.46 929.54 1104.65
87 Avg.Losing.Trade -324.23 -310.15 -428.57 -309.40 -310.25
88 Med.Losing.Trade -377.80 -289.92 -311.99 -232.44 -336.51
89 Avg.Daily.PL 471.34 177.85 403.04 461.77 1278.23
90 Med.Daily.PL 129.71 -70.50 133.08 127.80 261.88
91 Std.Dev.Daily.PL 1404.62 726.63 947.51 931.19 2874.15
Ann.Sharpe 5.33 3.89 6.75 7.87 7.06
92 Max.Drawdown -2623.12 -2791.39 -3237.00 -2463.55 -1756.33
93 Profit.To.Max.Draw 5.17 2.66 3.74 5.40 18.19
94 Avg.WinLoss.Ratio 2.93 2.86 1.91 3.50 7.00
95 Med.WinLoss.Ratio 0.90 1.87 2.00 4.00 3.28
96 Max.Equity
Min.Equity
15608.04 8133.92 12349.90 14777.73 32781.60
-473.13 -520.47 -146.25 -314.85 -63.98
97 End.Equity 13555.67 7434.57 12091.15 13301.24 31955.85
98
99 TLT XLB XLE XLF XLI
100 Num.Txns 66.00 69.00 75.00 67.00 61.00
101 Num.Trades 33.00 35.00 38.00 34.00 31.00
Net.Trading.PL 9170.38 6286.92 9724.35 6689.67 9474.51
102 Avg.Trade.PL 277.89 179.63 255.90 196.76 305.63
103 Med.Trade.PL -9.84 -92.05 -2.16 -65.18 165.99
104 Largest.Winner 2658.27 1727.45 2624.41 1948.36 1610.51
105 Largest.Loser -797.00 -821.66 -686.89 -470.70 -762.56
106 Gross.Profits 15454.01 12787.87 15637.04 10744.46 13894.65
Gross.Losses -6283.62 -6500.94 -5912.69 -4054.78 -4420.15
107 Std.Dev.Trade.PL 902.92 707.04 857.84 599.09 700.96
108 Percent.Positive 48.48 45.71 47.37 47.06 54.84
109 Percent.Negative 51.52 54.29 52.63 52.94 45.16
110 Profit.Factor 2.46 1.97 2.64 2.65 3.14
111 Avg.Win.Trade 965.88 799.24 868.72 671.53 817.33
Med.Win.Trade 721.04 790.04 632.93 515.21 885.41
112 Avg.Losing.Trade -369.62 -342.15 -295.63 -225.27 -315.72
113 Med.Losing.Trade -417.82 -309.84 -236.16 -188.27 -301.85
114 Avg.Daily.PL 277.89 126.90 186.61 198.53 262.31
115 Med.Daily.PL -9.84 -97.36 -3.15 -95.20 147.54
Std.Dev.Daily.PL 902.92 644.05 754.17 608.29 669.41
116 Ann.Sharpe 4.89 3.13 3.93 5.18 6.22
117 Max.Drawdown -4200.46 -2545.47 -3708.60 -2393.02 -2159.46
118 Profit.To.Max.Draw 2.18 2.47 2.62 2.80 4.39
119 Avg.WinLoss.Ratio 2.61 2.34 2.94 2.98 2.59
120 Med.WinLoss.Ratio 1.73 2.55 2.68 2.74 2.93
Max.Equity 11046.45 6319.32 10527.81 8171.61 9481.43
121 Min.Equity -367.76 -489.76 -301.10 -458.76 -348.23
End.Equity 9170.38 6286.92 9724.35 6689.67 9474.51
122
123
XLK XLP XLU XLV XLY
124 Num.Txns 65.00 73.00 57.00 71.00 59.00
125 Num.Trades 33.00 37.00 28.00 36.00 30.00
126 Net.Trading.PL 6074.82 6211.79 14167.62 2959.63 10416.38
127 Avg.Trade.PL 184.09 167.89 505.99 82.21 347.21
Med.Trade.PL 45.54 -6.88 218.93 35.12 54.36
128 Largest.Winner 2118.84 1518.72 2715.03 1515.40 2324.63
129 Largest.Loser -800.36 -710.82 -595.12 -1025.77 -814.78
130 Gross.Profits 12263.07 12061.50 16821.17 9472.62 14850.49
131 Gross.Losses -6188.26 -5849.72 -2653.56 -6512.99 -4434.12
132 Std.Dev.Trade.PL 714.36 617.33 862.82 582.88 861.18
Percent.Positive 51.52 45.95 75.00 55.56 53.33
133 Percent.Negative 48.48 54.05 25.00 44.44 46.67
134 Profit.Factor 1.98 2.06 6.34 1.45 3.35
135 Avg.Win.Trade 721.36 709.50 801.01 473.63 928.16
136 Med.Win.Trade 653.80 760.76 782.21 301.14 746.86
Avg.Losing.Trade -386.77 -292.49 -379.08 -407.06 -316.72
137 Med.Losing.Trade -300.13 -203.01 -491.90 -310.13 -267.52
138 Avg.Daily.PL 187.13 164.72 521.39 82.91 302.83
139 Med.Daily.PL 4.72 -7.27 249.47 33.66 23.93
140 Std.Dev.Daily.PL 725.58 625.78 875.33 591.37 840.78
141 Ann.Sharpe 4.09 4.18 9.46 2.23 5.72
Max.Drawdown -2615.33 -2565.79 -1696.88 -2604.95 -2431.47
142 Profit.To.Max.Draw 2.32 2.42 8.35 1.14 4.28
143 Avg.WinLoss.Ratio 1.87 2.43 2.11 1.16 2.93
144 Med.WinLoss.Ratio 2.18 3.75 1.59 0.97 2.79
145 Max.Equity 6786.94 6374.13 14371.31 4236.64 10538.74
146 Min.Equity -523.50 -269.87 0.00 -205.49 -154.97
End.Equity 6074.82 6211.79 14167.62 2959.63 10416.38
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
1
> (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
2 [1] 3.37825
3 > (aggCorrect <- mean(tStats$Percent.Positive))
4 [1] 55.921
5 > (numTrades <- sum(tStats$Num.Trades))
6 [1] 946
> (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
7 [1] 2.766667
8
If you scroll down to the end of the , you can see that the aggregate profit factor broke above 3 per trade
(that is, when you aggregate all the trades on their own merit, you take away more than $3 for every $1 that
the market claims, and even the standalone average per-instrument win to loss ratio improved (youll see
why in a moment).
1#dailyStats
2dStats <- dailyStats(Portfolios = portfolio.st, use="Equity")
3rownames(dStats) <- gsub(".DailyEndEq","", rownames(dStats))
4print(data.frame(t(dStats)))
1 EFA EPP EWA
EWC EWG
2
Total.Net.Profit 15389.57 15578.88 16204.18 11846.58
3 12646.07
4 Total.Days 1316.00 1323.00 1303.00
5 1297.00 1281.00
6 Winning.Days 713.00 722.00 730.00
729.00 719.00
7 Losing.Days 603.00 601.00 573.00
8 568.00 562.00
9 Avg.Day.PL 11.69 11.78 12.44
10 9.13 9.87
11 Med.Day.PL 16.38 16.83 21.71
21.41 20.15
12 Largest.Winner 677.61 663.11 1056.94
13 449.52 525.11
14 Largest.Loser -951.74 -1054.27 -1049.55
15 -576.75 -880.58
Gross.Profits 82528.83 93453.33 86183.11 74841.31
16 78603.60
17 Gross.Losses -67139.26 -77874.45 -69978.93 -62994.74
18 -65957.53
19 Std.Dev.Daily.PL 147.33 171.22 159.08
20 134.32 147.41
Percent.Positive 54.18 54.57 56.02
21 56.21 56.13
22 Percent.Negative 45.82 45.43 43.98
23 43.79 43.87
24 Profit.Factor 1.23 1.20 1.23
25 1.19 1.19
Avg.Win.Day 115.75 129.44 118.06
26 102.66 109.32
27 Med.Win.Day 96.00 104.99 98.53
28 88.60 88.28
29 Avg.Losing.Day -111.34 -129.57 -122.13
-110.91 -117.36
30
Med.Losing.Day -86.50 -92.48 -89.56
31 -82.30 -87.95
32 Avg.Daily.PL 11.69 11.78 12.44
33 9.13 9.87
34 Med.Daily.PL 16.38 16.83 21.71
21.41 20.15
35 Std.Dev.Daily.PL.1 147.33 171.22 159.08
36 134.32 147.41
37 Ann.Sharpe 1.26 1.09 1.24
38 1.08 1.06
39 Max.Drawdown -2338.43 -2430.09 -2168.66 -2982.50
-2904.73
40 Profit.To.Max.Draw 6.58 6.41 7.47
41 3.97 4.35
42 Avg.WinLoss.Ratio 1.04 1.00 0.97
43 0.93 0.93
Med.WinLoss.Ratio 1.11 1.14 1.10
44 1.08 1.00
45 Max.Equity 15968.41 16789.50 17435.72 13048.24
46 13773.99
47 Min.Equity -17.93 -188.46 0.00
48 -393.45 -100.42
End.Equity 15389.57 15578.88 16204.18 11846.58
49 12646.07
50
51 EWH EWJ EWS
52 EWT EWU
53 Total.Net.Profit 12085.74 7457.81 13356.23
54 6906.24 7492.71
Total.Days 1246.00 1108.00 1302.00
55 1173.00 1250.00
56 Winning.Days 667.00 568.00 726.00
57 608.00 670.00
58 Losing.Days 579.00 540.00 576.00
565.00 580.00
59 Avg.Day.PL 9.70 6.73 10.26
60 5.89 5.99
61 Med.Day.PL 12.96 11.70 20.48
62 8.74 15.91
63 Largest.Winner 518.58 677.56 659.34
716.23 557.67
64 Largest.Loser -977.28 -498.25 -1032.26
65 -1114.68 -922.23
66 Gross.Profits 76296.60 72956.55 76089.41 69500.58
67 71225.98
Gross.Losses -64210.86 -65498.74 -62733.18 -62594.34
68
-63733.27
69 Std.Dev.Daily.PL 148.85 163.57 140.94
70 149.35 139.57
71 Percent.Positive 53.53 51.26 55.76
72 51.83 53.60
Percent.Negative 46.47 48.74 44.24
73 48.17 46.40
74 Profit.Factor 1.19 1.11 1.21
75 1.11 1.12
76 Avg.Win.Day 114.39 128.44 104.81
77 114.31 106.31
Med.Win.Day 89.18 99.48 82.86
78 92.69 91.01
79 Avg.Losing.Day -110.90 -121.29 -108.91
80 -110.79 -109.88
81 Med.Losing.Day -82.75 -92.33 -86.52
-80.77 -82.34
82 Avg.Daily.PL 9.70 6.73 10.26
83 5.89 5.99
84 Med.Daily.PL 12.96 11.70 20.48
8.74 15.91
85
Std.Dev.Daily.PL.1 148.85 163.57 140.94
86 149.35 139.57
87 Ann.Sharpe 1.03 0.65 1.16
88 0.63 0.68
89 Max.Drawdown -2229.93 -4036.69 -2021.12 -3147.85
-2599.57
90 Profit.To.Max.Draw 5.42 1.85 6.61
91 2.19 2.88
92 Avg.WinLoss.Ratio 1.03 1.06 0.96
93 1.03 0.97
94 Med.WinLoss.Ratio 1.08 1.08 0.96
1.15 1.11
95 Max.Equity 13043.23 8395.92 14415.11
96 6906.24 9160.15
97 Min.Equity -33.87 -274.69 -699.37
98 -788.28 -327.38
End.Equity 12085.74 7457.81 13356.23
99 6906.24 7492.71
100
101 EWY EWZ EZU
102 IEF IGE
103 Total.Net.Profit 9736.49 16814.33 13135.27 17932.39
104 13435.41
Total.Days 1245.00 1368.00 1288.00
105 1220.00 1288.00
106 Winning.Days 687.00 770.00 702.00
107 647.00 710.00
108 Losing.Days 558.00 598.00 586.00
109 573.00 578.00
Avg.Day.PL 7.82 12.29 10.20
110 14.70 10.43
111 Med.Day.PL 19.58 24.81 18.68
112 15.24 22.02
113 Largest.Winner 551.45 638.31 641.61
718.28 641.62
114 Largest.Loser -850.59 -873.96 -1149.58
115 -629.94 -585.35
116 Gross.Profits 78146.89 91623.76 78313.24 89399.23
117 87064.44
118 Gross.Losses -68410.40 -74809.43 -65177.97 -71466.84
-73629.04
119 Std.Dev.Daily.PL 155.20 158.93 147.61
120 169.04 160.78
121 Percent.Positive 55.18 56.29 54.50
122 53.03 55.12
Percent.Negative 44.82 43.71 45.50
123
46.97 44.88
124 Profit.Factor 1.14 1.22 1.20
125 1.25 1.18
126 Avg.Win.Day 113.75 118.99 111.56
127 138.18 122.63
Med.Win.Day 92.47 99.96 89.60
128 111.01 100.82
129 Avg.Losing.Day -122.60 -125.10 -111.23
130 -124.72 -127.39
131 Med.Losing.Day -87.06 -96.89 -82.02
132 -100.19 -101.57
Avg.Daily.PL 7.82 12.29 10.20
133 14.70 10.43
134 Med.Daily.PL 19.58 24.81 18.68
135 15.24 22.02
136 Std.Dev.Daily.PL.1 155.20 158.93 147.61
169.04 160.78
137 Ann.Sharpe 0.80 1.23 1.10
138 1.38 1.03
139 Max.Drawdown -2689.16 -2599.61 -2764.56 -1475.57
-2439.52
140
Profit.To.Max.Draw 3.62 6.47 4.75
141 12.15 5.51
142 Avg.WinLoss.Ratio 0.93 0.95 1.00
143 1.11 0.96
144 Med.WinLoss.Ratio 1.06 1.03 1.09
1.11 0.99
145 Max.Equity 9736.49 17637.00 14909.94 18695.22
146 13435.41
147 Min.Equity -392.89 0.00 0.00
148 -127.86 -654.86
149 End.Equity 9736.49 16814.33 13135.27 17932.39
13435.41
150
151 IYR IYZ LQD
152 RWR SHY
153 Total.Net.Profit 13555.67 7434.57 12091.15 13301.24
154 31955.85
Total.Days 1312.00 1213.00 1290.00
155 1324.00 1403.00
156 Winning.Days 711.00 641.00 720.00
157 713.00 796.00
158 Losing.Days 601.00 572.00 570.00
159 611.00 607.00
Avg.Day.PL 10.33 6.13 9.37
160 10.05 22.78
161 Med.Day.PL 13.97 11.30 13.65
162 14.07 23.56
163 Largest.Winner 690.00 539.17 414.09
164 701.08 743.43
Largest.Loser -995.13 -747.09 -1607.99
165 -999.24 -896.22
166 Gross.Profits 80483.61 60768.48 65938.33 81751.44
167 104570.57
168 Gross.Losses -66927.95 -53333.91 -53847.18 -68450.20
-72614.72
169 Std.Dev.Daily.PL 150.96 123.03 125.94
170 153.36 167.92
171 Percent.Positive 54.19 52.84 55.81
172 53.85 56.74
173 Percent.Negative 45.81 47.16 44.19
46.15 43.26
174 Profit.Factor 1.20 1.14 1.22
175 1.19 1.44
176 Avg.Win.Day 113.20 94.80 91.58
177 114.66 131.37
Med.Win.Day 95.20 70.59 74.82
178
94.50 95.01
179 Avg.Losing.Day -111.36 -93.24 -94.47
180 -112.03 -119.63
181 Med.Losing.Day -79.94 -70.65 -73.01
182 -78.78 -95.52
Avg.Daily.PL 10.33 6.13 9.37
183 10.05 22.78
184 Med.Daily.PL 13.97 11.30 13.65
185 14.07 23.56
Std.Dev.Daily.PL.1 150.96 123.03 125.94
153.36 167.92
Ann.Sharpe 1.09 0.79 1.18
1.04 2.15
Max.Drawdown -2623.12 -2791.39 -3237.00 -2463.55
-1756.33
Profit.To.Max.Draw 5.17 2.66 3.74
5.40 18.19
Avg.WinLoss.Ratio 1.02 1.02 0.97
1.02 1.10
Med.WinLoss.Ratio 1.19 1.00 1.02
1.20 0.99
Max.Equity 15608.04 8133.92 12349.90 14777.73
32781.60
Min.Equity -473.13 -520.47 -146.25
-314.85 -63.98
End.Equity 13555.67 7434.57 12091.15 13301.24
31955.85
So a cash Sharpe (a conservative estimate) a healthy amount higher than 1. Now lets look at something
interesting. Remember those base instrument correlations at the beginning of this post?
1
2 #Portfolio comparisons to SPY
instRets <- PortfReturns(account.st)
3
4 #Correlations
5 instCors <- cor(instRets)
6 diag(instRets) <- NA
7 corMeans <- rowMeans(instCors, na.rm=TRUE)
names(corMeans) <- gsub(".DailyEndEq", "", names(corMeans))
8 print(round(corMeans,3))
9 mean(corMeans)
10
1
2 > print(round(corMeans,3))
EFA EPP EWA EWC EWG EWH EWJ EWS EWT EWU
3 0.503 0.456 0.406 0.392 0.457 0.392 0.354 0.417 0.355 0.452
4 EWY EWZ EZU IEF IGE IYR IYZ LQD RWR SHY
5 0.399 0.387 0.471 -0.001 0.384 0.322 0.375 0.078 0.313 -0.013
6 TLT XLB XLE XLF XLI XLK XLP XLU XLV XLY
0.009 0.447 0.362 0.407 0.434 0.407 0.333 0.314 0.336 0.412
7
8 > mean(corMeans)
9 [1] 0.3452772
10
With this (somewhat) simplistic market timer, the correlations of some dangerously correlated instruments
have been, on aggregate, sliced by more than 50%. Pretty impressive, no?
Notice how, with better order sizing, that the recession barely even scratched the equity curve?
First off: annualized Sharpe Ratio above 1.4. Daily returns. Next, something that personally impresses me:
annualized return higher than maximum drawdown. My interpretation of this is that while you *may* have
the occasional down year, on average, even in the absolute worst case scenario, it takes you less than a year
to recover from your absolute worst drawdown.
And keep in mind, this is with 30 instruments, most of them highly correlated!
Lastly, lets look at an individual instruments equity curve and how the position-sizing algorithm helps out.
Remember how even the average win to loss ratios went up? This is why. Notice the height of the blue
rectangles (position sizes up, duration of trade across), in relation to the ATR (the blue line across the
bottom). In the depths of the financial crisis, even though the actual strategy wasnt intelligent enough to
know to keep out, through the use of ATR position sizing, it reduced trade sizes considerably. The result was
a failsafe that prevented a great deal of damage to a system that had no idea it was trading against a massive
longer time horizon trend going in the opposite direction.
Of course, this does not mean that ATR order sizing on entry is a complete catch-all for everything that can
go wrong. At longer time frames, an entry signals calculation may have little relevance to the actual risk of
the position currently. On this strategy, because I was using a 20-day period, I felt that the issue of
rebalancing wouldnt be a major factor, because old positions were exited, and new positions were entered at
a good pace (about 1,000 trades in 7 years is 142 trades per year on average, over 30 instruments, thats
around 4 trades per year per instrument, which puts each trade at a couple of months on average, and
considering that rebalancing can happen monthly to quarterly, rebalancing isnt an issue at this frequency).
However, this does not apply to longer time frames.
For instance, if the period (both for the Trend Vigor and for the ATR computation) were changed to 60 days,
these are the results:
trade stats:
The trade stats look better mainly because of the strength of the indicator to identify periods of good entries.
However, the advantages end there.
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 1.099297
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.1393566
6 > maxDrawdown(portfRets)
7 [1] 0.183505
8
Turns out, a fair bit. The Sharpe Ratio comes down quite a bit, the returns are slightly lower, but the
drawdown is much higher, crossing back under that boundary of annualized returns higher than maximum
drawdown. Of course, this isnt all attributable to stale ATR calculations, as order sizing and rebalancing
wont save a strategy from holding onto bad trades, but not rebalancing certainly does nobody any favors at
the longer holding timeframes.
Finally, is the equity curve comparison taken further, to the original 100-day period.
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.8681733
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.1366685
6 > maxDrawdown(portfRets)
7 [1] 0.2241631
8
Definitely not a good look. Sharpe back under 1, and drawdowns at more than 20%? What kind of returns
can we get for such drawdowns? Wait until the end of the post, and Ill show you.
The beginning of the volatility preceding the financial crisis caused some violent drawdowns (notice the
actual bear market didnt cause so much). Could some of this damage have been mitigated?
So, with all of this in mind, as promised, lets see what sort of returns we can get for the 20 day period.
What if we took our original portfolio, and simply doubled the risk allocationin other words, leveraged the
original strategy 2:1?
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 1.465359
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.3088355
6 > maxDrawdown(portfRets)
7 [1] 0.2109036
8
About 31% annualized returns, with slightly over 20% drawdowns. Considering that the stock market has
much higher drawdowns, this is a pretty good risk-return profile. If were willing to go above 30%
drawdown, this is the resulting equity curve:
At this point, the equity curve for SPY becomes almost a flat line by comparisonnot because its drawdowns
are smaller (at the height of the crisis, they were over 60%), but because of proper position sizing. Here are
the corresponding three statistics:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 1.493416
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.4722279
6
> maxDrawdown(portfRets)
7 [1] 0.3022385
8
Of course, beyond this point, if one is willing to tolerate drawdowns, the annualized returns just start to get
silly, but the drawdowns start to really show in 2010, when the fact that this systems exits could still be
improved, and its instrument selection (with a base correlation across all instruments of .7) starts quickly
putting on the brakes on going beyond this point. For now anyway.
The last post gave a cursory overview of implementing a basic quantstrat strategy, and an introduction to Dr.
Ehlerss Trend Vigor indicator, with a less than intelligent exit. This post will delve further into this
indicator, and how it can be made to act as a long-term trend follower to a short-term momentum trader, to
probably anything in between, with a parameter called delta. Dr. Ehlerss indicators often use some
trigonometric math, the rationale of which is based on signal processing theory, and often times, that math
uses a user-input parameter denoted by a Greek letter, such as alpha, gamma, delta, and so forth.
Rather than leave these esoteric parameters completely unexplored, out of sheer curiosity, I decided to alter
the delta parameter. By tuning the delta parameter, its possible to tune how fast Trend Vigor oscillates, and
thereby, how fast it judges that newer observations are part of a new trend (but at the same time, how much
longer the indicator classifies a trend as ongoing). This would allow a user to tune the Trend Vigor indicator
in a similar fashion as other trend following indicators to capture different sorts of market phenomena, such
as long term trend-following/filtering, to shorter-term phenomena. On the *downside*, this means that as a
user tunes the period and the delta, that he or she could easily overfit.
Furthermore, this blog post is going to make the portfolio analysis portion of the analytics make far more
sense than a roughly-sketched, price-weighted equity curve. The results were that the benefits of
diversification werecertainly less than expected. This backtest is going to implement an equal-weight
asset allocation scheme, which will also showcase that quantstrat can implement custom order-sizing
functions. Equal-weight asset allocation schemes can be improved upon, but thats a discussion for a future
time.
The strategy for this blog post is slightly different, as well. In this case, the strategy will use a more
intelligent exit, by selling when the indicator crosses under its one-day lagged trigger, and buying not only
when the indicator crosses above 1, but also if it should cross over its one-day lagged computation, but still
be above 1. This is so that the strategy would stay in what it perceives as a trend, rather than sell and not
come back until the computation dipped back below 1 (an arbitrary value, to be sure) and came back up
again. The idea for the exit came from one of Dr. Ehlerss presentations, in which he presented several more
trend-following smoothers, some of which are already in the DSTrading package (FRAMA, KAMA,
VIDYA which I call VIDA, and the Ehlers filters), and which Ill visit in the future. The link to the
presentation can be found here:
http://www.mesasoftware.com/Seminars/TradeStation%20World%2005.pdf
One last detail to mind is that between the last blog post and this one, I changed the Trend Vigor indicator to
backfill any leading NAs as zero, since this is an indicator centered around zero. This will allow the strategy
to instantly jump into the middle of a trend, if one was under way the moment the first true value of the
indicator was computed.
My custom order-sizing function is found in my IKTrading package, which is my own personal collection of
non-Ehlers indicators, miscellaneous tools, custom order-sizing functions, and generally serves as a catch-all
for trading ideas I come across that dont have enough content behind them to warrant a package of their
own.
Delta is a parameter involved in an involved trigonometric calculation, lending to the idea that there may be
some sort of circular relationship with delta, rather than simply a trend sensitivity as I described. That
stated, considering that the default value that Dr. Ehlers used was .2, it implies that the parameter should
probably be held between 0 and 1. Once beyond that, theres no guarantee that the algorithm even runs (at
some point, it will just make later computations come out as NaNs).
The first function is a basic AND operator for signals in quantstrat. While quantstrat itself has a signal
function called sigFormula, I myself am not exactly a fan of it. The second function, however, is a bit more
innovative, in that it allows a strategy to control positions by their total current dollar value. What it doesnt
do, however, is remember the initial dollar value of a position. That is, if you invested $10,000, with a limit
of $20,000 in a position, and your initial position went to $11,000, this function would only allow up to
another $9,000 worth of the instrument to be ordered. Negative quantities should be used for shorting. That
is, if the goal is to short a security, rather than input 20000 as the maxSize and 10000 as the tradeSize, the
parameters should be -20000 and -10000, respectively.
Again, the order sizing function takes into account the value of the position at the time of a new order, not
the original value of the transaction. That is, the function does not do this: You have an existing position
that you entered into at some previous date, and your position then was $10,000, but now its $15,000, but
Ill order another $10,000 anyway. The function currently does this: You have a position with a current
market value of $15,000. You want to add $10,000 to your position, but have a limit of $20,000. Ill order
the nearest integer quantity to the difference ($5,000).
1 require(DSTrading)
2 require(IKTrading)
require(quantstrat)
3
4
5 #to rerun the strategy, rerun everything below this line
6 source("demoData.R") #contains all of the data-related boilerplate.
7
8 #trade sizing and initial equity settings
9 tradeSize <- 10000
initEq <- tradeSize*length(symbols)
10
11 strategy.st <- portfolio.st <- account.st <- "TVI_TF_2"
12 rm.strat(portfolio.st)
13 rm.strat(strategy.st)
14 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
15 initAcct(account.st, portfolios=portfolio.st, initDate=initDate,
currency='USD',initEq=initEq)
16 initOrders(portfolio.st, initDate=initDate)
17 strategy(strategy.st, store=TRUE)
18
19 #parameters (trigger lag unchanged, defaulted at 1)
20 delta=.2
21 period=100
22 #indicators
23 add.indicator(strategy.st, name="TVI", arguments=list(x=quote(Cl(mktdata)),
24 period=period, delta=delta), label="TVI")
25
26 #signals
27 add.signal(strategy.st, name="sigThreshold",
arguments=list(threshold=1, column="vigor.TVI",
28 relationship="gte", cross=FALSE),
29 label="TVIgtThresh")
30 add.signal(strategy.st, name="sigComparison",
31 arguments=list(columns=c("vigor.TVI","trigger.TVI"),
32 relationship="gt"),
label="TVIgtLag")
33 add.signal(strategy.st, name="sigAND",
34 arguments=list(columns=c("TVIgtThresh","TVIgtLag"),
35 cross=TRUE),
36
37
38
39
40
41 label="longEntry")
42 add.signal(strategy.st, name="sigCrossover",
43 arguments=list(columns=c("vigor.TVI","trigger.TVI"),
44 relationship="lt"),
label="longExit")
45
46 #rules
47 add.rule(strategy.st, name="ruleSignal",
48 arguments=list(sigcol="longEntry", sigval=TRUE, orderqty=100,
49 ordertype="market", orderside="long",
replace=FALSE, prefer="Open", osFUN=osMaxDollar,
50 tradeSize=10000, maxSize=10000),
51 type="enter", path.dep=TRUE)
52
53 add.rule(strategy.st, name="ruleSignal",
54 arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
55 ordertype="market", orderside="long", replace=FALSE,
prefer="Open"),
56 type="exit", path.dep=TRUE)
57
58
59 #apply strategy
60 t1 <- Sys.time()
61 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
62 t2 <- Sys.time()
print(t2-t1)
63
64 #set up analytics
65 updatePortf(portfolio.st)
66 dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
67 updateAcct(portfolio.st,dateRange)
68 updateEndEq(account.st)
69
70
71
72
73
For the moment, were going to go with the previous settings of delta = .2, and period = 100, but this time,
using an order size of the nearest integer quantity that $10,000 can buy of that particular security. Also,
were going to exit when the indicator shifts from moving up or flat (if its at 2), to downward, and buying
whenever it shifts from moving downward to moving upward (but still has a value above 1). Also, note that
now, there is an initial equity allocation, which will allow for the computation of portfolio returns.
Trade Stats:
1 #tradeStats
2 tStats <- tradeStats(Portfolios = portfolio.st, use="trades", inclZeroDays=FALSE)
3 tStats[,4:ncol(tStats)] <- round(tStats[,4:ncol(tStats)], 2)
print(data.frame(t(tStats[,-c(1,2)])))
4
1 EFA EPP EWA EWC EWG
Num.Txns 21.00 15.00 27.00 37.00 17.00
2
Num.Trades 11.00 8.00 14.00 19.00 9.00
3 Net.Trading.PL 3389.91 6323.15 6933.26 2213.97 4983.35
Avg.Trade.PL 308.17 790.39 495.23 116.52 553.71
4
Med.Trade.PL 298.45 549.42 107.35 110.98 206.57
5 Largest.Winner 869.60 2507.07 3410.61 1607.61 2600.90
6 Largest.Loser -297.25 -675.35 -452.79 -1191.14 -343.19
7 Gross.Profits 4073.17 7327.60 8446.23 5734.53 5847.80
8 Gross.Losses -683.26 -1004.45 -1512.97 -3520.55 -864.45
Std.Dev.Trade.PL 425.12 1189.75 1118.82 648.91 957.64
9 Percent.Positive 72.73 62.50 57.14 63.16 66.67
10 Percent.Negative 27.27 37.50 42.86 36.84 33.33
11 Profit.Factor 5.96 7.30 5.58 1.63 6.76
12 Avg.Win.Trade 509.15 1465.52 1055.78 477.88 974.63
13 Med.Win.Trade 485.27 1680.62 448.04 313.63 747.17
Avg.Losing.Trade -227.75 -334.82 -252.16 -502.94 -288.15
14 Med.Losing.Trade -266.17 -184.29 -220.96 -440.34 -274.72
15 Avg.Daily.PL 323.18 923.99 490.48 82.59 657.26
16 Med.Daily.PL 305.15 1057.21 54.49 100.34 465.90
17 Std.Dev.Daily.PL 445.03 1218.54 1164.36 650.14 968.40
Ann.Sharpe 11.53 12.04 6.69 2.02 10.77
18 Max.Drawdown -1730.57 -2428.18 -2827.76 -3214.22 -2070.43
19 Profit.To.Max.Draw 1.96 2.60 2.45 0.69 2.41
20 Avg.WinLoss.Ratio 2.24 4.38 4.19 0.95 3.38
21 Med.WinLoss.Ratio 1.82 9.12 2.03 0.71 2.72
22 Max.Equity 3785.99 8030.74 7734.21 4215.37 5775.50
Min.Equity -4.60 -186.62 -399.56 -272.20 -97.78
23 End.Equity 3389.91 6323.15 6933.26 2213.97 4983.35
24
25 EWH EWJ EWS EWT EWU
26 Num.Txns 17.00 19.00 21.00 23.00 24.00
27 Num.Trades 9.00 10.00 11.00 12.00 12.00
28 Net.Trading.PL 6281.71 1861.09 8115.03 1920.39 1113.36
Avg.Trade.PL 697.97 186.11 737.73 160.03 92.78
29 Med.Trade.PL 760.09 18.58 312.10 -180.82 -0.03
30 Largest.Winner 2223.08 2555.03 2736.35 3078.42 1243.53
31 Largest.Loser -496.03 -1392.37 -531.77 -1219.88 -1009.85
32 Gross.Profits 7214.77 4666.47 9381.95 6413.88 3747.87
Gross.Losses -933.06 -2805.38 -1266.92 -4493.49 -2634.51
33 Std.Dev.Trade.PL 959.96 1123.51 1178.37 1280.43 695.45
34 Percent.Positive 66.67 60.00 63.64 33.33 50.00
35 Percent.Negative 33.33 40.00 36.36 66.67 50.00
36 Profit.Factor 7.73 1.66 7.41 1.43 1.42
37 Avg.Win.Trade 1202.46 777.75 1340.28 1603.47 624.64
Med.Win.Trade 1046.89 314.63 1388.79 1505.03 541.51
38 Avg.Losing.Trade -311.02 -701.35 -316.73 -561.69 -439.08
39 Med.Losing.Trade -434.34 -591.09 -287.07 -478.14 -358.87
40 Avg.Daily.PL 785.55 142.07 780.29 69.58 92.78
41 Med.Daily.PL 857.90 6.87 291.84 -320.95 -0.03
Std.Dev.Daily.PL 987.05 1182.47 1233.16 1302.09 695.45
42
Ann.Sharpe 12.63 1.91 10.04 0.85 2.12
43 Max.Drawdown -2240.59 -3140.49 -2181.89 -4191.97 -2682.76
44 Profit.To.Max.Draw 2.80 0.59 3.72 0.46 0.42
45 Avg.WinLoss.Ratio 3.87 1.11 4.23 2.85 1.42
46 Med.WinLoss.Ratio 2.41 0.53 4.84 3.15 1.51
Max.Equity 7033.54 3474.11 8268.38 3740.33 3093.03
47 Min.Equity -101.98 -7.71 -715.49 -451.64 -298.31
48 End.Equity 6281.71 1861.09 8115.03 1920.39 1113.36
49
50 EWY EWZ EZU IEF IGE
51 Num.Txns 23.00 30.00 18.00 24.00 31.00
52 Num.Trades 12.00 15.00 9.00 12.00 16.00
Net.Trading.PL 11094.89 11017.62 3581.04 558.64 1425.90
53 Avg.Trade.PL 924.57 734.51 397.89 46.55 89.12
54 Med.Trade.PL 517.60 474.34 334.80 -21.76 -12.94
55 Largest.Winner 3654.10 3907.76 1415.98 544.99 2085.48
56 Largest.Loser -497.47 -1034.75 -442.68 -286.86 -1357.10
Gross.Profits 12382.75 14795.36 4311.30 1551.73 5886.15
57 Gross.Losses -1287.86 -3777.74 -730.26 -993.09 -4460.25
58 Std.Dev.Trade.PL 1396.50 1528.15 616.20 276.42 860.95
59 Percent.Positive 66.67 60.00 66.67 50.00 50.00
Percent.Negative 33.33 40.00 33.33 50.00 50.00
60 Profit.Factor 9.61 3.92 5.90 1.56 1.32
61 Avg.Win.Trade 1547.84 1643.93 718.55 258.62 735.77
62 Med.Win.Trade 1239.91 1050.52 707.66 245.34 614.69
63 Avg.Losing.Trade -321.97 -629.62 -243.42 -165.52 -557.53
Med.Losing.Trade -312.32 -654.53 -144.05 -161.22 -478.79
64 Avg.Daily.PL 946.10 734.51 397.89 46.55 29.55
65 Med.Daily.PL 347.37 474.34 334.80 -21.76 -95.89
66 Std.Dev.Daily.PL 1462.58 1528.15 616.20 276.42 856.36
67 Ann.Sharpe 10.27 7.63 10.25 2.67 0.55
68 Max.Drawdown -3706.83 -4468.28 -1746.39 -1033.18 -4134.77
Profit.To.Max.Draw 2.99 2.47 2.05 0.54 0.34
69 Avg.WinLoss.Ratio 4.81 2.61 2.95 1.56 1.32
70 Med.WinLoss.Ratio 3.97 1.60 4.91 1.52 1.28
71 Max.Equity 11151.40 14325.48 4132.73 1324.33 3847.27
72 Min.Equity -512.94 -669.31 -127.83 -496.42 -360.99
73 End.Equity 11094.89 11017.62 3581.04 558.64 1425.90
74 IYR IYZ LQD RWR SHY
75 Num.Txns 20.00 21.00 26.00 18.00 20.00
76 Num.Trades 10.00 10.00 13.00 9.00 10.00
77 Net.Trading.PL 3551.70 565.56 278.51 3846.21 1431.72
78 Avg.Trade.PL 355.17 56.56 21.42 427.36 143.17
Med.Trade.PL 285.85 -171.99 17.47 340.07 28.08
79 Largest.Winner 1457.62 1789.36 435.59 1465.93 1298.58
80 Largest.Loser -397.79 -832.16 -516.47 -611.82 -79.25
81 Gross.Profits 4524.52 2743.13 1145.51 4698.97 1605.02
82 Gross.Losses -972.82 -2177.58 -867.00 -852.76 -173.30
83 Std.Dev.Trade.PL 623.44 729.51 238.26 665.66 410.76
Percent.Positive 70.00 30.00 69.23 77.78 60.00
84 Percent.Negative 30.00 70.00 30.77 22.22 40.00
85 Profit.Factor 4.65 1.26 1.32 5.51 9.26
86 Avg.Win.Trade 646.36 914.38 127.28 671.28 267.50
87 Med.Win.Trade 497.94 737.25 32.03 531.59 55.68
88 Avg.Losing.Trade
Med.Losing.Trade
-324.27 -311.08 -216.75 -426.38 -43.32
-298.46 -242.80 -165.48 -426.38 -34.23
89 Avg.Daily.PL 355.17 -19.08 21.42 427.36 143.17
90 Med.Daily.PL 285.85 -211.89 17.47 340.07 28.08
91 Std.Dev.Daily.PL 623.44 730.99 238.26 665.66 410.76
92 Ann.Sharpe 9.04 -0.41 1.43 10.19 5.53
Max.Drawdown -2117.09 -2239.16 -853.57 -2500.81 -206.62
93 Profit.To.Max.Draw 1.68 0.25 0.33 1.54 6.93
94 Avg.WinLoss.Ratio 1.99 2.94 0.59 1.57 6.17
95 Med.WinLoss.Ratio 1.67 3.04 0.19 1.25 1.63
96 Max.Equity 4939.41 1994.72 729.12 5125.30 1496.21
97 Min.Equity 0.00 -803.23 -815.95 0.00 -178.23
End.Equity 3551.70 565.56 278.51 3846.21 1431.72
98
99 TLT XLB XLE XLF XLI
100 Num.Txns 24.00 23.00 35.00 22.00 25.00
101 Num.Trades 12.00 12.00 18.00 11.00 13.00
102 Net.Trading.PL 1685.94 6026.21 -721.62 1073.90 3270.62
103 Avg.Trade.PL 140.49 502.18 -40.09 97.63 251.59
Med.Trade.PL 43.43 511.49 -148.36 53.82 -3.41
104 Largest.Winner 1244.42 2158.21 2243.05 1390.72 1358.00
105 Largest.Loser -595.88 -750.40 -1151.80 -659.29 -378.11
106 Gross.Profits 2725.69 7456.59 4402.78 2346.46 4152.62
107 Gross.Losses -1039.76 -1430.38 -5124.39 -1272.56 -882.00
Std.Dev.Trade.PL 468.08 819.50 771.46 512.62 512.44
108 Percent.Positive 58.33 66.67 22.22 63.64 46.15
109 Percent.Negative 41.67 33.33 77.78 36.36 53.85
110 Profit.Factor 2.62 5.21 0.86 1.84 4.71
111 Avg.Win.Trade 389.38 932.07 1100.69 335.21 692.10
112 Med.Win.Trade 195.01 997.33 857.45 226.90 589.41
Avg.Losing.Trade -207.95 -357.59 -366.03 -318.14 -126.00
113 Med.Losing.Trade -100.91 -301.05 -193.50 -239.29 -59.12
114 Avg.Daily.PL 140.49 499.71 -97.77 97.63 241.51
Med.Daily.PL 43.43 511.49 -152.39 53.82 -18.57
115 Std.Dev.Daily.PL 468.08 887.28 754.14 512.62 533.87
116 Ann.Sharpe 4.76 8.94 -2.06 3.02 7.18
117 Max.Drawdown -2020.60 -2343.85 -5364.07 -1497.18 -1480.30
118 Profit.To.Max.Draw 0.83 2.57 -0.13 0.72 2.21
Avg.WinLoss.Ratio 1.87 2.61 3.01 1.05 5.49
119 Med.WinLoss.Ratio 1.93 3.31 4.43 0.95 9.97
120 Max.Equity 3278.94 6034.85 3638.56 2209.75 4344.89
121 Min.Equity -1033.35 -234.71 -1725.52 -353.34 0.00
122 End.Equity 1685.94 6026.21 -721.62 1073.90 3270.62
123
124 XLK XLP XLU XLV XLY
Num.Txns 25.00 25.00 16.00 21.00 21.00
125 Num.Trades 13.00 12.00 8.00 11.00 11.00
126 Net.Trading.PL 2786.08 1691.84 2071.68 329.36 1596.47
127 Avg.Trade.PL 214.31 140.99 258.96 29.94 145.13
128 Med.Trade.PL 19.79 27.51 -175.95 -152.51 42.65
129 Largest.Winner
Largest.Loser
2153.72 1339.05 2416.22 1492.98 914.92
-586.52 -1482.72 -1180.81 -511.62 -228.81
130 Gross.Profits 4276.67 3888.90 4456.67 2179.92 1974.22
131 Gross.Losses -1490.59 -2197.06 -2385.00 -1850.56 -377.75
132 Std.Dev.Trade.PL 687.96 747.73 1254.25 552.25 310.09
133 Percent.Positive 53.85 50.00 37.50 36.36 63.64
Percent.Negative 46.15 50.00 62.50 63.64 36.36
134 Profit.Factor 2.87 1.77 1.87 1.18 5.23
135 Avg.Win.Trade 610.95 648.15 1485.56 544.98 282.03
136 Med.Win.Trade 437.51 594.68 1966.92 301.18 180.59
137 Avg.Losing.Trade -248.43 -366.18 -477.00 -264.37 -94.44
138 Med.Losing.Trade -220.94 -132.84 -397.83 -238.91 -73.86
Avg.Daily.PL 195.71 125.23 258.96 10.26 149.62
139 Med.Daily.PL -14.41 -24.11 -175.95 -187.75 39.16
140 Std.Dev.Daily.PL 715.13 782.14 1254.25 578.04 326.49
141 Ann.Sharpe 4.34 2.54 3.28 0.28 7.28
142 Max.Drawdown -1984.37 -2106.40 -2360.48 -1327.16 -1540.71
143 Profit.To.Max.Draw
Avg.WinLoss.Ratio
1.40
2.46
0.80
1.77
0.88
3.11
0.25
2.06
1.04
2.99
144 Med.WinLoss.Ratio 1.98 4.48 4.94 1.26 2.45
145 Max.Equity 2883.68 2116.10 4325.65 653.11 2920.67
146 Min.Equity -739.92 -49.59 -1004.33 -1293.46 -393.05
147 End.Equity 2786.08 1691.84 2071.68 329.36 1596.47
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
Already, we can see that the equal dollar weighting scheme may not have been the greatest. For instance, the
gross profits (that is, total dollars gained, not net profit) vary wildly. For instance, the command:
1 summary(tStats$Gross.Profit)
Which means that either a few instruments were just fortunate, or more likely, that $10,000 in different
instruments buys different levels of risk. Ideally, with trend following (or any investment strategy, for that
matter), order sizing should be done with some measure of volatility in mind. Jeff Swanson of System
Trader Success uses position sizing scaled with ATR, for instance.
1#dailyStats
2dStats <- dailyStats(Portfolios = portfolio.st, use="Equity")
3rownames(dStats) <- gsub(".DailyEndEq","", rownames(dStats))
4print(data.frame(t(dStats)))
1 EFA EPP EWA
EWC EWG
2
Total.Net.Profit 3389.91 6323.15 6933.26
3 2213.97 4983.35
4 Total.Days 715.00 860.00 814.00
5 720.00 606.00
6 Winning.Days 397.00 472.00 457.00
394.00 337.00
7 Losing.Days 318.00 388.00 357.00
8 326.00 269.00
9 Avg.Day.PL 4.74 7.35 8.52
10 3.07 8.22
11 Med.Day.PL 11.05 13.86 24.93
16.08 17.82
12 Largest.Winner 349.71 520.81 651.16
13 383.73 430.91
14 Largest.Loser -441.39 -658.33 -737.68
15 -533.75 -511.74
Gross.Profits 29341.17 51407.19 56127.72 35108.16
16 35014.78
17 Gross.Losses -25951.26 -45084.04 -49194.46 -32894.18
18 -30031.43
19 Std.Dev.Daily.PL 101.85 151.01 173.50
20 121.89 138.19
Percent.Positive 55.52 54.88 56.14
21 54.72 55.61
22 Percent.Negative 44.48 45.12 43.86
45.28 44.39
23
Profit.Factor 1.13 1.14 1.14
24 1.07 1.17
25 Avg.Win.Day 73.91 108.91 122.82
26 89.11 103.90
27 Med.Win.Day 62.74 87.40 100.18
76.90 86.18
28 Avg.Losing.Day -81.61 -116.20 -137.80
29 -100.90 -111.64
30 Med.Losing.Day -60.59 -76.11 -98.03
31 -70.68 -84.29
32 Avg.Daily.PL 4.74 7.35 8.52
3.07 8.22
33 Med.Daily.PL 11.05 13.86 24.93
34 16.08 17.82
35 Std.Dev.Daily.PL.1 101.85 151.01 173.50
36 121.89 138.19
Ann.Sharpe 0.74 0.77 0.78
37 0.40 0.94
38 Max.Drawdown -1730.57 -2428.18 -2827.76 -3214.22
39 -2070.43
40 Profit.To.Max.Draw 1.96 2.60 2.45
41 0.69 2.41
Avg.WinLoss.Ratio 0.91 0.94 0.89
42 0.88 0.93
43 Med.WinLoss.Ratio 1.04 1.15 1.02
44 1.09 1.02
45 Max.Equity 3785.99 8030.74 7734.21
46 4215.37 5775.50
Min.Equity -4.60 -186.62 -399.56
47 -272.20 -97.78
48 End.Equity 3389.91 6323.15 6933.26
49 2213.97 4983.35
50
51 EWH EWJ EWS
EWT EWU
52 Total.Net.Profit 6281.71 1861.09 8115.03
53 1920.39 1113.36
54 Total.Days 694.00 499.00 853.00
55 554.00 639.00
56 Winning.Days 365.00 264.00 469.00
282.00 336.00
57 Losing.Days 329.00 235.00 384.00
58 272.00 303.00
59 Avg.Day.PL 9.05 3.73 9.51
60 3.47 1.74
Med.Day.PL 13.35 13.75 19.75
61
8.49 9.84
62 Largest.Winner 793.54 550.75 760.20
63 603.79 393.65
64 Largest.Loser -821.31 -560.56 -967.53
65 -606.81 -500.81
Gross.Profits 49928.51 29574.53 56989.19 36177.69
66 28874.40
67 Gross.Losses -43646.80 -27713.44 -48874.16 -34257.30
68 -27761.04
69 Std.Dev.Daily.PL 184.78 146.73 167.87
70 164.58 114.57
Percent.Positive 52.59 52.91 54.98
71 50.90 52.58
72 Percent.Negative 47.41 47.09 45.02
73 49.10 47.42
74 Profit.Factor 1.14 1.07 1.17
1.06 1.04
75 Avg.Win.Day 136.79 112.02 121.51
76 128.29 85.94
77 Med.Win.Day 103.78 94.05 89.94
110.89 72.42
78
Avg.Losing.Day -132.67 -117.93 -127.28
79 -125.95 -91.62
80 Med.Losing.Day -93.35 -86.26 -98.90
81 -88.80 -69.88
82 Avg.Daily.PL 9.05 3.73 9.51
3.47 1.74
83 Med.Daily.PL 13.35 13.75 19.75
84 8.49 9.84
85 Std.Dev.Daily.PL.1 184.78 146.73 167.87
86 164.58 114.57
87 Ann.Sharpe 0.78 0.40 0.90
0.33 0.24
88 Max.Drawdown -2240.59 -3140.49 -2181.89 -4191.97
89 -2682.76
90 Profit.To.Max.Draw 2.80 0.59 3.72
91 0.46 0.42
Avg.WinLoss.Ratio 1.03 0.95 0.95
92 1.02 0.94
93 Med.WinLoss.Ratio 1.11 1.09 0.91
94 1.25 1.04
95 Max.Equity 7033.54 3474.11 8268.38
96 3740.33 3093.03
Min.Equity -101.98 -7.71 -715.49
97 -451.64 -298.31
98 End.Equity 6281.71 1861.09 8115.03
99 1920.39 1113.36
100
101 EWY EWZ EZU
102 IEF IGE
Total.Net.Profit 11094.89 11017.62 3581.04
103 558.64 1425.90
104 Total.Days 819.00 938.00 625.00
105 559.00 636.00
106 Winning.Days 457.00 521.00 348.00
282.00 347.00
107 Losing.Days 362.00 417.00 277.00
108 277.00 289.00
109 Avg.Day.PL 13.55 11.75 5.73
110 1.00 2.24
111 Med.Day.PL 28.70 27.38 14.64
1.17 17.39
112 Largest.Winner 802.78 921.65 339.49
113 203.78 400.87
114 Largest.Loser -713.20 -983.67 -593.68
115 -193.81 -486.10
Gross.Profits 70050.73 98473.12 30606.55 11313.46
116
36042.39
117 Gross.Losses -58955.83 -87455.50 -27025.51 -10754.82
118 -34616.50
119 Std.Dev.Daily.PL 206.98 261.11 121.98
120 52.18 138.92
Percent.Positive 55.80 55.54 55.68
121 50.45 54.56
122 Percent.Negative 44.20 44.46 44.32
123 49.55 45.44
124 Profit.Factor 1.19 1.13 1.13
125 1.05 1.04
Avg.Win.Day 153.28 189.01 87.95
126 40.12 103.87
127 Med.Win.Day 124.00 151.02 70.52
128 33.60 90.45
129 Avg.Losing.Day -162.86 -209.73 -97.57
-38.83 -119.78
130 Med.Losing.Day -112.49 -157.93 -69.01
131 -28.87 -100.54
132 Avg.Daily.PL 13.55 11.75 5.73
1.00 2.24
133
Med.Daily.PL 28.70 27.38 14.64
134 1.17 17.39
135 Std.Dev.Daily.PL.1 206.98 261.11 121.98
136 52.18 138.92
137 Ann.Sharpe 1.04 0.71 0.75
0.30 0.26
138 Max.Drawdown -3706.83 -4468.28 -1746.39 -1033.18
139 -4134.77
140 Profit.To.Max.Draw 2.99 2.47 2.05
141 0.54 0.34
142 Avg.WinLoss.Ratio 0.94 0.90 0.90
1.03 0.87
143 Med.WinLoss.Ratio 1.10 0.96 1.02
144 1.16 0.90
145 Max.Equity 11151.40 14325.48 4132.73
146 1324.33 3847.27
Min.Equity -512.94 -669.31 -127.83
147 -496.42 -360.99
148 End.Equity 11094.89 11017.62 3581.04
149 558.64 1425.90
150
151 IYR IYZ LQD
152 RWR SHY
Total.Net.Profit 3551.70 565.56 278.51 3846.21
153 1431.72
154 Total.Days 734.00 573.00 652.00 739.00
155 1113.00
156 Winning.Days 403.00 294.00 346.00 400.00
157 621.00
Losing.Days 331.00 279.00 306.00 339.00
158 492.00
159 Avg.Day.PL 4.84 0.99 0.43
160 5.20 1.29
161 Med.Day.PL 14.36 3.93 3.56
12.15 1.30
162 Largest.Winner 670.92 284.62 121.85
163 697.50 47.06
164 Largest.Loser -589.08 -417.80 -158.88 -604.38
165 -56.73
166 Gross.Profits 38230.24 20613.93 9963.95 39597.03
5662.04
167 Gross.Losses -34678.54 -20048.37 -9685.44 -35750.82
168 -4230.32
169 Std.Dev.Daily.PL 137.96 93.19 39.22
170 142.32 11.76
Percent.Positive 54.90 51.31 53.07
171
54.13 55.80
172 Percent.Negative 45.10 48.69 46.93
173 45.87 44.20
174 Profit.Factor 1.10 1.03 1.03
175 1.11 1.34
Avg.Win.Day 94.86 70.12 28.80
176 98.99 9.12
177 Med.Win.Day 78.84 57.86 23.58
178 76.99 7.16
179 Avg.Losing.Day -104.77 -71.86 -31.65
180 -105.46 -8.60
Med.Losing.Day -71.21 -51.15 -23.52
181 -73.72 -6.71
182 Avg.Daily.PL 4.84 0.99 0.43
183 5.20 1.29
184 Med.Daily.PL 14.36 3.93 3.56
12.15 1.30
185 Std.Dev.Daily.PL.1 137.96 93.19 39.22
142.32 11.76
Ann.Sharpe 0.56 0.17 0.17
0.58 1.74
Max.Drawdown -2117.09 -2239.16 -853.57 -2500.81
-206.62
Profit.To.Max.Draw 1.68 0.25 0.33
1.54 6.93
Avg.WinLoss.Ratio 0.91 0.98 0.91
0.94 1.06
Med.WinLoss.Ratio 1.11 1.13 1.00
1.04 1.07
Max.Equity 4939.41 1994.72 729.12 5125.30
1496.21
Min.Equity 0.00 -803.23 -815.95 0.00
-178.23
End.Equity 3551.70 565.56 278.51 3846.21
1431.72
1 #portfolio cash PL
2 portPL <- .blotter$portfolio.TVI_TF_2$summary$Net.Trading.PL
3
4 #Cash Sharpe
5 (SharpeRatio.annualized(portPL, geometric=FALSE))
1 Net.Trading.PL
2 Annualized Sharpe Ratio (Rf=0%) 0.611745
1
2 instRets <- PortfReturns(account.st)
3 portfRets <- xts(rowMeans(instRets)*ncol(instRets), order.by=index(instRets))
cumPortfRets <- cumprod(1+portfRets)-1
4 firstNonZeroDay <- index(portfRets)[min(which(portfRets!=0))]
5 getSymbols("SPY", from=firstNonZeroDay, to="2010-12-31")
6 SPYrets <- diff(log(Cl(SPY)))[-1]
7 cumSPYrets <- cumprod(1+SPYrets)-1
comparison <- cbind(cumPortfRets, cumSPYrets)
8 colnames(comparison) <- c("strategy", "SPY")
9 chart.TimeSeries(comparison, legend.loc = "topleft",
10 main=paste0("Period=", period, ", Delta=",delta))
11
Here are the Sharpe based on returns, annualized returns, and max drawdown.
1 > SharpeRatio.annualized(portfRets)
[,1]
2
Annualized Sharpe Ratio (Rf=0%) 0.5891719
3 > Return.annualized(portfRets)
4 [,1]
5
Annualized Return 0.04025855
6 > maxDrawdown(portfRets)
7 [1] 0.0979138
8
Essentially, 4% annualized return for a max drawdown of nearly 10%, with a Sharpe Ratio nowhere close to
1. Definitely not the greatest strategy, but nevertheless, compared to SPY, far less volatile, when comparing
equity curves. Once again, however, keep in mind that currently, the Trend Vigor is being used as a market
mode indicator, rather than a dedicated trend follower. Keep that in the back of your mind as you look at the
value of the actual indicator over time.
1 chart.Posn(portfolio.st, "XLB")
2 tmp <- TVI(Cl(XLB), period=period, delta=delta, triggerLag=30)
3 add_TA(tmp$vigor)
4 add_TA(tmp$trigger, on=5, col="red")
To note, this is *not* the actual indicator I am using. The indicator for the strategy has triggerLag at 1that
is, the same computation, lagged a day. So I buy when the black line goes from heading downward to
heading upward, and sell vice versa. This lags the indicator by 30 days (the red line) just to illustrate the
concept for human visibility at that scale.
Now, its time to explore the interaction of delta and the period setting.
First, Im going to try setting delta to .05. In the demo, this is one of the earlier lines, and one of the earlier
lines in the blog post (scroll up to see the exact code). Here are the trade and daily statistics after running the
demo.
1 #Trade Statistics
EFA EPP EWA EWC EWG
2 Num.Txns 15.00 15.00 15.00 17.00 15.00
3 Num.Trades 8.00 8.00 8.00 9.00 8.00
4 Net.Trading.PL 8227.33 11063.30 13924.03 10733.98 9025.04
5 Avg.Trade.PL 1028.42 1382.91 1740.50 1192.66 1128.13
6 Med.Trade.PL 1019.15 887.77 1708.82 1057.51 589.32
Largest.Winner 2882.94 3910.23 4012.78 2849.73 4583.32
7 Largest.Loser -595.29 0.00 0.00 -700.52 -1555.36
Gross.Profits 9030.96 11063.30 13924.03 11563.24 10730.64
8
Gross.Losses -803.63 0.00 0.00 -829.26 -1705.60
9 Std.Dev.Trade.PL 1225.82 1230.98 1445.19 1273.92 1960.06
10 Percent.Positive 75.00 100.00 100.00 77.78 75.00
11 Percent.Negative 25.00 0.00 0.00 22.22 25.00
12 Profit.Factor 11.24 Inf Inf 13.94 6.29
13 Avg.Win.Trade 1505.16 1382.91 1740.50 1651.89 1788.44
Med.Win.Trade 1468.87 887.77 1708.82 1865.24 1383.92
14
Avg.Losing.Trade -401.82 NaN NaN -414.63 -852.80
15
Med.Losing.Trade -401.82 NA NA -414.63 -852.80
16
Avg.Daily.PL 1151.85 1511.09 1940.12 1274.24 1283.89
17 Med.Daily.PL 1300.39 1077.08 2230.28 1461.37 861.35
18 Std.Dev.Daily.PL 1269.20 1270.64 1436.92 1336.51 2062.93
19 Ann.Sharpe 14.41 18.88 21.43 15.13 9.88
20 Max.Drawdown -1973.63 -3221.65 -2703.01 -3330.82 -2252.59
Profit.To.Max.Draw 4.17 3.43 5.15 3.22 4.01
21
Avg.WinLoss.Ratio 3.75 NaN NaN 3.98 2.10
22
Med.WinLoss.Ratio 3.66 NA NA 4.50 1.62
23
Max.Equity 8783.50 11706.29 15425.82 10733.98 9377.84
24 Min.Equity -165.27 -186.62 -399.56 -272.20 -16.66
25 End.Equity 8227.33 11063.30 13924.03 10733.98 9025.04
26
27 EWH EWJ EWS EWT EWU
28 Num.Txns 17.00 15.00 17.00 23.00 15.00
Num.Trades 9.00 8.00 9.00 12.00 8.00
29 Net.Trading.PL 8607.81 3989.12 12749.63 1198.79 8737.48
30 Avg.Trade.PL 956.42 498.64 1416.63 99.90 1092.18
31 Med.Trade.PL 603.38 145.35 803.77 -126.44 717.13
32 Largest.Winner 2737.89 3119.88 4604.52 1186.44 3266.73
33 Largest.Loser 0.00 -897.44 -548.92 -1112.23 -683.09
Gross.Profits 8610.51 5284.21 13298.55 4610.06 9945.34
34 Gross.Losses -2.70 -1295.09 -548.92 -3411.26 -1207.86
35 Std.Dev.Trade.PL 1041.04 1245.20 1659.47 855.59 1509.68
36 Percent.Positive 88.89 62.50 88.89 41.67 75.00
37 Percent.Negative 11.11 37.50 11.11 58.33 25.00
Profit.Factor 3192.83 4.08 24.23 1.35 8.23
38 Avg.Win.Trade 1076.31 1056.84 1662.32 922.01 1657.56
39 Med.Win.Trade 625.45 519.93 1353.14 1032.56 1475.05
40 Avg.Losing.Trade -2.70 -431.70 -548.92 -487.32 -603.93
41 Med.Losing.Trade -2.70 -342.50 -548.92 -366.82 -603.93
42 Avg.Daily.PL 1076.31 495.60 1493.23 -30.81 1205.85
Med.Daily.PL 625.45 7.05 1163.55 -244.67 1005.50
43 Std.Dev.Daily.PL 1044.39 1344.93 1756.95 761.39 1593.25
44 Ann.Sharpe 16.36 5.85 13.49 -0.64 12.01
45 Max.Drawdown -3209.01 -2930.01 -2495.29 -5536.64 -1932.03
46 Profit.To.Max.Draw 2.68 1.36 5.11 0.22 4.52
47 Avg.WinLoss.Ratio 399.10 2.45 3.03 1.89 2.74
Med.WinLoss.Ratio 231.92 1.52 2.47 2.81 2.44
48 Max.Equity 9206.16 6227.40 12909.67 3431.84 9432.62
49 Min.Equity 0.00 -542.64 -412.24 -2104.80 -253.67
50 End.Equity 8607.81 3989.12 12749.63 1198.79 8737.48
51
52 EWY EWZ EZU IEF IGE
Num.Txns 21.00 12.00 20.00 19.00 29.00
53 Num.Trades 11.00 6.00 10.00 10.00 15.00
54 Net.Trading.PL 8265.64 24932.44 6119.14 585.45 9010.26
55 Avg.Trade.PL 751.42 4155.41 611.91 58.55 600.68
56 Med.Trade.PL 590.63 3411.21 281.39 56.80 17.71
57 Largest.Winner 3444.27 11178.58 2767.56 578.57 4768.77
Largest.Loser -1639.42 -1956.40 -1612.20 -452.40 -1044.03
58 Gross.Profits 11590.14 27007.07 9032.54 1631.70 13257.71
59 Gross.Losses -3324.50 -2074.63 -2913.40 -1046.25 -4247.45
60 Std.Dev.Trade.PL 1635.85 5142.41 1467.12 334.39 1601.62
61 Percent.Positive 54.55 66.67 50.00 50.00 53.33
Percent.Negative 45.45 33.33 50.00 50.00 46.67
62 Profit.Factor 3.49 13.02 3.10 1.56 3.12
Avg.Win.Trade 1931.69 6751.77 1806.51 326.34 1657.21
63
Med.Win.Trade 1663.75 6783.34 2240.25 283.77 1170.69
64 Avg.Losing.Trade -664.90 -1037.32 -582.68 -209.25 -606.78
65 Med.Losing.Trade -548.58 -1037.32 -380.50 -229.98 -496.26
66 Avg.Daily.PL 719.50 4155.41 611.91 43.81 582.55
67 Med.Daily.PL 207.44 3411.21 281.39 -11.57 -18.62
Std.Dev.Daily.PL 1720.73 5142.41 1467.12 351.22 1660.48
68 Ann.Sharpe 6.64 12.83 6.62 1.98 5.57
69 Max.Drawdown -4430.88 -8099.04 -2376.14 -1042.38 -3292.00
70 Profit.To.Max.Draw 1.87 3.08 2.58 0.56 2.74
71 Avg.WinLoss.Ratio 2.91 6.51 3.10 1.56 2.73
72 Med.WinLoss.Ratio 3.03 6.54 5.89 1.23 2.36
Max.Equity 9939.98 27860.83 6964.49 1297.25 10608.96
73 Min.Equity -275.58 -699.79 -119.44 -977.06 -360.99
74 End.Equity 8265.64 24932.44 6119.14 585.45 9010.26
75
76 IYR IYZ LQD RWR SHY
77 Num.Txns 18.00 23.00 20.00 18.00 17.00
Num.Trades 9.00 12.00 10.00 9.00 9.00
78 Net.Trading.PL 5152.30 1708.37 130.39 6718.45 1745.26
79 Avg.Trade.PL 572.48 142.36 13.04 746.49 193.92
80 Med.Trade.PL 563.22 -120.54 75.65 526.70 23.61
81 Largest.Winner 2918.00 3024.24 405.72 3621.48 1408.27
82 Largest.Loser -866.08 -719.83 -437.61 -525.69 -38.96
Gross.Profits 6578.80 4526.67 1234.36 7766.42 1838.06
83 Gross.Losses -1426.50 -2818.30 -1103.97 -1047.98 -92.81
84 Std.Dev.Trade.PL 1116.84 1009.69 279.74 1259.65 461.97
85 Percent.Positive 55.56 33.33 60.00 66.67 66.67
86 Percent.Negative 44.44 66.67 40.00 33.33 33.33
87 Profit.Factor 4.61 1.61 1.12 7.41 19.81
Avg.Win.Trade 1315.76 1131.67 205.73 1294.40 306.34
88 Med.Win.Trade 1065.80 607.86 193.42 935.99 94.33
89 Avg.Losing.Trade -356.62 -352.29 -275.99 -349.33 -30.94
90 Med.Losing.Trade -207.69 -356.49 -272.49 -422.60 -37.39
91 Avg.Daily.PL 572.48 83.84 13.04 746.49 206.09
Med.Daily.PL 563.22 -193.76 75.65 526.70 19.61
92 Std.Dev.Daily.PL 1116.84 1037.40 279.74 1259.65 492.32
93 Ann.Sharpe 8.14 1.28 0.74 9.41 6.65
94 Max.Drawdown -2630.67 -1861.72 -936.63 -2352.97 -256.48
95 Profit.To.Max.Draw 1.96 0.92 0.14 2.86 6.80
96 Avg.WinLoss.Ratio 3.69 3.21 0.75 3.71 9.90
Med.WinLoss.Ratio 5.13 1.71 0.71 2.21 2.52
97 Max.Equity 6254.05 2468.70 536.06 7751.61 1786.95
98 Min.Equity 0.00 -1795.37 -899.01 0.00 -89.54
99 End.Equity 5152.30 1708.37 130.39 6718.45 1745.26
100
101 TLT XLB XLE XLF XLI
Num.Txns 24.00 21.00 27.00 16.00 15.00
102
Num.Trades 12.00 11.00 14.00 8.00 8.00
103 Net.Trading.PL 2066.64 4949.22 12832.20 3832.14 4152.85
104 Avg.Trade.PL 172.22 449.93 916.59 479.02 519.11
105 Med.Trade.PL 64.51 299.25 29.49 177.45 432.70
106 Largest.Winner 1516.76 2465.37 10722.60 2492.20 2076.85
Largest.Loser -799.95 -1213.35 -1470.10 -1011.16 -780.27
107 Gross.Profits 3385.01 8451.54 16601.75 5130.47 5213.68
108 Gross.Losses -1318.37 -3502.32 -3769.55 -1298.33 -1060.84
109 Std.Dev.Trade.PL 571.70 1338.56 3059.14 1099.30 908.57
110 Percent.Positive 58.33 63.64 57.14 62.50 75.00
111 Percent.Negative 41.67 36.36 42.86 37.50 25.00
Profit.Factor 2.57 2.41 4.40 3.95 4.91
112 Avg.Win.Trade 483.57 1207.36 2075.22 1026.09 868.95
113 Med.Win.Trade 353.25 696.82 520.06 745.78 635.27
114 Avg.Losing.Trade -263.67 -875.58 -628.26 -432.78 -530.42
115 Med.Losing.Trade -127.47 -965.22 -502.49 -243.40 -530.42
Avg.Daily.PL 172.22 425.24 897.10 479.02 483.63
116 Med.Daily.PL 64.51 172.27 25.82 177.45 362.26
117 Std.Dev.Daily.PL 571.70 1408.32 3183.15 1099.30 975.37
Ann.Sharpe 4.78 4.79 4.47 6.92 7.87
118
Max.Drawdown -2230.38 -3263.67 -3550.72 -2353.27 -1876.74
119 Profit.To.Max.Draw 0.93 1.52 3.61 1.63 2.21
120 Avg.WinLoss.Ratio 1.83 1.38 3.30 2.37 1.64
121 Med.WinLoss.Ratio 2.77 0.72 1.03 3.06 1.20
122 Max.Equity 3450.04 6216.57 13284.80 5838.21 5031.19
Min.Equity -1261.84 -116.14 -468.28 -106.64 -134.79
123 End.Equity 2066.64 4949.22 12832.20 3832.14 4152.85
124
125 XLK XLP XLU XLV XLY
126 Num.Txns 19.00 13.00 15.00 21.00 15.00
127 Num.Trades 10.00 7.00 8.00 11.00 8.00
128 Net.Trading.PL 5676.09 3673.84 4611.03 -318.04 5316.38
Avg.Trade.PL 567.61 524.83 576.38 -28.91 664.55
129 Med.Trade.PL 437.57 170.61 273.18 12.47 254.12
130 Largest.Winner 3770.88 1614.82 3011.64 619.76 3835.14
131 Largest.Loser -698.78 -248.32 -966.49 -679.66 -601.28
132 Gross.Profits 7910.97 3943.62 5906.06 1676.74 6737.00
Gross.Losses -2234.88 -269.78 -1295.04 -1994.78 -1420.62
133 Std.Dev.Trade.PL 1370.91 704.46 1281.22 417.22 1496.94
134 Percent.Positive 60.00 71.43 75.00 54.55 62.50
135 Percent.Negative 40.00 28.57 25.00 45.45 37.50
136 Profit.Factor 3.54 14.62 4.56 0.84 4.74
137 Avg.Win.Trade 1318.49 788.72 984.34 279.46 1347.40
Med.Win.Trade 787.39 1043.70 426.32 208.37 600.59
138 Avg.Losing.Trade -558.72 -134.89 -647.52 -398.96 -473.54
139 Med.Losing.Trade -581.97 -134.89 -647.52 -424.36 -497.16
140 Avg.Daily.PL 600.09 583.87 596.95 -48.94 692.34
141 Med.Daily.PL 599.90 551.34 126.12 -65.24 38.23
142 Std.Dev.Daily.PL 1449.98 752.49 1382.45 434.18 1614.65
Ann.Sharpe 6.57 12.32 6.85 -1.79 6.81
143 Max.Drawdown -2163.21 -945.00 -2389.00 -1574.99 -2265.49
144 Profit.To.Max.Draw 2.62 3.89 1.93 -0.20 2.35
145 Avg.WinLoss.Ratio 2.36 5.85 1.52 0.70 2.85
146 Med.WinLoss.Ratio 1.35 7.74 0.66 0.49 1.21
Max.Equity 6522.36 3705.07 5776.84 731.12 7076.43
147 Min.Equity -206.72 -315.77 -772.21 -1189.32 -55.79
148 End.Equity 5676.09 3673.84 4611.03 -318.04 5316.38
149
150
151 #Daily Statistics
152 EFA EPP EWA EWC EWG
153 Total.Net.Profit 8227.33 11063.30 13924.03 10733.98 9025.04
Total.Days 1007.00 1161.00 1171.00 1131.00 1006.00
154 Winning.Days 554.00 639.00 661.00 635.00 558.00
155 Losing.Days 453.00 522.00 510.00 496.00 448.00
156 Avg.Day.PL 8.17 9.53 11.89 9.49 8.97
157 Med.Day.PL 12.93 13.50 21.99 24.36 19.33
Largest.Winner 408.21 705.97 644.19 517.86 510.65
158
Largest.Loser -446.90 -653.65 -726.30 -640.19 -578.28
159 Gross.Profits 48673.31 74367.84 84153.20 67767.25 62690.85
160 Gross.Losses -40445.98 -63304.54 -70229.17 -57033.27 -53665.80
161 Std.Dev.Daily.PL 116.11 164.25 178.88 143.87 150.39
162 Percent.Positive 55.01 55.04 56.45 56.15 55.47
Percent.Negative 44.99 44.96 43.55 43.85 44.53
163 Profit.Factor 1.20 1.17 1.20 1.19 1.17
164 Avg.Win.Day 87.86 116.38 127.31 106.72 112.35
165 Med.Win.Day 72.86 87.42 100.11 86.51 89.18
166 Avg.Losing.Day -89.28 -121.27 -137.70 -114.99 -119.79
167 Med.Losing.Day -66.22 -80.24 -95.53 -82.40 -89.92
Avg.Daily.PL 8.17 9.53 11.89 9.49 8.97
168 Med.Daily.PL 12.93 13.50 21.99 24.36 19.33
169 Std.Dev.Daily.PL.1 116.11 164.25 178.88 143.87 150.39
170 Ann.Sharpe 1.12 0.92 1.06 1.05 0.95
171 Max.Drawdown -1973.63 -3221.65 -2703.01 -3330.82 -2252.59
Profit.To.Max.Draw 4.17 3.43 5.15 3.22 4.01
172 Avg.WinLoss.Ratio 0.98 0.96 0.92 0.93 0.94
Med.WinLoss.Ratio 1.10 1.09 1.05 1.05 0.99
173
Max.Equity 8783.50 11706.29 15425.82 10733.98 9377.84
174 Min.Equity -165.27 -186.62 -399.56 -272.20 -16.66
175 End.Equity 8227.33 11063.30 13924.03 10733.98 9025.04
176
177 EWH EWJ EWS EWT EWU
178 Total.Net.Profit 8607.81 3989.12 12749.63 1198.79 8737.48
Total.Days 1012.00 726.00 1146.00 851.00 1014.00
179 Winning.Days 531.00 379.00 639.00 438.00 554.00
180 Losing.Days 481.00 347.00 507.00 413.00 460.00
181 Avg.Day.PL 8.51 5.49 11.13 1.41 8.62
182 Med.Day.PL 12.53 13.79 21.28 8.92 15.16
183 Largest.Winner 753.74 517.40 785.37 880.75 462.83
Largest.Loser -902.02 -782.59 -974.26 -1417.30 -590.98
184 Gross.Profits 72436.39 43880.76 82366.04 58541.90 54123.90
185 Gross.Losses -63828.58 -39891.64 -69616.41 -57343.10 -45386.42
186 Std.Dev.Daily.PL 182.08 149.18 181.55 183.18 128.25
187 Percent.Positive 52.47 52.20 55.76 51.47 54.64
Percent.Negative 47.53 47.80 44.24 48.53 45.36
188 Profit.Factor 1.13 1.10 1.18 1.02 1.19
189 Avg.Win.Day 136.42 115.78 128.90 133.66 97.70
190 Med.Win.Day 104.08 94.55 91.27 108.07 80.81
191 Avg.Losing.Day -132.70 -114.96 -137.31 -138.85 -98.67
192 Med.Losing.Day -95.34 -85.90 -104.28 -99.70 -72.84
Avg.Daily.PL 8.51 5.49 11.13 1.41 8.62
193 Med.Daily.PL 12.53 13.79 21.28 8.92 15.16
194 Std.Dev.Daily.PL.1 182.08 149.18 181.55 183.18 128.25
195 Ann.Sharpe 0.74 0.58 0.97 0.12 1.07
196 Max.Drawdown -3209.01 -2930.01 -2495.29 -5536.64 -1932.03
197 Profit.To.Max.Draw 2.68 1.36 5.11 0.22 4.52
Avg.WinLoss.Ratio 1.03 1.01 0.94 0.96 0.99
198 Med.WinLoss.Ratio 1.09 1.10 0.88 1.08 1.11
199 Max.Equity 9206.16 6227.40 12909.67 3431.84 9432.62
200 Min.Equity 0.00 -542.64 -412.24 -2104.80 -253.67
201 End.Equity 8607.81 3989.12 12749.63 1198.79 8737.48
202
EWY EWZ EZU IEF IGE
203 Total.Net.Profit 8265.64 24932.44 6119.14 585.45 9010.26
204 Total.Days 1115.00 1271.00 980.00 874.00 1212.00
205 Winning.Days 599.00 709.00 539.00 438.00 670.00
206 Losing.Days 516.00 562.00 441.00 436.00 542.00
207 Avg.Day.PL 7.41 19.62 6.24 0.67 7.43
Med.Day.PL 18.37 37.21 14.74 0.78 20.63
208 Largest.Winner 833.96 1516.20 511.25 359.14 456.89
209 Largest.Loser -757.96 -1676.65 -743.07 -195.28 -600.22
210 Gross.Profits 96772.35 173737.17 52301.23 17413.21 79833.45
211 Gross.Losses -88506.71 -148804.74 -46182.09 -16827.76 -70823.19
Std.Dev.Daily.PL 218.24 347.76 134.07 52.43 159.01
212
Percent.Positive 53.72 55.78 55.00 50.11 55.28
213 Percent.Negative 46.28 44.22 45.00 49.89 44.72
214 Profit.Factor 1.09 1.17 1.13 1.03 1.13
215 Avg.Win.Day 161.56 245.05 97.03 39.76 119.15
216 Med.Win.Day 131.89 190.69 76.26 32.13 100.58
Avg.Losing.Day -171.52 -264.78 -104.72 -38.60 -130.67
217 Med.Losing.Day -118.99 -181.70 -73.90 -29.23 -103.79
218 Avg.Daily.PL 7.41 19.62 6.24 0.67 7.43
219 Med.Daily.PL 18.37 37.21 14.74 0.78 20.63
220 Std.Dev.Daily.PL.1 218.24 347.76 134.07 52.43 159.01
221 Ann.Sharpe 0.54 0.90 0.74 0.20 0.74
Max.Drawdown -4430.88 -8099.04 -2376.14 -1042.38 -3292.00
222 Profit.To.Max.Draw 1.87 3.08 2.58 0.56 2.74
223 Avg.WinLoss.Ratio 0.94 0.93 0.93 1.03 0.91
224 Med.WinLoss.Ratio 1.11 1.05 1.03 1.10 0.97
225 Max.Equity 9939.98 27860.83 6964.49 1297.25 10608.96
Min.Equity -275.58 -699.79 -119.44 -977.06 -360.99
226 End.Equity 8265.64 24932.44 6119.14 585.45 9010.26
227
228 IYR IYZ LQD RWR SHY
Total.Net.Profit 5152.30 1708.37 130.39 6718.45 1745.26
229
Total.Days 981.00 948.00 770.00 992.00 1345.00
230 Winning.Days 536.00 480.00 408.00 538.00 753.00
231 Losing.Days 445.00 468.00 362.00 454.00 592.00
232 Avg.Day.PL 5.25 1.80 0.17 6.77 1.30
233 Med.Day.PL 14.23 3.87 3.36 16.33 1.31
Largest.Winner 670.92 455.49 211.30 697.50 47.38
234 Largest.Loser -589.08 -439.41 -274.58 -604.38 -57.51
235 Gross.Profits 53330.41 36851.16 12914.84 56782.80 6920.89
236 Gross.Losses -48178.11 -35142.78 -12784.45 -50064.35 -5175.63
237 Std.Dev.Daily.PL 139.95 100.60 44.38 146.67 11.97
238 Percent.Positive 54.64 50.63 52.99 54.23 55.99
Percent.Negative 45.36 49.37 47.01 45.77 44.01
239 Profit.Factor 1.11 1.05 1.01 1.13 1.34
240 Avg.Win.Day 99.50 76.77 31.65 105.54 9.19
241 Med.Win.Day 81.52 60.01 24.89 84.35 7.19
242 Avg.Losing.Day -108.27 -75.09 -35.32 -110.27 -8.74
Med.Losing.Day -76.06 -53.60 -27.51 -81.40 -6.81
243 Avg.Daily.PL 5.25 1.80 0.17 6.77 1.30
244 Med.Daily.PL 14.23 3.87 3.36 16.33 1.31
245 Std.Dev.Daily.PL.1 139.95 100.60 44.38 146.67 11.97
246 Ann.Sharpe 0.60 0.28 0.06 0.73 1.72
247 Max.Drawdown -2630.67 -1861.72 -936.63 -2352.97 -256.48
Profit.To.Max.Draw 1.96 0.92 0.14 2.86 6.80
248 Avg.WinLoss.Ratio 0.92 1.02 0.90 0.96 1.05
249 Med.WinLoss.Ratio 1.07 1.12 0.90 1.04 1.06
250 Max.Equity 6254.05 2468.70 536.06 7751.61 1786.95
251 Min.Equity 0.00 -1795.37 -899.01 0.00 -89.54
252 End.Equity 5152.30 1708.37 130.39 6718.45 1745.26
253
TLT XLB XLE XLF XLI
254 Total.Net.Profit 2066.64 4949.22 12832.20 3832.14 4152.85
255 Total.Days 755.00 1039.00 1154.00 738.00 1001.00
256 Winning.Days 390.00 569.00 630.00 386.00 544.00
257 Losing.Days 365.00 470.00 524.00 352.00 457.00
Avg.Day.PL 2.74 4.76 11.12 5.19 4.15
258 Med.Day.PL 4.68 16.84 23.19 6.77 9.43
259 Largest.Winner 546.86 543.49 649.18 521.32 567.57
260 Largest.Loser -387.41 -561.19 -898.57 -695.88 -461.98
261 Gross.Profits 29206.43 60629.30 91312.60 34177.43 41796.72
262 Gross.Losses -27139.79 -55680.08 -78480.40 -30345.29 -37643.87
Std.Dev.Daily.PL 102.19 145.68 196.01 128.37 105.23
263 Percent.Positive 51.66 54.76 54.59 52.30 54.35
264 Percent.Negative 48.34 45.24 45.41 47.70 45.65
265 Profit.Factor 1.08 1.09 1.16 1.13 1.11
266 Avg.Win.Day 74.89 106.55 144.94 88.54 76.83
Med.Win.Day 52.20 90.91 112.15 61.85 62.90
267
Avg.Losing.Day -74.36 -118.47 -149.77 -86.21 -82.37
268 Med.Losing.Day -59.22 -87.40 -108.63 -54.35 -59.78
269 Avg.Daily.PL 2.74 4.76 11.12 5.19 4.15
270 Med.Daily.PL 4.68 16.84 23.19 6.77 9.43
271 Std.Dev.Daily.PL.1 102.19 145.68 196.01 128.37 105.23
Ann.Sharpe 0.43 0.52 0.90 0.64 0.63
272 Max.Drawdown -2230.38 -3263.67 -3550.72 -2353.27 -1876.74
273 Profit.To.Max.Draw 0.93 1.52 3.61 1.63 2.21
274 Avg.WinLoss.Ratio 1.01 0.90 0.97 1.03 0.93
275 Med.WinLoss.Ratio 0.88 1.04 1.03 1.14 1.05
276 Max.Equity 3450.04 6216.57 13284.80 5838.21 5031.19
Min.Equity -1261.84 -116.14 -468.28 -106.64 -134.79
277 End.Equity 2066.64 4949.22 12832.20 3832.14 4152.85
278
279 XLK XLP XLU XLV XLY
280 Total.Net.Profit 5676.09 3673.84 4611.03 -318.04 5316.38
281 Total.Days 964.00 999.00 1024.00 892.00 795.00
Winning.Days 536.00 549.00 562.00 449.00 422.00
282 Losing.Days 428.00 450.00 462.00 443.00 373.00
Avg.Day.PL 5.89 3.68 4.50 -0.36 6.69
283
Med.Day.PL 13.65 8.52 11.94 3.04 8.89
284 Largest.Winner 431.35 298.68 359.25 291.48 741.77
285 Largest.Loser -462.31 -261.14 -429.41 -319.27 -560.75
286 Gross.Profits 45283.91 28445.54 41231.21 27231.30 37590.36
287 Gross.Losses -39607.82 -24771.70 -36620.18 -27549.33 -32273.98
Std.Dev.Daily.PL 117.38 67.41 101.36 79.97 123.15
288 Percent.Positive 55.60 54.95 54.88 50.34 53.08
289 Percent.Negative 44.40 45.05 45.12 49.66 46.92
290 Profit.Factor 1.14 1.15 1.13 0.99 1.16
291 Avg.Win.Day 84.48 51.81 73.37 60.65 89.08
292 Med.Win.Day 60.65 42.81 59.91 50.84 67.06
Avg.Losing.Day -92.54 -55.05 -79.26 -62.19 -86.53
293 Med.Losing.Day -64.97 -44.88 -54.05 -49.00 -56.79
294 Avg.Daily.PL 5.89 3.68 4.50 -0.36 6.69
295 Med.Daily.PL 13.65 8.52 11.94 3.04 8.89
296 Std.Dev.Daily.PL.1 117.38 67.41 101.36 79.97 123.15
Ann.Sharpe 0.80 0.87 0.71 -0.07 0.86
297 Max.Drawdown -2163.21 -945.00 -2389.00 -1574.99 -2265.49
298 Profit.To.Max.Draw 2.62 3.89 1.93 -0.20 2.35
299 Avg.WinLoss.Ratio 0.91 0.94 0.93 0.98 1.03
300 Med.WinLoss.Ratio 0.93 0.95 1.11 1.04 1.18
301 Max.Equity 6522.36 3705.07 5776.84 731.12 7076.43
Min.Equity -206.72 -315.77 -772.21 -1189.32 -55.79
302 End.Equity 5676.09 3673.84 4611.03 -318.04 5316.38
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
This time, while you take on more risk, your returns are definitely betteressentially keeping pace with SPY
in its bullish phases while missing the financial crisis (as any decent trend-follower does). That stated, the
drawdowns between this strategy and SPY happen at similar times, meaning that given that there are more
equity indices than the SPY (as well as bond indices), that position sizing for the strategy can be improved
for diversification.
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.7914488
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.08098812
6
> maxDrawdown(portfRets)
7 [1] 0.1283513
8
Finally, heres the new indicator plot, this time with the true lagged indicator plotted.
Notice that the indicator stays at more extreme values more often.
Basically, this variant seemingly maximizes market exposure. Here are the three relevant statistics:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.7911671
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.1111565
6 > maxDrawdown(portfRets)
7 [1] 0.2038124
8
And finally, an equity curve demonstrating the indicator at its reasonable limit (that is, zeroat the other end
of the spectrum, when I used a delta of .4, there was only one trade on one of the instruments, and it was a
bad trade, so thats definitely not an interesting case).
As you can see, as the delta parameter becomes smaller and smaller, the sensitivity to a trend increases. At
the limit, it essentially becomes akin to an either-or classifier. So basically, for those with the statistics
backgrounds (and if youve understood everything thus far, you have one), then the confusion matrix
becomes go long in a trend, go long without a trend, dont go long but miss a trend, stay out of the
market in which theres no trend, and this variant essentially leans towards the idea of I have a slight
hunch theres a trend. Oh well. Thats enough! Time to buy! And so long as so much as even a hunch
persists, the strategy will stay long.
Interestingly enough, as judging by the percentage correct and the trade statistics, the ability of the Trend
Vigor indicator to correctly predict seems to be served by the statistics. That, or it could just be that in quite
a few seemingly separate cases, I got lucky with my parameters (always a possibility).
One *last* variant to look at, with this evidence in hand, is whether the Trend Vigor, with its tendency to
buy (or not buy) at a whim, yet getting these correct, is whether or not it would work on a shorter time-
frame.
Lets set the period from 100 to, say, 20. That is, a period of 20, and a delta of 0.
As you may guess, this changes the characteristics of the strategy in terms of what trade statistics
considerably. It sacrifices the win-over-the-long-haul philosophy in favor of a style more akin to spray-and-
pray, rat-ta-tat-tat, or twitch trading.
Some aggregate trade stats (that I implemented as I was writing this post, so forgive the fact that they
werent in previous outputs):
1
2 > mean(tStats$Percent.Positive)
[1] 55.921
3 > (aggPF <- sum(tStats$Gross.Profits)/-sum(tStats$Gross.Losses))
4 [1] 2.889328
5 > (aggCorrect <- mean(tStats$Percent.Positive))
6 [1] 55.921
> (numTrades <- sum(tStats$Num.Trades))
7
[1] 946
8 > (meanAvgWLR <- mean(tStats$Avg.WinLoss.Ratio))
9 [1] 2.495
10
In other words, over nearly a thousand relatively short-term momentum trades (can you say whipsaw?),
the strategy still gets it right more than half the time, and the trades it *does* get right, on average, it gets
them right by a ratio of 2.5 to 1. Of course, this backtest doesnt take transaction costs into account, and
when dealing with these shorter-term strategies, if youre a retail investor out to try and rub two pennies
together and youre getting taken for a ride on the order of $10 per buy or sell order from ETrade, youll
have to look at the average trade P&L. (Also, if youre a retail investor, your bigger obstacle would be the
$3,000,000 to be able to even trade something like this.)
A lot of the cash Sharpe ratios (now in the *daily* statistics, not the trade statistics) are nearing 1. Not
exactly spectacular, by any stretch of the imagination, when considering a short-term trading strategy before
commissions/slippage, but definitely nothing to scoff at. Something I notice just by eyeballing the statistics
is that there definitely seems to be an edge in the percentage of days positive. Of course, I wont rule out the
fact that all of this may simply be the fact that many of these securities are equity indices, and thus, may
have a slightly inherent long bias in them.
Lets move onto the portfolio equity curve.
Looking at this equity curve, the first thing to note is that its strikingly similar to the SPY equity curve up
until the crisis, and the drawdowns happen at about the same exact times. Maybe this means that all of my
global equity ETFs that I chose for the data were correlated because at the end of the day, aside from the few
fixed income ETFs available before 2003, that theyre still separate slices of the equities asset class, or that
my volatility (and returns?) are mainly driven by the 9 XL sectors. Whats also worth noting is that the
strategy attempted to go dumpster diving, to put it kindlythat is, still try to find those positive-momentum
trades in the depths of the financial crisis. While it certainly seemed to try, in this case, the best move would
have been to do nothing at all. However, when the market rebounded, the strategy quickly made up its losses
(and then some).
For the final time in this post, the three portfolio statistics:
1
> SharpeRatio.annualized(portfRets)
2 [,1]
3 Annualized Sharpe Ratio (Rf=0%) 0.9692246
4 > Return.annualized(portfRets)
5 [,1]
Annualized Return 0.1113647
6 > maxDrawdown(portfRets)
7 [1] 0.1801516
8
In other words, an 11% return, at a maximum drawdown of 18%. I am of course, still not pleased with the
last two numbers, since a maximum drawdown larger than the annualized return means that its quite
possible to simply have a down year. However, the idea of an 18% drawdown while seemingly keeping pace
with an S&P 500 (with a simplistic asset allocation and order-sizing strategy, no less) in its good years is
nothing to scoff at. (In my last job, I was responsible for developing the analytics package in terms of
portfolio management. Suffice to say that there are a lot more ways to slice up performance.)
Finally, just for fun, heres the equity curve and the indicator of XLB for this two-parameter (three if you
count trigger lag, and four if you count the threshold setting) configuration:
In other words, for those that prefer the lots and lots of little ones to the long-term trend-following
alternative, there you go.
Interesting to note, comparing the few portfolio equity curves Ive shown in this post, overall, the
performances look somewhat similar regardless of the philosophywhether a very long-term trend-following
approach, or a more short-term orientation. The difference shows up in the trade statistics, but as they say
about roads leading to Romein my opinion, this probably counts as a vote for the robustness of the
strategy.
Now, to conclude this exploration, here are some things I can take away from it:
By adjusting the period and delta parameters, its possible to take what was originally a market mode filter
and:
Paradoxically (at least according to the basic risk/reward blabber found in typical academic finance), it
seems that by lowering delta and having a willingness to take on more risk in the form of greater market
exposure, one actually achieves a better annualized Sharpe Ratio than with the default, more risk-averse,
setting.
For those of you who follow me from a non-technical background, send me a message, and Ill help you get
started. For those simply not acquainted with the quantstrat package, this is the link to the definitive,
comprehensive guide on quantstrat.
http://www.rinfinance.com/agenda/2013/workshop/Humme+Peterson.pdf
To motivate this post, consider some common wisdom about both mean-reverting and trend-following
strategies. Mean reverting strategies often sport a high percentage of positive trades, but the few negative
trades can hammer the equity curve. On the other side of the equation, trend-following strategies run on a
philosophy of let your winners run and cut your losers, resulting in many small losses and a few wins that
make up for them. However, what if there were an indicator that behaved like the best of both worlds? That
is, capitalize on trends, while cutting out a good portion of whipsaws?
The Trend Vigor Indicator, or TVI as I call it in my DSTrading package, available on my github (see my
about page for the link), created by Dr. John Ehlers (whom I thank for providing the completed code),
attempts to do just that. Heres a link to the original presentation (code incomplete):
http://www.mesasoftware.com/Seminars/Trend%20Modes%20and%20Cycle%20Modes.pdf
As Dr. Ehlers says on the fifth slide, correctly identifying market mode means a great deal. Something I
noticed is that the plot for the trend vigor index seems to be very smooth in general, so when the trend vigor
rises, it generally doesnt whipsaw around very much about any particular quantity (at least at the daily
resolution), so there may be a strategy to take advantage of this possible property.
Heres the corresponding function from the DSTrading package, which I will use in the following demo.
There may also be a trend vigor calculation with one of Dr. Ehlerss adaptive period computation algorithms
built in. In any case, the way it works is this: it takes in a time series, and two parameters: a period, which is
identical to the n parameter in indicators such as SMA and so on, and a delta, which is a trigonometric
parameter to adjust the computation of the bandpass filter (I am not overly familiar with the rationale behind
signal processing, so Ill leave the commentary on the finer points of this parameter to someone more
experienced in this field).
TVI then outputs a time series of a 0-centered trend vigor indicator, along with a pair of oscillators (signal
and lead), which I may touch on in the future.
So in order to test this indicator, I wrote a quantstrat demo to test its momentum properties. First, we will
fetch the data, which consists of some ETFs that have been trading since before 2003. This is the data file I
will source for this demo (called demoData.R), along with many others.
1 require(DSTrading)
2 require(quantstrat)
3 options(width=80)
4 source("demoData.R") #contains all of the data-related boilerplate.
1 options("getSymbols.warning4.0"=FALSE)
2 rm(list=ls(.blotter), envir=.blotter)
initDate='1990-12-31'
3
4 currency('USD')
5 Sys.setenv(TZ="UTC")
6
7 symbols <- c("XLB", #SPDR Materials sector
8 "XLE", #SPDR Energy sector
"XLF", #SPDR Financial sector
9 "XLP", #SPDR Consumer staples sector
1 "XLI", #SPDR Industrial sector
0 "XLU", #SPDR Utilities sector
11 "XLV", #SPDR Healthcare sector
1 "XLK", #SPDR Tech sector
"XLY", #SPDR Consumer discretionary sector
2 "RWR", #SPDR Dow Jones REIT ETF
1
3 "EWJ", #iShares Japan
1 "EWG", #iShares Germany
4 "EWU", #iShares UK
"EWC", #iShares Canada
1
"EWY", #iShares South Korea
5 "EWA", #iShares Australia
"EWH", #iShares Hong Kong
1
"EWS", #iShares Singapore
6 "IYZ", #iShares U.S. Telecom
1 "EZU", #iShares MSCI EMU ETF
7 "IYR", #iShares U.S. Real Estate
1 "EWT", #iShares Taiwan
"EWZ", #iShares Brazil
8 "EFA", #iShares EAFE
1 "IGE", #iShares North American Natural Resources
9 "EPP", #iShares Pacific Ex Japan
2 "LQD", #iShares Investment Grade Corporate Bonds
0 "SHY", #iShares 1-3 year TBonds
"IEF", #iShares 3-7 year TBonds
2 "TLT" #iShares 20+ year Bonds
1 )
2
2 #SPDR ETFs first, iShares ETFs afterwards
2 if(!"XLB" %in% ls()) {
suppressMessages(getSymbols(symbols, from="2003-01-01", to="2010-12-31",
3 src="yahoo", adjust=TRUE))
2 }
4
2 stock(symbols, currency="USD", multiplier=1)
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
The reason Im using these symbols is fairly simple: theyre representative of a fairly broad aspect of both
the U.S. economy and other developed economies abroad (E.G. Japan, Germany), and contain a fair amount
of staple ETFs (SPDR sectors, EFA, internationals, etc.).
Lets begin the demo. The strategy will be simplethe indicator will be the trend vigor calculation, and the
strategy will go long on the next open when the trend vigor crosses above 1, sell the next open when it
crosses below 1.4, and will stop out on the next open when the trend vigor crosses under 1 if it never crossed
1.4 within the duration of the position.
The general syntax of indicators, signals, and rules in quantstrat (for the uninitiated) is fairly
straightforward:
add.indicator/add.signal/add.rule is a function which takes in the name of the strategy as the first argument
(which I always use strategy.st for), a name of an R function in the name argument, the arguments to the
function as the argument to arguments (that is, arguments=list(x=quote(Cl(mktdata)), period=100, delta=0.2)
is the argument to the TVI function which is the value for the name argument in the add.indicator function),
and finally, a label. While the labels may seem like so much window dressing, they are critical in terms of
linking indicators to signals, signals to rules, and everything to any optimization/robustness testing you may
deecide to do. Do not forget them.
While I kept Dr. Ehlerss default period setting, I found that the strategy works best starting at 60 days for
the period, and has a solid performance until the mid-hundreds, at which point it generates too few trades per
instrument to really be able to look at any individual statistics.
1 #To rerun the strategy, re-run everything from this line down.
2
3 strategy.st <- portfolio.st <- account.st <- "TVItrendFollowingLong"
rm.strat(portfolio.st)
4 rm.strat(strategy.st)
5 initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
6 initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD')
7 initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)
8
9 #indicator
1
0 add.indicator(strategy.st, name="TVI", arguments=list(x=quote(Cl(mktdata)),
11period=100, delta=0.2), label="TVI")
1
2 #signals
1
3 add.signal(strategy.st, name="sigThreshold",
arguments=list(threshold=1, column="vigor.TVI", relationship="gte",
1 cross=TRUE),
4 label="longEntry")
1
5 add.signal(strategy.st, name="sigThreshold",
1 arguments=list(threshold=1.4, column="vigor.TVI", relationship="lt",
cross=TRUE),
6
label="longExit")
1
7 add.signal(strategy.st, name="sigThreshold",
1 arguments=list(threshold=1, column="vigor.TVI", relationship="lt",
8 cross=TRUE),
1 label="wrongExit")
9
#rules
2
0 add.rule(strategy.st, name="ruleSignal",
2 arguments=list(sigcol="longEntry", sigval=TRUE, orderqty=100,
1 ordertype="market", orderside="long", replace=FALSE,
2 prefer="Open"),
2 type="enter", path.dep=TRUE)
2
add.rule(strategy.st, name="ruleSignal",
3 arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all",
2 ordertype="market", orderside="long", replace=FALSE,
4 prefer="Open"),
2 type="exit", path.dep=TRUE)
5
add.rule(strategy.st, name="ruleSignal",
2 arguments=list(sigcol="wrongExit", sigval=TRUE, orderqty="all",
6 ordertype="market", orderside="long", replace=FALSE,
2 prefer="Open"),
7 type="exit", path.dep=TRUE)
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
The strategy is then run with the applyStrategy call. I set verbose to FALSE for the purpose of not displaying
the order log in this blog post, but by default, quantstrat will start printing out trades at this point. Printing
out trades serves two purposes (in my experience): first off, you know your strategy is actually doing
something, and secondly, even if it is, the attentive eye will also see if the strategy is not behaving intuitively
(that is, is it loading up more lots when you thought it should only be trading 1 lot at a time? This happens
often with strategies that crisscross above and below a threshold, such as with simple RSI strategies.)
1
2 #apply strategy
3
t1 <- Sys.time()
4 out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st, verbose=FALSE)
5 t2 <- Sys.time()
6 print(t2-t1)
7
8 #tradeStats
9
10 updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)$summary)[-1]
11 updateAcct(portfolio.st,dateRange)
12 updateEndEq(account.st)
13
The last four lines are some more accounting boilerplate that are necessary for the following analytics.
Looking at the statistics on a per-trade basis on a 100-share trade size, the majority of configurations come
away with a clear profit. While I did not set any transaction costs/slippage, as this strategy is a long to
medium term horizon strategy, I wouldnt worry too much over it. The statistics that impress me are the
percentage correct and the profit factor. Looking at EWJ, even when the percentage correct is less than 50%,
the strategy still manages to eke out a profit (profit factor 1.5). A few instruments lose), but on a whole, for
an indicator thats supposed to be a mere filter, this isnt a particularly bad start.
For those wondering about the annualized Sharpe Ratio as it is calculated with trade statistics, this is my
interpretation of it: if you aggregated every single trades profit and loss into one day apiece, averaged them,
and divided by the standard error, which is the standard deviation divided by the square root of 252 (trading
days in a year).
Looking at the daily statistics, while the statistics arent exactly spectacular on the daily time frame, keep in
mind that these configurations are ones that hold for very long periods of time, and thus are subject to the
usual sloshings of the individual securities over a long period of time. The thing to see here is that this
indicator does a good job of creating an edge in most instruments.
Finally, out of a tagential interest, lets look at some portfolio statistics. Note that this probably makes the
least sense out of anything presented here since there is no intelligent asset allocation scheme at work here.
From a returns perspective, this doesnt make too much sense, as theres no initial equity, but it exists to just
get a perspective of the strategys performance at a portfolio level. Overall, it seems the system is profitable,
but drawdowns come quickly, meaning that as a strategy in and of itself, Trend Vigor is definitely worth
looking at. That stated, its original use was as a filter, and odds are, there exist indicators that are probably
more dedicated to trend following such as the FRAMA, ichimoku, and so on.
1) It is possible to create a trend-following strategy with a win percentage greater than that of 50%.
2) While the trend vigor indicator is a filter, it may be possible to put a dedicated trend-following filter on
top of it (such as the FRAMAmore on that in the future), to possibly get even better results.
3) This is (definitely) not a complete trading system. The exit logic definitely leaves something to be desired
(for instance, if the trend vigor reaches its maximum (2)which seems to be every time, waiting for it to
drop under 1.4 is definitely suboptimal), and it seems more improvements can be made.
1) In this case, given the smoothness of the trend vigor, is it possible to be more aggressive with it? For
instance, although it avoided the financial crisis completely, it did not re-enter the market until late 2009,
missing a chunk of the trend, and furthermore, it also managed to give back most of those profits (but not
all). Can this be rectified?
3) What can be done to keep strategies built on this indicator from giving back open equity?
While I myself have more indicators to write about, I also feel that input from readers may be worth testing
as well.