#by Garrett See #code borrowed heavily from existing quantstrat demos ## 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 the ratio of Cl(stockA)/Cl(stockB) # OR # The quantity of stock B will be Beta * qty of stock 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", "ret1"), silent=TRUE) require(quantstrat) initDate = '2010-05-01' endDate = '2011-05-01' startDate = '2010-05-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 getSymbols(c(symb1, symb2), from=startDate, to=endDate, adjust=TRUE) currency("USD") stock(symb1, currency="USD", multiplier=1) stock(symb2, currency="USD", multiplier=1) # Create a portfolio object for this pair portfolio1.st <- 'pair1' # Create an account to put pairs account.st <- 'pairs' #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) # 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=1000, longlevels=3, minpos=-1000, shortlevels=3) addPosLimit(portfolio=portfolio1.st, timestamp=initDate, symbol=symb2, maxpos=1000, longlevels=3, minpos=-1000, 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, ...) { pos <- getPosQty(portfolio, symbol, timestamp) PosLimit <- getPosLimit(portfolio, symbol, timestamp) if (symbol==symb2) { orderside=NULL orderqty <- -orderqty } if (is.null(orderside) & !isTRUE(orderqty == 0)) { #curqty <- pos #if (curqty > 0) { # orderside <- "long" #} #else if (curqty < 0) { # orderside <- "short" #} #else { if (orderqty > 0) orderside <- "long" else orderside <- "short" #} } if (orderqty > 0 & orderside == "long") { if ((orderqty + pos) < PosLimit[, "MaxPos"]) { if (orderqty <= (PosLimit[, "MaxPos"]/PosLimit[, "LongLevels"])) { orderqty = orderqty } else { orderqty = round(PosLimit[, "MaxPos"]/PosLimit[, "LongLevels"], 0) } } else { orderqty <- ifelse((PosLimit[, "MaxPos"] - pos) <= round(PosLimit[, "MaxPos"]/PosLimit[, "LongLevels"], 0), PosLimit[, "MaxPos"] - pos, round(PosLimit[, "MaxPos"]/PosLimit[, "LongLevels"], 0)) if (orderqty + pos > PosLimit[, "MaxPos"]) orderqty <- PosLimit[, "MaxPos"] - pos } return(orderqty) } if (orderqty < 0 & orderside == "long") { if (ruletype == "risk") { if (orderqty == "all") return(-1 * pos) else return(orderqty) } if ((orderqty + pos) >= 0) { return(orderqty) } else { orderqty <- pos return(orderqty) } } if (orderqty < 0 & orderside == "short") { if ((orderqty + pos) > PosLimit[, "MinPos"]) { if (orderqty <= (PosLimit[, "MinPos"]/PosLimit[, "ShortLevels"])) { orderqty = orderqty } else { orderqty = round(PosLimit[, "MinPos"]/PosLimit[, "ShortLevels"], 0) } } else { orderqty <- ifelse((PosLimit[, "MinPos"] - pos) >= round(PosLimit[, "MinPos"]/PosLimit[, "ShortLevels"], 0), PosLimit[, "MinPos"] - pos, round(PosLimit[, "MinPos"]/PosLimit[, "ShortLevels"], 0)) if (orderqty + pos > PosLimit[, "MaxPos"]) orderqty <- PosLimit[, "MinPos"] - pos } return(orderqty) } if (orderqty > 0 & orderside == "short") { if (ruletype == "risk") { if (orderqty == "all") return(-1 * pos) else return(orderqty) } if ((orderqty + pos) <= 0) { return(orderqty) } else { orderqty <- pos return(orderqty) } } return(0) } ######################################################################################################## #######################_RULE SIGNAL FUNCTION_########################################################### # same as ruleSignal, but reverses orderqty and orderside for 2nd stock ruleSignal2 <- function (data = mktdata, timestamp, sigcol, sigval, orderqty = 0, ordertype, orderside = NULL, threshold = NULL, tmult = FALSE, replace = TRUE, delay = 1e-04, osFUN = "osNoOp", pricemethod = c("market", "opside", "maker"), portfolio, symbol, ..., ruletype, TxnFees = 0, prefer = NULL, sethold = FALSE) { pos <- getPosQty(portfolio, symbol, timestamp) PosLimit <- getPosLimit(portfolio, symbol, timestamp) if (symbol==symb2) { orderside=NULL orderqty <- -orderqty } if (!is.function(osFUN)) osFUN <- match.fun(osFUN) if (!is.na(timestamp) && !is.na(data[timestamp][, sigcol]) && data[timestamp][, sigcol] == sigval) { pricemethod <- pricemethod[1] if (hasArg(prefer)) prefer = match.call(expand.dots = TRUE)$prefer else prefer = NULL switch(pricemethod, opside = { if (orderqty > 0) prefer = "ask" else prefer = "bid" orderprice <- try(getPrice(x = data, prefer = prefer))[timestamp] }, market = { if (is.BBO(mktdata)) { if (orderqty > 0) prefer = "bid" else prefer = "ask" } orderprice <- try(getPrice(x = data, prefer = prefer))[timestamp] }, maker = { if (hasArg(price) & length(match.call(expand.dots = TRUE)$price) > 1) { orderprice <- try(match.call(expand.dots = TRUE)$price) } else { if (!is.null(threshold)) { baseprice <- last(getPrice(x = data)[timestamp]) if (hasArg(tmult) & isTRUE(match.call(expand.dots = TRUE)$tmult)) { baseprice <- last(getPrice(x = data)[timestamp]) if (length(threshold) > 1) { orderprice <- baseprice * threshold } else { orderprice <- c(baseprice * threshold, baseprice * (1 + 1 - threshold)) } } else { if (length(threshold) > 1) { orderprice <- baseprice + threshold } else { orderprice <- c(baseprice + threshold, baseprice + (-threshold)) } } } else { stop("maker orders without specified prices and without threholds not (yet?) supported") if (is.BBO(data)) { } else { } } } if (length(orderqty) == 1) orderqty <- c(orderqty, -orderqty) }) if (inherits(orderprice, "try-error")) orderprice <- NULL if (length(orderprice > 1) & !pricemethod == "maker") orderprice <- last(orderprice[timestamp]) if (is.null(orderside) & !isTRUE(orderqty == 0)) { curqty <- getPosQty(Portfolio = portfolio, Symbol = symbol, Date = timestamp) if (curqty > 0) { orderside <- "long" } else if (curqty < 0) { orderside <- "short" } else { if (orderqty > 0) orderside <- "long" else orderside <- "short" } } orderqty <- osFUN(strategy = strategy, data = mktdata, timestamp = timestamp, orderqty = orderqty, ordertype = ordertype, orderside = orderside, portfolio = portfolio, symbol = symbol, ... = ..., ruletype = ruletype, orderprice = as.numeric(orderprice)) 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, threshold = threshold, status = "open", replace = replace, delay = delay, tmult = tmult, ... = ..., TxnFees = TxnFees) } } if (sethold) hold <<- TRUE } ######################################################################################################## # 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='ruleSignal2', arguments = list(sigcol="cross.dn", sigval=TRUE, orderqty=10000, ordertype='market', orderside=NULL, osFUN='osSpreadMaxPos'), type='enter' ) pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal2', arguments = list(sigcol="cross.up", sigval=TRUE, orderqty=-10000, ordertype='market', orderside=NULL, osFUN='osSpreadMaxPos'), type='enter') pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal2', arguments = list(sigcol="cross.mid.fb", sigval=TRUE, orderqty='all', ordertype='market', orderside=NULL), type='exit') pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal2', 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 getOrderBook(portfolio1.st) out1<-try(applyStrategy(strategy=pairStrat, portfolios=portfolio1.st)) getOrderBook(portfolio1.st) ##########Stop. Look at orderbook. It's wrong. 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())