Formula Guide

You can also download this guide from formulaguide.zip

Table of Contents

  1. An Overview of TecStock's Formula System (TSFS)
  2. The Core Details
  3. Functions
  4. Predefined Formulas

An Overview of TecStock's Formula System (TSFS)

Getting Started
TecStock's Formula System (TSFS) is a technical analysis tool that is powerful, flexible, yet easy to use. TSFS can be used to design user's custom indicators, stock screeners and produces high quality stock charts.

In TSFS, a formula can be as simple as just consisting of one word like the following example shows. The formula in the example displays a curve connecting all the closing prices in the chart.

close;

In fact, each of the following lines is a valid formula:

volume;
(close+open)/2;
(2*close + 2*open + high + low)/6;
(close - ma(close, 10))/ma(close, 10);

As you can see in the examples above, TSFS uses self-explanatory keywords to refer to market data like the open price, the closing price, the trading volume etc. As you have probably expected, ma is a built-in function that calculates the simple moving averages (some other tools use sma to refer to simple moving averages. In the terminology of TSFS, sma is used to indicate a special type of weighted moving averages. You will learn more about that later). ma(close,10) is the 10-day moving averages of the closing price. Also, TSFS uses the arithmetic operators in a natural way as everyone would expect. Because of this, in many cases formulas in TSFS are easy to understand and do not require any further explanations.

On the other hand, TSFS has powerful features that even simple formulas, only a few lines long, can produce complex results. The following short formula implements the standard indicator of MFI (Money Flow Index).

va :=if(high > low, (2*close-(high+low))/(high-low)*volume, 0);
mfi:= sum(va, 20)/sum(volume, 20);
fillrgn(mfi, 0), color006432, transparency5;
fillrgn(0, mfi), color963200, transparency5;
mfi, colorblack;

You may have noticed that all the colors used in the chart are specified in the formula. This implies that you can choose whatever color you want for your custom indicators. We will show you later that you have much more control over the chart, from colors, to line styles, icons, string texts etc.

Why TSFS
Tecstock.com covers more than 10,000 stocks trading in the North America stock exchange markets. Without computerized tools, it is impossible to us to monitor stock markets everyday. There are quite a few commercial and free stock screeners available on markets. While these stock screeners are useful under certain circumstances, they all share many of the same limitations:
  • The screeners are more focusing on fundamental facts like company's cap values, sales revenue, earning data, price-earnings ratio etc.

  • The screeners can only use standard indicators and predefined criteria to scan stocks. It is not possible to users to scan stocks using their custom indicators and patterns to meet special needs.

  • Many of the screeners only show results for the current date. Users seeking for stocks with trading opportunities showed up in the recent past can not find their targets.

  • The screeners are all lacking means for visually plotting signals on high quality charts.

TSFS is to provide a platform with the basic functions necessary for the technical analysis. By combining basic functions it provides users with unlimited possibilities to explore their trading ideas. In addition, it seeks to overcome potential shortcomings in many of the existing stock screening systems.
What Can TSFS Not Do
Even it is well known, we want to emphasize that stock markets are unpredictable and full of risks. TSFS is a computerized technical analysis tool. TecStock.com uses TSFS to scan and monitor stock markets using your formulas and report the results to you everyday. No matter how exciting the scan results seem to be, doing the fundamental analysis is always a must before any trading activities. Formulas alone can not and should not be used to make any kind of trading decisions.
Exploring TSFS
The way you really learn TSFS is to put your hands on and write formulas with it. TecStock.com has created an easy-to-use GUI for
editing and testing formulas. As you go through this guide, we encourage you to try out the formula system features as you learn about it.

If you already have a user account, log on now. If this is the first time you try TSFS, you may want to sign up for a trial account for free.

Before we go editing and testing formulas, let us first understand how formulas are organized in TSFS.

Formulas in TSFS are organized in a tree-like list structure. The root node of the tree list is Formulas, followed by two subnotes Sys Formulas and My Formulas (which is visible only when you have signed in and have defined some formulas in your account). All the TSFS predefined formulas are listed under the node Sys Formulas, while your custom formulas are shown under My Formulas.

Under both nodes of Sys Formulas and My Formulas, formulas are organized so that they are grouped in two primary categories: In Subchart and In MainChart. As the name indicates, formulas under the node In MainChart will be displayed inside the main stock chart, while formulas under the node In Subchart will be displayed either above or below the main stock chart.

In each of these two categories, formulas are further grouped together by the group name if the group name is available. Formulas without any group names are simply listed below the nodes of In Subchart and In MainChart.

You can try out the formula list in the left part of the page editing and testing formulas. Some common standard indicators predefined by TSFS are without group names. It has been done so that they can be easily referred to without having to check group nodes every time.

You saw a couple of formula examples in previous sections. Suppose you want to write a new formula representing an average system as the following:

ma10:  ma(close, 10);
ma30:  ma(close, 30);
ma50:  ma(close, 50);

The formula calculates the 10-day, 30-day and 50-day moving averages of the close price and displays the results with names ma10, ma30 and ma50, respectively.

Copy the formula above, go to the page editing and testing formulas, click on the "New Formula" button and paste the formula to the text area below the parameter fields as shown in the following figure.

Before you can test this formula, it is mandatory to provide a formula name. Type in MyMA as the formula name in the name field on the top. You may also want to give your formula a group name so that it will be listed together with other formulas having the same group name. In this example, you use Average System as the group name.

Note that both formula name and group name are case insensitive. While the formula name should not include any spaces, the group name containing spaces is perfectly valid.

Optionally, you can provide a full name for your formula and put whatever you want as the explanation text in the description field for your formula.

There is no parameters in this formula. So leave all the parameter fields blank. There is an In MainChart check box beside the parameter fields. This is to indicate whether or not the formula should be plotted in the main stock chart. While some indicators like MA (Moving Averages), BOLL (Bollinger Band), ZIG (Zig Zag) are always displayed inside the main chart, other indicators like RSI(Relative Strength Index), MACD(Moving Average Convergence Divergence) etc. are only plotted in subcharts. Because MyMA is kind of moving average indicators, you may want to check the box so that MyMA will be displayed inside the main chart. You may uncheck the box later to see how MyMA is displayed outside the main chart.

Type in a stock symbol you want to test against, for instance IBM, in the symbol field in the middle of the screen, as shown below. Then click on the Test button. Now you should see your custom indicator displayed with the name MyMA inside the IBM chart. Because we did not specify any colors in our formula, default colors are automatically assigned to the three curves.

If you uncheck the In MainChart box and click on the Test button again, the MyMA indicator will be plotted inside a subchart.

While exploring formulas, you will probably write code that does not work. Let's modify the formula above and change the word "close" to "clsoe" in the second line. Click on the Test button. An error message in red will show up on the top of the screen:

Line:2, Column:11: Invalid syntax: undefined symbol 'CLSOE'

see the figure below. This indicates that at character position 11 in the second line, the symbol 'clsoe' is not defined. Correct the word and click on the Test button, the formula should work again.

When you are satisfied with your formula, it is time to save it. Before you can do this, you will be asked to sign in.

The formula MyMA does not use any parameters. TSFS does support formulas with parameters. Take a look at the following formula. It uses another build-in function ema to calculate exponential moving averages of close prices. Variables p1, p2, p3 are not defined in the formula and will be used as parameters.

ema1:  ema(close, p1);
ema2:  ema(close, p2);
ema3:  ema(close, p3);

Copy the formula above, in the page editing and testing formulas, click on the "New Formula" button, and paste the formula to the formula text area; put formula name, group name and parameters as shown in the figure below. Please note that when specifying a parameter, the parameter name, its default value, the minimum and the maximum value have to be defined. The parameter name is case insensitive and can not include any spaces. The default value has to be in the range of the minimum value and the maximum value.

Now click on the Test button, three exponential moving averages of close prices will be displayed in the main chart with their default parameters. If you have already logged in, click on the Save button. You will notice that the formula list on the left side of the screen is updated. Both MyMA and MyEMA formulas are now grouped together under the node Average System in the subtree as shown in the figure on the left side.

Note that the tree-like formula list displayed in the left figure does not have the branch In SubChart under the node My Formulas. This is because in this particular user account, we have not defined any formulas yet that should be displayed in subcharts.

About Backtesting
The facility of backtesting is designed to help you evaluate and tune the performance of your custom formulas.

You can backtest your formulas against different stocks by choosing different price and market cap ranges. In addition, you can explicitly specify stocks you want to include in the backtesting process. The check box beside allows you to tell if you want to test your formula only against the stocks you have specified.

Depending on the complexity of formulas, online backtesting may demand a significant amount of computational power and thus the total number of stocks used in each backtesting is limited to 100. Time frame used in backtesting varies from 2 months to 2 years, depending on the number of stocks used in the test. The less stocks you specify, the bigger the time frame will be.

Before back testing, make sure that your formula contains an expression like

singal_signame : ... 

This tells TSFS that your formula is going to generate signals with the name signame.

For example, the following formula generates doji signals when doji patterns occur. A doji pattern is identified when the close price is the same as the open price and the high price is not equal to the low price.

signal_doji : close = open and high != low;

Once you have finished tuning and saved your formula, TecStock.com will use it to scan more than 10,000 stocks everyday after market close and generate signals for you.

For more details about generating signals, see Variable Names For Signals.

The Core Details

Case Insensitivity
TSFS is a case-insensitive system. That means that keywords, variables, function and formula names and any other identifiers are not case-sensitive. The keywords close, Close, CLOSE are all refer to the closing price. A formula with the name MyMA can be referred to as myma or myMA.

The formula below calculates the average value of open and close and assigns the result to the variable VAR. The variable is then referred to as var in the following two lines. The formula displays the 20-day and 30-day moving averages of VAR.

VAR := (close + open)/2;
ma20: ma(var, 20);
ma30: ma(var, 30);

Comments And Semicolons
In formulas, any text between the # character and the end of the line is treated as a comment and is ignored by TSFS. In addition, any text between the characters /* and */ is treated as a comment as well. These comments may span multiple lines but may not be nested.

Statements in formulas have to be terminated by semicolons(;). Semicolons serve to separate statements from each other. It is a syntax error if the semicolon is missed. You can put multiple statements in one line. This is valid as long as each statement is terminated by a semicolon.

The formula below contains two comments. It also has two statements sitting in the same line. The formula compares the difference between ma (moving averages) and ema (exponential moving averages) by showing both ma(close, 10) and ema(close, 10) in the main chart.

/* This is a comment:
 In the following, two statements are in the same line. This is valid
 because each statement is terminated by a semicolon.
 */
ma10: ma(close, 10); ema10: ema(close, 10); # this is again a comment

Single Numbers and Arrays of Numbers
TSFS is very easy to work with. It only supports one data type: numbers. Numbers are used either as single numbers or as a collection of numbers, often referred to as an array of numbers.

We have already seen the use of numbers many times so far. For example, ma(close, 10), where 10 is a single constant number. What we have not mentioned yet is that because every trading day has a closing price, close actually refers to an array of numbers rather than a single number. ma(close, 10) returns the 10-day moving averages of the closing price for each trading day. Thus, ma(close, 10) is an array of numbers as well.

The formula below calculates the 10-day bias, a stock fluctuate measurement commonly used in technical analysis. Obviously, the expression (close - ma(close, 10))/ma(close, 10) * 100 returns an array of numbers. The formula also uses 0 as a constant to plot the zero-line. When displaying the formula, the constant value 0 remains unchanged in the whole time frame defined in the chart, while the value of bias is changing in every trading day.

constant : 0;
bias : (close - ma(close, 10))/ma(close, 10) * 100;

Formula Names And Parameters
We have so far seen a couple of formula examples. A formula is in fact a collection of statements. A statement can be an expression for calculation, like (open + close)/2; or a function call that does not do any calculation but plotting, for example, vertline(close > open); which plots a vertical line in the chart when the condition close > open is true.

We have seen that each formula has to have a name. A formula name starts with a letter, followed by letters, numbers, or underscores.

Formula names are unique for each user. What it means is that you can not have more than one formulas with the same name in your account. If you try to save the second formula using the name of your first formula, the first formula will be overwritten. However, you can use formula names already used by other users or by the system. There will not be any name conflict. You can, for example, write a formula with the name MACD, although TSFS has predefined a formula with this name for the standard MACD indicator.

We have seen the formula MyEMA in the previous chapter with three parameters. Internally, a formula can have unlimited number of parameters. To avoid overusing this feature, the page editing and testing formulas only allows up to 4 parameters to be used in each formula, which should be sufficient in most cases.

When using parameters in formulas, it is mandatory to provide parameter name, the default value, the minimal and the maximum value for each parameter. Parameter names follow the same rule as formula names. They have to start with a letter, followed by letters, numbers, or underscores. Parameter names have to be unique in formulas. If, for example, P1 is used as the name of a parameter, other parameters and variables in the same formula are no more allowed to use P1 as their names.

The default value of parameters are the value used by TSFS when formulas are called without parameters. The default value should be always in the range formed by the minimal and the maximum values.

Variables
In formulas variables can be used to store constants and expression results. By storing expression results in variables, we can avoid calculating the same expressions multiple times and thus improve the performance of TSFS. Like formula names, a variable name starts with a letter, followed by letters, numbers, or underscores.

Variables can be either of external type or of internal type. External variables are accessible outside formulas (we will cover this when we talk about calling formulas from other formulas). When displaying a formula, values of external variables will be plotted and variable names will be shown in the chart. External variables are defined through the assignment operator ":". For instance,

var1 : 20;            # var1 is defined as an external variable
var2 : ma(close, 10); # var2 is defined as an external variable

Internal variables, on the other hand, are used inside formulas to hold intermediate results. Internal variables are not accessible from outside. When displaying formulas, values of internal variables will not be shown in charts. Internal variables are defined through the assignment operator ":=". For example,

var := ma(close, 20); # var is defined as an internal variable

Note that you can define a variable only once, i.e., you can not assign values to a variable multiple times. The formula below is not valid:

var1 : close; # okay, variable var1 is defined as external
var1 : open;  # error: variable var1 is already defined and can not be redefined

Unless drawing functions are used, a formula has to have at least one external variable (including anonymous variables). Formulas that consist only of internal variables are doing nothing when viewed from outside and are thus not valid. The following formula has only two interval variables defined. It is useless and invalid because none of its variables is accessible outside the formula, and when displaying the formula, nothing would be shown in charts.

# invalid formula
var1 := ma(close, 10);
var2 := ma(close, 20);

The following formula is an implementation of the standard indicator ROC (Price Rate-of-Change). ROC is a measurement of fluctuate as percentages around the zero line. In the formula, internal variables m and n are defined to store constants 6 and 12; the constant 0 is used without any variable name. In this case, TSFS will create an anonymous variable to hold its value. We will talk about this later. The internal variable l_roc is used to store the fluctuate percentage, where the function ref(close, n) refers to the close price 12 days ago (recall that n is equal to 12). l_roc is then followed by a description which specify the color and the line thickness. Again, l_roc is used without variable name and TSFS will create an anonymous variable to hold its value. The last statement calculates the 6-day moving averages of l_roc (recall that m is set to 6) and assigns the result to the external variable ma. When display the formula, it looks like this.

M := 6;
N := 12;
0;
l_roc := (close - ref(close, n))/ref(close, n) * 100;
l_roc, color4f4f4f, linethick2;
ma: ma(l_roc, m), colorred;

As you have seen above, when constants or expression results are not explicitly assigned to any variables, TSFS will create anonymous variables to store their values. Anonymous variables are always external. This means that values of anonymous variables will be plotted in charts. Because anonymous variables do not have names, values stored in anonymous variables can not be reused and when displaying the formula, no name but values will be shown in charts.

In the above example, the zero line is plotted because of the anonymous variable holding the constant 0. The value 0.00 is displayed in the chart without any name. Similarly, l_roc is stored in an anonymous variable and thus get displayed without any name. The name "ma" is shown in the chart because ma is an external variable.

Variable Names For Signals
There is a special case about variable names. When TSFS monitors markets using your custom formulas, variable names in your formulas prefixed with signal_ like
signal_yoursigname : ...
will trigger TSFS to generate signals with the name yoursigname when the value of the variable is true (i.e., not zero).

For example, the following formula generates filled_black_candles signals when one-day candlesticks characterized with a long black body having no shadows on either end are found (Filled black candle, also referred to as black marubozu, is an extremely strong bearish candlestick pattern).

range := high - low;
signal_filled_black_candles : open = high and close = low and range > ma(range, 10);

Calling Formulas From Other Formulas
It is important to be able to reuse formulas we have already defined when writing new formulas. The formula reusability will help us keep formulas small, simple, flexible and independent. We will not have to start from scratch every time when we write new formulas.

TSFS uses the following syntax to allow you call formulas from other formulas

"formulaname.varname"(param1, param2, ...)

where formulaname is the name of the formula you want to call, varname is an external variable name defined in formulaname, and param1, param2, ... are the formula parameters. It tells TSFS to execute the formula formulaname using the given parameters param1, param2, ... and returns the result stored in the variable with the name varname.

If the formula formulaname does not have any parameters, then the syntax of calling formulaname will be

"formulaname.varname"

Note that you can use the above syntax to omit parameters even if formulaname has parameters. In this case, because no parameters are explicitly given, TSFS will use the default values of parameters.

You can also omit varname and call formulaname in one of the following ways

"formulaname"(param1, param2, ...)
"formulaname"

This will return the result stored in the last external variable in formulaname. Note that the last external variable can be an anonymous variable.

So far enough about the syntax. Let's have some examples.

Recall that in the previous chapter, we have defined the formula MyMA

ma10:  ma(close, 10);
ma30:  ma(close, 30);
ma50:  ma(close, 50);

and the formula MyEMA with parameters p1,p2,p3

ema1:  ema(close, p1);
ema2:  ema(close, p2);
ema3:  ema(close, p3);

Now you can reuse these two formulas by calling them from your new formula like the following example shows

a1 : "myma.ma10";
a2 : "myma.ma50";
a3 : "myma";

b1 : "myema.ema1"(50, 100, 200);
b2 : "myema.ema3"(50, 100, 200);
b3 : "myema"(50, 100, 200);

c1 : "myema.ema1";
c2 : "myema.ema3";
c3 : "myema";

In the example above, "myma.ma10" in the first line executes the formula MyMA and returns the result stored in ma10. Thus, a1 contains the 10-day moving averages of the closing price. In the second line, "myma.ma50" executes the formula MyMA and returns the result of 50-day moving averages to a2. The formula call in the third line omits the variable name. According to the syntax discussed above, it returns the result stored in the last external variable in MyMA which is ma50. Thus, "myma" is equivalent to "myma.ma50" and a2 and a3 contain the same result.

You may have noticed that the first 3 lines in the example require the execution of the same formula three times. Is this really necessary? No, you are absolutely right, it is not. Internally, TSFS will detect that the first three lines execute the same formula with the same parameters, in this case no parameters, and will thus process the execution only once when the first line is encountered, and will reuse the results in the second and third lines.

Similarly, "myema.ema1"(50,100,200) calls the formula MyEMA with the parameter 50,100,200 and returns 50-day exponential averages to b1. "myema.ema3"(50,100,200) returns 200-day exponential averages. "myema(50,100,200) is equivalent to "myema.ema3"(50,100,200) because ema3 is the last external variable in MyEMA.

The last three lines call MyEMA without providing any parameters. So default values of parameters will be used. Recall that in the previous chapter, we defined the default parameters as (5,10,20). It returns 5-day exponential averages to c1 and 20-day exponential averages to both c2 and c3.

Please note that when using the above example to analyze stock, say XYZ, all the formula calls return results regarding to the stock XYZ, i.e., "myma.ma10" will return 10-day moving averages of the closing price of XYZ, "myma.ma50" will return 50-day moving averages of the closing price of XYZ, and so on.

In technical analysis, however, it is a common practice that we want also to take other stocks, often market indices into account. For instance, before generating buy signals for stock XYZ, we want to check if the market is under bullish condition. We want to generate buy signals only when the market is bullish.

To make this possible, TSFS provides another formula call syntax to return results in regard of a given stock or index:

"ticker$formulaname.varname"(param1, param2, ...)

which tells TSFS to execute the formula formulaname against the stock ticker using the given parameters param1, param2, ... and returns the result stored in the variable with the name varname.

You can also use the following syntax variations to omit parameters or varname when you want to use default values of parameters if any, or you want to return the result stored in the last external variable in formulaname:

"ticker$formulaname.varname"              # use default values of params if any
"ticker$formulaname"(param1, param2, ...) # return the result in the last external variable
"ticker$formulaname"                      # use default values of params if any and
                                          # return the result in the last external variable

Consider the following formula and save it with the name Bullish:

ma10 := ma(close, 10);
ma50 := ma(close, 50);
ma200:= ma(close, 200);
cond: ma50 > ma200 and ma10 > ma50 and close > ma10;

The formula makes use of comparison and logical operators that will be covered in the next section. The idea should be obvious. It compares different moving averages (MA) and sets the bullish condition cond as true when the 50-day MA is above the 200-day MA and the 10-day MA is above the 50-day MA and the price is closed above the 10-day MA.

Now take a look at the following formula:

nasdaq_cond := "^ixic$bullish.cond";
dow_cond    := "^dji$bullish.cond";
cond        := "bullish.cond";
buy         : cond and nasdaq_cond and dow_cond;

Tecstock.com uses Yahoo's ticker symbol convention. So ^IXIC refers to the Nasdaq Composite Index, while ^DJI is the symbol for Dow Jones Industrial Average Index.

In the formula, "^ixic$bullish.cond" returns the cond value of Nasdaq index to nasdaq_cond, "^dji$bullish.cond" returns the cond value of Dow Jones Industrial index to nasdaq_cond, cond stores the condition value of the current stock. Thus, if you apply the formula to stock XYZ, the variable buy will be set to true only if XYZ, the Nasdaq index and the Dow Jones Industrial index are all under the bullish condition.

It should be pointed out that this example is for demonstration purpose only. Bullish does not necessarily represent a precise criteria for checking the bullish condition of markets.

When you call an existing formula from your formulas, you have to make sure that

  • the formula you are calling does exist and is accessible from your formulas. You can access your own formulas and those predefined by TSFS. Formulas defined by other users are not accessible;

  • you are referring to an external variables in that formula;

  • the parameter values you pass over are in the ranges formed by minimum and maximum parameter values of that formula;

  • if you call the formula in regard of a given stock, the stock symbol exists in tecstock.com.

  • you do not call formulas recursively. TSFS does not allow any direct or indirect recursive formula calls. You can not call a formula from itself. If you have formulas F1, F2, F3, and F1 calls F2, F2 calls F3, then formula F3 can not call F1.

Arithmetic Operators
All the basic arithmetic operators that you are familiar with are supported in TSFS. This includes addition, subtraction, multiplication, division, and modules. The expression
(open + close)/2;
for example, is calculated in the way as you would expect: it first calculate open + close, the result is then divided by 2. Note that operator precedence has caused the open + close to be executed first.

While this seams easy and simple, what really happens behind the scene is that open + close is calculated for every trading day. The result is an array of sums of open and close. Each sum in the array is then divided by 2. The final result is an array of average values of open and close.

Comparison And Logical Operators
TSFS also supports the following comparison and logical operators:

greater than >    
greater than or equal to >=    
less than <    
less than or equal to <=    
equal to =    
not equal to != <>  
logical and and && &
logical or or || |

Comparison operators can be used to construct boolean expressions, for example,

close > open;
A boolean expression returns either true or false. TSFS uses 1 (one) to refer to the "true" boolean value, 0 (zero) to refer to the "false" boolean value. In the above example, because the close and the open price are compared in every trading day, the expression close > open returns an array of numbers containing 0s and 1s.

Boolean expressions can be conjuncted using the logical operators AND or OR to express complex conditions. For example,

is_up1 : close > open and close > ref(close, 1);
is_up2 : (close > open) * 2;

where is_up1 is set to 1 only when the price is closed above both the open price and the previous closing price, while is_up2 is set to 2 (not 1 because of the multiplication of 2) as long as the price is closed above the open price. is_up2 is multiplied by 2 because we want to see the difference between is_up1 and is_up2 more clearly in the chart.

Numbers can be conjuncted with other boolean expressions as well. In this case, all the non-zero numbers will be treated as true, while zero numbers will be treated as false.

For the sake of convenience, you can use "||" or "|" to replace AND and use "&&" or "&" to replace OR in your formulas. For example,

is_up_1 : close > open or close > ref(close, 1);
is_up_2 : close > open || close > ref(close, 1); # is_up_2 is equivalent to is_up_1
is_up_3 : close > open | close > ref(close, 1);  # is_up_3 is equivalent to is_up_1
is_up_4 : close > open && close > ref(close, 1);
is_up_5 : close > open & close > ref(close, 1);  # is_up_5 is equivalent to is_up_4

In the following, we will make use of comparison and logical operators to define a formula that can be used to identify the candlestick pattern "morning star".

The morning star is a major bottom reversal pattern formed by three-candlestick:

  • a long-bodied red candle extending the current downtrend,
  • a short middle candle that gaped down on the open,
  • and a long-bodied white candle that gaped up on the open and closed above the close of the first day.
The first long red candle shows the continuing bearish nature of the market. Then the short middle candle appears implying the incapacity of sellers to drive the market lower. The last long white candle shows that the market turned bullish now.

In the description above, what "a long-bodied red candle" really means is that on the first day, the market was down, close < open, and the difference between close and open was big; "short middle candle" means that on the second day, the market was either up or down and the difference between close and open was small; "a long-bodied white candle" means that on the third day, the market was up, close > open, and the difference between close and open was big.

Let us first define a list of variables as shown below, where abs(close-open) is a built-in function that returns the absolute value of close - open. The function llv(low,60) returns the lowest low price within the last 60 days.

is_up     := close > open;       # is_up is 1 when the market is up, 0 otherwise
is_down   := close < open;       # is_down is 1 when the market is down, 0 otherwise
diff      := abs(close - open);  # the absolute value of the difference between
                                 # open and close
diff_ma   := ma(diff, 10);       # 10-day average of diff
is_dtrend := low = llv(low, 60); # is_dtrend is 1 if the low is the
                                 # lowest low in the last 60 days.

Because there is no absolute measurement for when the difference between open and close is big and when it is small, we compare the difference to its 10-day average values. The difference is considered to be big if it is greater than its 10-day average, i.e., diff > diff_ma; it is considered small if diff < diff_ma/2. We also assume that the market is in its downtrend if the low price is the lowest low price in the last 60 days.

According to the definition above, a morning star pattern consists of three candlesticks. When trying to identify the pattern, the first candlestick is actually the candlestick two days ago, the second candlestick is that on the previous day and the last candlestick is the current one.

We have seen that ref(close, 12) refers to the 12-day-ago closing price. In general, we can use ref(x,n) to refer to the n-day-ago value of x.

The following code lists conditions each candlestick has to fulfill.

# The first candlestick has to fulfill the condition 1:
cond1: ref(is_down, 2) and             # 2 days ago, market was down
       ref(diff > diff_ma, 2);         # 2 days ago, the difference was big

# The second candlestick has to fulfill the condition 2:
cond2: ref(diff < diff_ma/2, 1) and    # 1 day ago, the difference was small
       ref(open, 1) < ref(low, 2) and  # the open 1 day ago was below the low 2 days ago,
                                       # i.e., it gapped down on the open 1 day ago
       ref(is_dtrend, 1);              # 1 day ago, the low was the lowest low
                                       # in the last 60 days.

# The third candlestick has to fulfill the condition 3:
cond3: is_up and                       # market is up on the current day
       diff > diff_ma and              # the difference is big
       open > ref(high, 1) and         # the current open is above the high 1 day ago,
                                       # i.e., it gapped up on the open on the current day
       open < ref(open, 2) and         # current open is below the open 2 days ago
       close > ref(close, 2);          # current close is above the close 2 days ago

After connecting all the conditions using the logical operator AND and putting all the code fragments together, we get the following formula. Notice that when the morning star pattern is identified, the formula marks the pattern with a small red flag below the low price using the function drawicon(cond,low,7). The formula identifies three morning star patterns in daily charts of CHI, STLD and X.

is_up     := close > open;
is_down   := close < open;
diff      := abs(close - open);
diff_ma   := ma(diff, 10);
is_dtrend := low = llv(low, 60);

cond := ref(is_down, 2) and
        ref(diff > diff_ma, 2) and
        ref(diff < diff_ma/2, 1) and
        ref(open, 1) < ref(low, 2) and
        ref(is_dtrend, 1) and
        is_up and
        diff > diff_ma and
        open > ref(high, 1) and
        open < ref(open, 2) and
        close > ref(close, 2);

drawicon(cond, low, 7), align1; # draw a red flag under the low price when cond = 1

Notice that what has been shown is just one possible implementation of "morning star". You may define diff_ma as ma(diff, 20) or modify other conditions to implement your variation of "morning star".

Functions

TSFS provides a set of basic functions necessary for the technical analysis. The functions are to serve as the foundation of TSFS. By combining these basic functions you get boundless flexibility to explore your great ideas and produce high quality charts.

This chapter discusses each of the TSFS functions in detail. They are arranged by category. For the sake of easy reference, an alphabetical index of functions are provided at the end of the guide.

Market Data
open, close, high, low, volume
These are the five basic market data in the technical analysis which, for a serial of trading periods, refer to the period opening price, the period closing price, the highest and the lowest prices and the total trading volume in each period. A period is not necessarily always a day, it can also be a week (in weekly charts, for example), a month (in monthly charts), or an 1-minute or 5-minute trading period (in intraday charts).

open, close, high, low, volume are the market data most often referred to in technical analysis. For the ease of reference, each of them has short form acronyms as shown in the table below.

keyword open close high low volume
acronym o c h l vol, v

In the following, we are going to provide more examples to demonstrate the use of the market data and their acronyms.

Example: Moving averages of closing price like ma(close, 50) and ma(close, 200) are commonly used in technical analysis to help extract the stock short term and long term trends. These moving averages only consider the closing price. Some investors believe that it might be more precise to also consider the open price. Some of them even want to take the high and the low prices into consideration. You can use the following formula to compare the differences between these approaches.

n  := 50;                   # compare 50-day averages. You can change the value
                            # of n to see how it will affect the result
v1 := close;
v2 := (c + o)/2;            # v2 is (close + open)/2
v3 := (2*c + o + h + l)/5;  # put a bit more emphasis on close price
ma1: ma(v1, n);
ma2: ma(v2, n);
ma3: ma(v3, n);

Example: The next formula displays a channel on the chart using the highest high price and the lowest low price in the recent 20 trading days.

n := 20;
top_line : hhv(h, n);  # hhv returns highest high in the last n-day
bot_line : llv(l, n);  # llv returns lowest low in the last n-day

Example: The following formula plots three volume average lines on the chart. In the formula, volstick is a special descriptor in TSFS used to draw volume sticks, color.. is for specifying the color for each average line.

volume, volstick;
ma(v, 5), colorblack;
ma(vol, 10), colorbrown;
ma(volume, 30), colorblue; 

Example: Investors are used to use RSI (Relative Strength Index) to measure the strength of stocks. The following formula tries to do the same thing but from a different perspective. It makes use of triple emas (Exponential Moving Average) to filter out the noisy data of (close+high+low)/3 and calculates the percentage of change compared to the previous day. The value of control is around the 0-line. The higher the absolute value of control is, the stronger the stock is under the control. To let you compare the results, both the formula and the standard RSI are plotted on the chart.

var1 := (close + high + low)/3;
var2 := ema(ema(ema(var1, 2), 13), 21);
control : (var2 - ref(var2, 1))/ref(var2, 1)*100, colorstick, linethick2;

capital
The keyword capital refers to the shares float, the number of shares held by the public and available for trading. capital is a single number and is often used to check stocks' trading liquidity in the market.

The trading volume is one of the most important factors in technical analysis. Many investors believe that volume precedes price, meaning that price change pressure shows up in the volume figures before presenting itself as a price trend reversal. In many cases, however, the trading volume can not be used to compare the trading liquidity across stocks. A trading volume of 10,000,000 may be huge for some stocks but can be just a small fraction for others. The next example implements a new volume indicator PVOL. It makes use of capital and compares volumes in an unified way. It calculates the volume percentage of capital and so makes comparison of the trading liquidity across stocks possible.

For instance, a stock XYZ with a capital of 100M (100 millions) was trading with the volume 5M. The volume percentage of capital will be 5%. A stock ZYX with a capital of 1,000M was trading with a much higher volume 10M. Because its volume percentage of capital is just 1%, stock XYZ has a much better trading liquidity than stock ZYX.

Unfortunately, for some reason capital is not always available. TSFS defaults capital to zero if it is not present. PVOL uses the built-in function if(..) to check the availability of capital. If capital is not available, i.e., capital = 0, it simply returns volume and the formula thus behaves just like a regular volume indicator. Otherwise it calculates the percentage of volume/capital * 100. Here are some daily charts with the new volume indicator PVOL: IBM, MSFT, SNDK.

# PVOL
pvol:= if(capital = 0, volume, volume/capital*100);
pvol, volstick;
ma(pvol, 5), colorblack;
ma(pvol, 10), colorbrown;
ma(pvol, 30), colorblue; 

PVOL is predefined in TSFS and can be found in the formula list under nodes:
Formulas -> Sys Formulas -> In Subchart,
Formulas -> Sys Formulas -> In Mainchart.

finance("data_name")
data_name: financial data name.
TSFS preserves the keyword finance to refer to a set of financial data. Currently the following data names are supported. Please notice that not all the financial data listed below are available for all the stocks. In case the data is not present, finance will return 0.

shares_outstanding the number of shares outstanding. Shares outstanding refers to the number of shares of common stock currently outstanding, the number of shares issued minus the shares held in treasury.
shares_float the number of shares float. This is the number of freely traded shares in the hands of the public. finance("shares_float") is equivalent to capital.
current_pe the current P/E ratio
forward_pe the forward P/E ratio
institutional_ownership the institutional ownership in percentage (e.g., 58.20 meaning 58.20%)
mutual_fund_ownership the mutual fund ownership in percentage
insider_ownership the insider ownership in percentage
gross_margin the gross margin in percentage
pre_tax_margin the pre tax margin in percentage
net_profit_margin the net profit margin in percentage
indu_gross_margin the industry gross margin in percentage
indu_pre_tax_margin the industry pre tax margin in percentage
indu_net_profit_margin the industry net profit margin in percentage
sp_gross_margin the SP gross margin in percentage
sp_pre_tax_margin the SP pre tax margin in percentage
sp_net_profit_margin the SP net profit margin in percentage

You can use finance("shares_outstanding") to calculate the market capitalization of stocks. For example,

...
market_cap := finance("shares_outstanding")*close / (1000*1000); # returns number in million
...
Date Functions
This section explains functions that can be used to process date related calculations.

day, weekday, month, year
The function day returns an array of numbers of the day in month from 1-31. For example
d : day;

The function weekday returns an array of weekday numbers from 1-7 (Sunday = 7). For example

wd: weekday;

The function month returns an array of month numbers from 1-12. For example

m : month;

The function year returns an array of year numbers like 1998, 1999, 2000 etc. For example

y : year;

Example: In the formula below, if the year of the current trading day is not the same as that of the previous trading day, i.e., the current day is the first trading day in the new year, it draws a happy face on the chart.

drawicon(year != ref(year, 1), low, 1), align1; 

date
Function date returns an array of integers representing dates. For dates before 2000-01-01, it returns integers in the format YYMMDD. For any other dates, the returned integers have the format 1YYMMDD. For example, for dates 1999-12-30, 2000-01-01, 2001-12-30, date returns 991230, 1000101, 1011230 respectively.

Example: the following formula plots vertical lines on the chart when the date is between 2008-12-16 and 2008-12-26.

vertline(date >= 1081216 and date <= 1081226);

If you have a set of well defined formulas and want to sell your formulas in an encrypted format, you can use date to restrict the use of your formulas to a predefined time frame.

Example: the next formula is a variation of the KDJ indicator. It has a restriction date defined inside and works properly only until 2008-12-01.

valid := if(date < 1081201, 1, 0); # valid becomes 0 after 2008-12-01
rsv := (close - llv(low, 9))/(hhv(high, 9) - llv(low, 9)) * 100;
k : sma(rsv, 3, 1) * valid;
d : sma(k, 3, 1) * valid;

datediff(date1, date2)
date1: integer or an array of integers representing dates;
date2: integer or an array of integers representing dates.
this function takes two input dates as its arguments and returns date1 - date2, the day difference number between date1 and date2.

Example: this example shows the difference number between the current date and the date 2008-11-01.

0, colorred;
diff : datediff(date, 1081101); 

Notice that datediff(date1, date2) returns the difference number between date1, date2 in terms of calendar day, i.e., numbers returned by datediff(date1, date2) will include days when markets were closed.

Example: if you want to get the difference number in terms of trading day, you can use the built-in function barslast. The formula below shows the difference between calendar day diff and trading day diff, where the function barslast(close > ma(close, 30)) returns the trading day number since the last occurrence of close > ma(close, 30).

0, colorred;
trading_day_diff : barslast(close > ma(close, 30));
calendar_day_diff : datediff(date, ref(date, trading_day_diff));
Reference Functions
Reference functions calculate data in the past periods in some way to help process data in the current period. We have met some reference functions like ma, ema, hhv, llv etc. Reference functions are the most basic functions in TSFS and are referred to in almost every formula.

count(condition, n)
condition: an array of numbers representing boolean values,
n: period number, it can be either a single integer or an array of integers.
The function counts the number of periods in the last n periods (including the current period) where the condition holds, i.e., condition != 0. If n = 0, the function counts from the first period.

Example: the formula below counts the number of periods in the past 3 periods (including the current period) where price was closed above its 5-day moving averages.

v1 : count(close > ma(close, 5), 3);

Example: the next formula is trying to do some kind of bottom fishing using the 9-day KDJ indicator. The function hhv(high,9) returns the highest high of the last 9 periods, llv(low,9) returns the lowest low of the last 9 periods, k is a percentage measuring how close the close price is to the bottom of the 9-day trading range. k is smoothed to remove noisy values using the function sma and the result is set to the variable d. If d < 20 (20%), it is believed that the price is close to the bottom.

The formula uses two additional conditions to make sure that the stock is in its uptrend and the volume is increasing. So what the final condition cond is saying is that for each period, set cond to 1 if the price is closed above ma(close,3) at least 5% and the 3-day volume average is at least 50% larger than its 20-day average value and in the past 20 periods, the condition d < 20 occurs at least once.

k := (close - llv(low, 9))/(hhv(high, 9) - llv(low, 9)) * 100;
d := sma(k, 3, 1);
j := sma(d, 3, 1); # kdj
cond : close/ma(close, 3) >= 1.05 and
       ma(volume, 3)/ma(volume, 20) > 1.5 and
       count(d < 20, 20) >= 1; 

Example: the next formula compares the close price with the previous close price ref(close,1) and calculates the percentage of periods in the recent 60 periods where the stock price is up, down and in balance.

is_up      := close > ref(close, 1);
in_balance := close = ref(close, 1);
is_down    := close < ref(close, 1);
m          := 60;
up_rate      : 100 * count(is_up, m)/m;
down_rate    : 100 * count(is_down, m)/m;
balance_rate : 100 * count(in_balance, m)/m;

sum(x, n)
x: an array of numbers,
n: period number, it can be either a single integer or an array of integers.
The function sum returns the total sum of x in the last n periods (including the current period). If n = 0, the calculation starts from the first period.

Example: this is an implementation of the standard indicator OBV (On Balance Volume). OBV is designed to track changes in volume over time. It is the running total of volume calculated in such a way as to add the day's volume to a cumulative total if the day's close was higher than the previous day's close and to subtract the day's volume from the cumulative total on down days. The assumption is that changes in volume will precede that in price trend. OBV was created by Joseph Granville and has a number of interpretive qualities and should be used in conjunction with other indications of price trend reversals.

The formula recursively uses the built-in function if and is simply consisting of one line code. The if function has the general form if(condition, a, b). It returns a if condition is true and b otherwise. The function sum is used with 0 as the period number, meaning that it counts the total sum from the beginning.

# OBV
sum(if(close > ref(close, 1), vol, if(close < ref(close, 1), -vol, 0)), 0); 

Example: this example is a RSI-like indicator (Relative Strength Index). It adds the closing price difference from the previous period to two different cumulative totals depending on if the price is closed higher or lower than the previous one. sum is used with the base of 24 periods. The final result is the percentage of advanced cumulative totals.

Note that the function max has the general form max(a,b). It compares a and b and returns the one with the greater value.

polyline(1, 30), coloraa00c8; # draw a horizontal line at position 30
polyline(1, 50), linestyle1, coloraa00c8;
polyline(1, 70), coloraa00c8;
sum_of_advance := sum(max(c - ref(c, 1), 0), 24);
sum_of_decline := sum(max(ref(c, 1) - c, 0), 24);
rsi : sum_of_advance/(sum_of_advance + sum_of_decline) * 100, coloraa00c8;

ref(x, n)
x: an array of numbers,
n: period number, it can be either a single integer or an array of integers.
ref(x, n) refers to the value of x n periods before the current period. For instance, ref(close, 1) refers to the previous closing price; ref(ma(close, 50), 1) is the value of 50-period moving averages of close in the previous period and ref(ref(close, 1), 1) is equivalent to ref(close, 2).

Example: this formula identifies the candlestick pattern where the stock price is closed higher in 3 consecutive days.

cond := c > ref(c, 1) and ref(c, 1) > ref(c, 2) and ref(c, 2) > ref(c, 3); 
drawicon(cond, low, 7), align1; # when cond is true, draw a red flag below the
                                # low price position
Note that this example can be rewritten as the following equivalent one using the count function:
cond := count(close > ref(close, 1), 3) = 3;
drawicon(cond, low, 7), align1;

ma(x, n)
x: an array of numbers,
n: period number, it can be either a single integer or an array of integers with n != 0.
ma(x, n) calculates the n-period simple moving averages of x by adding values of x from the last n periods and dividing the result by n.

Suppose that a stock has the following close prices in the last 3 trading days: $40, $41, $42, then for the last trading day, ma(close, 3) = (40+41+42)/3 = 41. In fact, ma(x, n) = sum(x, n)/n, where n != 0.

Function ma weights data from the last periods evenly and thus is a lagging indicator, i.e., its result does not react quickly to recent price changes. Institutional investors are often using ma(close, 50) and ma(close, 200) to determine the market short and long trends.

Example: in this example, v1 is the 10-day moving averages of close, v2 is the same as ma(ma(c, 10), 10), while v3 is identical to ma(ma(ma(c, 10), 10), 10).

v1 : ma(c, 10);
v2 : ma(v1, 10);
v3 : ma(v2, 10); 

ema(x, n)
x: an array of numbers,
n: period number, it can only be a single integer number.
ema(x,n) calculates the n-period exponential moving averages of x. The exponential moving average is a weighted moving average that puts more emphasis on the current period. Assume ema(t) is the value of ema(x,a) in the current period, x(t) is the value of x in the current period, ema(t-1) is the value of ema(x,a) in the previous period, then
ema(t) = (2*x(t) + (n - 1)*ema(t-1))/(n + 1).

Example: we have mentioned that ma weights data evenly in all periods and is thus a lagging indicator. Because ema put emphasis on the current period, it reacts faster to recent price changes than ma. The following formula demonstrates the difference between ma(close,10) and ema(close,10).

# compare ma and ema
ma10  : ma(close, 10); 
ema10 : ema(close, 10); 

Example: MACD (Moving Average Convergence Divergence) is a trend-following momentum indicator commonly used in technical analysis. It shows the relationship between two exponential moving averages of prices. MACD is calculated by subtracting the value of ema(close,26) from ema(close,12). The 9-period exponential moving averages of the subtracting result is then used as the signal line, functioning as a trigger of buy and sell signals.

# macd
diff : ema(close, 12) - ema(close, 26);
dea  : ema(diff, 9); # signal line
macd : 2*(diff - dea), colorstick; 
sma(x, n, m)
x: an array of numbers,
n: period number, it can only be a single integer number.
m: a single integer number, m < n
sma(x,n,m) is another type of weighted moving averages. It is calculated using the following recursive expression:
sma(t) = (m*x(t) + (n-m)*sma(t-1))/n.
where the time t indicates the current period, while t-1 represents the previous period.

According to the definition, m can be any integer number as long as m < n. In practice, however, m is often set to 1. That means that sma is often used to put more weight on previous periods. This is very different from ema. For this reason, when set m = 1, sma becomes a very lagging function.

Example: the formula below compares the differences between ma, ema and sma. You can see that ema(close,10) reacts fast to recent price changes and is the least lagging indicator. sma(close,10,1) moves far behind price changes and is the most lagging indicator in the chart.

ma10  : ma(close, 10);
ema10 : ema(close, 10);
sma10 : sma(close, 10, 1); 

Example: the function sma has an important use in the standard indicator RSI (Relative Strength Index). RSI is an indicator for overbought/oversold conditions. It is going up when the market is strong, and down, when the market is weak. The market is deemed to be overbought once RSI approaches the 70 level. If RSI approaches 30, it is an indication that the market may be getting oversold. In the following RSI formula, function fillrgn fills the region formed by rsi and horizontal lines 70 and 30 with the specified color, polyline is used to draw 3 horizontal lines.

# RSI
n    := 14;
diff := close - ref(close, 1);
rsi  :=sma(max(diff, 0), n, 1)/sma(abs(diff), n, 1)*100;
fillrgn(rsi, 70), color88aa88;
fillrgn(30, rsi), color88aa88;
polyline(1, 30), color4f4f4f;
polyline(1, 50), linestyle1, color4f4f4f;
polyline(1, 70), color4f4f4f;
rsi, color4f4f4f;

Example: KDJ is another example that uses sma. KDJ is derived from the Stochastic indicator. It draws 3 lines k,d,j and thus the indicator name. The value of j can go beyond [0, 100] in the chart. A negative value of j combined with k,d at the bottom range indicates a strong oversold signal. Likewise, when the j value goes above 100, combined with k,d at the top range, it will indicate a strong over bought signal.

# KDJ
n := 9;
rsv := (close - llv(low, n))/(hhv(high, n) - llv(low, n))*100;
k : sma(rsv, 3, 1);
d : sma(k, 3, 1);
j : 3*k - 2*d; 

dma(x, a)
x: an array of numbers,
a: a single number or an array of numbers with 0 < a < 1.
dma(x,a) calculates dynamic moving averages of x using the following recursive expression:
dma(t) = a*x(t) + (1 - a)*dma(t-1).
where the time t indicates the current period, while t-1 represents the previous period.

dma(x, a) is a generic form of weighted moving average. In fact, both ema and sma can be defined using dma

ema(x, n) = dma(x, 2/(n+1)),
sma(x, n, m) = dma(x, m/n).

Function dma can be used to calculate the market overall average holding cost, an important market fact that is ignored by most stock analysts. Consider the following formula where cyc stores the value of the overall holding cost.

cyc : dma(close, volume/capital);

Suppose stock XYZ with a capital of 100M (100 millions) has the initial price of $10 and is trading at this price on the first day. To reveal the meaning of cyc more clearly, let's suppose that for some reason it is trading 50% up and closed at $15 on the second day with a light volume of 1M. Then the simple moving average ma of XYZ is

(10 + 15) / 2 = $12.50.

On the other side, since capital = 100M and volume = 1M, according to the definition of dma,

cyc = 1/100 * 15 + (1 - 1/100) * 10 = $10.05.

Because on the second day, only 1M shares of XYZ have been exchanged with the price of $15, 99M shares of XYZ are still held with $10, the overall average holding cost of XYZ is

(1 * 15 + 99 * 10) / 100 = $10.05
That is the same as cyc. It should be clear to see that cyc describes the real value of XYZ more precisely. ma is inaccurate because it does not consider trading volumes.

The concept of holding cost is very important. TSFS has predefined a formula CYC that we will talk about in the next chapter.

Example: cyc can be very different from ma. This is specially true when the price is volatile with light volumes. The following example demonstrates the differences between ma(close,50), ma(close,200) and cyc.

ma50  : ma(close, 50);
ma200 : ma(close, 200);
cyc   : dma(close, volume/capital), colormagenta; 

hhv(x, n)
llv(x, n)
x: an array of numbers,
n: period number, it can be either a single integer or an array of integers.
Function hhv(x,n) returns the highest value of x in the last n periods (including the current period), while function llv(x,n) returns the lowest value of x in the last n periods (including the current period). If n = 0, the calculation starts from the first period.

Example: hhv and llv are often used to determine the current price position in the trading range of the last n days. In the following formula, rsv indicates how close the current closing price is to the bottom of the 9-day trading range.

n := 9;
polyline(1, 20), colorred;
polyline(1, 80), colorred;
rsv : (close - llv(low, n))/(hhv(high, n) - llv(low, n))*100;

Example: this example displays a channel using the 10-day highest high price as its top line and the 10-day lowest low price as its bottom line.

n := 10;
top_line : hhv(h, n);
bot_line : llv(l, n);

Example: the next example picks stocks based on 9-day KDJ and a number of other conditions. The cond becomes true when all the conditions hold.

# KDJ stock pick
n := 9;
rsv := (close - llv(low, n))/(hhv(high, n) - llv(low, n))*100;
k := sma(rsv, 3, 1);
d := sma(k, 3, 1);
j := 3*k - 2*d;
ma5  := ma(close, 5);
ma10 := ma(close, 10);
cond : ma5 > ref(ma5, 1)*1.01 and ma10 > ref(ma10, 1)*1.005 and d < 90 and
       j > k*1.05 and k > d and j > ref(j, 1)*1.1 and
       k > ref(k, 1)*1.05 and d > ref(d, 1)*1.05 and
       close > ref(close, 1)*1.03 and vol > ref(ma(vol, 20), 1);

hhvbars(x, n)
llvbars(x, n)
x: an array of numbers,
n: period number, it has to be a single integer number.
These two functions counts period numbers in the past since the period where hhv(x,n) and llv(x,n) occur, respectively. If hhv(x,n) or llv(x,n) occurs in the current period, then the number returned will be 0.

Example: this is to show the period numbers since the last 20-day highest high and 20-day lowest low. In the chart you can see how the returning numbers are changing.

# william's R
hh : hhvbars(high, 20);
ll : llvbars(low, 20);

sumbars(x, a)
x: an array of numbers,
a: a single number.
Function sumbars(x,a) counts the period number n in the past so that sum(x, n) >= a.

Example: this example is another approach to measure the market tradability and liquidity. The day_no is the number of trading days the market needs to have a cumulative total as capital. Obviously, the smaller day_no is, the more active is the market.

day_no : sumbars(vol, capital);

Example: the next example picks stocks (when dd is 1) based on the believe that when price goes down and OBV moves up, a rally is likely coming.

obv:= sum(if(close>ref(close, 1), vol, if(close<ref(close, 1), -vol, 0)), 0);
dn := sumbars(vol, capital);
aa := if(c > llv(c, dn), 1, -1);
bb := if(obv > llv(obv, dn), 1, -1);
cc := aa*bb;
dd : if(cc=-1, 1, 0), colorstick;

barscount(x)
x: an array of numbers.
The function barscount(x) counts the period number since the first period of x.

Example: in the formula below, no is the day number since the stock inception.

no : barscount(close);

Note that for the performance reason TSFS does not always load all the historical price data when calculating barscount. Internally, it loads historical data from the charts starting date two years ago. As a consequence, the value of no is precise only if the stock inception date is falling in the interval starting from the charts starting date two years ago to the charts end date.

barssince(x)
barslast(x)
x: an array of numbers,
n: period number, it has to be an integer.
Function barssince(x) counts the period number since the first x != 0, while function barslast(x) counts the period number since the last x != 0.

Example: the formula below demonstrates the use of barssince and barslast, where a equals 1 only in the last 10 days in the chart, the value of b is climbing in the last 10 days. d is the day number since the last occurrence of close = hhv(close, 5).

a := backset(islastbar, 10);
b :  barssince(a);
d :  barslast(close = hhv(close, 5));

backset(x, n)
x: an array of numbers,
n: period number, it can be either an integer or an integer array, n != 0.
Function backset(x,n) resets data in the past. If the current period is t, x(t) != 0 and y is the array returned by backset, then y(t) = 1, y(t-1) = 1, y(t-2) = 1,.., y(t-n+1) = 1.

backset is one of the two functions (another one is zig) in TSFS that use future data to calculate results in the current period and therefore should not be used in formulas that generate signals.

Example: the formula in this example shows a happy face 6 days before ma(c,5) is crossing above ma(c,10).

cond1 := cross(ma(c, 5), ma(c, 10));
cond2 := backset(cond1, 6);
cond3 := (cond2 > ref(cond2, 1));
drawicon(cond3, l*0.95, 1);

Example: this example draws a happy face above the last 30-day highest high and a sad face below the last 30-day lowest low in the chart. In the example, islastbar returns true if the current period is the last period in the chart, backset returns 1s after last hhv(h,30) and llv(l,30) occur. The results are stored in variables a1 and a2. You can see how a1 and a2 are changing values in subcharts below the main chart. The count function makes sure that condition1 and condition2 are set to true only when a1 and a2 switch from 0 to 1. drawicon plots icons when conditions are true.

a1 := backset(islastbar, hhvbars(high, 30)+1);
condition1 := count(a1, 30)=1;
drawicon(condition1, high, 1), align2;

a2 := backset(islastbar, llvbars(low, 30)+1);
condition2 := count(a2, 30)=1;
drawicon(condition2, low, 2), align1;

filter(condition, n)
condition: an array of numbers,
n: period number, it can be either an integer or an integer array, n != 0.
Function filter(condition, n) returns the first true value of condition and filter out all the m consecutive true values of condition within n periods, where m < n. If for the period t, condition(t) != 0, condition(t+1) != 0, .., condition(t+m) != 0 and y is the array returned by filter, then y(t) = 1, y(t+1) = 0, .., y(t+m) = 0.

Example: in this example, condition is true if the price is closed up. The variable result is set to 1 for the first true condition and other consecutive true conditions are ignored.

condition := c > ref(c, 1);
result    :  filter(condition, 4), colorbrown;

Example: the next example highlights the 11-day highest high and the 11-day lowest low with small blue and red balls, respectively. It then draws two horizontal lines as the resistant line and the support line using the function drawline.

n  := 5;
aa := ref(h, n)=hhv(h, 2*n+1);
bb := backset(aa, n+1);
cc := filter(bb, n) and h=hhv(h, n+1);
drawicon(cc, h, 10), align2;
aa2 := ref(l, n)=llv(l, 2*n+1);
bb2 := backset(aa2, n+1);
cc2 := filter(bb2, n) and l=llv(l, n+1);
drawicon(cc2, l, 11), align1;
drawline(cc, h, islastbar, ref(h, barslast(cc)), 1); # resistant line
drawline(cc2, l, islastbar, ref(l, barslast(cc2)), 1); # support line 
Logical Functions
Logical functions are used to analyze the relationship between different values and their relative positions in charts. Except the if function, logical functions return boolean values that are either true (1) or false (0).
if(condition, a, b)
condition: a single number or an array of numbers,
a: a single number or an array of numbers,
b: a single number or an array of numbers.
The function if(condition, a, b) returns a if condition != 0, b otherwise.

Example: the following formula is an alternative implementation of the indicator RSI, where d is set to diff only if diff > 0; likewisely, e = -diff only if diff < 0.

n  := 24;
diff := c - ref(c, 1);
d  := if(diff > 0, diff, 0);
e  := if(diff < 0, -diff, 0);
a  := ma(d, n);
b  := ma(e, n);
rsi:= 100*a/(a+b);
rsi;

cross(a, b)
a: a single number or an array of numbers,
b: a single number or an array of numbers.
The function cross(a, b) returns 1 if a crosses above b, i.e., in the previous period a < b and in the current period a > b. The function returns 0 otherwise.

Example: this formula displays small red flags when ma(c, 5) is crossing above ma(c, 20).

ma5  : ma(c, 5);
ma20 : ma(c, 20);
cond := cross(ma5, ma20);
drawicon(cond, ma(c, 5), 7), align1;

not(condition)
condition: a single number or an array of numbers.
The function not(condition) returns the opposite value of condition, i.e., when condition = 0, it returns 1, otherwise 0.

Example: in the formula below, b always has the opposite value of a.

a : close > ref(close, 1);
b : not(close > ref(close, 1));

isup, isequal, isdown
functions do not have any parameters.
These three functions are provided for easy reference. isup is equivalent to the comparison expression close > open. It returns 1 when the price is closed above the open price and returns 0 otherwise. Likewisely, isequal is equivalent to close = open and isdown is equivalent to close < open.

Example: the following formula draws candlesticks using different colors depending on whether or not the price is closed above, equal to or below the open price.

stickline(isequal and c>=ref(c, 1), c, c, 6, 1), colorblack;
stickline(isequal and c<ref(c, 1), c, c, 6, 1), colorred;
stickline(isup, h, c, 0, 0), colorblack;
stickline(isup, l, o, 0, 0), colorblack;
stickline(isup, c, o, 6, 1), colorblack;
stickline(isdown, h, l, 0, 0), colorred;
stickline(isdown, o, c, 6, 0), colorred;

islastbar
This function is used to check if the current period is the last period in the chart. It returns 1 if yes, otherwise, it returns 0. For example:
drawicon(islastbar, low, 1), align1;

isusmarket
iscamarket
isusmarket and iscamarket are provided to give the user a way to generate signals selectively. isusmarket returns 1 if it is applied to stocks in US markets, it returns 0 otherwise. iscamarket returns 1 if it is applied to stocks in Canadian markets and returns 0 otherwise.

isusmarket and iscamarket are useful if you just want your formulas to generate signals for US or CA markets when scanning stocks. The following example shows how you can use isusmarket and iscamarket in your formulas.

us_condition := ... 
ca_condition := ... 
singal_in_us_market: us_condition and isusmarket; 
singal_in_ca_market: ca_condition and iscamarket; 

between(x, a, b)
range(x, a, b)
x: a single number or an array of numbers,
a: a single number or an array of numbers,
b: a single number or an array of numbers.
Function between(x,a,b) returns 1 if either a <= x <= b or b <= x <= a is true. Otherwise, it returns 0. Function range(x,a,b) is more specific. It returns 1 only if a <= x <= b.

It is sometimes preferable to use between rather than range because between returns 1 as long as x is located in the interval formed by a and b, regardless of whether a < b or not.

Example: this is to show the difference between between and range.

a : between(close, ma(close, 5), ma(close, 30));
b : range(close, ma(close, 5), ma(close, 30)) * 2;

exist(condition, n)
every(condition, n)
condition: an array of numbers,
n: period number, it can be either an integer or an integer array.
Function exist(condition, n) returns 1 if there exists at least one period in the last n periods where condition is true, i.e., condition != 0. Otherwise, it returns 0. Function every(condition, n) returns 1 only if for every period in the last n periods, condition != 0.

exist(condition, n) is equivalent to the comparison expression count(condition, n) > 0, while every(condition, n) is equivalent to count(condition, n) = n.

Example: the following simple formula demonstrates the use of exist and every.

a : exist(c < ref(c, 1), 3);
b : every(c < ref(c, 1), 3) * 2;

last(condition, n1, n2)
condition: an array of numbers,
n1: period number, it has to be a single integer number,
n2: period number, it has to be a single integer with n2 <= n1.
Function last(condition, n1, n2) returns 1 if condition is always true from the previous n1-th period to the previous n2-th period, It returns 0 otherwise.

Example: this example checks if the market was up on the last 2 trading days.

last(isup, 3, 2);

longcross(a, b, n)
a: a single number or an array of numbers,
b: a single number or an array of numbers,
n: period number, it has to be a single integer.
The function longcross(a,b,n) returns 1 if in the current period, a > b and in the previous n periods, a < b. It returns 0 otherwise. longcross(a,b,n) is equivalent to the conjunction expression
cross(a, b) and last(a < b, n, 1)

Example: this example is to verify that longcross(a,b,n) is equivalent to "cross(a,b) and last(a<b,n,1)". In the formula, ma5, ma10 are the 5-day and 10-day moving averages, cross is the result of the conjunction expression, long_cross is the return value of longcross. long_cross is multiplied by 2 so that it will not overlap cross in the chart.

ma5  := ma(c, 5);
ma10 := ma(c, 10);
a := cross(ma5, ma10);
b := last(ma5 < ma10, 5, 1);
cross:a and b;
long_cross : longcross(ma5, ma10, 5) * 2;