Chapter 3 Using Quantsrat
In this book we use the quantstrat
library version 0.9.1739. quantstrat
provides the base functions we will use to build our strategies; adding indicators, signals and creating the rules of when to buy and when to sell.
quantstrat
is for signal-based trading strategies, not time-based. However, you can create functions that add signals based on time frames and implement those functions as indicators. We’ll get to that later.
quantstrat
also allows us to test a strategy on one or many symbols. The downside to using many symbols is that it can be resource-intensive. We can also test strategies with a range of parameters. Say, for example, you want to test a simple SMA strategy but want to find the best-performing SMA parameter; quantstrat
allows for this. Again, though, it can be resource-intensive.
3.1 Settings and Variables
Settings listed here will be used in all of our backtests. They are required; you will get errors if you run any of the strategies without including the below settings and variables. Some of these may change depending on the strategy which will be noted.
First we use Sys.setenv()
to set our timezone to UTC.
Sys.setenv(TZ = "UTC")
Next, since we’ll be working with stocks in the U.S. market we need to set our currency
object to USD.
currency('USD')
## [1] "USD"
When backtesting strategies you should always include periods of market turmoil. After all, you don’t want to just see how your strategy performs when the market is strong but also when it is weak. For this book we’ll use the years 2008 and 2009.
init_date
: The date we will initialize our account and portfolio objects. This date should be the day prior tostart_date
.start_date
: First date of data to retrieve.end_date
: Last date of data to retrieve.init_equity
: Initial account equity.adjustment
: Boolean - TRUE if we should adjust the prices for dividend payouts, stock splits, etc; otherwise, FALSE.
You should always work with adjusted pricing when possible to give you the truest results.
init_date <- "2007-12-31"
start_date <- "2008-01-01"
end_date <- "2009-12-31"
init_equity <- 1e4 # $10,000
adjustment <- TRUE
3.2 Symbols
Most our strategies will use three ETF’s: IWM, QQQ and SPY. This is only for demonstration purposes. They are loaded into basic_symbols()
.
basic_symbols <- function() {
symbols <- c(
"IWM", # iShares Russell 2000 Index ETF
"QQQ", # PowerShares QQQ TRust, Series 1 ETF
"SPY" # SPDR S&P 500 ETF Trust
)
}
Where we may want to test strategies on a slightly broader scale we’ll use enhanced_symbols()
which adds basic_symbols()
, TLT and Sector SPDR ETF’s XLB, XLE, XLF, XLI, XLK, XLP, XLU, XLV, and XLY.
enhanced_symbols <- function() {
symbols <- c(
basic_symbols(),
"TLT", # iShares Barclays 20+ Yr Treas. Bond ETF
"XLB", # Materials Select Sector SPDR ETF
"XLE", # Energy Select Sector SPDR ETF
"XLF", # Financial Select Sector SPDR ETF
"XLI", # Industrials Select Sector SPDR ETF
"XLK", # Technology Select Sector SPDR ETF
"XLP", # Consumer Staples Select Sector SPDR ETF
"XLU", # Utilities Select Sector SPDR ETF
"XLV", # Health Care Select Sector SPDR ETF
"XLY" # Consumer Discretionary Select Sector SPDR ETF
)
}
Lastly, we may use global_symbols()
for better insight into a strategy. However, the purposes of this book is to show how to backtest strategies, not to find profitable strategies.
global_symbols <- function() {
symbols <- c(
enhanced_symbols(),
"EFA", # iShares EAFE
"EPP", # iShares Pacific Ex Japan
"EWA", # iShares Australia
"EWC", # iShares Canada
"EWG", # iShares Germany
"EWH", # iShares Hong Kong
"EWJ", # iShares Japan
"EWS", # iShares Singapore
"EWT", # iShares Taiwan
"EWU", # iShares UK
"EWY", # iShares South Korea
"EWZ", # iShares Brazil
"EZU", # iShares MSCI EMU ETF
"IGE", # iShares North American Natural Resources
"IYR", # iShares U.S. Real Estate
"IYZ", # iShares U.S. Telecom
"LQD", # iShares Investment Grade Corporate Bonds
"SHY" # iShares 42372 year TBonds
)
}
3.3 checkBlotterUpdate()
The checkBlotterUpdate()
function comes courtesy of Guy Yollin. The purpose of this function is to check for discrepancies between the account object and portfolio object. If the function returns FALSE we must examine why (perhaps we didn’t clear our objects before running the strategy?).
# Guy Yollin, 2014
# http://www.r-programming.org/papers
checkBlotterUpdate <- function(port.st = portfolio.st,
account.st = account.st,
verbose = TRUE) {
ok <- TRUE
p <- getPortfolio(port.st)
a <- getAccount(account.st)
syms <- names(p$symbols)
port.tot <- sum(
sapply(
syms,
FUN = function(x) eval(
parse(
text = paste("sum(p$symbols",
x,
"posPL.USD$Net.Trading.PL)",
sep = "$")))))
port.sum.tot <- sum(p$summary$Net.Trading.PL)
if(!isTRUE(all.equal(port.tot, port.sum.tot))) {
ok <- FALSE
if(verbose) print("portfolio P&L doesn't match sum of symbols P&L")
}
initEq <- as.numeric(first(a$summary$End.Eq))
endEq <- as.numeric(last(a$summary$End.Eq))
if(!isTRUE(all.equal(port.tot, endEq - initEq)) ) {
ok <- FALSE
if(verbose) print("portfolio P&L doesn't match account P&L")
}
if(sum(duplicated(index(p$summary)))) {
ok <- FALSE
if(verbose)print("duplicate timestamps in portfolio summary")
}
if(sum(duplicated(index(a$summary)))) {
ok <- FALSE
if(verbose) print("duplicate timestamps in account summary")
}
return(ok)
}