Adding a Little Complexity – Triple Cross + RSI + MACD – Trailing Stop

     The last post introduced quantstrat, a really powerful tool for backtesting technical indicators based investing strategies.  Several references were cited and numerous, albeit for the most part sparse and/or simple strategies, can be found in the materials given.

      Rather than spending any more time on simple examples, we will move on to a more realistic and necessarily more complex situation that involves several indicators.  Even here, some may find the computation of signals to be simple.  For the moment,  we will just make use of whatever functions are available in the various packages that will be loaded into the environment.  Custom indicators and signals may indeed be incorporated quite easily into strategy formulation.  Just as an example, there is this Ehler’s Roofing / Super Smoother Filter or Connors RSI which has received a lot of attention but which need custom solutions since no R packages provide calculations for these.

      In addition, we will look at trying to limit our downside risk by incorporating trailing-stop type of orders.  There are other such stop-type orders, such as stop-loss, stop-limit etc., which can also be tested with quantstrat.  For explanations of these, any investment oriented sites such as Investopedia should help clarify terms.  We select trailing-stop because it is a little more realistic and studies have shown that it gives better results.  In keeping with our philosophy of testing all such claims, this will also be demonstrated here making it a little long-winded perhaps.  Three cases will be considered, simple market entry/exit. second is the stop-loss and the last is the trailing stop.   Only long situations will be shown, although it is quite trivial to also include short selling in the interests of keeping code not too long. 

We will use the same universe of stocks as previously, if only for the simple reason that we have already collected data for quick use.  There is nothing to prevent use of any other symbol  or ETF to evaluate alternate investment opportunities.

  Transaction fees have been ignored for the analysis but this can be accounted for as well.

    The strategy is based on 3 well known technical indicators, i.e., EMA (Exponential Moving Average), MACD (Moving Average Convergence/Divergence) and the RSI (Relative Strength Indicator.)  The triple cross moving average involves taking the EMA at 3 different lookback periods, viz., 4-day, 9-day and 18-day, we’ll call these EMA(4), EMA(9) & EMA(18) respectively.  The triple cross is defined as when the EMA4 > EMA9 > EMA18 for the upside; and the reverse is  EMA4 < EMA9 < EMA18.  RSI thresholds will be set at 30% and 70% and the MACD provides a standard signal line.  Entry and exit rules are :

       1>  Enter Long, i.e., Buy when either the triple cross is on upside, all greater OR RSI falls below 30% together with the MACD signal above zero;

       2> Exit Long position, i.e., Sell when either the triple cross is on downside, i.e., the less than case as above, OR RSI rises above 70% together with the MACD signal crossing below zero.

        All the indicators, signals and rules necessary for this are coded in the example below.  In addition, since we wish to compare the no-frills buy/sell with a stop trailing case, this has also been included as another case.  Notice that rules have been setup and are only enabled when required.  Also for testing the trailing stop situation, we use the same strategy but use different portfolio and account names.  Finally, after all backtesting done, we collect results and plot.

 

The complete code for this test is presented here below:

##############################################################################

###  

###   Triple Cross + MACD + RSI

###   Long Signal;  Stop Loss ; Trailing Stop

##########################################################

 

library(quantstrat)

require(PerformanceAnalytics)

####   clean up environments – start afresh

source(“Z:/WorkSpace/R/Project0/Quantstrat/QS_CLEANSTART.r”)

source(“Z:/WorkSpace/R/Project0/Quantstrat/QS_CUSTFUNCS.r”)

#########################################

###   What symbols are we testing one or many

###   Note ticker symbols ‘T’ (ATT) and ‘F’  for Fors will give problems  in getSymbols

#########################################

 

Symbols = c(“MO” ,  “UVV”,  “MMP”,  “GTY”, “ORI”,  “CINF” ,“BWLA”, “VVC”, “ATO”,  “RPM”,  “IFF”,  “LMT”,

            “RTN”,  “NOC” , “ODC” , “CPB”,  “R”,    “MMP” , “GTY” , “THFF”, “ABT”, “MMM”,  “MXIM”, “ITW” , “MSFT” )

 

###   for testing use just a few           

Symbols = c(“MO” ,  “UVV”,  “MMP”, “ABT”, “MMM”,  “MXIM”, “ITW”)      

###################################

## parameters for run in specific controller file

###################################

currency(‘USD’)

stock(Symbols,currency=‘USD’,multiplier=1)

 

startDate=‘2010-07-01’

startEquity=1000000

tradeSize = startEquity / length(Symbols)

 

QstRunId=‘QS_MAINDRIVER’

 

stratName<-portfName<-acctName<-QstRunId

 

###   swicthes for debugging – ignore for now

LOG_SW=FALSE

DBUG_SW = TRUE

bypass_pkg=FALSE      ## <<–  apply ind/signals/ separately for debugging & avoind going to package code

ordPosLimit = 1000

 

adjustment <- TRUE

.fast <- 10

.slow <- 30

.threshold <- 0.05   ## was .0005 in original

.orderqty <- 100

.txnfees <- 10

.stoploss <- .05      ###   was  3e-3 # 0.003 or 0.3%

 

Sys.setenv(TZ = “UTC”)

 

currency(‘USD’)stock(Symbols, currency = “USD”, multiplier = 1)

 

##    get historic price data for symbols

source(“Z:/WorkSpace/R/Project0/Quantstrat/QS_GETDATA.r”)

 

initPortf(portfName,symbols=Symbols, startDate=startDate)

initAcct(acctName,portfolios=portfName, startDate=startDate, initEq = startEquity )

initOrders(portfolio=portfName,startDate=startDate)

 

for (symbol in Symbols)

  addPosLimit(portfName, symbol, startDate, ordPosLimit , 1 ) #set max pos

# define and store the strategy with name=stratName

strategy(stratName, store=TRUE) 

 

exHLC = quote(c(grep(“(.+\\.Close)|(.+\\.Low)|(.+\\.High)”, colnames(mktdata)) ))

add.indicator(strategy=stratName, name=“ATR”, arguments=list(HLC = quote(mktdata[,eval(exHLC)]), maType=“EMA”), label=“ATR”)

 

add.indicator(strategy=stratName, name=“EMA”, arguments=list(x = quote(Cl(mktdata)), n=4), label=“EMA4”)

add.indicator(strategy=stratName, name=“EMA”, arguments=list(x = quote(Cl(mktdata)), n=9), label=“EMA9”)

add.indicator(strategy=stratName, name=“EMA”, arguments=list(x = quote(Cl(mktdata)), n=18), label=“EMA18”)

add.indicator(strategy=stratName, name=“MACD”, arguments=list(x = quote(Cl(mktdata)), maType=“EMA”), label=“MACD”)

add.indicator(strategy=stratName, name=“RSI”, arguments=list(price = quote(Cl(mktdata)[,1]), maType=“EMA”), label=“RSI”)

 

###   test just indicators with a symbol

allInds = applyIndicators(stratName, mktdata = get(Symbols[1]) )

 

####

##    setup signals for scenario tested

add.signal(strategy=stratName, name=“sigComparison”,

           arguments = list(columns=c(“EMA.EMA4”,“EMA.EMA9”),relationship=“gt”),label=“Up4X9”)

add.signal(strategy=stratName, name=“sigComparison”,

           arguments = list(columns=c(“EMA.EMA9”,“EMA.EMA18”),relationship=“gt”),label=“Up9X18”)

 

add.signal(strategy=stratName, name=“sigComparison”,

           arguments = list(columns=c(“EMA.EMA4”,“EMA.EMA9”),relationship=“lt”),label=“Down4X9”)

add.signal(strategy=stratName, name=“sigComparison”,

           arguments = list(columns=c(“EMA.EMA9”,“EMA.EMA18”),relationship=“lt”),label=“Down9X18”)

 

add.signal(strategy = stratName, name=“sigThreshold”,

           arguments = list(threshold=70, column=“RSI”,relationship=“gt”),label=“RSIGT70”)

 

add.signal(strategy = stratName, name=“sigThreshold”,

           arguments = list(threshold=30, column=“RSI”,relationship=“lt”),label=“RSILT30”)

 

add.signal(strategy=stratName,name=“sigThreshold”,

           arguments = list(column=“signal.MACD”, relationship=“gt”, threshold=0), label=“MACDGT0” )

 

add.signal(strategy=stratName,name=“sigThreshold”,

           arguments = list(column=“signal.MACD”, relationship=“lt”, threshold=0), label=“MACDLT0” )

# ###

 

add.signal(strategy=stratName, name=“sigFormula”,

           arguments = list(columns=c(“Up4X9”,“Up9X18”, “RSILT30” ),formula=“((Up4X9 & Up9X18) | RSILT30)”, cross=TRUE ),label=“longEntry”)

 

add.signal(strategy=stratName, name=“sigFormula”,

           arguments = list(columns=c(“Down4X9”,“Down9X18”, “RSIGT70”), formula=“(Down4X9 & Down9X18) | RSIGT70)”, cross=TRUE),label=“longExit”)add.signal(strategy=stratName, name=“sigFormula”,

           arguments = list(columns=c(“Up4X9”,“Up9X18”, “RSILT30”, “MACDGT0” ),

                            formula=“((Up4X9 & Up9X18) | (RSILT30 & MACDGT0) )”, cross=TRUE ),label=“longEntryMult”)

 

add.signal(strategy=stratName, name=“sigFormula”,

           arguments = list(columns=c(“Down4X9”,“Down9X18”, “RSIGT70”, “MACDLT0”),

                            formula=“((Down4X9 & Down9X18) | (MACDLT0 & RSIGT70))”, cross=TRUE),label=“longExitMult”)

##  to check signals

allSigs = checkSignals(.strategy[[stratName]], stratName, portfName)

 

################################

####     Lets setup rules for many situations    short setup is similar

####     1>  Just a straight market long (buy/sell)

####     2>  stopLimit long (buy/sell)

####     3>  ust a straight market long – but trailing stop

####     4>  stopLimit long (buy/sell)

####     5>  stopLimit & trailing stop

####     2>  stopLimit long (buy/sell)

####     Rules not enabled=FALSE on setup

####     test different variations by enabling rules

####     When enabling set of rules – watch label can use grep to match

####     give them all different pfolio/acct names

################################

 

add.rule(strategy = stratName,name=‘ruleSignal’,

         arguments = list(sigcol=“longEntryMult”,

                          sigval=TRUE,

                          orderqty= ordPosLimit,

                          ordertype=‘market’,

                          orderside=NULL,

                          tradeSize = tradeSize,

                          maxSize=tradeSize,

                          threshold=NULL,

                          osFUN=osMaxDollar),

         #osFUN=oEMAxPos),

         label=“longBuy”, enabled=FALSE,

         type=‘enter’)

 

add.rule(strategy = stratName,name=‘ruleSignal’,

         arguments = list(sigcol=“longExitMult”,

                          sigval=TRUE,

                          orderqty= ‘all’,

                          ordertype=‘market’,

                          orderside=NULL,

                          osFUN=osMaxDollar),

         #osFUN=oEMAxPos),

         label=‘longSell’, enabled=FALSE,

         type=‘exit’)

 

add.rule(strategy = stratName,name=‘ruleSignal’,

         arguments = list(sigcol=“longEntryMult”,

                          sigval=TRUE,

                          orderqty= ordPosLimit,

                          ordertype=‘stoplimit’,

                          orderside=NULL,

                          threshold = .threshold,

                          tmult = TRUE,

                          tradeSize = tradeSize,

                          maxSize=tradeSize,

                          orderset = “ocolong”,

                          osFUN=osMaxDollar),

         #osFUN=osMaxPos),

         label=“stopLimitBuy”, enabled=FALSE,

         type=‘enter’)

 

add.rule(strategy = stratName,name=‘ruleSignal’,

         arguments = list(sigcol=“longExitMult”,

                          sigval=TRUE,

                          orderqty= ‘all’,

                          ordertype=‘market’,

                          orderside=NULL,

                          orderset = “ocolong”,

                          osFUN=osMaxDollar),

         #osFUN=osMaxPos),

         label=‘stopLimitSell’,  enabled=FALSE,

         type=‘exit’)

 

add.rule(strategy = stratName,

         name = “ruleSignal”,

         arguments = list(sigcol = “longEntryMult” ,

                          sigval = TRUE,

                          replace = FALSE,

                          orderside = “long”,

                          ordertype = “stoplimit”,

                          tmult = TRUE,

                          threshold = quote(.stoploss),

                          #TxnFees = .txnfees,

                          orderqty = “all”,

                          orderset = “ocolong”),

         type = “chain”,

         parent = “longBuy”,

         label = “StopLossLONG”,

         enabled = FALSE)

 

add.rule(strategy = stratName,  name = ‘ruleSignal’,

         arguments=list(sigcol=“longEntryMult” , sigval=TRUE,

                        replace=FALSE,

                        orderside=‘long’,

                        ordertype=‘stoptrailing’,

                        tmult=TRUE,

                        threshold=quote(pctTrailingStop),

                        orderqty=‘all’,

                        orderset=‘ocolong’

         ),

         type=‘chain’, parent=“longBuy”,

         label=‘StopTrailingLong’,

         enabled=FALSE

)

 #####################################################

#####        End of rules setup

#####################################################

 for (symbol in Symbols)

  addPosLimit(portfName, symbol, startDate, ordPosLimit , 1 ) #set max pos

 

###   enable rules that start with long

enable.rule(stratName, type = “enter”, label = “long”)

enable.rule(stratName, type = “exit”, label = “long”)

#enable.rule(stratName, type = “chain”, label = “StopLoss”)

 

results <- applyStrategy(stratName, portfolios = portfName, verbose=LOG_SW)

updatePortf(portfName)

updateAcct(acctName)

updateEndEq(acctName)

if(checkBlotterUpdate(portfName, acctName, verbose = TRUE))

    print ( paste0(  Blotter Checks Passed – “, portfName) )##############################################

#########     Test a variation of strategy

#########     Give it a different portfolio & account

#########     Clean any old of same name & run

#########     Enable the StopTrailing  Rules

##############################################

pctTrailingStop <- 0.085    ## .085 in other had good results!

enable.rule(stratName, type = “chain”, label = “StopTrailing”)

portfNameOpt02<- acctNameOpt02 <- “QS_ALT01”

suppressWarnings(rm(“order_book.QS_ALT01”,pos=.strategy))

suppressWarnings(rm(“account.QS_ALT01”,“portfolio.QS_ALT01”,pos=.blotter))

rm.strat(“QS_ALT01”)

initPortf(portfNameOpt02,symbols=Symbols, startDate=startDate)

initAcct(acctNameOpt02,portfolios=portfNameOpt02, startDate=startDate, initEq = startEquity )

initOrders(portfolio=portfNameOpt02,startDate=startDate)

 

for (symbol in Symbols)

  addPosLimit(portfNameOpt02, symbol, startDate, ordPosLimit , 1 ) #set max pos

 

start_t<-Sys.time()

 

out_QS<-applyStrategy(strategy=stratName , portfolios=portfNameOpt02, symbols = Symbols, debug=DBUG_SW,verbose=LOG_SW)

 

end_t<-Sys.time()

print(end_tstart_t)

##

 

updatePortf(Portfolio=portfNameOpt02, Symbols, Dates=paste(‘::’,as.Date(Sys.time()),sep=))

updateAcct(acctNameOpt02)

updateEndEq(acctNameOpt02)

 

if(checkBlotterUpdate(portfNameOpt02, acctNameOpt02, verbose = TRUE))

  print ( paste0(  Blotter Checks Passed – “, portfNameOpt02) )

 

##############################################

#########     Test a variation of strategy

#########     Give it a different portfolio & account

#########     Clean any old of same name & run

#########     Enable the StopLoss/Limit Rules

##############################################

 

## this variation has stoplimit type so need to disable first long

enable.rule(stratName, type = “enter”, label = “long”, enabled = FALSE)

enable.rule(stratName, type = “exit”, label = “long”, enabled = FALSE)

enable.rule(stratName, type = “enter”, label = “stopLimit”)

enable.rule(stratName, type = “exit”, label = “stopLimit”)

enable.rule(stratName, type = “chain”, label = “StopLoss”)

#

portfNameOpt<- acctNameOpt <- “QS_ALT”

suppressWarnings(rm(“order_book.QS_ALT”,pos=.strategy))

suppressWarnings(rm(“account.QS_ALT”,“portfolio.QS_ALT”,pos=.blotter))

rm.strat(“QS_ALT”)

initPortf(portfNameOpt,symbols=Symbols, startDate=startDate)

initAcct(acctNameOpt,portfolios=portfNameOpt, startDate=startDate, initEq = startEquity )

initOrders(portfolio=portfNameOpt,startDate=startDate)

 

for (symbol in Symbols)

 addPosLimit(portfNameOpt, symbol, startDate, ordPosLimit , 1 )    ### set max pos

 

start_t<-Sys.time()

 

out_QS<-applyStrategy(strategy=stratName , portfolios=portfNameOpt, symbols = Symbols, debug=DBUG_SW,verbose=LOG_SW)

 

end_t<-Sys.time()

print(end_tstart_t)

 updatePortf(Portfolio=portfNameOpt, Symbols, Dates=paste(‘::’,as.Date(Sys.time()),sep=))

updateAcct(acctNameOpt)

updateEndEq(acctNameOpt)

 

if(checkBlotterUpdate(portfNameOpt, acctNameOpt, verbose = TRUE))

  print ( paste0(  Blotter Checks Passed – “, portfNameOpt) )

 

######           print some summary Stats  #######

#####     maybe give some meaningful names

printRunStats(portfName); printRunStats(portfNameOpt); printRunStats(portfNameOpt02)

 

#####   Finally we can plot all of them separately

 

listAccts <- list( list(acctName, “red”, “No Stop”),list(acctNameOpt, “green”, “Stop Loss”), list(acctNameOpt02, “blue”, “Trailing Stop”) )

plotStratCompares(listAccts, startDate= startDate)

#############   end of strategy test ##################

 

T

The summary of runs is shown below –  QS_MAINDRIVER is the regular long case;  QS_ALTis the stop loss; and QS_ALT01 is for the trailing stop.  Clearly the trailing stop fares better than all the others with trailing percentage set at 8.5%.  We can test effect of varying this also, but this will be deferred to a later post since we will need to get into parallel processing.

> printRunStats(portfName); printRunStats(portfNameOpt); printRunStats(portfNameOpt02)

[1] “   ##########    Statistics for Portfolio QS_MAINDRIVER    *************”

[1] ” Net Profit For Portfolio  is 309824.120000 “

[1] ” Aggregate Profit Ratio is 1.310533 “

[1] ” Percentage Positive Trades is 51.637143 “

[1] ” Total Number trades is 429 “

[1] ” Mean Win-Loss Ratio is 1.314286 “

                     ABT       ITW       MMM       MMP        MO      MXIM       UVV

Num.Txns          136.00    143.00    115.00    121.00    116.00    159.00    129.00

Net.Trading.PL  50017.11  -2177.27  72181.70  32378.92 103577.40  61464.12  -7617.86

Largest.Winner  14825.56  22611.69  17760.48  38272.08  23072.64  30875.52  51060.00

Largest.Loser  -11271.69 -37155.75 -11925.88 -70524.54 -17480.76 -16024.80 -25248.87

Ann.Sharpe          2.22     -0.07      4.25      0.72      6.32      1.32     -0.18

Max.Drawdown   -33838.49 -66374.65 -37647.43 -80631.36 -70912.74 -55392.88 -58646.17

[1] “   ##########    Statistics for Portfolio QS_ALT    *************”

[1] ” Net Profit For Portfolio  is -135519.000000 “

[1] ” Aggregate Profit Ratio is 0.730916 “

[1] ” Percentage Positive Trades is 37.675714 “

[1] ” Total Number trades is 141 “

[1] ” Mean Win-Loss Ratio is 1.238571 “

                     ABT       ITW       MMM       MMP        MO      MXIM       UVV

Num.Txns           34.00     36.00     32.00     46.00     41.00     54.00     38.00

Net.Trading.PL -47524.51  -3817.67 -38381.10   9286.83 -43951.05 -21551.76  10420.26

Largest.Winner   6722.82  16294.82  11146.26  31062.87  13257.61  16729.99  44511.00

Largest.Loser  -13439.00 -10307.22  -9132.64 -13607.14  -6755.22 -17872.51 -12147.15

Ann.Sharpe         -7.35     -0.45     -6.14      0.71      0.52     -1.79      0.68

Max.Drawdown   -59345.65 -36794.00 -56654.32 -32556.32 -66156.77 -46984.90 -54118.34

[1] “   ##########    Statistics for Portfolio QS_ALT01    *************”

[1] ” Net Profit For Portfolio  is 464635.110000 “

[1] ” Aggregate Profit Ratio is 1.496877 “

[1] ” Percentage Positive Trades is 51.592857 “

[1] ” Total Number trades is 446 “

[1] ” Mean Win-Loss Ratio is 1.508571 “

                     ABT       ITW       MMM       MMP        MO      MXIM       UVV

Num.Txns          139.00    146.00    117.00    126.00    120.00    162.00    136.00

Net.Trading.PL  47955.21     26.11  67515.94  96152.85 138963.83  84358.67  29662.50

Largest.Winner  14825.56  24122.92  17760.48  43626.97  23072.64  52533.65  51005.98

Largest.Loser  -12070.21 -11765.26 -14897.53 -14651.13 -11955.93 -16024.80 -12223.50

Ann.Sharpe          1.99      0.00      3.51      3.02      5.96      1.55      0.73

Max.Drawdown   -34356.61 -54113.14 -32473.27 -40696.45 -31862.95 -54346.93 -53233.45

 

The plot summaries are shown below for cases above as well as a consolidated one.

Thanx for reading

 

3 Comments on “Adding a Little Complexity – Triple Cross + RSI + MACD – Trailing Stop”

  1. 👉 $5,000 FREE EXCHANGE BONUSES BELOW 📈 👉 PlaseFuture FREE $3,000 BONUS + 0% Maker Fees 📈 + PROMOCODE FOR NEWS USERS OF THE EXCHANGE 👉 [M0345IHZFN] — 0.01 BTC 👉 site: https://buycrypto.in.net Our site is a secure platform that makes it easy to buy, sell, and store cryptocurrency like Bitcoin, Ethereum, and More. We are available in over 30 countries worldwide.

Comments are closed.