|
Navigation: Introduction > Developing Study Formulas > Anatomy of a Study Formula Anatomy of a Study Formula |
Top Previous Next |
In this section we will take a look at one of the built-in studies in detail. All study formulas have the same basic structure and layout requirements. The study we will examine is dsSMAStudy.pas which calculates a simple moving average.
Header

All study formulas will have a header, which consists of comments. Typically you would provide the file name of the study, a brief description, and any other pertinent information. More importantly, you will also specify the number of input parameters and input streams in a special format that is read by the ScanExpert program:
{params: N}
{streams: N}
The format is curly brace, the string "params:" or the string "streams:" followed by an integer value indicating the number of parameters or streams, followed by a closing curly brace. The smallest value of N for "params" is 1, and the smallest value of N for "streams" is also 1.
Note: Even if your particular study formula takes no input parameters, you would always enter a value of 1 into the params identifer. A study formula must always have at least one input stream so there will never be a case where your study formula takes input parameters but no streams.
In this particular case our SMA study takes 1 input parameter and 1 input stream so our code looks like this:
{params: 1}
{streams: 1}
Variable Declaration

Directly below the header section you will declare all variables used by your study formula. Variables are declared using the following format:
<VarName>:<VarType>;
Where <VarName> is a unique variable name that you assign, followed by a colon. <VarType> is the variable type (see below) followed by a semi-colon. Note that in Pascal, variable names are NOT case sensitive so a variable called "MyVar" is the same as a variable called "myvar". The variable name and the variable type MUST be separated by a colon, and the variable declaration MUST end with a semi-colon.
At a minimum you will always have at least two variables to declare:
| • | A TEStream variable to hold the input stream object (obviously if you have more than one input stream then you will need a TEStream variable for each input stream). |
| • | A boolean variable to track the state of the study initialization section. |
In the code snippet above these required variables would be source and bInit.
You can declare as many variables as you need and all declared variables will persist between script iterations....with the exception of dynamic array-type variables, which do NOT maintain their state between script iterations. If you need persistent array-type storage in your study formula then use either the TEStorage object or the TEDoubleArray object. All variables declared in this section are global to the study formula.
Pascal is a typed language so all variables that you create MUST be assigned a data type. The variable types that you will most commonly use are:
| • | TEStream |
| • | Integer |
| • | Double |
| • | String |
| • | Boolean |
| • | TEStorage |
| • | TEDoubleArray |
Initialization

After the variables are declared, the actual body of the study formula script begins. At the top of the script body we have an initialization section. The primary purpose of this initialization section is to obtain our input parameters, if any, and obtain our input streams (we will always have at least one input stream). We then check to make sure that any input parameters we have received are within a valid range (what the valid range is will depend on the specific input parameter) and, most importantly, we check any and all of our input streams to be sure that they actually exist. After this has been done we set our boolean initialization flag variable to true so that the initialization section of the code will not be called again.
Let's walk through the initialization section shown in the code snippet above:
| • | The first thing we to is to check to see if we have already completed the initialization. To do this we check the state our our boolean bInit variable. If this variable is set to true then it means we have already completed the initialization section. If this variable is set to false then it means we have not yet completed the initialization section. |
| • | If bInit is false the next step is to retrieve our input parameters. In this particular case the SMA study formula takes only one input parameter, which is the period or length of the moving average that we will create. We use the ThisStudy global study object variable to retrieve this parameter information. |
period := ThisStudy.getInputParameter(0);
| • | If we were expecting a second or a third input parameter, we would simply increment the offset in our getInputParameter() call. For example: |
secondParameter := ThisStudy.getInputParameter(1);
thirdParameter := ThisStudy.getInputParameter(2);
| • | The next step is to check the value of this parameter (which has now been assigned to our Integer variable called period) and make sure that it is in a valid range. For our SMA the input period must be at least 1. So we check the value of our variable period and make sure that the value it contains is at least 1. If it is less than 1 then we just exit. Now if we wanted to we could certainly add code to the initialization section to change the period value to 1 if the user entered a value that was less than 1...that is entirely up to you. |
if (period<1) then exit;
| • | Next, we obtain the input stream. Remember that in our variable declaration section we declared a variable called source of type TEStream. This variable will hold the input stream object that will be passed to us by the ThisStudy study object variable. The process of obtaining the input stream is almost identical to obtaining the input parameters, except that we call the getInputStream() method instead of getInputParameter(). |
source := ThisStudy.getInputStream(0);
| • | Again, if we had multiple input streams, we would just increment the offset in our getInputStream() call: |
source2 := ThisStudy.getInputStream(1);
source3 := ThisStudy.getInputStream(2);
| • | The next step is one of the most important. We need to check each input stream to make sure that it actually exists. If an input stream does NOT exist then it will have a value of nil. |
if (source = nil) then exit;
| • | Now in this case we only have one input stream. If we had, for example, 5 input streams then we would need to check each one for nil. If any of the input streams are nil then you MUST exit. Attempting to perform calculations when one or more of the input streams does not exist will lead to serious errors. |
| • | The last step in our initialization section is to set our boolean bInit variable to true. We will only make it this far in the code if our parameters check out and our input streams check out, so once we are here our initialization is complete. We want to make sure that we do not go through the initialization process again. |
bInit := true;
Body

The remaining code is where the actual work of performing calculations gets done and, obviously, this will vary from formula to formula. At a minimum you will always calculate a value and then store this value in the stream object associated with the study (i.e., so the calculation can then be used as input to other studies or simply displayed in the ScanExpert Viewer Grid.
Let's walk through the logic in the SMA study formula.
| • | The first thing we do is to check the number of bars available in our input stream: |
bars := source.getNumBars;
| • | To do this we call the getNumBars() method of our source variable, which happens to be a TEStream object. Each stream object has a number of methods associated with it that we can use to obtain information about the stream. See the Stream Functions section for information on these methods. |
| • | Once we have a bar count, we check to see if the number of bars available is greater than the specified period. If so we will begin our calculations. If not then we do nothing. |
| • | If we determine that we have enough bars to calculate our SMA, then it is time to perform the simple moving average calculation. In this case we will make use of one of the stream utility functions to do the work for us. |
v := strmAvg(source, 0, period);
| • | The strmAvg function calculates a simple moving average. We pass it a valid TEStream object (the variable source in our case), the offset we wish to use, if any (0 in our case) and the number of bars to use when calculating the simple moving average (the period Integer variable in our case). |
| • | The strmAvg() function returns a floating point value which we store in our variable v, which we declared as a Double. |
| • | All that is left to do is to return this moving average calculation to our study object so that it can be inserted into our study stream. Now exactly how we send this value back depends upon whether we are starting a new bar or updating an existing bar. We can determine this by calling the getBarState() method of our source TEStream variable. |
if (source.getBarState = BARSTATE_NEWBAR) then
ThisStudy.addValue(v)
else
ThisStudy.setValue(v);
| • | If our input stream source is starting a new bar, then our study formula must start a new bar as well and, in this case, we will add a new record into our study stream: |
ThisStudy.addValue(v)
| • | If our input stream source is updating an existing bar, then our study formula must also update the existing bar of the study stream: |
ThisStudy.setValue(v)
Here is the complete study formula:

As mentioned several times throughout this guide, the best way to create a new study formula is to use an existing study formula as a template. This will save you a lot of time and effort and will reduce errors. Use the debugPrint() procedure in the General Utility Functions section as a debugging aid. With it you can print out variable or stream values from within any study formula to the ScanExpert Debug Window at runtime. The Debug Window can be accessed via the Help menu option in the ScanExpert Viewer module. Whether you write your study formula from scratch or use an existing study formula as a template, remember to use the Syntax Check feature in the Editor frequently.
In the next section we will examine how to register a study formula with the ScanExpert system so that it can be used in the Wizard and/or called from within scripts that you write in the Editor.
Add Parameter and Usage Information