I was recently developing code to perform circular indexing of arrays where my need was to calculate the correct circiular index of a bar that was far into the future or far into the past relative to the current bar.
I was perplexed when my code did not produce the desired results. I spent quite a bit of time locating the problem and also learned some new things about formulas for calculating circular indexes that I felt were worth sharing with others.
To create a circular index for an array, you will often see simple code as follows:
vars: index(0), Size(12);
Index = Index + 1
if Index > Size then Index = 1;
Or this:
vars: index(0), Size(12);
Index = Index – 1;
if Index < 1 then Index = Size;
There are some situations, however, where you wish to calculate the circular index of a point many bars into the future, or many bars into the past.
My initial attempt at creating a more robust formula was:
CircularIndex = MOD(Index – 1, Size) + 1;
This seemed to work fine when tested in Excel, but when using it in EasyLanguage I was getting errors for referencing the Array out of bounds.
So, the first thing I learned about circular indexing is that the MOD function in Excel is NOT the same as the MOD function in EasyLanguage!
With positive offsets, the values are the same:
Excel | EasyLanguage |
MOD(1, 10) = 1 | MOD(1, 10) = 1 |
MOD(11, 10) = 1 | MOD(11, 10) = 1 |
Mod(21, 10) = 1 | MOD(21, 10) = 1 |
But with negative offsets, the values are different:
Excel | EasyLanguage |
MOD(-1, 10) = 9 | MOD(-1, 10) = -1 |
MOD(-11, 10) = 9 | MOD(-11, 10) = -11 |
Mod(-21, 10) = 9 | MOD(-21, 10) = -21 |
The EasyLanguage MOD function does not work for negative offsets. Therefore, it can not easily be used when backreferencing circular arrays with negative offsets since Arrays can only reference non-negative offsets:
e.g. A[0], A[1]…etc. but not A[-2]
Fortunately, I did find an EasyLanguage function that did work well:
CircularIndex = Index – Floor((Index – 1)/Size) * Size
This function calculates the circular index from non-circular indices, and is especially useful in situations where the index offset is a very large positive or negative number (several multiples of the Size).
My preference is to use circular indices that do not use the number 0. It is easier to debug code when, for example, calculated element 3 belongs in array[3] rather than in array[2]. This avoids the mental juggling of the offset required when also utilizing the element [0] to store data.
Secondly, the Array[0] element can often be used to hold “status” information about the array or its state of processing. When circular arrays are passed to functions for further calculations, having the “status” information in element Array[0] avoids having to create a separate status variable to pass this information.
The following simple example may be used to illustrate the CircularIndex function and the corresponding CircularIndexTest indicator, attached below.
A circular array of size 12 is set up and loaded with data starting with an index of 1 (BarNumber = 1):
BarNumber = 1: Array[1] = close;
BarNumber = 2: Array[2] = close;
….. …..
BarNumber = 12: Array[12] = close;
BarNumber = 13: Array[1] = close;
etc.
What is the correct circular index to use when BarNumber = 4092 ?
The answer is given by the formula:
CircularIndex | = | Index – floor((index – 1)/size) * size; |
= | 4092 – floor((4092 – 1)/12) * 12 | |
= | 4092 – 340 * 12 = 4092 – 4080 = 12 |
Suppose you are at the LastBarOnChart in the above case. The BarNumber is now 5027 and you need to know what is the correct circular index to use for a bar that is 1137 bars earlier. The answer is:
Index | = | 5027 – 1137 = 3890 |
CircularIndex | = | Index – floor((index – 1)/size) * size; |
= | 3890 – floor( (3890 – 1) / 12) * 12 | |
= | 3890 – 324 * 12 = 3890 – 3888 = 2 |
It is interesting to note that the correct circular index is given by the SAME FORMULA for both positive and negative numbers! This is because of the unique properties of the floor function when calculated for negative fractions.
For example floor(-1/10) = -1
A test indicator CircularIndexTest is shown here calculating the circular index with Length = 12:
Fig. 1. CircularIndexTest Indicator demonstrating the function CircularIndex
The OutputBar from the above CircularIndexTest Indicator shows:
Index = -15.00 Circular Index = 9.00 Index = -14.00 Circular Index = 10.00 Index = -13.00 Circular Index = 11.00 Index = -12.00 Circular Index = 12.00 Index = -11.00 Circular Index = 1.00 Index = -10.00 Circular Index = 2.00 Index = -9.00 Circular Index = 3.00 Index = -8.00 Circular Index = 4.00 Index = -7.00 Circular Index = 5.00 Index = -6.00 Circular Index = 6.00 Index = -5.00 Circular Index = 7.00 Index = -4.00 Circular Index = 8.00 Index = -3.00 Circular Index = 9.00 Index = -2.00 Circular Index = 10.00 Index = -1.00 Circular Index = 11.00 Index = 0.00 Circular Index = 12.00 Index = 1.00 Circular Index = 1.00 Index = 2.00 Circular Index = 2.00 Index = 3.00 Circular Index = 3.00 Index = 4.00 Circular Index = 4.00 Index = 5.00 Circular Index = 5.00 Index = 6.00 Circular Index = 6.00 Index = 7.00 Circular Index = 7.00 Index = 8.00 Circular Index = 8.00 Index = 9.00 Circular Index = 9.00 Index = 10.00 Circular Index = 10.00 Index = 11.00 Circular Index = 11.00 Index = 12.00 Circular Index = 12.00 Index = 13.00 Circular Index = 1.00 Index = 14.00 Circular Index = 2.00 Index = 15.00 Circular Index = 3.00 |
While this is admittedly a trivial program, I think it is useful to:
- Document the quirk about the MOD function behaving differently in Excel and EasyLanguage
- Show how the Floor function can be used in place of the MOD function to create a simpler formula for circular index calculations that works for offsets of ANY size, and in both the POSITIVE and NEGATIVE directions.
- Provide a function that can be called to calculate the circular index of an offset of any size in either postive or negative directions.
Downloads
Initially posted version: 08/31/09
Latest Update: 01/31/10
*.ELD files are compiled for TS 8.7
All ELD and code text files packaged here:
Users of earlier versions fo TradeStation may compile the code
from the text files included in the above *.zip file.
The code may be visualized here:
Recent Comments