#Kindly contributed to quantstrat by Garrett See #code borrowed heavily from existing quantstrat demos
This is a simple pairs trading example intended to illustrate how you can
extend existing quantstrat functionality. It uses addPosLimits to specify
levels and position limits, and shows how to pass a custom order sizing
function to osFUN
Note that it would be easier to build a spread first and treat it as a single
instrument instead of dealing with a portfolio of stocks.
given 2 stocks, calculate the ratio of their notional values. If the ratio
falls below it’s 2 stdev band, then when it crosses back above it, buy stock 1
and sell stock 2. If the ratio rises above it’s 2 stdev band, then when it
crosses back below it, sell stock 1 and buy stock 2. If the ratio crosses
its moving average, then flatten any open positions.
The Qty of Stock A that it buys (sells) = MaxPos / lvls
The Qty of Stock B that is sells (buys) = MaxPos * Ratio / lvls
require(quantstrat)
suppressWarnings(rm(“order_book.pair1”,pos=.strategy)) suppressWarnings(rm(“account.pairs”, “portfolio.pair1”, pos=.blotter)) suppressWarnings(rm(“startDate”, “endDate”, “startDate”, “initEq”, “SD”, “N”, “symb1”, “symb2”, “portfolio1.st”, “account.st”, “pairStrat”, “out1”))
startDate <- ‘2009-01-01’
endDate <- ‘2011-05-01’
startDate <- ‘2009-01-02’
initEq <- 100000
SD <- 2
N <- 20
MaxPos <- 1500 #max position in stockA; # max position in stock B will be max * ratio, i.e. no hard position limit in # Stock B lvls <- 3 #how many times to fade; Each order’s qty will = MaxPos/lvls
symb1 <- ‘SPY’ #change these to try other pairs symb2 <- ‘DIA’ #if you change them, make sure position limits still make sense
portfolio1.st <- ‘pair1’ account.st <- ‘pairs’
getSymbols(c(symb1, symb2), from=startDate, to=endDate, adjust=TRUE)
The following function is used to make sure the timestamps of all symbols are
the same deletes rows where one of the stocks is missing data
alignSymbols <- function(symbols, env=.GlobalEnv) { # This is a simplified version of qmao::alignSymbols() if (length(symbols) < 2) stop(“Must provide at least 2 symbols”) if (any(!is.character(symbols))) stop(“Symbols must be vector of character strings.”) ff <- get(symbols[1],env=env) for (sym in symbols[-1]) { tmp.sym <- get(sym,env=env) ff <- merge(ff, tmp.sym, all=FALSE) } for (sym in symbols) { assign(sym,ff[,grep(sym, colnames(ff))], env=env) } symbols } alignSymbols(c(symb1, symb2))
Define Instruments
currency(“USD”) stock(symb1, currency=“USD”, multiplier=1) stock(symb2, currency=“USD”, multiplier=1)
Initialize Portfolio, Account, and Orders
initPortf(name=portfolio1.st, c(symb1,symb2)) initAcct(account.st, portfolios=portfolio1.st, initEq=initEq) initOrders(portfolio=portfolio1.st)
osFUN will need to know which symbol is leg 1 and which is leg 2 as well as
what the values are for MaxPos and lvls. So, create a slot in portfolio to
hold this info.
pair <- c(1, 2, MaxPos, lvls) names(pair) <- c(symb1, symb2, “MaxPos”, “lvls”) .blotter[[paste(‘portfolio’, portfolio1.st, sep=‘.’)]]$pair <- pair
Create initial position limits and levels by symbol
allow 3 entries for long and short if lvls=3.
addPosLimit(portfolio=portfolio1.st, timestamp=startDate, symbol=symb1, maxpos=MaxPos, longlevels=lvls, minpos=-MaxPos, shortlevels=lvls) addPosLimit(portfolio=portfolio1.st, timestamp=startDate, symbol=symb2, maxpos=MaxPos, longlevels=lvls, minpos=-MaxPos, shortlevels=lvls)
Create a strategy object
pairStrat <- strategy(‘pairStrat’)
Indicator function
calcRatio <- function(x) { #returns the ratio of notional close prices for 2 symbols x1 <- get(x[1]) x2 <- get(x[2]) mult1 <- getInstrument(x[1])\(multiplier mult2 <- getInstrument(x[2])\)multiplier rat <- (mult1 * Cl(x1)) / (mult2 * Cl(x2)) colnames(rat) <- ‘Ratio’ rat } # Indicator used for determining entry/exits Ratio <- calcRatio(c(symb1[1], symb2[1]))
Store hedge ratio in portfolio so that it’s available for order sizing
function. In this example, the hedge ratio happens to be the same as the
Ratio indicator.
.blotter[[paste(‘portfolio’,portfolio1.st,sep=‘.’)]]\(HedgeRatio <- Ratio #and make a function to get the most recent HedgeRatio getHedgeRatio <- function(portfolio, timestamp) { portf <- getPortfolio(portfolio) timestamp <- format(timestamp,"%Y-%m-%d %H:%M:%S") # above line ensures you don't get last value of next day if using intraday # data and timestamp=midnight toDate <- paste("::", timestamp, sep="") Ratio <- last(portf\)HedgeRatio[toDate]) as.numeric(Ratio) }
Create an indicator - BBands on the Ratio
pairStrat <- add.indicator(strategy=pairStrat, name = “calcRatio”, arguments=list(x=c(symb1,symb2))) pairStrat <- add.indicator(strategy=pairStrat, name = “BBands”, arguments=list(HLC=quote(Ratio), sd=SD, n=N, maType=‘SMA’))
#applyIndicators(strategy=pairStrat,mktdata=get(symb1[1])) #for debugging
Create signals - buy when crossing lower band from below, sell when crossing
upper band from above, flatten when crossing mavg from above or from below
pairStrat <- add.signal(strategy=pairStrat, name=“sigCrossover”, arguments=list(columns=c(“Ratio”,“up”), relationship=“lt”), label=“cross.up”) pairStrat <- add.signal(strategy=pairStrat, name=“sigCrossover”, arguments=list(columns=c(“Ratio”,“dn”), relationship=“gt”), label=“cross.dn”) pairStrat <- add.signal(strategy=pairStrat, name=“sigCrossover”, arguments=list(columns=c(“Ratio”,“mavg”), relationship=“lt”), label=“cross.mid.fa”) pairStrat <- add.signal(strategy=pairStrat, name=“sigCrossover”, arguments=list(columns=c(“Ratio”,“mavg”), relationship=“gt”), label=“cross.mid.fb”)
make an order sizing function
#######################ORDER SIZING FUNCTION################################## # check to see which stock it is. If it’s the second stock, reverse orderqty and # orderside osSpreadMaxPos <- function (data, timestamp, orderqty, ordertype, orderside, portfolio, symbol, ruletype, …, orderprice) { portf <- getPortfolio(portfolio) #check to make sure pair slot has the things needed for this function if (!any(portf\(pair == 1) && !(any(portf\)pair == 2))) stop(‘pair must contain both values 1 and 2’) if (!any(names(portf\(pair) == "MaxPos") || !any(names(portf\)pair) == “lvls”)) stop(‘pair must contain MaxPos and lvls’)
if (portf\(pair[symbol] == 1) legside <- "long"
if (portf\)pair[symbol] == 2) legside <- “short”
MaxPos <- portf\(pair["MaxPos"]
lvls <- portf\)pair[“lvls”]
ratio <- getHedgeRatio(portfolio, timestamp)
pos <- getPosQty(portfolio, symbol, timestamp)
PosLimit <- getPosLimit(portfolio, symbol, timestamp)
qty <- orderqty
if (legside == “short”) {#symbol is 2nd leg
## Comment out next line to use equal ordersizes for each stock.
addPosLimit(portfolio=portfolio, timestamp=timestamp, symbol=symbol,
maxpos=round(MaxPosratio,0), longlevels=lvls,
minpos=round(-MaxPosratio,0), shortlevels=lvls)
##
qty <- -orderqty #switch orderqty for Stock B
}
if (qty > 0) orderside = ‘long’ if (qty < 0) orderside = ‘short’
orderqty <- osMaxPos(data=data,timestamp=timestamp, orderqty=qty, ordertype=ordertype, orderside=orderside, portfolio=portfolio, symbol=symbol, ruletype=ruletype, …)
#Add the order here instead of in the ruleSignal function if (!is.null(orderqty) & !orderqty == 0 & !is.null(orderprice)) { addOrder(portfolio=portfolio, symbol=symbol, timestamp=timestamp, qty=orderqty, price=as.numeric(orderprice), ordertype=ordertype, side=orderside, replace=FALSE, status=“open”, …=…) } return(0) #so that ruleSignal function doesn’t also try to place an order } ################################################################################
Create entry and exit rules for longs and for shorts. Both symbols will get
the same buy/sell signals, but osMaxPos will reverse those for the second
symbol.
orderqty’s are bigger than PosLimits allow. osMaxPos will adjust the orderqty
down to 1/3 the max allowed. (1/3 is because we are using 3 levels in
PosLimit)
pairStrat <- add.rule(strategy=pairStrat, name=‘ruleSignal’, arguments=list(sigcol=“cross.dn”, sigval=TRUE, orderqty=1e6, ordertype=‘market’, orderside=NULL, osFUN=‘osSpreadMaxPos’), type=‘enter’) pairStrat <- add.rule(strategy=pairStrat, name=‘ruleSignal’, arguments=list(sigcol=“cross.up”, sigval=TRUE, orderqty=-1e6, ordertype=‘market’, orderside=NULL, osFUN=‘osSpreadMaxPos’), type=‘enter’) pairStrat <- add.rule(strategy=pairStrat, name=‘ruleSignal’, arguments=list(sigcol=“cross.mid.fb”, sigval=TRUE, orderqty=‘all’, ordertype=‘market’, orderside=NULL), type=‘exit’) pairStrat <- add.rule(strategy=pairStrat, name=‘ruleSignal’, arguments=list(sigcol=“cross.mid.fa”, sigval=TRUE, orderqty=‘all’, ordertype=‘market’, orderside=NULL), type=‘exit’)
for debugging
applySignals(strategy=pairStrat,
mktdata=applyIndicators(strategy=pairStrat, mktdata=get(symb1)))
out1<-applyStrategy(strategy=pairStrat, portfolios=portfolio1.st)
updatePortf(Portfolio=portfolio1.st, Dates=paste(“::”, as.Date(Sys.time()), sep=’’)) updateAcct(account.st, Dates=paste(startDate, endDate, sep=“::”)) updateEndEq(account.st, Dates=paste(startDate, endDate, sep=“::”)) getEndEq(account.st, Sys.time())
dev.new() chart.Posn(Portfolio=portfolio1.st, Symbol=symb1) dev.new() chart.Posn(Portfolio=portfolio1.st, Symbol=symb2) dev.new() chartSeries(Cl(get(symb1))/Cl(get(symb2)), TA=“addBBands(n=N,sd=SD)”)
ret1 <- PortfReturns(account.st) ret1$total <- rowSums(ret1) #ret1
if(“package:PerformanceAnalytics” %in% search() || require(“PerformanceAnalytics”,quietly=TRUE)) { # getSymbols(“SPY”, from=‘1999-01-01’) # SPY.ret <- Return.calculate(SPY\(SPY.Close) # tmp <- merge(SPY.ret,ret1\)total,all=FALSE) dev.new() charts.PerformanceSummary(ret1$total, geometric=FALSE, wealth.index=TRUE) }