Chapter 8 Stop-Loss Orders

We’ll continue using a variation of the Luxor strategy. This time we’re going to implement stop-loss orders.

We’re also going to keep all of our settings in variables so as to make the code easier to work with from here forward.

.fast <- 10
.slow <- 30
.threshold <- 0.0005
.orderqty <- 100
.txnfees <- -10
.stoploss <- 3e-3 # 0.003 or 0.3%
portfolio.st <- "Port.Luxor.Stop.Loss"
account.st <- "Acct.Luxor.Stop.Loss"
strategy.st <- "Strat.Luxor.Stop.Loss"

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

initPortf(name = portfolio.st,
          symbols = symbols,
          initDate = init_date)
## [1] "Port.Luxor.Stop.Loss"
initAcct(name = account.st,
         portfolios = portfolio.st,
         initDate = init_date,
         initEq = init_equity)
## [1] "Acct.Luxor.Stop.Loss"
initOrders(portfolio = portfolio.st,
           symbols = symbols,
           initDate = init_date)

strategy(strategy.st, store = TRUE)

8.1 Add Indicators

add.indicator(strategy.st, 
              name = "SMA",
              arguments = list(x = quote(Cl(mktdata)),
                               n = .fast),
              label = "nFast")
## [1] "Strat.Luxor.Stop.Loss"
add.indicator(strategy.st, 
              name = "SMA",
              arguments = list(x = quote(Cl(mktdata)),
                               n = .slow),
              label = "nSlow")
## [1] "Strat.Luxor.Stop.Loss"

8.2 Add Signals

add.signal(strategy.st, 
           name = "sigCrossover",
           arguments = list(columns = c("nFast", "nSlow"),
                            relationship = "gte"),
           label = "long"
)
## [1] "Strat.Luxor.Stop.Loss"
add.signal(strategy.st, 
           name = "sigCrossover",
           arguments = list(columns = c("nFast", "nSlow"),
                            relationship = "lt"),
           label = "short")
## [1] "Strat.Luxor.Stop.Loss"

8.3 Add Rules

Our rules are largely the same as they were in our original Luxor strategy. However, we have added some slight modifications.

Let’s start off with osFUN which is abbreviated for order size function. It is defined as:

function or text descriptor of function to use for order sizing.

The default value for this parameter is osNoOp which is an ordering function that performs no operation. In other words, if you pass 100 as orderqty that is what is purchased.

In the EnterLong rule below we pass a different function, osMaxPos(). osMaxPos() works with addPosLimit() (next section) to set a maximum position per symbol. This will keep us from executing the same orders repeatedly.

We’ve also included the orderset parameter with a value of “ocolong”. This will help group our long and short orders together.

add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "long" , 
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "long" ,
                          ordertype = "stoplimit",
                          prefer = "High",
                          threshold = .threshold,
                          TxnFees = .txnfees,
                          orderqty = +.orderqty,
                          osFUN = osMaxPos,
                          orderset = "ocolong"),
         type = "enter",
         label = "EnterLONG")
## [1] "Strat.Luxor.Stop.Loss"
add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "short", 
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "short",
                          ordertype = "stoplimit",
                          prefer = "Low",
                          threshold = .threshold,
                          TxnFees = .txnfees,
                          orderqty = -.orderqty,
                          osFUN = osMaxPos,
                          orderset = "ocoshort"),
         type = "enter",
         label = "EnterSHORT")
## [1] "Strat.Luxor.Stop.Loss"
add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "short", 
                          sigval = TRUE,
                          replace = TRUE,
                          orderside = "long" ,
                          ordertype = "market",
                          TxnFees = .txnfees,
                          orderqty = "all",
                          orderset = "ocolong"),
         type = "exit",
         label = "Exit2SHORT")
## [1] "Strat.Luxor.Stop.Loss"
add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "long", 
                          sigval = TRUE,
                          replace = TRUE,
                          orderside = "short",
                          ordertype = "market",
                          TxnFees = .txnfees,
                          orderqty = "all",
                          orderset = "ocoshort"),
         type = "exit",
         label = "Exit2LONG")
## [1] "Strat.Luxor.Stop.Loss"

Up to this point our Luxor.Stop.Loss strategy has been the same as our original Luxor strategy. When we take a long position we stay in it until we get a short signal, rinse and repeat.

However, now we’re going to put stops in place. From the onset there isn’t much different from the previous rules we have added. Many of the parameters are similar. We do have some new ones though.

First, we’ve created rule StopLossLONG as a child rule of the parent rule EnterLONG, part of the orderset ocolong. Currently it is not enabled.

The critical portion of StopLossLONG is the tmult and threshold parameter. When a long order is filled threshold and tmult work together to determine the stoplimit price (ordertype). .stoploss is multiplied (tmult) against the price of the filled long order. That price serves as the stop-loss price.

For example,

\[ \text{StopLossLONG} = \text{fill price } - \left( \text{.stoploss } * \text{fill price}\right) \]

\[ \text{StopLossLONG} = 134.39 - \left(0.003 * 134.39\right) \]

\[ \text{StopLossLONG} = $133.9868 \]

If market price moves below $ $133.9868 $ the StopLossLONG order becomes a market order and the Exit2SHORT order is cancelled (OCO).

The same applies to StopLossSHORT which is a child of EnterSHORT except .stoploss is added to the fill price.

add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "long" , 
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "long",
                          ordertype = "stoplimit",
                          tmult = TRUE,
                          threshold = quote(.stoploss),
                          TxnFees = .txnfees,
                          orderqty = "all",
                          orderset = "ocolong"),
         type = "chain", 
         parent = "EnterLONG",
         label = "StopLossLONG",
         enabled = FALSE)
## [1] "Strat.Luxor.Stop.Loss"
add.rule(strategy.st, 
         name = "ruleSignal",
         arguments = list(sigcol = "short", 
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "short",
                          ordertype = "stoplimit",
                          tmult = TRUE,
                          threshold = quote(.stoploss),
                          TxnFees = .txnfees,
                          orderqty = "all",
                          orderset = "ocoshort"),
         type = "chain", 
         parent = "EnterSHORT",
         label = "StopLossSHORT",
         enabled = FALSE)
## [1] "Strat.Luxor.Stop.Loss"

8.4 Add Position Limit

As mentioned previously when using osMaxPos() we must supply a position limit to each symbol our strategy is working. We do this with addPosLimit. For now the only parameter we apply is maxpos which we set to .orderqty.

for(symbol in symbols){
    addPosLimit(portfolio = portfolio.st,
                symbol = symbol,
                timestamp = init_date,
                maxpos = .orderqty)
}

8.5 Enable Rules

When we wrote StopLossLONG and StopLossSHORT we disabled them by assigning enabled = FALSE. Now we enable both rules set. This is very beneficial when you want to test a strategy versus different rulesets (rather than rewriting code).

label can apply to a specific rule or by matching the value to all rules with a similar value (grep). By supply “StopLoss” to label we are instructing quantstrat to enable all of our rules with the string “StopLoss” in the label, StopLossLONG and StopLossSHORT.

enable.rule(strategy.st, 
            type = "chain", 
            label = "StopLoss")
## [1] "Strat.Luxor.Stop.Loss"

8.6 Apply Strategy

cwd <- getwd()
setwd("./_data/")
results_file <- paste("results", strategy.st, "RData", sep = ".")
if( file.exists(results_file) ) {
    load(results_file)
} else {
    results <- applyStrategy(strategy.st, portfolios = portfolio.st)
    if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) {
        save(list = "results", file = results_file)
        save.strategy(strategy.st)
    }
}
setwd(cwd)