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

[This article was first published on QuantStrat TradeR » R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

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

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

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

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

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

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

Here’s the syntax for this strategy:

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

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

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

source("demoData.R")

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

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

#parameters
nSMA1=10
nSMA2=30
nSMA3=5
profMargin=1.5

period=10
pctATR=.1


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Thanks for reading.


To leave a comment for the author, please follow the link and comment on their blog: QuantStrat TradeR » R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)