This document provides code for backtesting a trading strategy using the quantstrat package in R. It loads relevant packages, defines parameters like the ticker ("SPY"), dates, and a trade size. Technical indicators like moving averages and RSI are calculated on SPY data. Signals are defined to generate buy/sell signals when indicators cross thresholds. Rules are added to enter/exit positions when signals occur. The strategy is applied using applyStrategy() and results are analyzed.
1. Trading Code
Jiaxiang Li
Thanks so much to the developers of quantstrat 99% of this code comes from the demos in the quantstrat
package
Load the quantstrat-package and ggvis-package for html documents
rm(list=ls())
library(quantstrat)
## Loading required package: quantmod
## Loading required package: xts
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Loading required package: TTR
## Version 0.4-0 included new data defaults. See ?getSymbols.
## Loading required package: blotter
## Loading required package: FinancialInstrument
## Loading required package: PerformanceAnalytics
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
## Loading required package: foreach
library(ggvis)
##
## Attaching package: 'ggvis'
## The following object is masked from 'package:quantmod':
##
## add_axis
Create initdate, from, and to charater strings
initdate <- "1999-01-01"
from <- "2003-01-01"
to <- "2016-12-31" #latest
Set the timezone to UTC
Sys.setenv(TZ = "UTC")
1
2. Set the currency to USD
currency("USD")
library(quantmod)
getSymbols("SPY", from = "2003-01-01", to = "2016-12-31", src = "yahoo", adjust = TRUE)
## As of 0.4-0, 'getSymbols' uses env=parent.frame() and
## auto.assign=TRUE by default.
##
## This behavior will be phased out in 0.5-0 when the call will
## default to use auto.assign=FALSE. getOption("getSymbols.env") and
## getOptions("getSymbols.auto.assign") are now checked for alternate defaults
##
## This message is shown once per session and may be disabled by setting
## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for more details.
## [1] "SPY"
Use the stock command to initialize SPY and set currency to USD
stock("SPY", currency = "USD")
## [1] "SPY"
# Define your trade size and initial equity
tradesize <- 100000
initeq <- 100000
Define the names of your strategy, portfolio and account
strategy.st <- "firststrat"
portfolio.st <- "firststrat"
account.st <- "firststrat"
Remove the existing strategy if it exists
rm.strat(strategy.st)
initialize the portfolio
initPortf(portfolio.st, symbols = "SPY", initDate = initdate, currency = "USD")
## [1] "firststrat"
initialize the account
initAcct(account.st, portfolios = portfolio.st, initDate = initdate, currency = "USD", initEq = initeq)
## [1] "firststrat"
initialize the orders
initOrders(portfolio.st, initDate = initdate)
store the strategy
strategy(strategy.st, store = TRUE)
Create a 200-day moving average
spy_ma <- SMA(x = Cl(SPY), n = 200)
Create an RSI with a 3 day lookback period
2
3. spy_rsi <- RSI(price = Cl(SPY), n = 3)
par(mfrow=c(2,1))
plot.ts(spy_ma)
plot.ts(spy_rsi)
Time
spy_ma
0 500 1000 1500 2000 2500 3000 3500
80
Time
spy_rsi
0 500 1000 1500 2000 2500 3000 3500
080
Plot the closing prices of SPY
plot(Cl(SPY))
# Overlay a 200-day SMA
lines(SMA(Cl(SPY), n = 200), col = "red")
3
4. Jan 02
2003
Jan 03
2005
Jan 03
2007
Jan 02
2009
Jan 03
2011
Jan 02
2013
Jan 02
2015
Dec 30
2016
100150200 Cl(SPY)
Is this a trend or reversion indicator?
"trend"
## [1] "trend"
plot the closing price of SPY
par(mfrow=c(2,1))
plot(Cl(SPY))
# plot the RSI 2
plot(RSI(Cl(SPY), n = 2))
4
5. Jan 02
2003
Jan 03
2005
Jan 03
2007
Jan 02
2009
Jan 03
2011
Jan 02
2013
Jan 02
2015
Dec 30
2016
100 Cl(SPY)
Jan 02
2003
Jan 03
2005
Jan 03
2007
Jan 02
2009
Jan 03
2011
Jan 02
2013
Jan 02
2015
Dec 30
2016
080
RSI(Cl(SPY), n = 2)
3.5 Implementing an indicator
## [1] "firststrat"
## [1] "firststrat"
## [1] "firststrat"
## [1] "firststrat"
## [1] "firststrat"
3.10 Apply the indicator
• use applyIndicators to test out your indicators
test <- applyIndicators(strategy = strategy.st, mktdata = OHLC(SPY))
• subset your data between Sep. 1 and Sep. 5 of 2013
test_subset <- test["2003-09-01/2003-09-05"]
test_subset
## SPY.Open SPY.High SPY.Low SPY.Close SMA.SMA200 SMA.SMA50
## 2003-09-02 77.47918 78.42442 77.02943 78.36344 NA 75.77683
## 2003-09-03 78.53876 79.04949 78.34819 78.79032 NA 75.85215
## 2003-09-04 78.59212 78.93516 78.33295 78.82844 NA 75.92670
## 2003-09-05 78.47016 78.93516 78.05852 78.38631 NA 76.00750
## EMA.RSI_3 RSI_avg.RSI_3_4 DVO.DVO_2_126
## 2003-09-02 95.35548 93.23960 89.68254
## 2003-09-03 96.59124 94.80571 84.92063
## 2003-09-04 96.70854 94.95093 61.90476
5
6. ## 2003-09-05 60.49185 63.92274 43.65079
4. Signals
4.1 Signal or not?
test["2010-09-20/2010-09-20"]
## SPY.Open SPY.High SPY.Low SPY.Close SMA.SMA200 SMA.SMA50
## 2010-09-20 99.22294 100.6118 98.90649 100.392 97.32048 96.01428
## EMA.RSI_3 RSI_avg.RSI_3_4 DVO.DVO_2_126
## 2010-09-20 97.03421 95.32398 67.46032
test["2010-09-30/2010-09-30"]
## SPY.Open SPY.High SPY.Low SPY.Close SMA.SMA200 SMA.SMA50
## 2010-09-30 101.1304 101.7809 99.84703 100.3217 97.51358 96.89952
## EMA.RSI_3 RSI_avg.RSI_3_4 DVO.DVO_2_126
## 2010-09-30 46.74999 49.90234 19.84127
4.3 Using sigComparison
# add a sigComparison which specifies that SMA50 must be greater than SMA200, call it longfilter
add.signal(strategy.st, name = "sigComparison",
# we are interested in the relationship between the SMA50 and the SMA200
arguments = list(columns = c("SMA50", "SMA200"),
# particularly, we are interested when the SMA50 is greater than the SMA200
relationship = "gt"), # gt = greater than
# label this signal longfilter
label = "longfilter")
## [1] "firststrat"
4.4 Using sigCrossover
# add a sigCrossover which specifies that the SMA50 is less than the SMA200 and label it filterexit
add.signal(strategy.st, name = "sigCrossover",
# we're interested in the relationship between the SMA50 and the SMA200
arguments = list(columns = c("SMA50", "SMA200"),
# the relationship is that the SMA50 crosses under the SMA200
relationship = "lt"),
# label it filterexit
label = "filterexit")
## [1] "firststrat"
6
7. 4.5 Using sigThreshold
# implement a sigThreshold which specifies that DVO_2_126 must be less than 20, label it longthreshold
add.signal(strategy.st, name = "sigThreshold",
# use the DVO_2_126 column
arguments = list(column = "DVO_2_126",
# the threshold is 20
threshold = 20,
# we want the oscillator to be under this value
relationship = "lt",
# we're interested in every instance that the oscillator is less than 20
cross = FALSE),
# label it longthreshold
label = "longthreshold")
## [1] "firststrat"
# add a sigThreshold signal to your strategy that specifies that DVO_2_126 must cross above 80 and label
add.signal(strategy.st, name = "sigThreshold",
# reference the column of DVO_2_126
arguments = list(column = "DVO_2_126",
# set a threshold of 80
threshold = 80,
# the oscillator must be greater than 80
relationship = "gt",
# we are interested only in the cross
cross = TRUE),
# label it thresholdexit
label = "thresholdexit")
## [1] "firststrat"
4.9 Combining signals
# add a sigFormula signal to your code specifying that both longfilter and longthreshold must be TRUE, l
add.signal(strategy.st, name = "sigFormula",
# specify that longfilter and longthreshold must be TRUE
arguments = list(formula = "longfilter & longthreshold",
# specify that cross must be TRUE
cross = TRUE),
7
8. # label it longentry
label = "longentry")
## [1] "firststrat"
add.signal(strategy.st, name = "sigFormula",
# specify that filterexit and thresholdexit must be TRUE
arguments = list(formula = "filterexit & thresholdexit",
# specify that cross must be TRUE
cross = TRUE),
# label it shortexit
label = "shortexit")
## [1] "firststrat"
test_init <- applyIndicators(strategy.st, mktdata = OHLC(SPY))
test <- applySignals(strategy = strategy.st, mktdata = test_init)
length(test)
## [1] 52875
summary(test)
## Index SPY.Open SPY.High SPY.Low
## Min. :2003-01-02 Min. : 57.55 Min. : 59.29 Min. : 56.83
## 1st Qu.:2006-07-03 1st Qu.: 93.94 1st Qu.: 94.53 1st Qu.: 93.29
## Median :2009-12-31 Median :112.61 Median :113.41 Median :111.56
## Mean :2010-01-01 Mean :124.77 Mean :125.48 Mean :124.01
## 3rd Qu.:2013-07-03 3rd Qu.:153.23 3rd Qu.:154.18 3rd Qu.:152.75
## Max. :2016-12-30 Max. :226.57 Max. :227.00 Max. :226.00
##
## SPY.Close SMA.SMA200 SMA.SMA50 EMA.RSI_3
## Min. : 57.69 Min. : 71.81 Min. : 64.4 Min. : 1.065
## 1st Qu.: 93.94 1st Qu.: 94.67 1st Qu.: 93.9 1st Qu.:35.255
## Median :112.60 Median :112.72 Median :112.3 Median :59.079
## Mean :124.79 Mean :123.76 Mean :124.5 Mean :56.134
## 3rd Qu.:153.25 3rd Qu.:145.91 3rd Qu.:152.5 3rd Qu.:77.974
## Max. :226.43 Max. :211.40 Max. :218.5 Max. :99.833
## NA's :199 NA's :49 NA's :3
## RSI_avg.RSI_3_4 DVO.DVO_2_126 longfilter filterexit
## Min. : 1.858 Min. : 0.7936 Min. :0.0000 Min. :1
## 1st Qu.:37.316 1st Qu.: 25.3968 1st Qu.:1.0000 1st Qu.:1
## Median :58.565 Median : 49.2064 Median :1.0000 Median :1
## Mean :56.011 Mean : 49.8085 Mean :0.7751 Mean :1
## 3rd Qu.:76.026 3rd Qu.: 73.8095 3rd Qu.:1.0000 3rd Qu.:1
## Max. :99.452 Max. :100.0000 Max. :1.0000 Max. :1
## NA's :4 NA's :126 NA's :199 NA's :3518
## longthreshold thresholdexit longentry shortexit
## Min. :0.0000 Min. :0.0000 Min. :0.000 Min. :0.0000
## 1st Qu.:0.0000 1st Qu.:0.0000 1st Qu.:0.000 1st Qu.:0.0000
## Median :0.0000 Median :0.0000 Median :0.000 Median :0.0000
## Mean :0.1995 Mean :0.1162 Mean :0.084 Mean :0.0015
## 3rd Qu.:0.0000 3rd Qu.:0.0000 3rd Qu.:0.000 3rd Qu.:0.0000
8
9. ## Max. :1.0000 Max. :1.0000 Max. :1.000 Max. :1.0000
## NA's :126 NA's :127 NA's :144 NA's :910
summary(test$shortexit)
## Index shortexit
## Min. :2003-01-02 Min. :0.0000
## 1st Qu.:2006-07-03 1st Qu.:0.0000
## Median :2009-12-31 Median :0.0000
## Mean :2010-01-01 Mean :0.0015
## 3rd Qu.:2013-07-03 3rd Qu.:0.0000
## Max. :2016-12-30 Max. :1.0000
## NA's :910
5 Rules
# add a rule that uses an osFUN to size an entry position
tradesize = 1e+05
add.rule(strategy = strategy.st, name = "ruleSignal",
arguments = list(sigcol = "longentry", sigval = TRUE, ordertype = 'market',
orderside = "long", replace = FALSE, prefer = "Open",
# set orderqty to 500
orderqty = 500,
# the tradeSize argument should be equal to tradesize (defined earlier)
tradeSize = tradesize,
# the maxSize argument should be equal to tradesize as well
maxSize = tradesize),
type = "enter")
## [1] "firststrat"
add.rule(strategy = strategy.st, name = "ruleSignal",
arguments = list(sigcol = "shortexit", sigval = TRUE, ordertype = 'market',
orderside = "long", replace = FALSE, prefer = "Open",
# set orderqty to 500
orderqty = 500,
# the tradeSize argument should be equal to tradesize (defined earlier)
tradeSize = tradesize,
# the maxSize argument should be equal to tradesize as well
maxSize = tradesize),
type = "exit")
## [1] "firststrat"
9
10. 6. Analyzing results
6.1 Running the strategy
from chapter 6
# use applyStrategy() to apply your strategy. Save this to out
out <- applyStrategy(strategy = strategy.st, portfolios = portfolio.st)
# update your portfolio (portfolio.st)
updatePortf(portfolio.st)
daterange <- time(getPortfolio(portfolio.st)$summary)[-1]
# update your account (account.st)
updateAcct(account.st, daterange)
updateEndEq(account.st)
# what is the date of the last trade?
"2016-12-29"
6.2 Profit factor
portfolio.st
## [1] "firststrat"
# Get the tradeStats for your portfolio
tstats <- tradeStats(portfolio.st)
# Print the profit factor
tstats$Profit.Factor
## NULL
6.3 Percent positive
tstats
## NULL
6.4 Using chart.Posn()
# use chart.Posn to view your system's performance on SPY
chart.Posn(Portfolio = portfolio.st, Symbol = "SPY")
10
11. 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016
Jan 02
2003
Jan 02
2004
Jan 03
2005
Jan 03
2006
Jan 03
2007
Jan 02
2008
Jan 02
2009
Jan 04
2010
Jan 03
2011
Jan 03
2012
Jan 02
2013
Jan 02
2014
Jan 02
2015
Jan 04
2016
Dec 30
2016
SPY 2003−01−02 / 2016−12−30
60
80
100
120
140
160
180
200
220
60
80
100
120
140
160
180
200
220
50
100
150
200
250
Positionfill 144000
0
50000
100000
150000
0
50000
100000
150000
0
50000
100000
150000
CumPL 13220349.21332
−5000000
0
5000000
10000000
15000000
−5000000
0
5000000
10000000
15000000
Drawdown −415173.81869
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
6.5 Adding an indicator to a chart.Posn() chart
# compute the SMA50
sma50 <- SMA(x = Cl(SPY), n = 50)
# compute the SMA200
sma200 <- SMA(x = Cl(SPY), n = 200)
# compute the DVO_2_126 with an navg of 2 and a percentlookback of 126
dvo <- DVO(HLC = HLC(SPY), navg = 2, percentlookback = 126)
# recreate the chart.Posn of the strategy from the previous exercise
chart.Posn(Portfolio = portfolio.st, Symbol = "SPY")
11
12. 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016
Jan 02
2003
Jan 02
2004
Jan 03
2005
Jan 03
2006
Jan 03
2007
Jan 02
2008
Jan 02
2009
Jan 04
2010
Jan 03
2011
Jan 03
2012
Jan 02
2013
Jan 02
2014
Jan 02
2015
Jan 04
2016
Dec 30
2016
SPY 2003−01−02 / 2016−12−30
60
80
100
120
140
160
180
200
220
60
80
100
120
140
160
180
200
220
50
100
150
200
250
Positionfill 144000
0
50000
100000
150000
0
50000
100000
150000
0
50000
100000
150000
CumPL 13220349.21332
−5000000
0
5000000
10000000
15000000
−5000000
0
5000000
10000000
15000000
Drawdown −415173.81869
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
# overlay the SMA50 on your plot as a blue line
add_TA(sma50, on = 1, col = "blue")
12
13. 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016
Jan 02
2003
Jan 02
2004
Jan 03
2005
Jan 03
2006
Jan 03
2007
Jan 02
2008
Jan 02
2009
Jan 04
2010
Jan 03
2011
Jan 03
2012
Jan 02
2013
Jan 02
2014
Jan 02
2015
Jan 04
2016
Dec 30
2016
SPY 2003−01−02 / 2016−12−30
60
80
100
120
140
160
180
200
220
60
80
100
120
140
160
180
200
220
50
100
150
200
250
Positionfill 144000
0
50000
100000
150000
0
50000
100000
150000
0
50000
100000
150000
CumPL 13220349.21332
−5000000
0
5000000
10000000
15000000
−5000000
0
5000000
10000000
15000000
Drawdown −415173.81869
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
−1.4e+07
−1.2e+07
−1.0e+07
−8.0e+06
−6.0e+06
−4.0e+06
−2.0e+06
0.0e+00
50
100
150
200
250
# overlay the SMA200 on your plot as a red line
add_TA(sma200, on = 1, col = "red")
13