In today’s post I will be demonstrating how to back-test intra-day  forex data using the very popular engulfing strategy.
For the purpose of this post I will be using the EUR/USD pair but the code can be altered to test any pair.
I have been using this strategy in my live trading with some success so I decided to back-test it on a larger data sample to see how it fairs.

Strategy Outline:

  • Identify Peaks and Troughs
  •  At peaks look for a bearish engulfing signal  – short trade
  • At troughs look for a bullish engulfing signal – long trade.

I will first post the code then discuss the highlights further on.

Be sure to include the candlesticks package.
https://r-forge.r-project.org/projects/candlesticks/

Get Forex Data Function : forexdata.R

fxhistoricaldata <- function
(
  Symbol,
  timeframe,
  download = FALSE
) 
{   
  
  # setup temp folder
  temp.folder <- paste(getwd(), 'temp', sep='/')
  dir.create(temp.folder, F)
  filename <- paste(temp.folder, '/',"fxhistoricaldata_",Symbol ,"_" ,timeframe,".csv", sep='')
  
  if(download) {
    downloadfile <- paste("http://api.fxhistoricaldata.com/v1/indicators?instruments=" ,Symbol ,"&expression=open,high,low,close&item_count=10000&format=csv&timeframe=" ,timeframe,sep='')
    download.file(downloadfile, filename,  mode = 'wb')
  }
    
    tempdf <- read.csv(filename)
    colnames(tempdf) <- c("Curr","Date","Open","High","Low","Close")
    tempdf <- tempdf[c("Date","Open","High","Low","Close")]
    tempdf$Date <- ymd_hms(tempdf$Date)
    out <-  xts(tempdf[,-1], order.by=tempdf[,1])
  
    return(out)
}

Engulfing Indicator : strategies.R

engulfing <- function(OHLC , profMargin=1.5 ,SLMargin=0.004) {
  LAGTS <- LagOC(OHLC, k = 1)
  BullEngulfing <- reclass(Op(LAGTS) > Cl(LAGTS) & Cl(OHLC) > 
                             Op(OHLC) & Cl(LAGTS) >= Op(OHLC) & Cl(OHLC) >= Op(LAGTS), OHLC)
  BearEngulfing <- reclass(Cl(LAGTS) > Op(LAGTS) & Op(OHLC) > 
                             Cl(OHLC) & Op(LAGTS) >= Cl(OHLC) & Op(OHLC) >= Cl(LAGTS), OHLC)
  bulllimits <- OHLC[BullEngulfing==1,]
  bulllimits$stopLoss <- Lo(OHLC) - SLMargin
  bulllimits$takeProfit <- Hi(OHLC) + (Hi(OHLC)-bulllimits$stopLoss)*profMargin 
  bearlimits <- OHLC[BearEngulfing==1,]
  bearlimits$stopLoss <- Hi(OHLC) + SLMargin
  bearlimits$takeProfit <- Lo(OHLC) + (Lo(OHLC)-bearlimits$stopLoss)*profMargin 
  result <- cbind(BullEngulfing,BearEngulfing, bulllimits$stopLoss ,bulllimits$takeProfit, bearlimits$stopLoss ,bearlimits$takeProfit)
  colnames(result) <- c("Bull", "Bear", "Bull.SL" ,"Bull.TP",  "Bear.SL" ,"Bear.TP")
  xtsAttributes(result) <- list(bars = 2)
  return(result)
 
}

Main R File

require(quantmod)
require(IKTrading)
require(lubridate)
require(quantstrat)
require(PerformanceAnalytics)
require(candlesticks)
require(ggplot2)

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

source("forexdata.R")
source("strategies.R")

SLMargin <- 0.004
profMargin=2

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

symbols <- c("EURUSD")

stock(symbols, currency="USD", multiplier=1)

EURUSD <- fxhistoricaldata('EURUSD' ,'hour',download=F)

EURUSD$row <- seq(1, nrow(EURUSD), 1)
EURUSD$smoothed <- ksmooth(as.numeric(EURUSD$row) , EURUSD$Close, "normal", bandwidth = 5)$y
peaks <- which(diff(diff(EURUSD$smoothed)>=0)<0)+1
troughs <- which(diff(diff(EURUSD$smoothed)>0)>0)+1

EURUSD$peaks <- 0
EURUSD$troughs <- 0
EURUSD$peaks <- ifelse(EURUSD$row %in% peaks, 1, EURUSD$peaks)
EURUSD$troughs <- ifelse(EURUSD$row %in% troughs,1, EURUSD$troughs)

#SEE what we have identified

ggplot(EURUSD[1:1000,],aes(row)) +
  geom_point(aes(y = Close,colour ="Close"),size=3) +
  geom_line(aes(y=smoothed,colour="smoothed"),size= 1.2) +
  geom_point(data= subset(EURUSD,EURUSD$peaks == 1 & EURUSD$row <= 1000),
             aes(y=smoothed),color="red",size=4) +
  geom_point(data= subset(EURUSD,EURUSD$troughs == 1 & EURUSD$row <= 1000),
             aes(y=smoothed),color="green",size=4) +
  theme_bw() +
  theme(legend.position="none")

#trade sizing and initial equity settings
tradeSize <- 100000
initEq <- 100000

strategy.st <- portfolio.st <- account.st <- "Engulfing"
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)

#indicators

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

#signals

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("peaks", 
                                    "Bear.engulfing"), 
                          cross=TRUE),
           label="shortEntry")


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

#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty = tradeSize,
                        prefer="Open",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="bullengulfentry")

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bull.SL.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bullengulfentry",
         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$Bull.TP.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bullengulfentry",
         label="takeProfitLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty = -tradeSize,
                        prefer="Open",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="bearengulfentry")

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bear.SL.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bearengulfentry",
         label="stopLossShort",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="limit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bear.TP.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bearengulfentry",
         label="takeProfitShort",
         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)

tstats <- t(tradeStats(portfolio.st, 'EURUSD'))

orderbook <- getOrderBook(portfolio.st)[[portfolio.st]]$EURUSD

chart.Posn(Portfolio = portfolio.st, Symbol = "EURUSD")


portpl <- .blotter$portfolio.Engulfing$summary$Net.Trading.PL

The first bit of code we will be looking at is identifying the peaks and troughs.

EURUSD$row <- seq(1, nrow(EURUSD), 1)
EURUSD$smoothed <- ksmooth(as.numeric(EURUSD$row) , EURUSD$Close, "normal", bandwidth = 5)$y
peaks <- which(diff(diff(EURUSD$smoothed)>=0)<0)+1
troughs <- which(diff(diff(EURUSD$smoothed)>0)>0)+1

EURUSD$peaks <- 0
EURUSD$troughs <- 0
EURUSD$peaks <- ifelse(EURUSD$row %in% peaks, 1, EURUSD$peaks)
EURUSD$troughs <- ifelse(EURUSD$row %in% troughs,1, EURUSD$troughs)

#SEE what we have identified

ggplot(EURUSD[1:1000,],aes(row)) +
  geom_point(aes(y = Close,colour ="Close"),size=3) +
  geom_line(aes(y=smoothed,colour="smoothed"),size= 1.2) +
  geom_point(data= subset(EURUSD,EURUSD$peaks == 1 & EURUSD$row <= 1000),
             aes(y=smoothed),color="red",size=4) +
  geom_point(data= subset(EURUSD,EURUSD$troughs == 1 & EURUSD$row <= 1000),
             aes(y=smoothed),color="green",size=4) +
  theme_bw() +
  theme(legend.position="none")

We first create a sequential row then create a smoothed regression estimate to eliminate the noise.
From there we can identify the peaks and troughs.
You can try changing the bandwidth value.

The Engulfing function/indicator below takes in extra parameters of profMargin and SLMargin to define your exit strategies.
You can change these variables in the main file to test out taking bigger profits with bigger risks or taking smaller profits with smaller risks.

engulfing <- function(OHLC , profMargin=1.5 ,SLMargin=0.004) {
  LAGTS <- LagOC(OHLC, k = 1)
  BullEngulfing <- reclass(Op(LAGTS) > Cl(LAGTS) & Cl(OHLC) > 
                             Op(OHLC) & Cl(LAGTS) >= Op(OHLC) & Cl(OHLC) >= Op(LAGTS), OHLC)
  BearEngulfing <- reclass(Cl(LAGTS) > Op(LAGTS) & Op(OHLC) > 
                             Cl(OHLC) & Op(LAGTS) >= Cl(OHLC) & Op(OHLC) >= Cl(LAGTS), OHLC)
  bulllimits <- OHLC[BullEngulfing==1,]
  bulllimits$stopLoss <- Lo(OHLC) - SLMargin
  bulllimits$takeProfit <- Hi(OHLC) + (Hi(OHLC)-bulllimits$stopLoss)*profMargin 
  bearlimits <- OHLC[BearEngulfing==1,]
  bearlimits$stopLoss <- Hi(OHLC) + SLMargin
  bearlimits$takeProfit <- Lo(OHLC) + (Lo(OHLC)-bearlimits$stopLoss)*profMargin 
  result <- cbind(BullEngulfing,BearEngulfing, bulllimits$stopLoss ,bulllimits$takeProfit, bearlimits$stopLoss ,bearlimits$takeProfit)
  colnames(result) <- c("Bull", "Bear", "Bull.SL" ,"Bull.TP",  "Bear.SL" ,"Bear.TP")
  xtsAttributes(result) <- list(bars = 2)
  return(result)
 
}

The signals below check for a bullish engulfing candle at the troughs and a bearish engulfing candle at the peaks.
It then signals either a log entry or a short entry.

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("peaks", 
                                    "Bear.engulfing"), 
                          cross=TRUE),
           label="shortEntry")


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

The rule signals chain your entries with your defined stoploss and take profit points with stoplimit orders.

#rules
add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty = tradeSize,
                        prefer="Open",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="bullengulfentry")

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="longEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="long", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bull.SL.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bullengulfentry",
         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$Bull.TP.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bullengulfentry",
         label="takeProfitLong",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty = -tradeSize,
                        prefer="Open",
                        orderset="orders"), 
         type="enter", path.dep=TRUE,
         label="bearengulfentry")

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="stoplimit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bear.SL.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bearengulfentry",
         label="stopLossShort",
         path.dep=TRUE)

add.rule(strategy.st, name="ruleSignal", 
         arguments=list(sigcol="shortEntry", 
                        sigval=TRUE, 
                        ordertype="limit", 
                        orderside="short", 
                        replace=FALSE, 
                        orderqty='all',
                        order.price=quote(mktdata$Bear.TP.engulfing[timestamp]),
                        orderset="orders"), 
         type="chain", 
         parent="bearengulfentry",
         label="takeProfitShort",
         path.dep=TRUE)

So how did the strategy do?

Looking at the stats, it didn't do too badly.
I will definitely be investigating and further refining this strategy.

Thanks.