Nuts and Bolts of Quantstrat, Part III

This post will focus on signals in quantstrat.

In comparison to indicators, signals in quantstrat are far more cut-and-dry, as they describe the interaction of indicators with each other–whether that indicator is simply the close price (“Close”), or a computed indicator, there are only so many ways indicators can interact, and the point of signals is to provide the user with a way of describing these relationships–is one greater than another, is the concern only when the cross occurs, does the indicator pass above or below a certain number, etc.

Here’s the code that will provide the example for the demonstration, from the atrDollarComparison strategy:

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "sma"), relationship="gt"),
           label="filter")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("filter", "rsiLtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "sma"), relationship="lt"),
           label="filterExit")

Adding signals to a strategy has a very similar format to adding indicators. The structure is very similar:

1) The call to add.signal
2) The name of the strategy (again, strategy.st makes this very simple)
3) The name of the signal function (the majority of which are on display in the preceding block of code)
4) The arguments to said signal function, passed in the same way they are to indicators (that is, arguments=list(args)), but which are far more similar compared to indicators
5) The label for the signal column, which is highly similar to the labeling for indicator columns.

The first two steps are identical to the add.indicator step, except with add.signal instead of add.indicator. This is cut and dry.

Beyond this, all of the signal functions I use are presented above. They are:

sigComparison, sigThreshold, sigAND, and sigCrossover.

The arguments for all four are very similar. They contain some measure of columns, a threshold, a relationship between the first and second column (or between the first column and the threshold), and whether the signal should return TRUE for the entire duration of the relationship being true, or only on the first day, with the cross argument.

Relationships are specified with a two or three character identifier: “gt” stands for greater than (E.G. SMA50 > SMA200), “gte” stands for greater than or equal to, “lt” and “lte” work similarly, and “eq” stands for equal to (which may be useful for certain logic statements such as “stock makes a new seven-day low”, which can be programmed by comparing the close to the running seven-day min, and checking for equality).

Here’s an explanation of all four sig functions:

The sigComparison function compares two columns, and will return TRUE (aka 1) so long as the specified relationship comparing the first column to the second holds. E.G. it will return 1 if you specify SMA50 > SMA200 for every timestamp (aka bar, for those using OHLC data) that the 50-day SMA is greater than the 200-day SMA. The sigComparison function is best used for setting up filters (EG the classic Close > SMA200 formation). This function takes two columns, and a relationship comparing the first to the second columns.

The sigCrossover is identical to the above, except only returns TRUE on the timestamp (bar) that the relationship moves from FALSE to TRUE. E.G. going with the above example, you would only see TRUE the day that the SMA50 first crossed over the SMA200. The sigCrossover is useful for setting up buy or sell orders in trend-following strategies.

The sigThreshold signal is identical to the two above signals (depending on whether cross is TRUE or FALSE), but instead uses a fixed quantity to compare one indicator to, passed in via the threshold argument. For instance, one can create a contrived example of an RSI buy order with a sigCrossover signal with an RSI indicator and an indicator that’s nothing but the same identical buy threshold all the way down, or one can use the sigThreshold function wherever oscillator-type indicators or uniform-value type indicators (E.G. indicators transformed with a percent rank), wherever all such indicators are involved.

Lastly, the sigAND signal function, to be pedantic, can also be called colloquially as sigIntersect. It’s a signal function I wrote (from my IKTrading package) that checks if multiple signals (whether two or more) are true at the same time, and like the sigThreshold function, can be set to either return all times that the condition holds, or the first day only. I wrote sigAND so that users would be able to structurally tie up multiple signals, such as an RSI threshold cross coupled with a moving-average filter. While quantstrat does have a function called sigFormula, it involves quoted code evaluation, which I wish to minimize as much as possible. Furthermore, using sigAND allows users to escalate the cross clause, meaning that the signals that are used as columns can be written as comparisons, rather than as crosses. E.G. in this RSI 20/80 filtered on SMA200 strategy, I can simply compare if the RSI is less than 20, and only generate a buy rule at the timestamp after both RSI is less than 20 AND the close is greater than its SMA200. It doesn’t matter whether the close is above SMA200 and the RSI crosses under 20, or if the RSI was under 20, and the close crossed above its SMA200. Either combination will trigger the signal.

One thing to note regarding columns passed as arguments to the signals: quantstrat will do its best to “take an educated guess” regarding which column the user attempts to refer to. For instance, when using daily data, the format may often be along the lines of XYZ.Open XYZ.High XYZ.Low XYZ.Close, so when “Close” is one of the arguments, quantstrat will make its best guess that the user means the XYZ.Close column. This is also, why, once again, I stress that reserved keywords (OHLC keywords, analogous tick data keywords) should not be used in labeling. Furthermore, unlike indicators, whose output will usually be something along the lines of FUNCTION_NAME.userLabel, labels for signals are as-is, so what one passes into the label argument is what one gets.

To put it together, here is the chunk of code again, and the English description of what the signals in the chunk of code do:

#signals
add.signal(strategy.st, name="sigComparison",
           arguments=list(columns=c("Close", "sma"), relationship="gt"),
           label="filter")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=buyThresh, 
                          relationship="lt", cross=FALSE),
           label="rsiLtThresh")

add.signal(strategy.st, name="sigAND",
           arguments=list(columns=c("filter", "rsiLtThresh"), cross=TRUE),
           label="longEntry")

add.signal(strategy.st, name="sigThreshold",
           arguments=list(column="rsi", threshold=sellThresh,
                          relationship="gt", cross=TRUE),
           label="longExit")

add.signal(strategy.st, name="sigCrossover",
           arguments=list(columns=c("Close", "sma"), relationship="lt"),
           label="filterExit")

1) The first signal checks to see if the “Close” column is greater than (“gt”) the “sma” column (which had a setting of 200), and is labeled “filter”.
2) The second signal checks to see if the “rsi” column is less than (“lt”) the threshold of buyThresh (which was defined earlier as 20), and is labeled as “rsiLtThresh”.
3) The third signal checks when both of the above signals became TRUE for the first time, until one or the other condition becomes false, and is labeled as “longEntry”. NB: the signals themselves do not place the order–I just like to use the label “longEntry” as this allows code in the rules logic to be reused quicker.
4) The fourth signal checks if the “rsi” column crossed over the sell threshold (80), and is labeled as “longExit”.
5) The fifth signal checks if the “Close” column crossed under the “sma” column, and is labeled “filterExit”.

In quantstrat, it’s quite feasible to have multiple signals generate entry orders, and multiple signals generate exit orders. However, make sure that the labels are unique.

The next post will cover rules.

Thanks for reading.

16 thoughts on “Nuts and Bolts of Quantstrat, Part III

  1. Pingback: The Whole Street’s Daily Wrap for 9/20/2014 | The Whole Street

  2. Dear Ilya,
    I was trying to repeat your demo in the intro quantstrat video you gave in BigMike forum. After I finished coping all the codes to add indicators, I ran the line
    out <- applyIndicators(strategy.st, SPY)
    tail(out)
    in order to check to see if I got things right, and everything went fine.

    However, the error came when I ran the lines —
    out <- applySignals(strategy.st, SPY)

    after I coped all the codes to add signals, and the error message was —
    Error in if (length(j) == 0 || (length(j) == 1 && j == 0)) { :
    missing value where TRUE/FALSE needed

    I looked into a similar question here http://stackoverflow.com/questions/6960013/error-message-missing-value-where-true-false-needed-when-applying-a-quantstrat?answertab=votes#tab-top
    but I could not make use of it to solve the error here.

    I have attached the codes I coped from your video which I was running below.

    Thanks in advance

    #####1. every new R.file, the packages need to be required###########
    require(quantstrat)

    require(devtools)

    require(IKTrading)
    require(DSTrading)

    options("getSymbols.warning4.0"=FALSE)
    rm(list=ls(.blotter), envir=.blotter)
    currency("USD")
    Sys.setenv(TZ="UTC")

    symbols <- "SPY"
    suppressMessages(getSymbols(symbols, from="1998-01-01", to="2012-12-31"))
    stock(symbols, currency="USD", multiplier=1)
    initDate="1990-01-01"

    tradeSize <- 100000
    initEq <- tradeSize*length(symbols)

    strategy.st <- portfolio.st <- account.st <- "RSI_10_6" # "DollarVsATRos"
    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) # apply all indicators, signals and rules to strategy

    nRSI = 2
    thresh1 = 10
    thresh2 = 6

    nSMAexit = 5
    nSMAfilter = 200

    period = 10
    pctATR = .02
    maxPct = .04

    add.indicator(strategy.st, name ="lagATR", arguments = list(HLC = quote(HLC(mktdata)), n = period), label = "atrX")

    add.indicator(strategy.st, name = "RSI", arguments = list(price = quote(Cl(mktdata)), n = nRSI), label = "rsi")

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

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

    out <- applyIndicators(strategy.st, SPY)
    tail(out)
    names(out) # all the names of cols with SPY…in front

    ####7. add signals ##########
    ##################################

    add.signal(strategy.st, name = "sigComparison", arguments = list(columns = c("SPY.Close", "SMA.filterMA"), relationship = "gt"), label = "uptrend")
    # I try to change the column name from "Close" to "SPY.Close", from "filterMA" to "SMA.filterMA", but it still produced the same error.

    add.signal(strategy.st, name = "sigThreshold", arguments = list(column = "rsi", threshold = thresh1, relationship = "lt", cross = FALSE), label = "rsiThresh1")

    add.signal(strategy.st, name = "sigThreshold", arguments = list(column = "rsi", threshold = thresh2, relationship = "lt", cross = FALSE), label = "rsiThresh2")

    add.signal(strategy.st, name = "sigAND", arguments = list(columns = c("rsithresh1", "uptrend"), cross = TRUE), label = "longEntry1")

    add.signal(strategy.st, name = "sigAND", arguments = list(columns = c("rsiThresh2", "upTrend"), cross = TRUE), label = "longEntry2")

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

    add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = c("Close", "filterMA"), relationship = "lt"), label = "exitLongFilter")

    out <- applySignals(strategy.st, SPY)
    tail(out)

    • Check the names of your indicators/signals.

      Do this:

      test <- applyIndicators(strategy.st, mktdata=OHLC(SPY))

      test <- apply.indicators(strategy.st, mktdata=test)

      And then check the names of the indicators/signals. Make sure your labels reflect that.

  3. Thanks for your very quick reply.
    I did try similar lines in the codes I pasted above

    out <- applyIndicators(strategy.st, SPY)
    tail(out)
    names(out)

    so, I am aware that the column names (OHLC and indicators) are different from the column names given in the codes of adding signals:

    SPY.Open SPY.High SPY.Low SPY.Close atr.atrX EMA.rsi SMA.quickMA SMA.filterMA
    2012-12-21 142.17 144.09 141.94 142.79 1.375988 21.596916 144.268 139.1757
    2012-12-24 142.48 142.56 142.19 142.35 1.556389 17.109190 143.984 139.2022

    whereas, the column names used in the code I pasted above are "Close", "rsi", "quickMA" without "SPY" or "EMA" or "SMA" in front of them.

    however, I did try to change all the column names to names like SPY.Close and SMA.quickMA, but same error still occurred.

    Where I still got wrong?

    thanks

    Kenny

  4. Hey Ilya,

    I wanted to use your sigAND function in RStudio, but could not download the IKTrader package…

    So I wanted to plug in the function itself in my code.

    “sigAND” <- function(label, data=mktdata, columns, cross = FALSE) {
    ret_sig = NULL
    colNums <- rep(0, length(columns))
    for(i in 1:length(columns)) {
    colNums[i] <- match.names(columns[i], colnames(data))
    }
    ret_sig <- data[, colNums[1]]
    for(i in 2:length(colNums)) {
    ret_sig <- ret_sig & data[, colNums[i]]
    }
    ret_sig <- ret_sig*1
    if (isTRUE(cross))
    ret_sig <- diff(ret_sig) == 1
    colnames(ret_sig) <- label
    return(ret_sig)
    }

    just like this.

    So my question is, what do I have to change in this function coding to fit to my Coding and to use sigAND?

    Hopfully this question is not to stupid…

    my used data is XXX.csv do I need to put this in somewhere?

    thanks

    Dennis

  5. Hi Ilya. It it possible to fetch the result of the signals in some similar way as with the indicators?
    I mean more somewhere to find true/false, value 1 (for each respective signal).
    To clarify i do not mean the printout that shows the buy and sell result, when running the strategy.
    Thanks.

  6. Pingback: Nuts and Bolts of Quantstrat, Part V | QuantStrat TradeR

  7. Pingback: Nuts and Bolts of Quantstrat, Part V – 极智投研

  8. Hi Ilya,

    Not sure if you still keep up with this awesome tutorial. But “sigAND” is throwing an error and I can’t find your trading package online with this custom argument. Nor does the most recent version of quanstrat seem to support it. Any suggestions?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s