rquantstrat

Cannot make stoplimit orders work in quantstrat


Reproducible code:

package <- c("compiler",
             "quantmod",
             "dygraphs",
             "plyr",
             "devtools",
             "PerformanceAnalytics",
             "doParallel")
lapply(X = package,
       FUN = function(this.package){
         if (!require(package = this.package,
                      character.only = TRUE))
         {
           install.packages(pkgs = this.package,
                            repos = "https://cloud.r-project.org")
           library(package = this.package,
                   character.only = TRUE)
         } else {
           library(package = this.package,
                   character.only = TRUE)    
         }
       })
install_github("braverock/FinancialInstrument")
install_github("braverock/blotter")
install_github("braverock/quantstrat")
install_github("braverock/PerformanceAnalytics")
require(quantstrat)

# +------------------------------------------------------------------
# | The registerDoParallel() function is used to register the 
# | parallel backend with the foreach package. detectCores() attempts
# | to detect the number of CPU cores on the current host.
# +------------------------------------------------------------------

registerDoParallel(detectCores())

# +------------------------------------------------------------------
# | Get data.
# +------------------------------------------------------------------

symbols <- c('SPY', 'TLT', 'GLD')
getSymbols(Symbols = symbols)   

# +------------------------------------------------------------------
# | osTotEq() is an order sizing function which should return the
# | maximum amount of purchasable securities as for current equity
# | available (assuming you're not invested at the moment of
# | calculation).
# +------------------------------------------------------------------

osTotEq <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...)
{
  if (orderqty == "all" && !(ruletype %in% c("exit", "risk")) || 
      orderqty == "trigger" && ruletype != "chain")
  {
    stop(paste("orderqty 'all'/'trigger' would produce nonsense, maybe use osMaxPos instead?\n", 
               "Order Details:\n", "Timestamp:", timestamp, "Qty:", 
               orderqty, "Symbol:", symbol))
  }
  endEq <- getEndEq(Account = portfolio,
                    Date = timestamp)
  refPrice <- Cl(mktdata[, 1:4])[timestamp, ]
  orderqty <- floor(endEq / refPrice)
  return(orderqty)
}

# +------------------------------------------------------------------+ #
# +------------------------------------------------------------------+ #
# | Main: William's %R                                               | #
# +------------------------------------------------------------------+ #
# +------------------------------------------------------------------+ #

# +------------------------------------------------------------------
# | Parameters
# +------------------------------------------------------------------

name <- 'WPR'
currency <- 'USD'
initEq <- 300000

# +------------------------------------------------------------------
# | Initialization
# +------------------------------------------------------------------

rm.strat(name = name)
currency(currency)
for (symbol in symbols)
{
  stock(primary_id = symbol,
        currency = currency,
        multiplier = 1)
}
initPortf(name = name, 
          symbols = symbols,
          currency = currency)
initAcct(name = name, 
         portfolios = name, 
         initEq = initEq)
initOrders(portfolio = name)
strategy(name = name,
         store = TRUE)

# +------------------------------------------------------------------
# | Indicators
# +------------------------------------------------------------------

add.indicator(strategy = name,
              name = 'volatility',
              arguments = list(OHLC = quote(OHLC(mktdata)),
                               n = 5,
                               calc = 'yang.zhang',
                               N = 1),
              label = 'sigma',
              store = TRUE)
add.indicator(strategy = name,
              name = 'WPR',
              arguments = list(HLC = quote(HLC(mktdata)),
                               n = 14),
              label = 'wpr',
              store = TRUE)

# +------------------------------------------------------------------
# | Signals
# +------------------------------------------------------------------

add.signal(strategy = name,
           name = 'sigThreshold',
           arguments = list(column = 'wpr',
                            threshold = .2,
                            relationship = 'gt',
                            cross = TRUE),
           label = 'wpr.buy')
add.signal(strategy = name,
           name = 'sigThreshold',
           arguments = list(column = 'wpr',
                            threshold = .8,
                            relationship = 'lt',
                            cross = TRUE),
           label = 'wpr.sell')

# +------------------------------------------------------------------
# | Rules
# +------------------------------------------------------------------

add.rule(strategy = name,
         name = 'ruleSignal',
         arguments = list(sigcol = 'wpr.buy',
                          sigval = TRUE,
                          orderqty = 1,
                          ordertype = 'market',
                          orderside = 'long',
                          osFUN = osTotEq),
         type = 'enter',
         label = 'wpr.buy.enter',
         store = TRUE)
add.rule(strategy = name,
         name = 'ruleSignal',
         arguments = list(sigcol = 'wpr.buy',
                          sigval = TRUE,
                          orderqty = 'all',
                          ordertype = 'stoplimit',
                          orderside = 'long',
                          tmult = TRUE,
                          threshold = quote(mktdata[timestamp, 'X1.sigma']),
                          orderset = 'stop.loss'),
         parent = 'wpr.buy.enter',
         type = 'chain',
         label = 'wpr.buy.chain',
         store = TRUE)
# add.rule(strategy = name,
#          name = 'ruleSignal',
#          arguments = list(sigcol = 'wpr.sell',
#                           sigval = TRUE,
#                           orderqty = 'all',
#                           ordertype = 'market',
#                           orderside = 'long',
#                           pricemethod = 'market',
#                           replace = TRUE,
#                           osFUN = osNoOp),
#          path.dep = TRUE,
#          type = 'exit',
#          label = 'wpr.buy.exit',
#          store = TRUE)

# +------------------------------------------------------------------
# | Strategy backtest
# +------------------------------------------------------------------

try(applyStrategy(strategy = name,
                  portfolios = name))
updatePortf(Portfolio = name,
            Dates = paste('::',as.Date(Sys.time()), sep = ''))
updateAcct(name = name)
updateEndEq(Account = name)

# +------------------------------------------------------------------
# | Performance analysis
# +------------------------------------------------------------------

for (symbol in symbols)
{
  dev.new()
  chart.Posn(Portfolio = name,
             Symbol = symbol)
}
dev.new()
R <- PortfReturns(Account = name)
R$total.DailyEqPl <- rowSums(R)
charts.PerformanceSummary(R = R,
                          ylog = TRUE,
                          main = "Smoothing spline performance",
                          geometric = TRUE)
getOrderBook(portfolio = name)

I've intentionally left the exit rules commented in order to better highlight the usage of stoplimit orders. As for the order book, it seems that they're not working. This is not the first time I cannot make stoplimit orders work properly. Even if I copy demo codes (and slightly amend it for my purposes), sometimes it doesn't work and I'm not getting why. Above there's an example of "dynamic" stoplimit price, but in my order book I see no stoplimit orders even if I use a fixed value (like quote(0.001)). Nonetheless, orderset, parent and type seem ok.

Could you please explain what I'm missing?

Here's a snippet of my getOrderBook(portfolio = name):

           Order.Qty Order.Price Order.Type Order.Side Order.Threshold Order.Status Order.StatusTime      Prefer Order.Set Txn.Fees Rule            Time.In.Force
2002-08-15 "3487"    "86.02"     "market"   "long"     NA              "closed"     "2002-08-16 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-09-06 "3430"    "87.46"     "market"   "long"     NA              "closed"     "2002-09-09 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-09-25 "3348"    "89.58"     "market"   "long"     NA              "closed"     "2002-09-26 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-10-01 "3373"    "88.94"     "market"   "long"     NA              "closed"     "2002-10-02 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-11-14 "3457"    "86.76"     "market"   "long"     NA              "closed"     "2002-11-15 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-12-13 "3467"    "86.53"     "market"   "long"     NA              "closed"     "2002-12-16 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2002-12-31 "3387"    "88.57"     "market"   "long"     NA              "closed"     "2003-01-02 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-01-27 "3422"    "87.65"     "market"   "long"     NA              "closed"     "2003-01-28 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-02-05 "3428"    "87.51"     "market"   "long"     NA              "closed"     "2003-02-06 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-02-10 "3416"    "87.8"      "market"   "long"     NA              "closed"     "2003-02-11 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-02-21 "3397"    "88.3"      "market"   "long"     NA              "closed"     "2003-02-24 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-03-13 "3351"    "89.52"     "market"   "long"     NA              "closed"     "2003-03-14 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-05-02 "3396"    "88.32"     "market"   "long"     NA              "closed"     "2003-05-05 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-05-27 "3187"    "94.12"     "market"   "long"     NA              "closed"     "2003-05-28 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-06-11 "3132"    "95.78"     "market"   "long"     NA              "closed"     "2003-06-12 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-06-16 "3116"    "96.26"     "market"   "long"     NA              "closed"     "2003-06-17 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-09-02 "3655"    "82.06"     "market"   "long"     NA              "closed"     "2003-09-03 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-09-08 "3605"    "83.2"      "market"   "long"     NA              "closed"     "2003-09-09 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""           
2003-09-11 "3567"    "84.1"      "market"   "long"     NA              "closed"     "2003-09-12 00:00:00" ""     NA        "0"      "wpr.buy.enter" ""   

As you can see, no stoplimit order is present at all.


Solution

  • I suspect your problem will be solved by simply adding Sys.setenv(TZ ="UTC") at the top of your script (many of the quantstrat demos on daily data do this). This sometimes happens when you're working with daily data in quantstrat.

    What's happening is in ruleSignal quantstrat isn't correctly getting the chain.price at the time a market order is filled, and when it can't find a chain price, it ignores creating the stop loss.

    If you're curious and want to convince yourself, try setting the debugger in ruleSignal under a condition where a stoploss is being filled (you could create your own ruleSignal function very similar to ruleSignal and supply that function's name in add.rule name argument for the stoplimit rule.

    If you set the debugger to pause on the first order fill (in ruleSignal or an equivalent when the chain stoplimit rule triggered), you'll see this:

    getTxns(portfolio, "SPY")
                        Txn.Qty Txn.Price Txn.Fees Txn.Value Txn.Avg.Cost Net.Txn.Realized.PL
    1950-01-01 00:00:00       0      0.00        0       0.0         0.00                   0
    2007-01-25 19:00:00    2108    142.13        0  299610.1       142.13                   0
    

    For things to work correctly, that first txn timestamp should be 2007-01-26.

    The chain.price being supplied to ruleSignal for the stoplimit is an empty xts object, which arises because in quantstrat's function applyRules, these lines in the switch chain chain case don't work correctly on the daily data:

    txns <- getTxns(Portfolio=portfolio, Symbol=symbol, Dates=timestamp)
    txn.price <- last(txns$Txn.Price)   
    
    ruleProc(rules[j], timestamp=timestamp, path.dep=path.dep, mktdata=mktdata, portfolio=portfolio, symbol=symbol, ruletype=type, mktinstr=mktinstr, parameters=list(chain.price=txn.price), curIndex=curIndex)
    

    Specifically, the parameter argument in ruleProc isn't passing in a valid chain.price. In applyRules, getTxns is supplied Dates = timestamp and timestamp = "2007-01-26", (the transaction is filled at 2007-01-26, and the entry signal was on 2007-01-25) which is after "2007-01-25 19:00:00", so txns is an empty xts object. Why does the transaction on day "2007-01-26" have a timestamp of "2007-01-25 19:00:00"?. Because in my R system environment, timezone corresponds to UTC midnight in New York time.

    In short, it's a bug (if you don't set the time zone to UTC for daily data sets), but since the authors and most serious users of quantstrat probably don't work with daily bars, it's probably not a big priority to fix. Just be sure to set the timezone to UTC when working with xts objects that have Date time time indices as opposed to POSIXct type.