#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. Also, 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 as a portfolio of stocks. ## given 2 stocks, calculate their ratio. 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 cross it's moving average, # then flatten any open positions. # currently it buys / sells an equal number of shares in each symbol. # It shouldn't be too difficult to have # The quantity of stock A that you buy (sell) always be 100 (-100) # and either: # The quantity of stock B that you sell (buy) will be equal to # QtyStockA times the ratio of Cl(stockA)/Cl(stockB) # OR # The quantity of stock B will be Beta * QtyStock A where Beta is the coef from regression. try(rm("order_book.pair1",pos=.strategy),silent=TRUE) try(rm("account.pairs", "portfolio.pair1", pos=.blotter), silent=TRUE) try(rm("initDate", "endDate", "startDate", "initEq", "SD", "N", "symb1", "symb2", "portfolio1.st", "account.st", "pairStrat", "out1"), silent=TRUE) require(quantstrat) initDate = '2009-01-01' endDate = '2011-05-01' startDate = '2009-01-02' initEq = 100000 SD = 2 N = 20 symb1 <- 'CVX' #change these to try other pairs symb2 <- 'XOM' #if you change them, you need to update PosLimits portfolio1.st <- 'pair1' account.st <- 'pairs' getSymbols(c(symb1, symb2), from=startDate, to=endDate, adjust=TRUE) 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), initDate=initDate) initAcct(account.st, portfolios=portfolio1.st, initDate=initDate, initEq=initEq) initOrders(portfolio=portfolio1.st,initDate=initDate) #make symb1 and symb2 available to osFUN pair <- c('long','short') names(pair) <- c(symb1,symb2) .blotter[[paste('portfolio',portfolio1.st,sep='.')]]$pair <- pair # Create position limits by symbol # Always buy sym[1] first and sell sym[2] # allow 3 entries for long and short. addPosLimit(portfolio=portfolio1.st, timestamp=initDate, symbol=symb1, maxpos=900, longlevels=3, minpos=-900, shortlevels=3) addPosLimit(portfolio=portfolio1.st, timestamp=initDate, symbol=symb2, maxpos=900, longlevels=3, minpos=-900, shortlevels=3) # Create a strategy object pairStrat <- strategy('pairStrat') calcRatio <- function(x) { #returns the ratio of close prices for 2 symbols x1 <- get(x[1]) x2 <- get(x[2]) rat <- Cl(x1) / Cl(x2) colnames(rat) <- 'Ratio' rat } Ratio <- calcRatio(c(symb1[1],symb2[1])) # 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) legside <- portf$pair[symbol] #"long" if symbol=symb1, "short" if symbol=symb2 if (legside != "long" && legside != "short") stop('pair must contain "long" and "short"') pos <- getPosQty(portfolio, symbol, timestamp) PosLimit <- getPosLimit(portfolio, symbol, timestamp) if (orderqty > 0) orderside = 'long' if (orderqty < 0) orderside = 'short' # osMaxPos uses qty <- osMaxPos(data=data,timestamp=timestamp,orderqty=orderqty,ordertype=ordertype, orderside=orderside,portfolio=portfolio,symbol=symbol,ruletype=ruletype, ...) #if (is.null(orderside) & !isTRUE(orderqty == 0)) { if (orderqty > 0) { #buying spread if (legside == "short") { #symbol is the 2nd leg orderside <- "short" qty <- -qty } else orderside <- "long" #symbol is 1st leg } else if (orderqty < 0) { #selling spread if (legside == "short") { #symbol is 2st leg orderside <- "long" qty <- -qty } else orderside <- "short" #symbol is 1st leg } #} orderqty <- qty #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, status = "open", ... = ...) } #return (0) so that ruleSignal function doesn't also try to place an order return(0) } ######################################################################################################## # Create entry and exit rules for longs and for shorts. Both symbols will get simultaneous buy/sell signals, but osMaxPos will sort out which to buy and which to sell # 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=10000, ordertype='market', orderside=NULL, osFUN='osSpreadMaxPos'), type='enter' ) pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal', arguments = list(sigcol="cross.up", sigval=TRUE, orderqty=-10000, 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') #applySignals(strategy=pairStrat, mktdata=applyIndicators(strategy=pairStrat,mktdata=get(symb1))) #for debugging out1<-try(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()") 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(cbind(tmp[,2],tmp[,1]),geometric=FALSE,wealth.index=TRUE) }