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.