I am currently working on a pairs trading backtest R script and have hit a wall in my efforts to generate dollar based backtesting that adjusts for the hedgeRatio ("beta" in my script) to each leg in a stock pair backtest. My existing script is mostly taken from Joshua Ulrich's pairs quantstrat backtest example.
The script as-is backtests equal dollar amounts per pair, but this value is different between sets of pairs because Joshua's script uses a ratio spread, which is used to adjust the amount to buy for stock B in the pair. Stock A could be any price and the share amount remains fixed, meaning the dollar investment amount will vary between pairs. This is nonetheless ok, as I am only testing for risk-adjusted returns - *most importantly I would simply like to adjust the dollar invest amount based on the hedge ratio*. I've tried unsuccessfully to use the IKTrading osMaxDollar function, but have hit a wall since Joshua's script uses an order sizing function that makes the 2nd stock shares to trade the opposite of the first stock. I've also looked at Ilya Kipnis's example of modeling the spread itself( https://quantstrattrader.wordpress.com/category/quantstrat/) but this apparently doesn't work since the dollar investment amount needs to be adjusted for each leg on a per trade basis. I have tried to resolve this for literally more than 2 weeks (stack exchange, demo deconstructing, etc) without any luck and was wondering if anyone on this list would be so kind as to have a look at my script? Perhaps you could point me in the right direction with a couple lines of helper code? I really don't know where else to turn and could very much use the help. Below is the code with the working script (ie all versions which incorporate osMaxDollar have been left out because they don't work!). -T ====================================================== ## 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 crosses it's 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 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) require(FinancialInstrument) symb1 <- 'data' symb2 <- 'panw' symb1 <- toupper(symb1) symb2 <- toupper(symb2) initDate = '2000-01-01' startDate = '2014-08-21' endDate = '2015-08-21' SD = 1.5 N = 20 initEq = 100000 #max position in stock B will be max * ratio, i.e. no hard position limit in Stock B lvls = 0 #how many times to fade; Each order's qty will = MaxPos/lvls portfolio1.st <- 'pair1' account.st <- 'pairs' suppressWarnings(getSymbols(c(symb1, symb2), from=startDate, to=endDate, adjust=TRUE)) MaxPos = 1000 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) #create a slot in portfolio for symb1 and symb2 to make them available to osFUN pair <- c('long','short') names(pair) <- c(symb1,symb2) .blotter[[paste('portfolio',portfolio1.st,sep='.')]]$pair <- pair # Create initial position limits and levels by symbol # allow "3" entries for long and short. addPosLimit(portfolio=portfolio1.st, timestamp=initDate, symbol=symb1, maxpos=MaxPos, longlevels=lvls, minpos=-MaxPos, shortlevels=lvls) addPosLimit(portfolio=portfolio1.st, timestamp=initDate, symbol=symb2, maxpos=MaxPos, longlevels=lvls, minpos=-MaxPos, shortlevels=lvls) # 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 <- Ad(x1) / Ad(x2) colnames(rat) <- 'Ratio' rat } Ratio <- calcRatio(c(symb1[1],symb2[1])) #let's go ahead and put this in a slot in portfolio .blotter[[paste('portfolio',portfolio1.st,sep='.')]]$Ratio <- Ratio # TS change from Ratio #and make a function to get the most recent Ratio getRatio <- function(portfolio, timestamp) { portf <- getPortfolio(portfolio) toDate <- paste("::", timestamp, sep="") Ratio <- last(portf$Ratio[toDate]) as.numeric(Ratio) } #calculate hedge ratio and egcm / ADF p-value stckY <- zoo(Ad(stckY)) stckX <- zoo(Ad(stckX)) m <- lm(stckY ~ stckX + 0) beta <- coef(m)[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='EMA')) #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 #TS - changed for No REV pairStrat <- add.signal(strategy = pairStrat, name = "sigCrossover", arguments= list(columns=c("Ratio","up"), relationship="gt"), label="cross.up") pairStrat <- add.signal(strategy = pairStrat, name = "sigCrossover", arguments= list(columns=c("Ratio","dn"), relationship="lt"), 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"') ratio <- getRatio(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=MaxPos*ratio, longlevels=lvls, minpos=-MaxPos*ratio, shortlevels=lvls) #TODO: is it okay that MaxPos and lvls come from .GlobalEnv ? 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, ...) orderqty <- round(orderqty,0) #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 } ######################################################################################################## # 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, prefer = "Open", TxnFees="pennyPerShare", osFUN='osSpreadMaxPos'), type='enter' ) pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal', arguments = list(sigcol="cross.up", sigval=TRUE, orderqty=-1e6, ordertype='market', orderside=NULL, prefer = "Open", TxnFees="pennyPerShare",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, prefer = "Open", TxnFees="pennyPerShare"), type='exit') pairStrat <- add.rule(strategy = pairStrat, name='ruleSignal', arguments = list(sigcol="cross.mid.fa", sigval=TRUE, orderqty='all', ordertype='market', orderside=NULL, prefer = "Open", TxnFees="pennyPerShare"), 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)) { tmp <- ret1$total #dev.new() charts.PerformanceSummary(tmp,geometric=FALSE,wealth.index=TRUE, main = paste(symb1,symb2,sep="/")) } table.Drawdowns(ret1) getEndEq(account.st, Sys.time()) table.AnnualizedReturns(ret1) [[alternative HTML version deleted]] _______________________________________________ R-SIG-Finance@r-project.org mailing list https://stat.ethz.ch/mailman/listinfo/r-sig-finance -- Subscriber-posting only. If you want to post, subscribe first. -- Also note that this is not the r-help list where general R questions should go.