rquantstrat

Stoplimit order when orderPrice crosses above/below Close of the bar in quantstrat?


I am trying put a stoplimit order to close my position when a specific order price crosses above or below the Close of the candle.

My rule function as follows:

add.rule(strategy = strategy.st, name = "ruleSignal",arguments = 
list(sigcol = "lenter", sigval = TRUE,TxnFees="fee",replace = FALSE, 
orderside = "long", TxnFees = "fee", ordertype = "stoplimit", 
orderqty = "all", tmult = TRUE, order.price=quote(myprice), 
orderset="ocolong", prefer="Close"), type = "chain", parent = 
"longbuy", path.dep=TRUE, label = "stop_loss_long", enabled=TRUE)

I tried to change the related part in source file of ruleOrderProc.R:

if ((orderQty > 0 && orderType != "stoplimit") || 
            (orderQty < 0 && (orderType == "stoplimit"))) {
            if ((has.Lo(mktdata) && orderPrice > 
as.numeric(Lo(mktdataTimestamp)[, 
              1])) 

to

 if ((orderQty > 0 && orderType != "stoplimit") || 
            (orderQty < 0 && (orderType == "stoplimit"))) {
            if ((has.Cl(mktdata) && orderPrice > 
as.numeric(Cl(mktdataTimestamp)[, 
              1]))

And I also tried to add "prefer" argument to my rule but it didn't work. Rule still triggers according to Low/High prices of the bar.

Thank you.

Edit for a minimum reproducible example:

library(quantstrat)

start_date <- as.Date("2018-02-02")
end_date <- as.Date("2018-09-24")
init_date <- as.Date("2018-01-01")
init_equity <- "50000"
adjustment <- TRUE
symbol <- "AAPL"


getSymbols(symbol, src = "yahoo",
       from = start_date, to=end_date,
       adjust = adjustment)


portfolio.st <- "basic_port"
account.st <- "basic_account"
strategy.st <- "basic_strategy"


rm.strat(portfolio.st)
rm.strat(account.st)

stock(symbol, currency = currency("USD"), multiplier = 1)
initPortf(name = portfolio.st, symbols = symbol, initDate =init_date)

initAcct(name = account.st, portfolios = portfolio.st, 
         initDate = init_date, initEq =init_equity)
initOrders(portfolio.st, symbol, init_date)
strategy(strategy.st, store = TRUE)



add.indicator(strategy = strategy.st, name = "SMA",
              arguments = list(x = quote(Cl(mktdata)), n=10),
              label ="nFast")


add.indicator(strategy = strategy.st, name = "SMA",
              arguments = list(x = quote(Cl(mktdata)), n=30),
              label = "nSlow")

add.signal(strategy = strategy.st, 
           name= "sigCrossover",
           arguments =  list(columns = c("nFast", "nSlow"),
                             relationship = "gte"),
           label = "longenter")

add.signal(strategy = strategy.st,
           name= "sigCrossover", 
           arguments =  list(columns = c("nFast",
                                         "nSlow"), 
                             relationship = "lt"),
           label = "longexit")

#Add rules for entering positions
#enter long position
add.rule(strategy.st, 
         name = "ruleSignal", 
         arguments = list(sigcol = "longenter",
                          sigval = TRUE,
                          orderqty = 100,
                          ordertype = "market",
                          orderside = "long",
                          orderset= "ocolong",
                          prefer = "Close",
                          TxnFees = -.8,
                          replace = FALSE),
         type = "enter",
         label = "EnterLong")



#stoploss long
add.rule(strategy = strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "longenter",
                          sigval = TRUE,
                          TxnFees=-.8,
                          replace = FALSE, 
                          orderside = "long", 
                          ordertype = "stoplimit",
                          orderqty = "all", 
                          tmult = TRUE,
                          prefer = "Close",
         order.price=quote(as.numeric(mktdata$AAPL.Low[timestamp])),
                          orderset="ocolong"), 
         type = "chain", parent = "EnterLong", 
         path.dep=TRUE, 
         label = "stop_loss_long", 
         enabled=TRUE)


#Apply strategy
applyStrategy(strategy.st, portfolios = portfolio.st,debug = TRUE)
updatePortf(portfolio.st)
updateAcct(account.st)
updateEndEq(account.st)

When we look at the last transaction closed by stop-loss rule, "Close" price of the bar is not below my stop-loss order price which is 47.13 although I add prefer="Close" argument to my stoplimit rule:

mktdata[, 1:4]["2018-07-30"]

           AAPL.Open AAPL.High AAPL.Low AAPL.Close
2018-07-30  47.80733  47.88207 47.10231   47.31158

Normally, I expect that the position should not be closed until the end of the dataset.

sessionInfo()

R version 4.1.1 (2021-08-10)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19044)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    
system code page: 1254

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] quantstrat_0.16.9          foreach_1.5.1             
[3] blotter_0.15.0             PerformanceAnalytics_2.0.6
[5] FinancialInstrument_1.3.0  quantmod_0.4.18           
[7] TTR_0.24.2                 xts_0.12.1.1              
[9] zoo_1.8-9                 

loaded via a namespace (and not attached):
 [1] magrittr_2.0.1    MASS_7.3-54       tidyselect_1.1.1 
 [4] lattice_0.20-44   R6_2.5.1          quadprog_1.5-8   
 [7] rlang_0.4.11      fansi_0.5.0       dplyr_1.0.7      
[10] tools_4.1.1       grid_4.1.1        data.table_1.14.0
[13] utf8_1.2.2        DBI_1.1.1         iterators_1.0.13 
[16] ellipsis_0.3.2    assertthat_0.2.1  tibble_3.1.4     
[19] lifecycle_1.0.0   crayon_1.4.1      purrr_0.3.4      
[22] codetools_0.2-18  vctrs_0.3.8       curl_4.3.2       
[25] glue_1.4.2        compiler_4.1.1    pillar_1.6.2     
[28] generics_0.1.0    boot_1.3-28       pkgconfig_2.0.3

Solution

  • You need to make one more modification in rules.R (on approximately lines 580-590), setting posQty and negQty to the close prices for the stoplimit order type (instead of High and Low prices):

      if(is.BBO(mktdata)) {
            mktPrices <- list(
              stoplimit = list(
                  posQty = mktdata[,has.Ask(mktdata,which=TRUE)[1]],
                  negQty = mktdata[,has.Bid(mktdata,which=TRUE)[1]]),
              limit = list(
                  posQty = mktdata[,has.Ask(mktdata,which=TRUE)[1]],
                  negQty = mktdata[,has.Bid(mktdata,which=TRUE)[1]]),
              stoptrailing = list(
                  posQty = getPrice(mktdata, prefer='offer')[,1],
                  negQty = getPrice(mktdata, prefer='bid')[,1]))
        } else if (is.OHLC(mktdata)) {
            mktPrices <- list(
              stoplimit = list(
                  posQty = mktdata[,has.Cl(mktdata,which=TRUE)[1]], #modified to the close
                  negQty = mktdata[,has.Cl(mktdata,which=TRUE)[1]]), #modified to the close
    

    This section of the rules.R code is about speeding up the simulation by only checking rows of the market data where there might be an update to an open order. So what you are doing by making this change is effectively saying, find all the rows where the Close price (as opposed to the Low price) for a long stop limit order might cross (below) the stop order price.

    With this change the stop order is not triggered on 2018-07-30.

    Note that if you go through the code carefully in rules.R you'll see prefer="Close" does not matter in finding the market data rows where the stoplimit might be triggered. prefer can come into play when you're not working with OHLC bar data.