Developing indicators within eSignal is one of the first steps to developing your own custom trading system. eSignal indicators are designed to plot and return values to the chart as well as the cursor window. Indicators can return information to the price chart or a new indicator pane (a new window) but not both at the same time.
There are relatively few tricks to creating custom indicators within eSignal. This guide will walk you through the basics and even teach you some more advanced topics.
Indicator Basics
All eSignal EFS files step through the chart (from oldest bar to newest bar or left to right) and executes your code. The code (or eSignal Formula Script EFS) is a set of instructions that are executed in order (top to bottom).
Within the EFS code, developers control variables, conditions and execute any functions necessary with a series of simple commands. The structure of these commands and of the order of the commands is critical to the accuracy and execution of your proposed systems.
Let's begin by learning some of the EFS components.
The preMain() Function
The preMain() function is required for all EFS files and acts as a setup routine for your code. Within preMain() you should include the necessary code to:
- Set the study title.
- Set the cursor window title.
- Set the label/indicator color.
- (Optional) Set the indicator min/max values.
- (Optional) Set the horizontal bands.
- (Optional) Set the indicator to plot on the price chart.
Setting the Study Title
To set the Study Window title, you can use the following statement:
setStudyTitle("Your Indicator Title");
This statement has to be included in all of your EFS files, and this statement plots the title text onto the chart or indicator pane.
NOTE: If you don’t want any text to be displayed, simply comment out this line by placing ‘//’ at the beginning of the statement.
Set the Cursor Window Title
To set the Cursor Window Title, you can use the following statement:
setCursorLabelName("Indicator Value 1");
In addition to setting the Cursor Window Title, you will also need to define the label for the returned value of your indicator.
- If your indicator returns more than one value, you will need to add multiple setCursorLabelName statements in your preMain().
- For each value your indicator returns, you will need to add a definition to the setCursorLabelName statement, with indexes for each returned value where ‘0’ is the first, ‘1’ for the second, and so on.
An example of how to format the definitions for multiple returned values is as follows:
setCursorLabelName("Value1", 0);
setCursorLabelName("Value2", 1);
The above statements define Cursor Window Labels for two values that are returned by our EFS file.
- When the EFS indicator returns the values, the first value will be displayed in Value1, and the second in Value2.
- If our indicator only returns one value, then the second line (or any other label definition) is ignored.
Set the Label/Indicator Color
To set the Label/Indicator Color, you can use the following statement:
setDefaultBarFgColor(Color.red);
Where <color> is the color you wish to set for the Label/Indicator Color. Additionally:
- This statement is optional and does not need to be included in your code but does allow developers to control the color of the indicator and the color of the label in the cursor window, which effectively color codes the returned values of the indicator.
- While optional, if used the statement should be defined for any returned setCursorLabelName statements, such that if you have two setCursorLabelName statements in your code, you should have two setDefaultBarFgColor statements as well.
- Some examples of available color types are as follows:
- Color.red
- Color.blue
- Color.green
- Color.yellow
- But note that you are not limited to just the four colors above as there are many other default color options available, and you can also use Color.RGB(<color>) where <color> is a three-number RGB value for the color you want (Ex: 0,0,0 for black, and 255,255,255 for white).
- Just like with setCursorLabelName, you need to define multiple color statements by adding the corresponding label order number to the end of the statement, where ‘0’ is the first, ‘1’ is the second, and so on. An example is as follows:
setDefaultBarFgColor(Color.red, 0);
setDefaultBarFgColor(Color.blue, 1);
Set the Indicator Min/Max Values (Optional)
To set the minimum and maximum values for for your indicator, use the following statements:
setStudyMin(-10);
setStudyMax(10);
The setStudyMin() and setStudyMax() statements allow you to define the minimum and maximum levels of your custom indicator. If however, you do not know the minimum or maximum values of your indicator, then you can simply omit these statements from your preMain(), and eSignal will automatically scale your indicator within the maximum and minimum range of your indicator.
Set the Horizontal Bands (Optional)
If you need to add Horizontal Bands on the Indicator Pane, you can utilize the following statement:
addBand(<value>, <type>, <width>, Color);
Where:
- <value> is where the horizontal band should be place (if the range is from 0 to 100, you could set the bands to 30 and 70 as an example).
- <type> defines what style line you want to use (in this case a solid line).
- <width> defines the width (thickness) of the line.
- <color> is a three-number value in the RGB format (where 0,0,0 is black and 255,255,255 is white).
For our Example Indicator we are going to set the Bands to be as follows:
addBand(20, PS_SOLID, 1, Color.RGB(0,0,0));
addBand(80, PS_SOLID, 1, Color.RGB(0,0,0));
Note that Color.RGB(<color>) is just a means by which Developers can replace the default color definitions with user-defined color codes, and that in the above scenario, developers are required to use individual color values that represent the colors for Red, Green, and Blue as opposed to
Set the Indicator to Plot On the Price Chart (Optional)
If you want your indicator to plot directly on the Price Chart rather than in the Indicator window, you can use the following statement:
SetPriceStudy(true);
The use of setPriceStudy() determines if your indicator will plot ON the Price Chart or within the Indicator Pane.
- If your indicator is designed to plot on the Price Chart rather than the Indicator Pane, developers must include setPriceStudy(true) in the preMain() function.
- If your indicator is designed to plot in an Indicator Pane, then developers must omit the setPriceStudy() statement from the preMain() function.
The main() Function
The main() function is required for all EFS files.
- This function is the main routine for your code.
- Developers will write code within the main() function to calculate and execute the indicator, strategy, graphics or other actions.
- Within main() you should include the necessary code to accomplish whatever tasks are necessary to calculate and plot your custom indicator.
Notice: Additional sub-functions are available to the developers to better define code segments and to develop modular applications.
Returning the Indicator Values
Your custom indicators MUST return at least one value otherwise the indicator will not plot on the chart or indicator pane. As such, developers may use the return function to define which values are returned from the main() function to the chart:
Return <value>;
Or if a developer wishes to return multiple values from within their code, they must include a single return statement that returns an array of values at the end of the main() function:
return new Array (<value1>, <value2>,...);
Notice: Any value returned by the Return statement will be plotted on the chart or on the indicator pane, which may require developers to separate individual functions of the indicators into multiple EFS files to properly display the information on the chart or indicator pane.
Developing Your First Custom Indicator
The first step in developing a custom indicator is to attempt to define what type of indicator you are going to try to develop.
In this guide we will use a Velocity Indicator as an example, and it will attempt to monitor the markets ability to accelerate in bullish and bearish modes. It will also only return positive values (numbers greater than or equal to zero), and track the difference between two (user-defined) moving averages (this may not be considered a velocity indicator by some, but it’s a good example to begin with).
Developing the preMain() Function
The first process of developing a new indicator is to answer some basic questions:
- Will our indicator plot on the Price Chart or on an Indicator Pane? (For our example, we will plot on the Indicator Pane)
- Do we know the range of values that will be returned by our Indicator? (For our example, we do not have an idea as of yet)
Now that we know what needs to be in the preMain() function, we then need to add the Study Title and the Cursor Window Label name. An example of our preMain() function for our indicator is as follows:
function preMain() {
setCursorLabelName("Velocity");
}
We don’t need to add anything else at the moment, because we don’t know how our new indicator will react. All we need to do is add labels to our code to assist us in identifying our code.
Developing the main() Function
When developing the main() function, the first step is to determine what user-defined variables we need for our function. For our example, the variables we are going to define for our indicator are:
- vLength -- The length of our initial short-term Moving Average.
- vFactor -- The multiplier of vLength to be used to calculate our long-term Moving Average.
Defining user-input can be accomplished using the getValue() function, but we also need to check the input value for an erroneous value or missing value immediately after the main() function starts. As such, our first main() code might look like this:
function main(vLength, vFactor) {
if (vLength == null) {
vLength = 7;
}
if (vFactor == null) {
vFactor = 3;
}
return;
}
For every user-input, we need to check for a "null" input, or missing initial value, then define the initial values as needed. You can see from the example above, creating using input can be accomplished by adding the getValue() statements between parentheses of the main() function, using semicolons to separate each line of getValue() functions. Keep in mind that for every user-input, you also need to add code to check and verify the value of the input.
Finishing the main() Function
At this point we have begun to create the custom indicator, but as we have not developed the code to calculate, test, and return the indicator values, it serves no real function at this point. Let’s get started on that part next.
Performing the Calculation
The process of our proposed velocity indicator is to calculate a short-term moving average based on a user input, and then calculate a second moving average based on a factor of the first moving average (this also will be a user input). Then, we will calculate the difference between the short-term moving average and the long-term moving average to give us a relevant indicator for trend direction and strength.
function main(vLength, vFactor){ if (vLength == null){ vLength = 7; } if (vFactor == null){ vFactor = 3; } // create the MA studies for our indicator var study1 = new MAStudy(vLength, 0, "Close", MAStudy.WEIGHTED); var study2 = new MAStudy(vLength * vFactor, 0, "Close", MAStudy.WEIGHTED); //Get the values of the Moving Average studies var vMA1 = study1.getValue(MAStudy.MA); var vMA2 = study2.getValue(MAStudy.MA); //Test our variables for null values/returns. //If vMA1 or vMA2 = null, then RETURN (nothing) // || is a Boolean OR statement if ((vMA1 == null) || (vMA2 == null)) return; // Return the short-term average the long-term average. return (vMA1 - vMA2); }
That’s it! You’ve just created your first indicator, and hopefully it wasn’t as difficult as you thought!
Advanced Topics
Now that we have covered the basics, let’s get into some topics that are a bit more advanced, such as:
- Returning arrays of variables to the cursor window.
- Using price variables.
- Returning text variables to the cursor window.
- Returning price action to the cursor window.
Each of these points are easily addressed by changing just a few lines of the code we have written so far. Let’s begin with how to return an array of variables.
Returning an Array of Variables
First off, what is an Array? Simply put, an Array is a list of items, and in this case, it is a list of variables you want to return to the cursor window. To return an array of variables, the only thing a developer needs to do is change the return line and address the new variables in the preMain() function. An example of this is below:
function preMain(){ setStudyTitle("Velocity"); // Define the returned value labels in the cursor window setCursorLabelName("Velocity", 0); setCursorLabelName("MA1", 1); setCursorLabelName("MA2", 2); // Set colors for the returned values in the cursor window setDefaultBarFgColor(Color.blue, 0); setDefaultBarFgColor(Color.red, 1); setDefaultBarFgColor(Color.red, 2); } function main(vLength, vFactor){ if (vLength == null){ vLength = 7; } if (vFactor == null){ vFactor = 3; } // create the MA studies for our indicator var study1 = new MAStudy(vLength, 0, "Close", MAStudy.WEIGHTED); var study2 = new MAStudy(vLength * vFactor, 0, "Close", MAStudy.WEIGHTED); // Get the values of the Moving Average studies var vMA1 = study1.getValue(MAStudy.MA); var vMA2 = study2.getValue(MAStudy.MA); // Test our variables for null values/returns. // If vMA1 or vMA2 = null, then RETURN (nothing) // || is a Boolean OR statement if ((vMA1 == null) || (vMA2 == null)) return; // Return the ARRAY of variables to the cursor window. return new Array(vMA1 - vMA2, vMA1, vMA2); }
Global Variables vs. Local Variables
As we begin to develop more and more complex indicators and trading systems, it will become important to know the difference between a Local Variable, a Global Variable, and where and why you want to use them. As developers, we want to develop our indicators based on logical progressions or mathematic calculations.
Either way, we may need to include some variables that stay active throughout the entire instance of our code (Global Variables) and still other variables that are only temporary (Local Variables). This is the primary difference between Global Variables and Local variables is as follows:
- Global Variables
- Get declared outside the main() and preMain() functions.
- Are available within any portion of your code.
- Can be manipulated by other functions within your code.
- Can carry previous values to the next instance of your code.
- Local Variables
- Are declared within the main() function, or other functions.
- Are only available within the function they are declared.
- Are only available for use AFTER being declared.
The difference in variable life is what we refer to as “Scope” which is understood as the lifetime of your declared variable, where Local Variables have a limited Scope (they are only available within the function they are declared in), and Global Variables have an unlimited Scope (they can be used or modified anywhere in your code).
If you are trying to declare a new Local Variable for use within your code, you would want to declare it within your function before you use it. If, however, you are trying to declare a new Global Variable (such as one that will be used to control global logic within your code, or one that needs to continue through the entire scope of your code), then you would want to declare it above your main() function.
An example of how these variables work and how they would be used can be seen as follows:
function preMain() {
...
}
var NewGlobalVariable;
function main() {
var NewLocalVariable;
return;
}
Creating Additional Functions to Handle Specific Tasks
As you develop more and more complex indicators and trading systems, it will become necessary to develop unique functions within your code to accomplish specific tasks. The reasons why you would want to create a new function to complete a certain task might include:
- To simplify your code by creating a single function that can be called throughout your program instead of typing the same lines of code over and over.
- To segment your code into easy-to-understand code blocks and make it easier to debug your code.
As you develop your code, there are some situations you should keep a look out for where you would want to use a function, rather than trying to write a whole complex piece of code. They are as follows:
- If you find yourself using the same code segment over and over throughout different sections of your code, you can instead convert the repeated code segment into a simple function.
- Similar tasks that are often performed together can be grouped into a single function, which then allows you to pass your function a parameter or parameters to accomplish multiple tasks at a time.
- If you are using graphic calls, most of them can be grouped into a single function.
- If you are using trading system logic, most can be grouped into a single function.
Rules for Creating Additional Functions
When creating additional functions, there are a few rules you need to abide by to avoid issues:
- New functions should always be created before the preMain() and main() functions at the top of your EFS file.
- New functiosn should be given a unique name.
- Function parameters should be handled just like user-inputs, where you want to check for null entries.
Here is an example of an additional function declared within an EFS file:
function rnd(value) {
//<Rounds a price value to two decimal places
value *= 100;
return Math.round(value,2) / 100;
}
function preMain() {
...
}
function main() {
return rnd(close());
}
Using Price Values in Your EFS Code
While creating your EFS file, you might wonder how to address price variables in your code (such as for use with price patterns, indicator patterns, or buy/sell signal patterns). To accomplish this, all price variables are easily accessible in any EFS simply by invoking the following:
- open()
- high()
- low()
- close()
These variables automatically return the current bar’s price action, however, if you want to use older bar information, you simply need to add a numeric parameter to designate how many bars in the past to offset the price variable, with “-1” being one bar in the past, “-2” being two bars in the past, and so on. For example:
- open(-1) -- The open of the previous bar.
- high(-1) -- The high of the previous bar.
- low(-1) -- The low of the previous bar.
- close(-1) -- The close of the previous bar.
Using this information to create a price pattern is simple, and we could also create a compound statement with it using Boolean operators (such as && (AND) and || (OR)). An example of complex statements using Boolean operators is as follows:
if (high() == high(-1)) {
// Japanese Candlestick Tweezers Tops
//This pattern occurs when two bars have the same HIGH price.
Do something
}
if ((high(-2) <high(-1)) &&="&&" (high(-1)="(high(-1)"><high()){> </high()){></high(-1))>
// Pivot High Pattern
//This pattern occurs when a single bar forms with a higher high
//than the previous and subsequent bar.
Do something
}
if ((low(-2) low (-1)) && (low (-1) low ()){
// Pivot low Pattern
//This pattern occurs when a single bar forms with a lower low
//than the previous and subsequent bar.
Do something
}
Note: Keep in mind that when you are creating complex statements using Booleans, you need to remember to use parentheses to separate or join your complex conditions together.
Using debugPrintIn() to Debug Your EFS Code
Now that we've shown you how to begin creating your own indicator (which will help lead you to create your own trading system), it might be wise to show you how to debug errors in your code.
ESignal provides a couple of tools for you to debug your code and coding logic and it really boils down to three types of errors that you may experience when trying to develop EFS code :
- Syntax Errors - Syntax Errors are errors in the coding language very similar to grammatical errors in a letter or spelling errors. These normally occur when a user mis-spells a function or uses an incorrect function parameter. Other instances where a syntax error may occur is with Case Sensitivity. EFS codeis VERY case sensitive.
- Logic Errors - These types of errors are normally user created where the code is instructing something to change/happen when it should not. For example, let's say we create code to do something if the price is above a moving average variable (close() vMA). If we make a mistake and reverse the greater than sign or make it greater than or equal to, then our logic is messed up and our code will not operate effectively. The syntax checker within eSignal will not identify these errors, so the user must attempt to identify these problems.
- Assignment Errors - These types of errors normally happen in the use of and conditional statements including variables. One problem that is common is the mixing of = and == The single equal sign (=) is used to assign a variable a value. The double equal sign (==) is used to test the condition of a variable value. Often, developers will mix-up the use of these operators, resulting is ineffective code. Also, these errors are not identified as errors of syntax, so it is up to the user to identify/resolve these errors.
ESignal does provide a syntax checker that will check your code for grammatical errors and some spelling errors; this can be accessed by simply clicking on the checkmark button within the editor and your code will be verified for syntax errors. Any potential problems will be presented in the Output Window which can be viewed by clicking on the “Tools” menu, and selecting “Output Window”.
ESignal also provides users with the ability to write information into the output window for debug and system reporting abilities. This function is called debugPrintln(). Users can place any information necessary into the debugPrintln() statement , such as variables, text, or date and time. Developers should place the debugPrintln() statement into portions of the code they believe are not operating properly to assist in debugging the code. An example of how this looks is as follows:
debugPrintln(Sample text : +close()+ : +variable);
debugPrintln(variable1+ : +variable2+ : +variable3);
Indicator Building Tips
Finally, a few reminders to keep in mind from this article to help you on your journey with creating EFS files:
- Remember to use the setCursorLabelName() in the preMain() to identify your returned variables.
- Remember to use the setDefaultBarFgColor() to color each returned value. This helps developer to identify conditions as they change within your indicator.
- Remember to use the setPriceStudy(true) if your indicator will return/plot values on the price chart and not on a separate indicator pane.
- Remember to test all user-inputs for null parameters before attempting to execute your EFS code.
- Remember to create a user input for setComputeOnClose() within your indicator. This allows you to change the indicator quickly and easily to or from real-time mode.
- Remember to create all indicator logic variables as Global Variables. These variables need to be global as they will be manipulated by your code and control the logic of your indicator as it progresses.
- Remember to create all indicator temporary variables as Local Variables. These are variables that will be created, used by your code, then dumped.
- Remember to create the proper return statement for your indicator.
Some Coding Examples
Example EFS Code
/********************************************************************** Copyright Matheny Enterprises 2002. All rights reserved. **********************************************************************/ function preMain() { setStudyTitle("Ment Velocity"); setCursorLabelName("Ment Velocity"); } function main(vLength, vFactor, vSTDLength) { if (vLength == null) { vLength = 7; } if (vFactor == null) { vFactor = 3; } if (vSTDLength == null) { vSTDLength = 11; } // create the MA studies. var study1 = new MAStudy(vLength, 0, "Close", MAStudy.WEIGHTED); var study2 = new MAStudy(vLength * vFactor, 0, "Close", MAStudy.WEIGHTED); //Get the values of the MA studies var vMA1 = study1.getValue(MAStudy.MA); var vMA2 = study2.getValue(MAStudy.MA); //Test for null returns. if (vMA1 == null || vMA2 == null) return; return vMA1 - vMA2; }
Example Code by Matheny Enterprises (Velocity Indicator)
Testing for the Current Bar
If (getCurrentBarIndex() == 0) {
// Do something.
}
Testing for a New Bar Formation (Using the Time of the Bar)
//First, declare a global variable to keep track of the last time of the previous bar(s).
var nLastRawTime;
If (getvalue(rawtime,0) != nLastRawTime) {
//New bar so do something.
nLastRawTime = getvalue(rawtime,0);
}
Installing Your EFS Files
Installing your EFS files is fairly easy but varies slightly depending on which version of eSignal you are using. Primarily you just need to make sure that the EFS files are getting put in the “My Formulas” folder. The steps for eSignal 12.x and 21.x are as follows:
- For Version 12.x
- Copy the EFS file you wish to add to your eSignal program.
- Open File Explorer by right-clicking the Windows icon and selecting “File Explorer” in the menu that pops-up.
- Navigate to your Documents folder.
- Double-click on the “Interactive Data” folder to open it.
- Within the “Interactive Data” folder, double-click on the folder labeled “Formulas” to open it.
- Within the “Formulas” folder, double-click on the folder labeled “My Formulas” to open it.
- Paste the file into the folder by right-clicking in an empty space within the folder, and in the context menu that pops up, click the “Paste” option.
- If eSignal is currently running, close and restart the program.
- For Version 21.x and Higher
- Copy the EFS file you wish to add to your eSignal program.
- Open File Explorer by right-clicking the Windows icon and selecting “File Explorer” in the menu that pops-up.
- Navigate to your Documents folder.
- Double-click on the “ICE eSignal” folder to open it.
- Within the “Interactive Data” folder, double-click on the folder labeled “Formulas” to open it.
- Within the “Formulas” folder, double-click on the folder labeled “My Formulas” to open it.
- Paste the file into the folder by right-clicking in an empty space within the folder, and in the context menu that pops up, click the “Paste” option.
- If eSignal is currently running, close and restart the program.