4

Good day!

Friends I really need your help!

My question is: How could I put a stop-loss at a specific price?

Quantstrat works like this (for long position): Stop price = entry price – entry price * threshold.

For example, I try to run my code. But StopLossLONG does not work.

How to write code for stop?

library('TTR')
library('blotter')
library("quantmod")
require(quantstrat)

from <- "2016-04-01"
to <- "2016-07-01"

SPY <- getSymbols.yahoo('SPY',
                        env = .GlobalEnv,
                        return.class = 'xts',
                        index.class = 'Date',
                        from = from,
                        to = to,
                        periodicity = "daily",
                        auto.assign = FALSE)

SPY <- SPY[, c(1, 2, 3, 4, 5)]
names(SPY) <- c('Open','High','Low','Close','Volume')

level <- function(ts, level) {
    ts$level <- level
    res <- ts$level
    names(res) <- c("")
    return(res)
}

rm(list = ls(.blotter), envir = .blotter)
symbols = "SPY"
currency('USD')
initDate = from
from = from
to = to
initEq = 100000
strName = "test"

stock(symbols, currency = "USD", tick_size = 0.001, multiplier = 1)
getInstrument(symbols, type = "instrument")

strategy.st <- strName
portfolio.st <- strName
account.st <- strName
rm.strat(portfolio.st)
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)

addPosLimit(portfolio.st, symbols, timestamp = initDate, maxpos = 1, minpos = -1)

# indicators

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 208.0),
              label = "LEV208")

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 212.0),
              label = "LEV212")

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 207.0),
              label = "LEV207")

# signals

add.signal(strategy.st, name = "sigCrossover",
           arguments = list(columns = c("Close", "LEV208"),
                            relationship = "gt"),
           label = "OPEN")

add.signal(strategy.st, name = "sigCrossover",
           arguments = list(columns = c("Close", "LEV212"),
                            relationship = "gt"),
           label = "CLOSE")

# rules

add.rule(strategy.st, name = "ruleSignal",
         arguments = list(sigcol = "OPEN", sigval = TRUE,
                        orderside = "long",
                        ordertype = "market",
                        prefer = "Open",
                        orderqty = 1,
                        replace = FALSE,
                        osFUN = osMaxPos
         ),
         type = "enter",
         label = "LE"
)

add.rule(strategy.st, name = "ruleSignal",
         arguments = list(sigcol = "CLOSE", sigval = TRUE,
                        orderside = "long",
                        ordertype = "market",
                        prefer = "Open",
                        orderqty = "all",
                        replace = FALSE
         ),
         type = "exit",
         label = "LX"
)

add.rule(strategy.st,
         name = "ruleSignal",
         arguments = list(sigcol = "OPEN",
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "long",
                          ordertype = "stoplimit",
                          threshold = quote(0.005),
                          orderqty = "all",
                          orderset = "ocolong"),
         type = "chain",
         parent = "LE",
         label = "StopLossLONG",
         enabled = FALSE
)


applyStrategy(strategy.st, portfolio.st)
save.strategy(strategy.st)

orderbook <- getOrderBook(portfolio.st)
orderbook

Thank you!

Anatolye
  • 75
  • 5

1 Answers1

5

Your code has some odd settings which I'll point out in case it is part of your problem:

1) You have set enabled = FALSE for the stop rule with label StopLossLONG, so this rule would not be applied regardless.

2) It seems SPY never reaches 208 in your data period, so you won't get any long entry trades.

You ask if you can set a stop at a specific price. After reading your question carefully I think you mean can I set a stop at some absolute level, like 0.005 here? Rather than some absolute level of "entry price - some threshold amount". Yes, you can, if you modify ruleSignal. This is why name = 'ruleSignal is exposed in the add.rule function ... so you can make your own tweaks to how the orders are generated.

Here is an example, where your entry is now at 202 which permits one trade in your example. It sets a stop level at 9.999 (you could set any absolute price level below your entry price). I've added comments to the function ruleSignalAbsoluteStopPrice, which will replace ruleSignal. You can use any function you want, provided you have the correct expected arguments (like those for ruleSignal), and you call addOrder (or something like addOrder if you want to use a modified version of this function too ... but you need to understand the quantstrat source before doing this).

If you want to understand how to modify ruleSignal in a way that is correct in relation to the rest of the existing quantstrat code, try placing browser() inside ruleSignal and step line by line to see what you need to change to make things work correctly.

See the comments I've added, where I've tweaked parts of the existing ruleSignal code.

library('TTR')
library('blotter')
library("quantmod")
require(quantstrat)

# Define new function to replace 'ruleSignal' for long stoplimit orders:

ruleSignalAbsoluteStopPrice <- function (mktdata = mktdata, timestamp, sigcol, sigval, orderqty = 0,
                                         ordertype, orderside = NULL, orderset = NULL, threshold = NULL,
                                         tmult = FALSE, replace = TRUE, delay = 1e-04, 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 = "",
                                         absoluteStopPrice = 9.999)
{

    if (!is.function(osFUN))
        osFUN <- match.fun(osFUN)
    if (hasArg(curIndex))
        curIndex <- eval(match.call(expand.dots = TRUE)$curIndex,
                         parent.frame())
    else curIndex <- mktdata[timestamp, which.i = TRUE]
    # Just for long orderside and stoplimit order types.
    if (curIndex > 0 && curIndex <= nrow(mktdata) && ordertype == "stoplimit" && orderside == "long" && (ruletype ==
                                                                                                         "chain" )) {
        pricemethod <- pricemethod[1]
        if (hasArg(prefer))
            prefer = match.call(expand.dots = TRUE)$prefer
        else prefer = NULL

        # chain.price is the transaction price of the long trade.
        # Handle the case where the price may be less than your absolute stop level (here we skip entering a long position):
        if (chain.price <= absoluteStopPrice)
            return()

        threshold <- chain.price - absoluteStopPrice
        # Ensure that tmult = FALSE when using this approach.

        if (is.null(orderside) & !isTRUE(orderqty == 0)) {
            curqty <- getPosQty(Portfolio = portfolio, Symbol = symbol,
                                Date = timestamp)
            if (curqty > 0) {
                orderside <- "long"
            }
            else if (curqty < 0) {
                orderside <- "short"
            }
            else {
                if (orderqty > 0)
                    orderside <- "long"
                else orderside <- "short"
            }
        }
        if (orderqty == "all") {
            if (orderside == "long") {
                tmpqty <- 1
            }
            else {
                tmpqty <- -1
            }
        }
        else {
            tmpqty <- orderqty
        }
        if (!is.null(order.price)) {
            orderprice <- order.price
        }
        else if (!is.null(chain.price)) {
            orderprice <- chain.price
        }
        else {

        }
        if (is.null(orderset))
            orderset = NA
        if (orderqty != "all") {
            orderqty <- osFUN(strategy = strategy, data = mktdata,
                              timestamp = timestamp, orderqty = orderqty, ordertype = ordertype,
                              orderside = orderside, portfolio = portfolio,
                              symbol = symbol, ... = ..., ruletype = ruletype,
                              orderprice = as.numeric(orderprice))
        }
        if (!is.null(orderqty) && orderqty != 0 && length(orderprice)) {

            # All the arguments passed to `addOrder` are reasonable, and similar to what ruleSignal expects
            addOrder(portfolio = portfolio, symbol = symbol,
                     timestamp = timestamp, qty = orderqty, price = as.numeric(orderprice),
                     ordertype = ordertype, side = orderside, orderset = orderset,
                     threshold = threshold, status = "open", replace = replace,
                     delay = delay, tmult = tmult, ... = ..., prefer = prefer,
                     TxnFees = TxnFees, label = label, time.in.force = time.in.force)
        }
    }
    if (sethold)
        hold <<- TRUE
}



from <- "2016-04-01"
to <- "2016-07-01"

SPY <- getSymbols.yahoo('SPY',
                        env = .GlobalEnv,
                        return.class = 'xts',
                        index.class = 'Date',
                        from = from,
                        to = to,
                        periodicity = "daily",
                        auto.assign = FALSE)

SPY <- SPY[, c(1, 2, 3, 4, 5)]
names(SPY) <- c('Open','High','Low','Close','Volume')

level <- function(ts, level) {
    ts$level <- level
    res <- ts$level
    names(res) <- c("")
    return(res)
}

rm(list = ls(.blotter), envir = .blotter)
symbols = "SPY"
currency('USD')
initDate = from
from = from
to = to
initEq = 100000
strName = "test"

stock(symbols, currency = "USD", tick_size = 0.001, multiplier = 1)
getInstrument(symbols, type = "instrument")

strategy.st <- strName
portfolio.st <- strName
account.st <- strName
rm.strat(portfolio.st)
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)

addPosLimit(portfolio.st, symbols, timestamp = initDate, maxpos = 1, minpos = -1)

# indicators

# Set the level to 202 to allow one entry trade at least:

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 202.0),
              label = "LEV202")

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 212.0),
              label = "LEV212")

add.indicator(strategy.st, name = "level",
              arguments = list(ts = quote((mktdata)), level = 207.0),
              label = "LEV207")

# signals

add.signal(strategy.st, name = "sigCrossover",
           arguments = list(columns = c("Close", "LEV202"),
                            relationship = "gt"),
           label = "OPEN")

add.signal(strategy.st, name = "sigCrossover",
           arguments = list(columns = c("Close", "LEV212"),
                            relationship = "gt"),
           label = "CLOSE")

# rules

add.rule(strategy.st, name = "ruleSignal",
         arguments = list(sigcol = "OPEN", sigval = TRUE,
                          orderside = "long",
                          ordertype = "market",
                          prefer = "Open",
                          orderqty = 1,
                          replace = FALSE,
                          osFUN = osMaxPos
         ),
         type = "enter",
         label = "LE"
)

add.rule(strategy.st, name = "ruleSignal",
         arguments = list(sigcol = "CLOSE", sigval = TRUE,
                          orderside = "long",
                          ordertype = "market",
                          prefer = "Open",
                          orderqty = "all",
                          replace = FALSE
         ),
         type = "exit",
         label = "LX"
)

add.rule(strategy.st,
         name = "ruleSignalAbsoluteStopPrice",
         arguments = list(sigcol = "OPEN",
                          sigval = TRUE,
                          replace = FALSE,
                          orderside = "long",
                          ordertype = "stoplimit",
                          #threshold = quote(0.005),  don't bother setting threshold argument as ruleSignalAbsoluteStopPrice won't used the passed in argument 'threshold'.
                          absoluteStopPrice = 9.999, # Demonstrat that we can use new arguments related to the `ruleSignalAbsoluteStopPrice`` function
                          tmult = FALSE, # tmult is potentially used in `addOrder`
                          orderqty = "all",
                          orderset = "ocolong"),
         type = "chain",
         parent = "LE",
         label = "StopLossLONG",
         enabled = TRUE # Enable this rule
)


applyStrategy(strategy.st, portfolio.st)
save.strategy(strategy.st)

orderbook <- getOrderBook(portfolio.st)
orderbook

Now check you get what you expected (a stoplimit at the price 9.999):

> orderbook
$test
$test$SPY
           Order.Qty Order.Price Order.Type  Order.Side Order.Threshold Order.Status Order.StatusTime      Prefer Order.Set Txn.Fees Rule           Time.In.Force
2016-04-13 "1"       "201.827"   "market"    "long"     NA              "closed"     "2016-04-14 00:00:00" "Open" NA        "0"      "LE"           ""           
2016-04-14 "all"     "9.999"     "stoplimit" "long"     "-192.812707"   "open"       NA                    ""     "ocolong" "0"      "StopLossLONG" ""  

And we see the trade is still open:

> getTxns(portfolio.st, "SPY")
                    Txn.Qty Txn.Price Txn.Fees Txn.Value Txn.Avg.Cost Net.Txn.Realized.PL
2016-04-01 00:00:00       0      0.00        0      0.00         0.00                   0
2016-04-13 20:00:00       1    202.87        0    202.87       202.87                   0

Hope this helps.

FXQuantTrader
  • 6,821
  • 3
  • 36
  • 67
  • Does this mean that in your code, if Low price falls below 9.999 and we have an open long position, then StopLossLONG becomes market order and LX (exit order) is canceled? – Stat Dec 16 '20 at 23:32
  • BTW ... I think you don't even need getTxns(portfolio.st, "SPY") to see if the trade is still open or not ... in the orderbook, we have an "Order.Status" cloumn that shows the order is still "open". – Stat Dec 16 '20 at 23:35
  • 1
    The rule for LX will trigger an order when the signal value is TRUE, independent of the stop order. Checking the transactions depends on your definition of *need*. The point of showing the transactions, for me, is to act as a double on the consistency of the states in the strategy, which I would always do, particularly if strategies get complex. I always need to spot check transactions against orderbook behaviour for at least a few trades for any new strategy implementation. It can save you a lot of time in the long run if you discover your rule logic isn't quite what you intended. – FXQuantTrader Dec 17 '20 at 01:19
  • 1
    if you want to introduce dependencies between orders, so cancel one when the other is triggered (between the stop and exit market order), you need to create order sets. the OP didn't do this, so I didn't add it. You'd look at adding something like `parent = "LE" to the rule of the exit market order – FXQuantTrader Dec 17 '20 at 01:26
  • 1
    Thank you so much. I have a better understanding now. I will try to fully understand the part of your code with add.rule & ruleSignalAbsoluteStopPrice to see when it is exactly triggered. Aslo a very wise comment on double checking with getTxns. – Stat Dec 17 '20 at 02:34
  • In add.rule, can the value of 9.999 in `absoluteStopPrice = 9.999` be replaced with another function? For instance, get the low from 3 bars ago. – johnatasjmo Dec 23 '20 at 04:22
  • @johnatasjmo Yes. Please post a question if you still want help with this – FXQuantTrader Feb 06 '22 at 07:36