2

I opened a new question as the original post was already too long and the solution I have arrived at pretty much negates that post.

Below is the workaround I have found for adding indicators of a periodicity different to that of your data. The solution was built upon this post on the R-SIG-Finance mailing list by Brian Peterson.

There are two main issues with the code.

1) The strategy does not take any positions despite signals being generated (the appropriate columns exist in mktdata after running the strategy)

2) Strangely, if indMerge() does not explicitly rename the SMA column to "SPY.SMA" and add.signal(columns = c("SPY.Close", SPY.SMA")) is used then the following error is thrown (that is to say I cannot pass columns = c("Close", "SMA") in add.signal:

Warning message:
In match.names(columns, colnames(data)) :
  all columns not located in Close SMA for SPY.Open SPY.High SPY.Low SPY.Close 
SPY.Volume SPY.Adjusted SMA Cl.gt.SMA

As the only way I have found to get around this error is the code as it is below, the below solution is effectively useless in a portfolio with more than one symbol. Anyway, here's the code:

require(quantstrat)
require(quantmod)
require(FinancialInstrument)

symbols = "SPY"

initDate="2000-01-01"
from="2003-01-01"
to="2016-12-31"
options(width=70)

options("getSymbols.warning4.0"=FALSE)

#set account currency and system timezone
currency('USD')
stock("SPY",currency="USD",multiplier=1)
Sys.setenv(TZ="UTC")

#trade sizing and initial equity settings
tradeSize <- 1e6
initEq <- tradeSize*length(symbols)

#Brians code 
#>> http://r.789695.n4.nabble.com/R-Quantstrat-package-question-td3772989.html
indMerge <- function(x, period, k, SMAlength, maType){
  mktdata <- getSymbols(x, auto.assign = FALSE)

  xW = to.period(mktdata, period = period, k = k, indexAt = "startof")
  smaW = wSMA = SMA(Cl(xW), n = SMAlength, maType = maType)

  x <- cbind(mktdata, smaW[paste(first(index(mktdata)) ,
                                 last(index(mktdata)) , sep='/')])
  colnames(x)[ncol(x)] = paste("SPY", ".", "SMA", sep = "")
  x <- na.locf(x)
  x
} 

#get the data
getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE)

#apply the weekly SMA to the data without the use of add.indicator
SPY = indMerge(x = symbols, period = "weeks", k = 1, SMAlength = 14, maType = "SMA")

#set up the portfolio, account and strategy
strategy.st <- portfolio.st <- account.st <- "mtf.strat"
rm.strat(strategy.st)

initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, 
              currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)

strategy(strategy.st, store=TRUE)

#add signals
add.signal(strategy.st, name = "sigComparison", arguments = list(columns = 
              c("SPY.Close", "SPY.SMA"), relationship = "gt"), label = "Cl.gt.SMA")
add.signal(strategy.st, name = "sigComparison", arguments = list(columns = 
              c("SPY.Close", "SPY.SMA"), relationship = "lt"), label = "Cl.lt.SMA")

#test = applySignals(strategy.st, mktdata = SPY)

#add.rules
add.rule(strategy.st, name = "ruleSignal", arguments = list(sigCol = "Cl.gt.SMA", 
              sigval = 1, orderqty = 900, ordertype = "market", orderside = "long"), 
              type = "enter")

add.rule(strategy.st, name = "ruleSignal", arguments = list(sigCol = "Cl.lt.SMA", 
              sigval = 1, orderqty = "all", ordertype = "market", orderside = "long"), 
              type = "exit")


strat = getStrategy(strategy.st)
summary(strat)

#apply the strategy and get the transactions
applyStrategy(strategy = strategy.st, portfolios = portfolio.st)
getTxns(Portfolio = portfolio.st, Symbol = "SPY")
FXQuantTrader
  • 6,821
  • 3
  • 36
  • 67
user3180258
  • 97
  • 13

1 Answers1

1

This is a working example of your code (more than one instrument, just 2017 data to make the example more manageable for testing), with adjustments that I hope address all your concerns:

require(quantstrat)
require(quantmod)
require(FinancialInstrument)

symbols = c("SPY", "GOOG")

initDate="2000-01-01"
from="2017-01-01"
to=Sys.Date()
options(width=70)

options("getSymbols.warning4.0"=FALSE)

#set account currency and system timezone
currency('USD')
stock("SPY",currency="USD",multiplier=1)
stock("GOOG", currency = "USD")
Sys.setenv(TZ="UTC")

tradeSize <- 1e6
initEq <- tradeSize*length(symbols)

getSymbols(symbols, from=from, to=to, src="yahoo", adjust=TRUE)

rm.strat(strategy.st)

initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, 
         currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)

strategy(strategy.st, store=TRUE)



#SPY = indMerge(x = symbols, period = "weeks", k = 1, SMAlength = 14, maType = "SMA")
#Brians code 
#>> http://r.789695.n4.nabble.com/R-Quantstrat-package-question-td3772989.html
AddWeeklyMA <- function(x, period, k, SMAlength, maType){
  # Don't use getSymbols here. It is redundant.  Use x, which is the daily OHLC for the symbol you've selected
  #mktdata <- getSymbols(x, auto.assign = FALSE)

  # YOU MUST USE indexAt as "endof" instead of "startof" otherwise you introduce look ahead bias in your weekly indicator signal. (Sees the future in the backtest)  Bad!!!
  xW <- to.period(x, period = period, k = k, indexAt = "endof")
  wSMA <- SMA(Cl(xW), n = SMAlength, maType = maType)

  y <- merge(wSMA, xts(, order.by = index(x)), fill = na.locf) 
  x <- y[index(x)] # just to be safe, in case any extra timestamps where generated in the merge above to make y.
  # Note that the column name of x is simply "SMA" rather than "SPY.SMA" etc.  You can of course relabel the column name here if you wish.
  x
} 

add.indicator(strategy = strategy.st, 
              name = "AddWeeklyMA", 
              arguments = list(x = quote(mktdata),
                               period = "weeks",
                               k = 1,
                               SMAlength = 14,
                               maType = "SMA"),
              label = "weeklyMA")


#add signals.
# one way to handle column names is simply rename the OHLC columns, removing the name of the symbol.  e.g. .Open .High .Low .Close, then pass columns = c(".Close", "SMA.weeklyMA") which will work for multiple symbols.
#add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = 
#                                                                   c("SPY.Close", "SMA.weeklyMA"), relationship = "gt"), label = "Cl.gt.SMA")

sigCrossoverWrapper <- function(x, label, col1 = "Close", col2 = "weeklyMA", relationship) {
  Col1 <- grep(pattern = col1, colnames(x), value = TRUE)[1]
  Col2 <- grep(pattern = col2, colnames(x), value = TRUE)[1]  
  # Basic checks.  If we can't find a column name like col1, col2, throw an error. also use the [1] index, particular for col1 values like "Close", in case multiple column labels are returned that all contain "Close".  The assumption is that the OHLC data are always the first for columns in the symbol data object.
  stopifnot(length(Col1) == 1)
  stopifnot(length(Col2) == 1)
  columns <- c(Col1, Col2)
  r <- sigCrossover(data = x, label = label, columns = columns, relationship = relationship)
  r
}

add.signal(strategy.st, name = "sigCrossoverWrapper", arguments = list(x = quote(mktdata), 
                                                                       col1 = "Close", 
                                                                       col2 = "weeklyMA", 
                                                                       relationship = "gt"), 
           label = "Cl.gt.SMA")

add.signal(strategy.st, name = "sigCrossoverWrapper", arguments = list(x = quote(mktdata), 
                                                                       col1 = "Close", 
                                                                       col2 = "weeklyMA", 
                                                                       relationship = "lt"), 
           label = "Cl.lt.SMA")

add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "Cl.gt.SMA", 
                                                            sigval = 1, 
                                                            orderqty = 900, 
                                                            ordertype = "market", 
                                                            orderside = "long"), 
         label = "enterL", # Add label names for rules.
         type = "enter")

add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "Cl.lt.SMA", 
                                                            sigval = 1, 
                                                            orderqty = "all", 
                                                            ordertype = "market", 
                                                            orderside = "long"), 
         label = "exitL", # Add label names for rules.
         type = "exit")


applyStrategy(strategy = strategy.st, portfolios = portfolio.st)

# [1] "2017-07-06 00:00:00 GOOG 900 @ 906.690002"
# [1] "2017-07-07 00:00:00 GOOG -900 @ 918.590027"
# [1] "2017-07-10 00:00:00 GOOG 900 @ 928.799988"
# [1] "2017-07-28 00:00:00 GOOG -900 @ 941.530029"
# [1] "2017-09-14 00:00:00 GOOG 900 @ 925.109985"
# [1] "2017-09-15 00:00:00 GOOG -900 @ 920.289978"
# [1] "2017-09-28 00:00:00 GOOG 900 @ 949.5"
# [1] "2017-04-18 00:00:00 SPY 900 @ 231.585741736316"
# [1] "2017-08-21 00:00:00 SPY -900 @ 241.70049982835"
# [1] "2017-08-23 00:00:00 SPY 900 @ 243.352306359548"

for (sym in symbols) {
  print(paste0("txns for ", sym, ":"))
  print(getTxns(Portfolio = portfolio.st, Symbol = sym)  )
}

You had several problems with your code:

No rules were being triggered because you used sigCol instead of sigcol in add.rules. This is basically an error, and meant none of the rules where being tested. (To see this for yourself, stick browser() in your version of ruleSignal and go through the debugger).

Change sigComparison to sigCrossover. Sig comparison returns1/TRUE for all values of one column greater than another column when relationship = gt. You want to enter on a crossover of the Close and MA, I think.

It is good practice to give unique labels for rules.

The above code generalises your code to multiple symbols. Part of doing this involves generalising your custom indicator which I've named AddWeeklyMA.

Regarding your comment on wanting to be able to pass "general" column names, columns = c("Close", "SMA"), one way to get your desired affect is by making a wrapper function as I did with sigCrossoverWrapper. You could also avoid the problem of wanting to test against a column name of the form [symbol].Close by simply renaming the column in question inside each symbol object (i.e. drop the ticker name in the OHLC column names, as I've mentioned above in the code).

FXQuantTrader
  • 6,821
  • 3
  • 36
  • 67