Nuts and Bolts of Quantstrat, Part II
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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:
#parameters pctATR <- .02 period <- 10 atrOrder <- TRUE nRSI <- 2 buyThresh <- 20 sellThresh <- 80 nSMA <- 200 #indicators 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=nSMA), label="sma")
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 strategy.st, 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
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'  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
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(strategy.st, name="INDICATOR_FUNCTION", arguments=list(x=quote(Cl(mktdata)), OTHERINPUTS), label="LABEL")
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:
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(strategy.st, "SMA", arguments=list(x=quote(Cl(mktdata)), n=200), label="sma")
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(strategy.st, mktdata=OHLC(SOME_DATA_HERE))
For example, using XLB:
test <- applyIndicators(strategy.st, 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.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.