If you started at the beginning of this tutorial and followed through, you have already seen an example of how indicators can be used within formula indicator definitions. Here we will deal with the subject of indicators not only as components of formula definitions, but as functions that you can define yourself.Indicator Types
Built-in and Custom Indicators
Trademan deals with three types of indicator. The original built-in indicator, the custom indicator, which is essentially a stand-alone program called by Trademan, and the formula indicator, which is defined in terms of Trademan's formula language. The main difference in the behaviors of the various indicator types lies in the way they treat input parameters. The built-in and custom indicators are designed to receive fixed parameters from the Control Panel. Since these numbers are constant for any given calculation, they serve more as control constants than as input data.Formula Indicators
Anything you define with a formula in Trademan is an indicator. However, formula indicators can be designed to look like either indicators, which perform calculations on predefined data, or like functions, which calculate using data passed to them in their argument list. Formula indicators that are designed to be plotted must treat parameters in the standard way, since Trademan will treat them in every way as if they were built-in indicators. Thus, the parameters will be passed to them, and they will have no other source of input data than is defined for them in their function definitions. Formula indicators designed to behave as functions should be called only from within formula definitions, but they can accept variable data expressions where parameters are specified in their own definitions. We'll examine this use of indicators as functions in more detail later. First, let's take a look at indicator syntax.Indicator Syntax
The preceding image shows the syntax hint provided for the Relative Strength Index when it is selected from the function list. What does this string of text mean and why is it structured the way it is?
Indicator Parameters
Trademan indicators currently accept up to three parameters, which can be described as control variables. These variables might specify how many data points are used in an average, or the span over which a difference is calculated, or any other number which affects the calculation but which stays constant throughout the calculation.Indicator Data Sets
Indicators in Trademan currently comprise up to six distinct data sets: the primary indicator, the secondary indicator, the tertiary indicator, the buy signal, the sell signal, and the action price. The data set returned by a particular reference to the indicator is determined by the value of the fourth argument. The following table summarizes the available data sets and their corresponding designators. Only the primary indicator curve is required for a legal indicator definition in Trademan. The other data sets are optional. A data set is assumed to contain a time series whose values correspond one-to-one with the dates in the input data set. The input data set is always synchronized with the closing prices of the security for which the indicator is being calculated. Thus, every output value of an indicator data set is assigned to a particular date in the data set for the security.Fourth Indicator Argument
Designator Data Set 0 primary indicator 1 secondary indicator 2 tertiary indicator 3 buy signal 4 sell signal 5 action price Default Arguments
All indicators require four input arguments: three parameters and a data set designator. All the arguments have default values assigned to them. The parameter defaults are set by the user in defining the indicator by entering values into the Parameter 1...3 Default boxes. The data set designator defaults to zero, which is the designator for the primary indicator. If a parameter is not used by the indicator, then clearly the value assigned to it does not matter. As a matter of convention, the syntax hints put "0" into an unused parameter location. Looking at the syntax hint at the beginning of this section, you may notice a lot of square brackets. These brackets enclose optional arguments. Everything, even the parentheses are optional. That is, if you want to use nothing but default values in the argument list, you don't even need the parentheses. However, if you want to specify the value of one of the arguments, you have to specify a value for all of the arguments that precede it in the list. For instance, if I can use the default parameters of 26, 12, and 9 in the MACD, I would write "MACD," but if I need the third parameter to be 10 instead of 9, I would have to write "MACD(26, 12, 10)."The preceding is true of the data parameters, that is, those in the first three positions, but it is no longer true of the last parameter, which specifies the data set. If you want to specify a data set other than the primary for an indicator, append # and then the data set number to the name. For instance, if I want the buy signal for RSI with a 28-day period, I write "RSI#3(28)." If I want the sell signal for RSI with the default period of 14 days, I would write "RSI#4." Examples in the following discussions will include examples of both the long and the short forms for designating data sets, since they are equivalent. Of course, it saves typing and a fair amount of aggravation to use the short form.
Indicator Aliasing
The simplest application of the indicator as function may be just renaming or specializing an indicator. For instance the standard 14 data point Relative Strength Index could be defined as a new indicator named RSI and the primary indicator defined as "Relative Strength Index."
Note the entries for the buy and sell signals. These are just the expressions for the default buy and sell signals of the built-in indicator. The rest of the indicator is just the same as the original Relative Strength Index except that there are no parameters, and I did take the liberty of adding a horizontal line at 50.
Alter Signals
The preceding is nice, but pretty boring. What if we like the Relative Strength Index, but don't like the default signals for the built-in indicator? Well, since we had to actually type in the signals for the built-in indicator it should be pretty obvious that we can modify them. Maybe you think that a positive crossing at 30 is a better buy signal than a failure swing after a fall past 30. Match that with a sell signal consisting of a negative crossing at 70 and we have the following definitions:
These signals certainly come earlier than the built-in defaults, but they are also more numerous. Maybe this suits your purpose, especially in combination with other indicators, where you can use RSI#3 and RSI#4 as buy and sell signals, respectively.
Combine with Other Indicators
That last example produced a good aggressive set of buy and sell signals, but there are too many of them. Why don't we combine them with another set of signals to get a consensus? Let's try MACD.
Note that we have to specify all arguments for MACD since we are specifying a non-default fourth argument. Since we are using default arguments in the data parameters, though, we could have specified MACD#3 and MACD#4 instead. The "since" function returns true if the argument was true within the number of periods in the past specified by the second argument. Looking at the results, we get some good signals and not too many of them, but we also miss some important ones. See the plot for BAC.
Seems like we would have wanted to buy around 24 November 2000, but there is no signal. Let's raise the tolerance number, that is, the second argument of all the "since" functions. Looks like we finally get a signal when the tolerance is 8.
Write a function
There are some problems with this system. We would have been buying a losing trend back in 1999, for instance, and it doesn't perform too well on other stocks, such as BNI and CAH. However, it does offer me an opportunity to illustrate the use of indicators as functions within other indicators. In order to find a workable number for tolerance in the last example, we had to change the second argument at four locations. Wouldn't it be nice if we could combine the separate since functions expressing agreement between signals into one function? Let's try it. Define a new indicator and name it "within." Its syntax will be "within(parm1, parm2, parm3), where parm1 is the first data set, parm2 is the second data set, and parm3 is the tolerance.
Note that the "Enabled" box is not checked, since we don't care about using this indicator as a plottable curve, but rather as a function within another definition. Here's how it is used in RSI:
Instead of passing constant scalar numbers to the "within" indicator, we are passing data sets to the first two arguments. That's what makes it a function. The "within" function you just defined is nevertheless an indicator in some important respects. The first is that it can take only a maximum of three data arguments. The second is that the fourth argument is still a data set designator. This means you could return up to five different calculations on the same three data inputs. I haven't tried this other than to test it, and I don't know how useful it is; but it is there if you need it.
Write a more useful function: first since
The preceding example seems pretty trivial, even if it is reasonably useful. Let's try something more challenging. Write a function that returns true if the current data point is the first occurrence of a specified condition since the last occurrence of another specified condition. Maybe it would be the first buy signal since the last sell signal or the first 10-day high since RSI crossed 30. It doesn't matter just what you pass to it, as long as both arguments evaluate to values that can be reasonably interpreted as true or false. Here is a formula that returns true if the current data point is the first occurrence of a condition since the condition in the second argument last occurred.
This formula first tests for the occurrence of the first argument at the current data point. If it is true, then it computes a rather interesting function called "if." This is a "computed if" similar to that found in a few programming languages such as Fortran and in most formula languages. It tests the value of the first argument and returns the second argument if the first is true or the third argument otherwise. Here the first argument checks to see if the parm1 condition has ever occurred before, using the size of the closing price data array. Since all data arrays used in computing a formula indicator have to be the same size, this is valid. If the parm1 condition has occurred previously, then we check to see if the time since the last occurrence of the first condition is greater than the time since the second condition, excluding the current point. If the first condition hasn't occurred before, then we already know that this is the first occurrence. Of course, the second condition might not have occurred at all, in which case the function returns the first since condition2 or the first ever. If we modify the test condition in the if function to test for prior occurrence of the second condition, then we eliminate the second possibility. I use this function in at a couple of others that are used to trigger buy/sell signals or compute profitability. You might find it useful as well.
Complex Indicators
If you get really ambitious about designing new indicators or even moving some fancy indicators over to Trademan, you may find the single-line 256-character formula entry box somewhat limiting. Well, there's a facetious saying I learned as an engineer that applies here: "If you can't fix it, feature it!" I won't apologize for the limited programming space, but I will offer you a way around its limitations, such as they are. It is this. When you have a chunk of formula that takes up a lot of space, particularly if it is used repeatedly, turn it into an indicator or function, then put the new indicator or function where that chunk of formula used to be. That way, you have an automatic organizing principle that makes writing and testing formulas a lot easier and you have removed any limitations on the complexity of your indicators. Let's look at some examples.Ultimate Oscillator
Williams's Ultimate Oscillator uses the weighted sum of three oscillators, each of which uses a different period. These oscillators are based on the concept of buying and selling pressure. Buying pressure is said to be high when the distance between the close and the low is close to the distance between the high and low. This buying pressure is expressed as the ratio between "true range" defined as the greater of the difference between today's close and today's low and the difference between today's close and yesterday's low. Thus, we define:true range: max(close-low, close-delay(low,1))
Total activity is defined as the greatest of:
total activity: max(high-low, high-delay(high,1), delay(high,1)-low, delay(high,1)-delay(low,1))
- today's high minus today's low
- today's high minus yesterday's low
- yesterday's high minus today's low
- yesterday's high minus yesterday's low
The Ultimate Oscillator is defined as a normalized sum of the ratios of three different moving averages of the true range and the total activity:
(mov(true range, 7)/mov(total activity, 7)*4 +
mov(true range, 14)/mov(total activity, 14)*2 +
mov(true range, 28)/mov(total activity, 28))/7*100
Yes, it runs on past the formula edit box a little way, but imagine what it would be if the total activity and true range were typed in each place they are used in the formula.
The buy and sell signals also reference formula indicators used as functions. Bullish divergence detects a pattern of increasing lows in the oscillator accompanied by price lows that continue to decline. Bearish divergence finds declining highs in the oscillator with a continued pattern of higher highs in the price. Both of these are too complicated to get into at this point, so let's look at another indicator and get back to these signals later.
Relative Momentum Index
This indicator is a generalization of Wilder's Relative Strength Index in that it calculates the ratio of the difference in closing price a parameterized number of days apart instead of just a single day.
Here, the indicators serving as functions are pgains and plosses. The first argument in each is a data array, which in this case is the set of closing prices.
Define pgains as:
if(parm1-delay(parm1, parm2) > 0, parm1-delay(parm1,parm2), 0), and
plosses as:
if(parm1-delay(parm1,parm2) < 0, abs(parm1-delay(parm1,parm2)),0).
In each indicator, parm1 is replaced by the data array in the first argument, while parm2 is replaced by the scalar value in the second argument.
Projection Bands
Chande's projection bands are based on linear regression predictions, and the signals seem almost magical in signalling turning points. The calculation in
formula language poses some interesting problems. Here it is in closed form:Upper Band:
max(delay(high,1) + slope(high, N), delay(high,2) + 2*slope(high,N), delay(high,3) + 3*slope(high,N), ..., delay(high,N-1) + (N-1)*slope(high,N))
Lower Band:
min(delay(low,1) + slope(low, N), delay(low,2) + 2*slope(low,N), delay(low,3) + 3*slope(low,N), ..., delay(low,N-1) + (N-1)*slope(low,N)),
where slope(data,N) is the linear regression slope of the previous N points of data.
Since the number of arguments presented to max and min varies with the parameter N, how do I specify this indicator in a single formula? One answer is to use a macro expression that can be expressed in compact closed form and can be expanded by the program into the full expression required for
the formula that goes with a particular parameter. This macro expression is the "for" macro, introduced here. To properly introduce the syntax for the "for" macro, let's use it to rewrite the preceding expressions and then go over what each part does.Upper Projection Band:
max(for(delay(high, var) + var*slope(high, parm1),; 1; var < parm1; var+1))
Lower Projection Band:
min(for(delay(low, var) + var*slope(low, parm1),; 1; var < parm1; var+1))
Note that the arguments of the "for" macro are separated by semicolons and that the last three arguments specify the loop parameters. These parameters follow programming conventions that should be familiar to C programmers.
The first argument in the "for" macro specifies the expression to be repeatedly expanded with new values in place of "var." Note especially that the expression ends with a comma, which separates each iteration in this case. It is a characteristic of the "for" macro that the last character of the first argument will appear in all but the last member of the expansion. If the last character were "+," for instance, the macro would expand to a sum. With the comma, the macro expands to an argument list instead.
- The first loop parameter specifies the starting value assigned to the loop variable "var" -- in this case, 1.
- The second loop parameter specifies the condition that must be true for the loop to continue. For the loop to proceed, the loop variable "var" must be less than the parameter parm1.
- The third loop parameter specifies a calculation to perform at each iteration. Here the loop variable "var" is incremented by 1.
Now that the upper and lower projection bands are defined, we can define the Projection Bands indicator as:
Upper Projection Band(parm1) for primary and Lower Projection Band(parm1) for secondary.
Try plotting this out for a 200-day parameter. I think you'll find the interaction of the prices and the bands quite uncanny.
The buy and sell signals correspond to points at which the price meets one of the projection bands and then departs from it. To generate these signals, we use yet another indicator derived from the projections bands called the Projection Oscillator. This indicator represents the percentage of the distance between the bands that the close falls above the lower band.
Projection Oscillator:
(close - Lower Projection Band(parm1))/(Upper Projection Band(parm1)- Lower Projection Band(parm1))*100
When the Projection Oscillator crosses above 10, and the linear regression slope for the period is negative, we buy:
pcross(Projection Oscillator(parm1), parm2) && slope(close, parm1) > 0, where parm2 is 10 and parm1 is 200.
When the Projection Oscillator crosses below 90, we sell:
ncross(Projection Oscillator(parm1), parm3), where parm3 is 90 and parm1 is 200.
In order to display these signals with the projection bands, we reuse them in defining signals for the Projection Bands indicator:
Here we use the buy (#3) and sell (#4) signals for the Projection Oscillator to define signals for the Projection Bands indicator. This example tells the indicator to use default values for the second and third parameters, in this case 10 and 90, respectively.