desktop:desktop_chart_scripting_overview

Scripting Overview

Code Structure

Scripting code consists of one or more statements that are executed by the script interpreter.

Individual statements are separated by semicolons. White spaces (spaces, tabs, carriage returns, etc.) have no effect on how scripts are interpreted. Scripts are case-insensitive (e.g. sma is the same as SMA).

Statements can do the following:

Perform a calculation. Assign a value or calculation to a variable name. Accept user input into a variable. Plot values on a chart in various different formats. Configure the chart.

Lets take a look at a script that computes the Disparity Index indicator:


EX1 VSCALE_DECIMALS(2);
 periods = INPUT("MA Periods", 14, 1, 100); 
 indcolor = INPUT("Color", Color.Blue); 
 ma = SMA(CLOSE, periods); 
 di = 100 * ((CLOSE - ma) / ma); 
 PLOT_HISTOGRAM(di, 0.75, indcolor); 
 SUMMARY("Disparity Index({?}) {?:F2}", periods, di);  

The first line, VSCALE_DECIMALS(2);, is a chart configuration command that states that we want numbers on the indicator value scale to display out to two decimal places.

Next, we accept some user input for the number of moving average periods and the color to draw the indicator. These values are stored in variable names (periods and indcolor) for later use in the script.

The indicator values are calculated next. This is done in two steps to make it easier to read and also to improve efficiency of the script. A simple moving average of the closing price is first computed and stored in variable ma. As you can see, we feed the user configurable number periods into the SMA function. This allows for the periods to be adjusted through the chart properties window without having to open and modify the script every time. Finally, we compute the disparity index as a percentage and store it in variable di. This is simply a computation of the percent difference of the current closing price to the moving average and the syntax was designed to be as intuitive as possible.

The last thing done by the script is to plot the values and display a summary.

This script produces the following output:

Image

Also, because we used the input statement, the number of periods and the drawing color can be easily changed using the chart properties window:

Image

Assign Values to Variables

Variables, in the scripting system, are simply names that hold values. Variable use is crucial to success in working with scripts. Not only do variables make scripts easier to read, they play a huge role in allowing scripts to run efficiently by not having to perform the same computation multiple times.

Take a look at the Disparity Index indicator one more again:


EX2 VSCALE_DECIMALS(2);
 periods = INPUT("MA Periods", 14, 1, 100); 
 indcolor = INPUT("Color", Color.Blue); 
 ma = SMA(CLOSE, periods); 
 di = 100 * ((CLOSE - ma) / ma); 
 PLOT_HISTOGRAM(di, 0.75, indcolor); 
 SUMMARY("Disparity Index({?}) {?:F2}", periods, di);  

If we didn't use the ma and di variables to store intermediate parts of our computation, here is what the script might look like:


| EX3 VSCALE_DECIMALS(2);
periods = INPUT("MA Periods", 14, 1, 100);
indcolor = INPUT("Color", Color.Blue);
 
PLOT_HISTOGRAM(100 * ((CLOSE - SMA(CLOSE, periods)) / SMA(CLOSE, periods)), 0.75, indcolor);
SUMMARY("Disparity Index({?}) {?:F2}", periods, 100 * ((CLOSE - SMA(CLOSE, periods)) / SMA(CLOSE, periods)));

(Note: we still have to variables for INPUT statements because there is no other way).

This new script is perfectly valid and will run just fine, producing the same exact result as before. However, this script will run much slower because it is going to compute the same simple moving average 4 times, and the disparity index twice (once for the plot and once for the summary).

It goes without saying that you are going to want your scripts to evaluate as quickly as possible, especially in extremely busy markets where it could get executed many times per second.

Variables have an additional, slightly more obscure benefit as well. When you create a variable and assign it the result of a computation, you can think of the variable as representing a column in a spreadsheet…

Thinking of Scripts Like a Spreadsheet

A feature of the T4 chart scripting language is that if you can model your algorithm in a spreadsheet like Excel, then you should have no problems re-creating the same using the chart scripting language.

Image

Think of rows in the spreadsheet as data points and columns as values of or at those data points (e.g. open, high, low, close, volume, etc.) When you create a variable and assign it a value, you are essentially creating a new column in the spreadsheet. You can then use column names as values in your equations, or plot the values of a column on the chart by passing the column name to a plotting function.

Spreadsheet rows represent data points. The base data points are the bars that get plotted on the chart. Each bar occurs at a specific time that depends on the charting interval being used (e.g. 15 minute, 1 day, etc.) Each data point also implicitly gets assigned an index value starting from 0 (for the first or latest bar).

When a script is executed, the interpreter starts at the first data point, or what would be the first row in the spreadsheet. The mathematical expressions are computed and the results get assigned to their respective values. Then the next data point (row) is evaluated and variables assigned values, and so on until every data point is evaluated and the spreadsheet is completely filled in.

Lets look at a simple example. The following script computes what is known as the Typical Price. This is simply the average of the open, high, and close prices of the bar.


title
 x = (OPEN + HIGH + CLOSE) / 3 

Our initial data looks as follows: (Note how we have created a column for the x variable in our script.)


Initial Data

INDEX DATE OPEN HIGH LOW CLOSE VOLUME X
0 06/01/2011 75100 76050 74100 75875 106528
1 06/02/2011 75750 77000 75450 76650 81891
2 06/03/2011 76550 76900 75100 75200 80819
3 06/06/2011 75400 75800 73150 73175 82726
4 06/07/2011 73200 73775 72925 73675 72927
5 06/08/2011 73875 76650 73450 76275 113339
6 06/09/2011 76400 79300 75950 78425 120824
7 06/10/2011 78500 79975 77725 78575 86989

The script has not executed yet, so we have the values of our data points which were loaded from the historical chart data servers, but the x column has not yet been computed.

The script interpreter will evaluate the data points one at a time starting with the first one at index 0.


Interpreter Evaluates the First Data Point

INDEX DATE OPEN HIGH LOW CLOSE VOLUME X
0 06/01/2011 75100 76050 74100 75875 106528 75675
1 06/02/2011 75750 77000 75450 76650 81891
2 06/03/2011 76550 76900 75100 75200 80819
3 06/06/2011 75400 75800 73150 73175 82726
4 06/07/2011 73200 73775 72925 73675 72927
5 06/08/2011 73875 76650 73450 76275 113339
6 06/09/2011 76400 79300 75950 78425 120824
7 06/10/2011 78500 79975 77725 78575 86989

Then the next data point will be evaluated:


Interpreter Evaluates the SecondData Point

INDEX DATE OPEN HIGH LOW CLOSE VOLUME X
0 06/01/2011 75100 76050 74100 75875 106528 75675
1 06/02/2011 75750 77000 75450 76650 81891 76466.66666666667
2 06/03/2011 76550 76900 75100 75200 80819
3 06/06/2011 75400 75800 73150 73175 82726
4 06/07/2011 73200 73775 72925 73675 72927
5 06/08/2011 73875 76650 73450 76275 113339
6 06/09/2011 76400 79300 75950 78425 120824
7 06/10/2011 78500 79975 77725 78575 86989

Evaluation will continue until the last data point is computed.

You could get the same result by placing the data point values in a spreadsheet and assigning a cell in the first row the function =(B2+C2+E2)/3 and then copy/pasting the cell formula down to every cell in the column.

Image

Look Back Functions

Continuing with the spreadsheet analogy, we will now take a look at special functions that look back at past data point values.

Consider how a simple moving average is computed. A simple moving average is an average of the current value and some number of previous values. This is how it would look in a spreadsheet:

Image

The chart scripting language includes a number a built-in functions that “look back” at previous values. The SUM function is one example. SUM takes two parameters: the value to compute the average of (this would be a column in the spreadsheet), and the number of periods, or rows, to add up.

We can use the SUM function to easily compute the simple moving average of, say, a closing price:

| title
 x = SUM(CLOSE, 3) / 3 

In the script above, the interpreter will add the current close price and the previous two close prices. The formula then divides by 3 to get the average.

There are a number of additional built-in functions that look back at previous values, including a dedicated SMA function. These will be detailed in the function reference.

Offset Values

Similar to look back functions, your computation may call the the previous value of a data point. You can obtain this using field offsets. A field offset looks like this:

| title
 CLOSE[-1] 

The square brackets tell the interpreter look retrieve the previous value (the -1) of the close price. You may use this, for instance, to compute the rate of change of something:

| title
 rocclose = (CLOSE - CLOSE[-12]) / CLOSE[-12]; 

Another way to accomplish the same thing is to use the OFFSET function. The OFFSET function take a value and a number of periods to look back. The difference between the OFFSET function and bracket notation is subtle. The bracket notation requires you to enter a fixed value, whereas the OFFSET function can take any value (constant, variable, etc).

Here is an example:

| title
rocperiods = INPUT("ROC Periods", 12, 1, 1000);
rocclose = (CLOSE - OFFSET(CLOSE, -1 * rocperiods) / OFFSET(CLOSE, -1 * rocperiods); 

In this example we wanted to make the ROC periods configurable so that we can tweak the study without having to edit the script. The OFFSET function can accept an expression that computes a value. The bracket notation would raise an error in this case.

Note that both the bracket notation and the OFFSET function take a negative value to look back. It is possible to use positive values to look forward as well if needed. An offset period of 0 would simply return the current value.

Plotting Functions

There are a number of functions that will plot your computations on the chart. Some plots can be placed over the price bars (overlays) and some plots display in a separate panel above or below the price bar chart (indicators). Here is a summary of the available plot functions:


Plot Functions

Function Overlay Indicator Example
PLOT_LINE
PLOT_STEP
PLOT_BANDS
PLOT_WAVECREST
PLOT_POINTS
PLOT_HISTOGRAM

All plot functions can be customized extensively. See the Scripting Function Reference page for more information.

Plot Limit Lines, Center Lines and Range Markers

In addition to the standard plotting functions, there are a number of non-data based plotting functions for drawing reference lines on the chart. These functions are illustrated below.

Plot Functions


Function Description Example
PLOT_CENTERLINE Draws a center line at the specified value.
PLOT_CENTERLINE(0);
PLOT_LIMITLINE Draws a limit line at the specified value.
PLOT_LIMITLINE(0);
PLOT_RANGEMARKERS Draws range markers at the specified values.
PLOT_RANGEMARKERS(60, -60);

There are many configurable options for plotting reference lines. Please the Scripting Function Reference page for more details.

Display a Customized Summary

Custom summaries display at the upper left-hand side of the chart. If based on computed data and you have crosshairs on, the data values in the summary will update as the mouse is moved over the chart. Here is an example:

Image


| Chande Momentum Oscillator
// Chande Momentum Oscillator
// --------------------------
// A momentum oscillator developed by Tushar Chande.
 
// CHART CONFIGURATION
VSCALE_RANGE(-100, 100);
 
// USER INPUT
periods = INPUT("Periods", 14, 1, 100);
ovrb = INPUT("Over-Bought Level", 50, 0, 100);
ovrs = INPUT("Over-Sold Level", -50, -100, 0);
linecolor = INPUT("Line Color", Color.Red);
linethickness = INPUT("Line Thickness", 1, 1, 10);
fillcolor = INPUT("Fill Color", Color.Pink);
 
// CALCULATION
cmo1 = SUM(IF(CLOSE > CLOSE[-1], CLOSE - CLOSE[-1], 0), periods);
cmo2 = SUM(IF(CLOSE[-1] > CLOSE, CLOSE[-1] - CLOSE, 0), periods);
cmo = 100 * ((cmo1 - cmo2) / (cmo1 + cmo2));
 
// PLOTTING AND SUMMARY
PLOT_WAVECREST(cmo, 0, ovrb, ovrs, linethickness, linecolor, fillcolor);
 
SUMMARY("CMO({?})  {?:F0}", periods, cmo);

The summary was specified using the SUMMARY command (the last line of the script). The first value you pass to the summary command is a text value in quotation marks “CMO({?}) {?:F0}” Within the text you specify a value placeholder using the notation {?} (question mark enclosed in curly braces). Additional comma-separated values after the text will get substituted in for the {?} at run time.

The script interpreter is smart enough to figure out which values are based on calculated values so that the summary can be updated to track the cross hairs if turned on.

The notation {?:F0} notation tells the interpreter how many decimals you would like to display if the value substituted in is a number. F0 means display no decimals. F3 would display 3 decimals, etc. The F is a modifier that means decimals. This should be sufficient for most of your needs, however advanced users can check out the Microsoft standard format strings at http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx The script interpreter use the Microsfot formatting library for string substitution.

Please continue reading the next section about tick value conversions, this information goes hand in hand with our discussion of summaries.

Tick Value Conversions

The script interpreter performs all calculations using market tick values. Tick values are typically different than the values that appear in the DOM or any other window for a given price. It is very ill advised to attempt performing calculations with values that are formatted for display (display values), however there are times where you may want to convert tick values to their cash equivalent, or format a tick value for display so that the numbers displayed make more sense when you are analyzing chart data.

Consider the following script:


| Example1
smaval = SMA((OPEN + HIGH + CLOSE) / 3, 10);
plot_line(smaval, 1, Color.Blue);
SUMMARY("SMA(10)  {?}", smaval);

Here is how it appears on some example charts:

E-Mini S&P 500:

Image

Corn:

Image

You will notice for both charts, the summary display for the SMA shows decimal values. These computed values are clearly not valid market prices.

One might try to fix this by removing the decimal points by adding a F0 modifier to the script as follows:


| Example2
smaval = SMA((OPEN + HIGH + CLOSE) / 3, 10);
plot_line(smaval, 1, Color.Blue);
SUMMARY("SMA(10)  {?:F0}", smaval);

E-Mini S&P 500:

Image

Corn:

Image

However as you can see above, this fix is still not correct. 128806 is not a valid price for E-Mini which trades in multiples of 25, and the Corn price still is even further off.

The proper way to fix this display is to tell the script interpreter to convert the tick value to a market display value using the DISPLAY function:


| Example3
smaval = SMA((OPEN + HIGH + CLOSE) / 3, 10);
plot_line(smaval, 1, Color.Blue);
SUMMARY("SMA(10)  {?}", DISPLAY(smaval));

E-Mini S&P 500:

Image

Corn:

Image

As you can see above, the SMA values are now show correctly formatted (and rounded) for display.

Please note, you should never attempt to use display-formatted values in your computations. These value are not always suitable for conventional math. Also, attempting to do so will probably result in the script not running to completion. There also is no function to convert a display value back into a tick value.

Because the DISPLAY function is expected to be used extensively, a shorthand was created for it. Instead of typing DISPLAY, simply prepend the value with the @ symbol (and no parenthesis are needed):


| Example4
smaval = SMA((OPEN + HIGH + CLOSE) / 3, 10);
plot_line(smaval, 1, Color.Blue);
SUMMARY("SMA(10)  {?}", @smaval);

Similar to the DISPLAY function, there is a CASH function. The CASH function, converts the tick value to a cash value. The shortcut for this function is $.

To convert a cash value back to ticks use the CASHTICKS function.

The IF Statement

The IF statement allows you to make decisions on the fly in your scripts. It's syntax is a follows:

IF( < test condition >, < true part >, < false part > )

The test condition is evaluated (for example: CLOSE > OPEN which would be true when the close price of the bar is greater than the open price of the bar). If true, the true part is returned, otherwise the false part is returned.

IF statements are very flexible and can be used to solve a large number of different problems.

Here is an example Money Flow Index script:


| example
// Make the number of periods configurable.
mfperiods = INPUT("MFI Periods", 14, 1, 100);
 
// Net money flow for this period.
mf = TYP * V;
 
// Aggregate the net money flow over the user-specified number of periods.
posmf = SUM(IF(TYP > TYP[-1], mf, 0), mfperiods);
negmf = SUM(IF(TYP < TYP[-1], mf, 0), mfperiods);
 
// Compute the money flow index for this period, taking care not to divide by zero.
mfi = IF( negmf = 0, 
          IF(posmf = 0, 50, 100),
          100 - 100 / (1 + posmf / negmf));
 
// Plot the money flow as a wavecrest centered at 50 with overbought and oversold levels at 80 and 20.
PLOT_WAVECREST(mfi, 50, 70, 30, 1, Color.Brown, Color.Brown); 
SUMMARY("MFI({?})  {?:F2}", mfperiods, mfi);

The IF statement was used in a number of different places within this script. It's first use is when we want to add up the positive and negative money flows into two distinct variables. IF(TYP > TYP-1, mf, 0) What this is saying, is if the Typical price is greater than the previous Typical price add the net money flow for this bar, otherwise add zero. Vice versa for the negative money flow: IF(TYP < TYP-1, mf, 0), if the Typical price is smaller than the previous Typical price, add the net money flow for this bar.

The next uses of the IF statement in this script is even more interesting. We nested an IF statement within another IF statement, which perfectly legal syntax. In this case we want to ensure we don't divide by zero (because that would cause our computation to fail). We also want default values based on whether the neg and/or the pos money flow are equal to zero. The logic in computing the mfi variable could be broken down into a table for easier understanding:


Initial Data

negmf = 0 posmf = 0 Result
False False 100 - 100 / (1 + posmf / negmf))
False True 100 - 100 / (1 + posmf / negmf))
True False 100
True True 50

If statements can be used in other places as well. Look at the following example:


| Example
PLOT_HISTOGRAM(VOLUME, 0.72, Color.Blue);
SUMMARY("Volume  {0:N0}", VOLUME);

This produces:

Image

However, suppose we want to color the bars a different color if the market closes higher than it opened? Here is one way to accomplish that:


| example
PLOT_HISTOGRAM(VOLUME, 0.72, IF(CLOSE > OPEN, Color.Blue, Color.Green));
SUMMARY("Volume  {0:N0}", VOLUME);

Image

As you can see, the IF statement is very versatile. Use as needed to make your scripts smarter.

The NIL Field

NIL is a field value similar to OPEN, HIGH, LOW, CLOSE, VOLUME, etc. However, NIL is special in that is represents nothing. You can use NIL to short-circuit computations when certain conditions are met, and more importantly, you can use it to suppress plotting when you don't want a certain data point plotted.


| Consider a script for Pro-Rated volume:
prvol = IF(BARPERCENT > 0.05, VOLUME / BARPERCENT, VOLUME[-1]);
 
// Plot the pro-rated volume bar.
PLOT_HISTOGRAM(IF(INDEX >= COUNT - 1, prvol, NIL), 0.9, COLOR.Yellow);
 
// Plot the real volume.
PLOT_HISTOGRAM(VOLUME, 0.7, IF(CLOSE >= OPEN, COLOR("#F08080"), COLOR("#696969")));
 
// Summary.
SUMMARY("PRVOL: {?:F0}  VOL: {?} ({?:F0}%)", IF(INDEX >= COUNT - 1, prvol, "-"), VOLUME, 100 * BARPERCENT);

Image

In this script, we pro-rate the volume for the current bar. We accomplish this using the special BARPERCENT field which returns the percentage of time into the current bar we are. We want to plot the pro-rated volume only for the last bar on the chart, and then we want to overlay a plot of the real volume.

To ensure the first histogram plots only the last bar, we check the current bar INDEX against the total bar COUNT. If not on the last bar, we pass NIL as the value to plot which basically means “don't plot anything.”

Gotcha 1: Don't Reassign Variables

Once you assign a value or compuation to a variable name. Do not re-assign a different value to that same variable name. The following example illustrates what not to do:


| Gotcha example 1
xval = 10;
xval = xval + 1;

Though this sort of syntax works in many conventional programming languages, it will result in a script interpreter error and the result will not be as expected.

Remember to think of variables as columns in a spreadsheet. You cannot give a spreadsheet cell more than one formula which is essentially what the above script is attempting to do.

Gotcha 2: Don't Cause Circular References

Similar to variable re-assignment, you should watch out for circular references. A circular reference is when two or more variables depend on each other in a circular fashion. For example:


| Gotcha example 2
xval = yval;
yval = xval + 1;

The script interpreter cannot compute xval until it computes yval. It also cannot compute yval until it computes xval. A naive interpreter might get stuck in an endless loop trying to solve these equations, however the script interpreter has a mechanism to detect these situations and report them as errors.

Recursion

There is a special case where you can use circular-like syntax called recursion. Here is an example:


| Example
xval = IF(INDEX = 0, 0, yval[-1]);
yval = xval + 1;

or

| Example2
yval = IF(INDEX = 0, 0, yval[-1] + 1);

In this case, because you using the offset mechanism to refer to a previously calculated value, no circular reference error is generated. However, you must be sure to check the stop condition and assign it a value. In this case our stop condition is the first bar. On the first bar, the previous value of yval would evaulate to NIL and that would short-circuit all following computations to NIL. We used the IF statement to initialize yval on the first bar.

Recursion is considered an extremely advanced topic. It is rarely needed for most indicator calculations. However there are some calculations which are impossible without it (Parabolic SAR is one example). If find find yourself using recursion to solve a computation problem, please feel free to contact support for assistance, further documentation and possibly more examples.

  • desktop/desktop_chart_scripting_overview.txt
  • Last modified: 2023/09/21 19:58
  • by 127.0.0.1