Nuts and Bolts of Quantstrat, Part II

Last week, I covered the boilerplate code in quantstrat.

This post will cover parameters and adding indicators to strategies in quantstrat.

Let’s look at a the code I’m referring to for this walkthrough:

pctATR <- .02
period <- 10
atrOrder <- TRUE

nRSI <- 2
buyThresh <- 20
sellThresh <- 80
nSMA <- 200

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

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

add.indicator(, name="SMA",
              arguments=list(x=quote(Cl(mktdata)), n=nSMA),

This code contains two separate chunks–parameters and indicators. The parameters chunk is simply a place to store values in one area, and then call them as arguments to the add.indicator and add.signal functions. Parameters are simply variables assigned to values that can be updated when a user wishes to run a demo (or in other ways, when running optimization processes).

Indicators are constructs computed from market data, and some parameters that dictate the settings of the function used to compute them. Most well-known indicators, such as the SMA (simple moving average), EMA, and so on, usually have one important component, such as the lookback period (aka the ubiquitous n). These are the parameters I store in the parameters chunk of code.

Adding an indicator in quantstrat has five parts to it. They are:

1) The add.indicator function call
2) The name of the strategy to add the indicator to (which I always call, standing for strategy string)
3) The name of the indicator function, in quotes (E.G. such as “SMA”, “RSI”, etc.)
4) The arguments to the above indicator function, which are the INPUTS in this statement arguments=list(INPUTS)
5) The label that signals and possibly rules will use–which is the column name in the mktdata object.

Notice that the market data (mktdata) input to the indicators has a more unique input style, as it’s wrapped in a quote() function call. This quote function call essentially tells the strategy that the strategy will obtain the object referred to in the quotes later. The mktdata object is initially the OHLCV(adjusted) price time series one originally obtains from yahoo (or elsewhere), as far as my demos will demonstrate for the foreseeable future. However, the mktdata object will later come to contain all of the indicators and signals added within the strategy. So because of this, here are some functions that one should familiarize themselves with regarding some time series data munging:

Op: returns all columns in the mktdata object containing the term “Open”
Hi: returns all columns in the mktdata object containing the term “High”
Lo: returns all columns in the mktdata object containing the term “Low”
Cl: returns all columns in the mktdata object containing the term “Close”
Vo: returns all columns in the mktdata object containing the term “Volume”
HLC: returns all columns in the mktdata object containing “High”, “Low”, or “Close”.
OHLC: same as above, but includes “Open”.

These all ignore case.

For these reasons, please avoid using these “reserved” terms when labeling (that is, column naming in step 5) your indicators/signals/rules. One particularly easy mistake to make is using the word “slow”. For instance, a naive labeling convention may be to use “maFast” and “maSlow” as labels for, say, a 50-day and 200-day SMA, respectively, and then maybe implement an indicator that uses an HLC for an argument, such as ATR. This may create errors down the line when more than one column has the name “Low”. In the old (CRAN) version of TTR–that is, the version that gets installed if one simply types in


as opposed to

install.packages("TTR",  repos="")

the SMA function will still append the term “Close” to the output. I’m sure some of you have seen some obscure error when calling applyStrategy. It might look something like this:

length of 'dimnames' [2] not equal to array extent

This arises as the result of bad labeling. The CRAN version of TTR runs into this from time to time, and if you’re stuck on that version, a kludge to work around this is instead of using


to use


instead. That [,1] specifies only the first column in which the term “Close” appears. However, I simply recommend upgrading to a newer version of TTR from R-forge. On Windows, this means using R 3.0.3 rather than 3.1.1, due to R-forge’s lack of binaries for Windows for the most recent version of TTR (only source is available), at least as of the time of this writing.

On a whole, however, I highly recommend avoiding reserved market data keywords (open, high, low, close, volume, and analogous keywords for tick data) for labels.

One other aspect to note about labeling indicators is that the indicator column name is not merely the argument to “label”, but rather, the label you provide is appended onto the output of the function. In DSTrading and IKTrading, for instance, all of the indicators (such as FRAMA) come with output column headings. So, when computing the FRAMA of a time series, you may get something like this:

> test <- FRAMA(SPY)
> head(test)
           FRAMA trigger
2000-01-06    NA      NA
2000-01-07    NA      NA
2000-01-10    NA      NA
2000-01-11    NA      NA
2000-01-12    NA      NA
2000-01-13    NA      NA

When adding indicators, the user-provided label will come after a period following the initial column name output, and the column name will be along the lines of “FunctionOutput.userLabel”.

Beyond pitfalls and explanations of labeling, the other salient aspect of indicators is the actual indicator function that’s called, and how its arguments function.

When adding indicators, I use the following format:

add.indicator(, name="INDICATOR_FUNCTION",
              arguments=list(x=quote(Cl(mktdata)), OTHERINPUTS),

This is how these two aspects work:

The INDICATOR_FUNCTION is an actual R function that should take in some variant of an OHLC object (whether one column–most likely close, HLC, or whatever else). Functions such as RSI, SMA, and lagATR (from my IKTrading library) are all examples of such functions. To note, there is nothing “official” as opposed to “custom” about the functions I use for indicators. Indicators are merely R functions (that can be written by any R user) that take in a price series as one of the arguments.

The inputs to these functions are enclosed in the arguments input to the add.indicator function. That is, the part of the syntax that looks like this:

arguments=list(x=quote(Cl(mktdata)), n=nSMA)

These arguments are the inputs to the function. For instance, if one would write:


One would see:

function (x, n = 10, ...) 

In this case, x is a time series based on the market data (that is, the mktdata object), and n is a parameter. As pointed out earlier, the syntax for the mktdata involves the use of the quote function. However, all other parameters to the SMA (or any other) function call are static, at least per individual backtest (these can vary when doing optimization/parameter exploration). Thus, for the classic 200-day simple moving average, the appropriate syntax would contain:

add.indicator(, "SMA",
              arguments=list(x=quote(Cl(mktdata)), n=200),

In my backtests, I store the argument to n above the add.indicator call in my parameters chunk of code for ease of location. The reason for this is that when adding multiple indicators, signals, and rules, it’s fairly easy to lose track of a hard-coded value among the interspersed code, so I prefer to keep my numerical values collected in one place and reference them in the actual indicator, signal, and rule syntax.

Lastly, one final piece of advice is that when constructing a strategy, one need not have all the signals and rules implemented just to check how the indicators will be added to the mktdata object. Instead, try this, after running the code through the add.indicator syntax and no further if you’re ever unsure what your mktdata object will look like. Signals (at least in my demos) will start off with a commented


bit of syntax. If you see that line, you know that there are no more indicators to add. In any case, the following is a quick way of inspecting indicator output.

test <- applyIndicators(, mktdata=OHLC(SOME_DATA_HERE))

For example, using XLB:

test <- applyIndicators(, mktdata=OHLC(XLB))
head(test, 12)

Which would give the output:

> head(test, 12)
           XLB.Open XLB.High  XLB.Low XLB.Close atr.atrX  EMA.rsi SMA.sma
2003-01-02 15.83335 16.09407 15.68323  16.08617       NA       NA      NA
2003-01-03 16.03877 16.05457 15.91235  15.99926       NA       NA      NA
2003-01-06 16.10988 16.41011 16.10988  16.30740       NA 78.00000      NA
2003-01-07 16.38641 16.38641 16.18098  16.25209       NA 60.93750      NA
2003-01-08 16.19679 16.19679 15.80964  15.83335       NA 14.13043      NA
2003-01-09 15.95186 16.12568 15.92026  16.07827       NA 54.77099      NA
2003-01-10 15.94396 16.27579 15.94396  16.25209       NA 72.94521      NA
2003-01-13 16.35480 16.37061 16.12568  16.18098       NA 54.89691      NA
2003-01-14 16.12568 16.25999 16.12568  16.25999       NA 70.89800      NA
2003-01-15 16.17308 16.17308 15.90445  15.99136       NA 20.77648      NA
2003-01-16 15.95976 16.18098 15.95976  16.07827       NA 45.64200      NA
2003-01-17 15.97556 16.13358 15.92816  15.95186 0.281271 23.85808      NA

This allows a user to see how the indicators will be appended to the mktdata object in the backtest. If the call to applyIndicators fails, it means that there most likely is an issue with labeling (column naming).

Next week, I’ll discuss signals, which are a bit more defined in scope.

Thanks for reading.

10 thoughts on “Nuts and Bolts of Quantstrat, Part II

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

  2. Pingback: Webinar: An Introduction To R for Trading w/Ilya Kipnis (Page 13) - Matlab, R project and Python | Big Mike Trading

  3. I run into an error with the example at the line ‘test <- applyIndicators(, mktdata=OHLC(XLB))'. I'm thinking the problem is that the add.indicator for RSI and lagATR functions require a maType parameter which is not supplied. When I run that line I get 'Error in `colnames<-`(`*tmp*`, value = c("XLB.Close.SMA.200", "XLB.Close.EMA.2.rsi.SMA.200" :
    length of 'dimnames' [2] not equal to array extent'.

    I'm new to this. I tried modifying the add.indicator to add.indicator(, name="lagATR",
    arguments=list(HLC=quote(HLC(mktdata)), maType="SMA", n=period),
    and received the error Error in runSum(x, n) : Series contains non-leading NAs

  4. Hi Ilya,
    I have run into the same error. My TTR package is version 0.22-0. Are there any known problems with this version of TTR?


  5. Why is the result of executing ” head(test, 12)” as follows?
    I hope that “EMA.rsi” should be “RSI.rsi”.

    ———————- the result of executing ” head(test, 12)” ——————————————
    XLB.Open XLB.High XLB.Low XLB.Close atr.atrX EMA.rsi SMA.sma
    2003-01-02 15.83335 16.09407 15.68323 16.08617 NA NA NA


  6. Hi Ilya, the blog posts are very useful and can save a lot of trial and error, however I have run into difficulties while experimenting with some custom indicators that are not part of any technical analysis package and as such cannot be called via add.indicator (name) function. So, if I already have a set of indicators and would like to simply backtest them in quantstrat, how can I accomplish this?

    1) For example, if I do a PCA of a very large data set (no interest to repeat this as part of the backtest) and merge the results with OHLC time series, is there any way I can refer to the newly create column with the results and create an indicator while skipping the required indicator calculation in the package? This is quite troublesome!

    If I opt not to formally define the indicator but proceed with directly adding the signal and rules (bare minimum 2×2), the pre-processing works (no error), mktdata variable is created and columns with rules are added but the backtest is not actually run. When I apply the strategy, it returns NULL. No trades are made.

    What would be the best way of dealing with this?

    Thank you!

    • Indicators are simply functions. EG SMA is no more than a runMean function. And so on. Furthermore, you can append any arbitrary data to your OHLC object ahead of time and just refer to those columns in the backtest with an add.signal command.

Leave a Reply

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

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s