3

Hi have a rule of type chain that will submit a limit order profit target upon entering a position. All very standard stuff so far, but how can I then modify this open order later on? Specifically, if (after a certain time delay) the signal that generated the rule initially is triggered again, I want to modify the price of the profit target's open order.

From the documentation, I think I should be using a rule of type order to achieve this, but I can't seem to find any documentation or examples of using this type of rule and how it works.

Ed Wilson
  • 267
  • 3
  • 12
  • Still working on an `order` `rule`, but I was able to achieve the result of modifying the profit target when the entry signal is re-triggered, by creating another exit rule where `sigCol` is the entry signal, and the `order.set` is the same as the initial chained exit Rule. Not the cleanest of solutions but it works. – Ed Wilson Sep 01 '16 at 06:57

1 Answers1

2

"From the documentation, I think I should be using a rule of type order to achieve this, but I can't seem to find any documentation or examples of using this type of rule and how it works."

The rule type "order" is for processing open orders, if you use the out of the box quantstrat function ruleOrderProc to process open orders (which is the default function for handling open orders in quantstrat, but you could plug in your own "order" processing function too to replace ruleOrderProc). ruleOrderProc doesn't offer the ability to move limit orders, like the way it does with stoptrailing orders, for example.

To answer your question, you could create a custom rule function that updates the limit order. Below is how it could work using a simple strategy.

  • The instrument is GBPUSD, and the data is from quantstrat.
  • The strategy enters a long position when the MACD signal crosses 0 from below.
  • A take profit order is added, set at 0.25% above the price the trade was entered at.
  • A stoptrailing order is also included to make the example a bit more realistic (for a purely directional trade), which is set at 0.5% below the price at which the trade was entered.
  • If another MACD signal for crossing above 0 from below occurs and a long position is on, and at least 60 minutes have past since this takeprofit was last updated, then the limit order is moved to a new level of 0.5% of the current price. (You didn't specifically specify in your question how the time delay would be modeled, so I'm taking a reasonable guess on how you'd like to do this).

Note that the ruletype is set to "risk" for updating the limit order, so this custom rule is evaluated before orders are processed with ruletype == "order" (see the source for applyRules in quantstrat if this is unclear). This means if the signal to update the limit order is triggered on the current bar, and if by chance the price also touches the existing take profit limit order, then the position will not exit and take profit, but will rather update to the new take profit level. (Because processing whether the take profit limit order is triggered happens after we check whether the signal for updating the limit order has fired).

Hope this example helps in seeing how making custom rules can make life a lot easier.

library(quantstrat)

from <- "2002-10-20"
to <- "2002-10-24"

symbols <- "GBPUSD"
# Load 1 minute data stored in the quantstrat package
getSymbols.FI(Symbols = symbols,
              dir=system.file('extdata',package='quantstrat'),
              from=from, 
              to=to
)

currency(c('GBP', 'USD'))
exchange_rate('GBPUSD', tick_size=0.0001)

strategy.st <- "switchOrderSignal"
portfolio.st <- "switchOrderSignal"
account.st <- "switchOrderSignal"

rm.strat(strategy.st)

initPortf(portfolio.st, symbols = symbols)
initAcct(account.st, portfolios = portfolio.st, initEq = 1e5)
initOrders(portfolio.st)
strategy(strategy.st, store = TRUE)

tradeSize <- 1000
for (sym in symbols) {
  addPosLimit(portfolio.st, sym, start(get(sym)), tradeSize)
}


strategy(strategy.st, store=TRUE)

fastMA = 12 
slowMA = 26 
signalMA = 9
maType = "EMA"

add.indicator(strategy.st, name = "MACD", 
              arguments = list(x=quote(Cl(mktdata)),
                               nFast=fastMA, 
                               nSlow=slowMA),
              label='co' 
)

add.signal(strategy.st,name="sigThreshold",
           arguments = list(column="signal.co",
                            relationship="gt",
                            threshold=0,
                            cross=TRUE),
           label="signal.gt.zero"
)


add.signal(strategy.st,name="sigThreshold",
           arguments = list(column="signal.co",
                            relationship="lt",
                            threshold=0,
                            cross=TRUE),
           label="signal.lt.zero"
)

add.rule(strategy.st,name='ruleSignal', 
         arguments = list(sigcol="signal.gt.zero",
                          sigval=TRUE, 
                          orderqty=tradeSize, 
                          ordertype='market', 
                          orderside='long', 
                          osFUN = "osMaxPos",
                          threshold=NULL),
         type='enter',
         label='enterL',
         storefun=FALSE
)

targetThres <- 0.0025
add.rule(strategy.st,name='ruleSignal', 
         arguments = list(sigcol="signal.gt.zero",
                          sigval=TRUE, 
                          orderqty="all", 
                          ordertype='limit', 
                          orderside='long', 
                          threshold= targetThres,
                          tmult = TRUE,
                          orderset='sysMACD',
                          replace = FALSE),
         type='chain',
         label='profitTarget',
         parent = "enterL"
)



# add.rule(strategy.st,name='ruleSignal', 
#          arguments = list(sigcol="signal.lt.zero",
#                           sigval=TRUE, 
#                           orderqty='all', 
#                           ordertype='market', 
#                           orderside='long', 
#                           threshold=NULL,
#                           orderset='sysMACD',
#                           replace = TRUE),
#          type='exit',
#          label='exitL',
#          enable = FALSE
# )

stopThreshold <- 0.005
add.rule(strategy.st,name='ruleSignal', 
         arguments = list(sigcol="signal.gt.zero", 
                          sigval=TRUE, 
                          orderqty='all', 
                          ordertype='stoptrailing', 
                          orderside='long', 
                          threshold=-stopThreshold, 
                          tmult=TRUE, 
                          orderset='sysMACD',
                          replace = FALSE),
         type='chain', 
         parent='enterL', 
         label='movingStop')

# If a position is on, update the limit order price (the take profit) if another
# entry signal is fired (macd signal crosses above 0 again), but only if more
# than the holding.period.secs has passed.  Define a custom rule function to handle this logic:

update_profit_target <- function(mktdata = mktdata, 
                                 timestamp, 
                                 sigcol, 
                                 sigval,
                                 orderqty=0, 
                                 ordertype, 
                                 orderside=NULL, 
                                 orderset=NULL, 
                                 threshold=NULL, 
                                 tmult=FALSE, 
                                 replace=TRUE, 
                                 delay=0.0001, 
                                 osFUN='osNoOp', 
                                 pricemethod = c('market','opside','active'), 
                                 portfolio, 
                                 symbol, 
                                 ..., 
                                 ruletype, 
                                 TxnFees=0, 
                                 prefer=NULL, 
                                 sethold=FALSE, 
                                 label='', 
                                 order.price=NULL, 
                                 chain.price=NULL, 
                                 time.in.force='',
                                 holding.period.secs
) {

  # First, we do not process this "ruleSignal" function if the position quantity
  # is not 0, because its purpose is only to modify the stoptrailing on a
  # position already open:
  if (ruletype!='risk' || getPosQty(portfolio.st, symbol, timestamp) == 0) {
    return()
  }

  if(hasArg(curIndex))
    curIndex <- eval(match.call(expand.dots=TRUE)$curIndex, parent.frame())
  else
    curIndex <- mktdata[timestamp,which.i=TRUE]

  if(hasArg(prefer)) prefer=match.call(expand.dots=TRUE)$prefer
  else prefer = NULL


  if (!is.na(mktdata[curIndex,sigcol]) && mktdata[curIndex,sigcol] == sigval) {

    #browser()
    orderbook <- getOrderBook(portfolio)
    ordersubset <- orderbook[[portfolio]][[symbol]]
    # Use quantstrat helper function to identify which row in orderbook for this symbol (ordersubset) has the order we want to change:
    ii <- getOrders(portfolio=portfolio, 
                    symbol=symbol, 
                    status="open", 
                    timespan=timespan, 
                    ordertype="limit", 
                    side = orderside,
                    which.i = TRUE)
    if (length(ii) > 0) {
      # Check first condition, that a specific amount of time has passed:
      end.of.holding <- index(ordersubset[ii, ]) + holding.period.secs
      if (timestamp < end.of.holding) return()

      if (length(ii) > 1) 
        stop("Have not got logic for handling case with more than one open limit order on orderside of the open position.")

      ordersubset[ii, "Order.Status"] <- 'replaced' 
      ordersubset[ii, "Order.StatusTime"] <- format(timestamp, "%Y-%m-%d %H:%M:%S")

      price <- mktdata[curIndex, "Close"]

      orderSide <- ordersubset[ii,"Order.Side"]
      # Calculate the new limit order price:
      if(isTRUE(tmult))
      {
        threshold = price*threshold
        if (orderSide == "long" && threshold < 0)
          threshold <- -threshold
        else if (orderSide == "Short" && threshold > 0)
          threshold <- -threshold
      }

      price <- price + threshold
      if(hasArg(prefer)) prefer=match.call(expand.dots=TRUE)$prefer
      else prefer = NULL
      neworder <- addOrder(portfolio=portfolio,
                           symbol=symbol,
                           timestamp=timestamp,
                           qty=ordersubset[ii,"Order.Qty"],
                           price= price - threshold,
                           ordertype="limit",
                           prefer=prefer,
                           side=ordersubset[ii,"Order.Side"],
                           threshold = threshold,
                           status="open",
                           replace=FALSE, 
                           return=TRUE,
                           orderset=ordersubset[ii,"Order.Set"],
                           label=label,
                           ...=..., 
                           TxnFees=TxnFees)
      # ^ Do not set the statustimestamp because any new orders start with statustimestamp = NA.

      ordersubset<-rbind(ordersubset, neworder)

      # we we have updated the orderbook for this symbol, we should reflect this
      # where the orderbook is stored (in the .strategy environment):

      orderbook[[portfolio]][[symbol]] <- ordersubset
      put.orderbook(portfolio, orderbook)
    }
  }
}

add.rule(strategy.st, name = 'update_profit_target', 
         arguments = list(sigcol="signal.gt.zero", 
                          sigval=TRUE, 
                          orderqty='all', 
                          ordertype='limit', 
                          orderside='long', 
                          threshold=targetThres,
                          tmult=TRUE, 
                          orderset='sysMACD',
                          # Set the minimum amount of time that must pass before the current active limit order can be updated again:
                          holding.period.secs = 3600),
         # Setting type as risk means we will update the limit order price on the current bar before processing whether the take profit price (limit price) was touched on this bar.
         type = 'risk',  # process and update this order after processing whether the trailing stop was touched, any chain exit and entry orders
         label='movingProfitTarget')


out<-applyStrategy(strategy.st, portfolios=portfolio.st, verbose=TRUE)

tx <- getTxns(portfolio.st, "GBPUSD")

sum(tx$Net.Txn.Realized.PL)

tx
# Txn.Qty Txn.Price Txn.Fees Txn.Value Txn.Avg.Cost Net.Txn.Realized.PL
# 1950-01-01 00:00:00       0  0.000000        0     0.000     0.000000              0.0000
# 2002-10-20 21:31:00    1000  1.547700        0  1547.700     1.547700              0.0000
# 2002-10-21 05:10:00   -1000  1.542361        0 -1542.361     1.542361             -5.3385
# 2002-10-21 06:22:00    1000  1.542600        0  1542.600     1.542600              0.0000
# 2002-10-22 22:39:00   -1000  1.548863        0 -1548.862     1.548863              6.2625
# 2002-10-22 23:40:00    1000  1.549000        0  1549.000     1.549000              0.0000
# 2002-10-24 09:28:00   -1000  1.552271        0 -1552.271     1.552271              3.2710
# 2002-10-24 11:33:00    1000  1.554200        0  1554.200     1.554200              0.0000

ob <- getOrderBook(portfolio.st)

# Print part of the order book:
ob$switchOrderSignal$GBPUSD[1:20, ]

#                            Order.Qty Order.Price  Order.Type     Order.Side Order.Threshold Order.Status Order.StatusTime      Prefer Order.Set Txn.Fees Rule                 Time.In.Force
# 2002-10-20 21:30:00.00010 "1000"    "1.5478"     "market"       "long"     NA              "closed"     "2002-10-20 21:31:00" ""     NA        "0"      "enterL"             ""           
# 2002-10-20 21:31:00.00010 "all"     "1.55156925" "limit"        "long"     "0.00386925"    "replaced"   "2002-10-20 23:38:00" ""     "sysMACD" "0"      "profitTarget"       ""           
# 2002-10-20 21:31:00.00010 "all"     "1.5399615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 21:33:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 21:33:00.00001 "all"     "1.5400615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 21:34:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 21:34:00.00001 "all"     "1.5403615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:03:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:03:00.00001 "all"     "1.5404615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:06:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:06:00.00001 "all"     "1.5408615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:20:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:20:00.00001 "all"     "1.5409615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:23:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:23:00.00001 "all"     "1.5413615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:24:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:24:00.00001 "all"     "1.5416615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:25:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:25:00.00001 "all"     "1.5418615"  "stoptrailing" "long"     "-0.0077385"    "replaced"   "2002-10-20 22:26:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 22:26:00.00001 "all"     "1.5423615"  "stoptrailing" "long"     "-0.0077385"    "closed"     "2002-10-21 05:10:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-20 23:38:00.00001 "all"     "1.55277225" "limit"        "long"     "0.00387225"    "replaced"   "2002-10-21 01:58:00" ""     "sysMACD" "0"      "movingProfitTarget" ""           
# 2002-10-21 01:58:00.00001 "all"     "1.551469"   "limit"        "long"     "0.003869"      "replaced"   "2002-10-21 03:09:00" ""     "sysMACD" "0"      "movingProfitTarget" ""           
# 2002-10-21 03:09:00.00001 "all"     "1.5508675"  "limit"        "long"     "0.0038675"     "canceled"   "2002-10-21 05:10:00" ""     "sysMACD" "0"      "movingProfitTarget" ""           
# 2002-10-21 06:21:00.00010 "1000"    "1.5427"     "market"       "long"     NA              "closed"     "2002-10-21 06:22:00" ""     NA        "0"      "enterL"             ""           
# 2002-10-21 06:22:00.00010 "all"     "1.5464565"  "limit"        "long"     "0.0038565"     "replaced"   "2002-10-21 08:46:00" ""     "sysMACD" "0"      "profitTarget"       ""           
# 2002-10-21 06:22:00.00010 "all"     "1.534887"   "stoptrailing" "long"     "-0.007713"     "replaced"   "2002-10-21 07:01:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-21 07:01:00.00001 "all"     "1.534987"   "stoptrailing" "long"     "-0.007713"     "replaced"   "2002-10-21 07:02:00" ""     "sysMACD" "0"      "movingStop"         ""           
# 2002-10-21 07:02:00.00001 "all"     "1.535387"   "stoptrailing" "long"     "-0.007713"     "replaced"   "2002-10-21 07:04:00" ""     "sysMACD" "0"      "movingStop"         ""    

# Reasonablness checks:  Let's check the results make sense.  Consider first trade entered at 2002-10-20 21:30:00.  Here is what the signals look like after the trade has been on for 2 hours:

mktdata["2002-10-20 23:36/2002-10-21 00:10"]

#                       Open   High    Low  Close Volume       macd.co     signal.co signal.gt.zero signal.lt.zero
# 2002-10-20 23:36:00 1.5489 1.5492 1.5489 1.5492      0  1.414846e-03 -9.644645e-04              0              0
# 2002-10-20 23:37:00 1.5492 1.5492 1.5492 1.5492      0  3.160196e-03 -1.395325e-04              0              0
# 2002-10-20 23:38:00 1.5489 1.5489 1.5489 1.5489      0  2.946509e-03  4.776759e-04              1              0  **
# 2002-10-20 23:39:00 1.5489 1.5489 1.5489 1.5489      0  2.745513e-03  9.312433e-04              0              0
# 2002-10-20 23:40:00 1.5488 1.5488 1.5488 1.5488      0  2.041720e-03  1.153339e-03              0              0
# 2002-10-20 23:41:00 1.5487 1.5487 1.5487 1.5487      0  9.520094e-04  1.113073e-03              0              0
# 2002-10-20 23:42:00 1.5487 1.5487 1.5487 1.5487      0  8.738715e-05  9.079357e-04              0              0
# 2002-10-20 23:43:00 1.5487 1.5487 1.5487 1.5487      0 -5.910279e-04  6.081430e-04              0              0
# 2002-10-20 23:44:00 1.5484 1.5484 1.5484 1.5484      0 -2.661021e-03 -4.568975e-05              0              1
# 2002-10-20 23:45:00 1.5484 1.5484 1.5484 1.5484      0 -4.252555e-03 -8.870627e-04              0              0
# 2002-10-20 23:46:00 1.5485 1.5485 1.5485 1.5485      0 -4.935972e-03 -1.696845e-03              0              0
# 2002-10-20 23:47:00 1.5484 1.5484 1.5484 1.5484      0 -5.930296e-03 -2.543535e-03              0              0
# 2002-10-20 23:48:00 1.5484 1.5484 1.5484 1.5484      0 -6.641779e-03 -3.363184e-03              0              0
# 2002-10-20 23:49:00 1.5483 1.5483 1.5483 1.5483      0 -7.638686e-03 -4.218284e-03              0              0
# 2002-10-20 23:50:00 1.5483 1.5483 1.5483 1.5483      0 -8.332735e-03 -5.041174e-03              0              0
# 2002-10-20 23:51:00 1.5483 1.5483 1.5483 1.5483      0 -8.781583e-03 -5.789256e-03              0              0
# 2002-10-20 23:52:00 1.5483 1.5483 1.5483 1.5483      0 -9.033199e-03 -6.438045e-03              0              0
# 2002-10-20 23:53:00 1.5483 1.5483 1.5482 1.5482      0 -9.642596e-03 -7.078955e-03              0              0
# 2002-10-20 23:54:00 1.5485 1.5486 1.5485 1.5486      0 -7.949460e-03 -7.253056e-03              0              0
# 2002-10-20 23:55:00 1.5486 1.5486 1.5486 1.5486      0 -6.532340e-03 -7.108913e-03              0              0
# 2002-10-20 23:56:00 1.5486 1.5486 1.5486 1.5486      0 -5.347620e-03 -6.756654e-03              0              0
# 2002-10-20 23:57:00 1.5486 1.5486 1.5486 1.5486      0 -4.358482e-03 -6.277020e-03              0              0
# 2002-10-20 23:58:00 1.5486 1.5486 1.5486 1.5486      0 -3.533847e-03 -5.728385e-03              0              0
# 2002-10-20 23:59:00 1.5489 1.5492 1.5489 1.5492      0  2.432916e-04 -4.534050e-03              0              0
# 2002-10-21 00:00:00 1.5492 1.5492 1.5492 1.5492      0  3.199644e-03 -2.987311e-03              0              0
# 2002-10-21 00:01:00 1.5493 1.5493 1.5493 1.5493      0  5.994337e-03 -1.190982e-03              0              0
# 2002-10-21 00:02:00 1.5493 1.5493 1.5492 1.5492      0  7.600410e-03  5.672967e-04              1              0 **
# 2002-10-21 00:03:00 1.5492 1.5492 1.5492 1.5492      0  8.772034e-03  2.208244e-03              0              0
# 2002-10-21 00:04:00 1.5491 1.5491 1.5491 1.5491      0  9.074934e-03  3.581582e-03              0              0
# 2002-10-21 00:05:00 1.5490 1.5490 1.5490 1.5490      0  8.693797e-03  4.604025e-03              0              0
# 2002-10-21 00:06:00 1.5490 1.5490 1.5490 1.5490      0  8.296106e-03  5.342441e-03              0              0
# 2002-10-21 00:07:00 1.5489 1.5489 1.5489 1.5489      0  7.374970e-03  5.748947e-03              0              0
# 2002-10-21 00:08:00 1.5488 1.5488 1.5488 1.5488      0  6.054223e-03  5.810002e-03              0              0
# 2002-10-21 00:09:00 1.5487 1.5487 1.5487 1.5487      0  4.435429e-03  5.535087e-03              0              0
# 2002-10-21 00:10:00 1.5488 1.5488 1.5487 1.5487      0  3.116584e-03  5.051387e-03              0              0

#  See that the limit order was updated on a `signal.gt.zero` signal fired at 2002-10-20
#  23:38:00. Then you can see there was another `signal.gt.zero``  signal at 2002-10-21 00:02:00
#  which, as expected, did not result in updating the limit order again.  The limit
#  order updates at 2002-10-21 01:58:00, 2 hours and 20 minutes after the previous limit order update (at 2002-10-20 23:38:00).
FXQuantTrader
  • 6,821
  • 3
  • 36
  • 67