In this next tutorial, a more elaborate backtest script will be introduced. Please close all currently open windows, and then open the file sector_etfs.rts from the Examples directory.
The above is, of course, a partial screen shot. This script is longer than the prior examples, and there's a lot going on, so take some time to study it carefully.
This example contains elements introduced in prior tutorials, including Import, TestSettings, Parameters, Data and Strategy sections.
Two new sections are introduced here, which are both derivations of Strategy: Template and Benchmark.
The Strategy section is where a trading strategy is defined for the purpose of backtesting it, as seen in the first two tutorials.
The Template section is an optional element that can be used to avoid repeating the same elements in multiple strategies. In other words, it is where you might define any elements that are common to more than one strategy. In this example, the Template called "base" is only used by one strategy, so it doesn't serve any purpose besides illustrating what a Template is.
In fact, this part of this script:
could just as easily have been written as follows, with the template omitted:
The two snippets above are functionally identical. The Using: base statement in the first snippet instructs the script parser to copy all the elements from the base template and insert them in the sector_etfs strategy. I am in the habit of using "Template: base" for strategy elements such as Commission and Slippage that are usually the same in any strategy I'm testing.
In contrast to Template, a Benchmark section defines a strategy that will be included as a separate entity in the backtest. It is "traded" for each date in the test just as a regular strategy is, and will generate its own statistics and be given its own equity curve. The only difference between a benchmark and a strategy is that the benchmark's stats are not counted as part of the combined results of the backtest.
In this script example, the benchmark is used to simply generate an equity curve for the SPY ETF in a buy-and-hold simulation, to make it easy to visually compare the strategy's equity curve to this common benchmark:
(because Yahoo data is not dividend-adjusted, in order for the SPY dividends to be re-invested, the benchmark simulates exiting and re-entering the SPY position on each morning after an ex-dividend date)
Now on to the specific strategy and its implementation in this script.
The concept is to trade the 9 standard sector ETFs derived from components of the S&P 500 index (XLB, XLE, XLF, XLI, XLK, XLP, XLU, XLV, XLY) using a monthly rotational rule based on momentum. The Data section defines all the variables needed to implement this strategy. Parameters are used in data formulas to enable some optimization.
•Strength is simply the ratio of the current close to the lowest close in some number of days (63 by default, i.e., one calendar quarter).
•MomoRank uses the cross-sectional rank function to calculate the strength rank for each ETF for each date relative to the other ETFs on that date.
•ValueRank is the inverse of MomoRank
•MyRank selects which of the above two ranks to used depending on the Momo parameter (allows comparison of momentum vs. mean-reversion as the ranking criterion).
•Note also that these two rank calculations deliberately exclude the SPY benchmark from the top ranks by checking the InList value in the rank formula
•Rotate evalutes to 1 (TRUE) for the last trading day of each month
The strategy is defined to use the above data items as follows:
•EntrySetup must evaluate to TRUE (1) for there to be an entry at the next open (because that's the EntryTime specification). It will evaluate to TRUE for each ETF that is not SPY and whose rank is low enough (1 is top-ranked) to be within the MaxPositions threshold. All this will only occur if the next open will be the first open of a new month. The three clauses of the EntrySetup formula and the data elements that they refer to suffice to specify all of these factors.
•ExitRule is one of several exit elements a strategy can have (in this case it's the only one). This rule simply says to exit if it's a new month and the ETF is no longer among the top ranked ones.
•Quantity specifics the position size, in shares. S.Equity is the current account balance (including mark-to-market, i.e., net liquidation value) on any given date during the test, so this formula is simply a fixed-fraction of equity based on the Positions parameter.
To see all of this in action, first run this script as and then as as we did in the first tutorial.
A results window will appear with a new line for the test results (yours will be slightly different):
Not very promising. Let's open the equity graph to see how it looks in more detail (double-click on the above row):
(note that you can use the L key to toggle between Log scale and Arithmetic scale, or press the right mouse button on the graph to see this and other options on its menu)
This graph shows the purpose of the SPY benchmark. Buy and hold SPY is not included in the reported 5.55% ROR or 51.59% MaxDD. Those are the stats for the ETF strategy alone. But the SPY benchmark is included in the above graph as the red line, showing that, overall, this ETF rotational strategy did not beat buy-and-hold of the index, though prior to 2015 it was slightly ahead.
Since we have some parameters, we can go ahead and optimize them, just for fun. Click on and then just select the Lookback parameter for now:
Run the 5 tests and look at the results:
Double-click on the top row (252 bars, a one-year lookback) to show how this parameter has slightly out-performed the index:
Now if you want to go nuts with data-mining, go back into Optimize and check all 3 parameter boxes.
Run all 50 tests and sort by net profit to see what would have worked best:
Now the rotation is substantially beating the benchmark. But which parameters were the winners?
Apparently relative weakness did better than relative strength in this case.
In fact, the top 12 results are all using this anti-momo factor:
This is a good example of how we can sometimes learn something from an optimization, even if the goal is not (and should never be) to over-fit the parameters to the data for actual trading.