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 2014-11-08 and 2014-11-18.

vertline(date >= 1141108 and date <= 1141118);

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 2014-10-01.

valid := if(date < 1141001, 1, 0); # valid becomes 0 after 2014-10-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 2014-09-01.

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

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;
Math Functions
TSFS does not provide a long list of math functions because most complicated calculations are integrated into different functions. This section covers some very basic math functions you may need when writing your custom formulas.
abs(x)
x: a single number or an array of numbers.
Function abs(x) returns the absolute value of x.

max(a, b)
min(a, b)
a: a single number or an array of numbers,
b: a single number or an array of numbers.
Function max(a,b) compares the value of a and b and returns the one with greater value. Similarly, function min(a,b) compares the value of a and b and returns the one with smaller value.

Functions max and min only take two arguments. If you have more values to compare, use for example max(max(a, b), c) to get the maximum value of a,b,c or min(min(a, b), c) to get the minimum value of a,b,c.

Example: the indicator ATR (Average True Range) was created by J. Welles Wilder. Its primary use is for determining the volatility of the market. The idea is to replace the high-low trading range for the given period, as it does not consider gaps and limit moves. The true range in ATR takes the previous close into account and is calculated in the following way.

max(max((high-low), abs(ref(close,1)-high)), abs(ref(close,1)-low))
What follows is an implementing of ATR.
# ATR
n  := 14;
tr := max(max((high - low), abs(ref(close, 1) - high)), abs(ref(close, 1) - low));
sma(tr, n, 1), linethick2, colorbrown;

Example: DMI (Directional Movement Index) is another indicator developed by J. Welles Wilder for identifying when a definable trend is present in a stock. The following formula is an implementation of DMI.

# DMI
tr  := sum(max(max(high - low, abs(high - ref(close, 1))), abs(low - ref(close, 1))), 14); 
hd  := high - ref(high, 1); 
ld  := ref(low, 1) - low; 
dmp := sum(if(hd > 0 and hd > ld, hd, 0), 14); 
dmm := sum(if(ld > 0 and ld > hd, ld, 0), 14); 
pdi : dmp*100/tr; 
mdi : dmm*100/tr; 
adx : ma(abs(mdi - pdi)/(mdi + pdi)*100, 6); 
adxr:(adx + ref(adx, 6))/2;

Example: the next formula is an implementation of the indicator DDI (Directional Divergence Index).

# DDI
tr := max(abs(h - ref(h, 1)), abs(l - ref(l, 1)));
dmz := if((h + l) <= (ref(h, 1) + ref(l, 1)), 0, tr);
dmf := if((h + l) >= (ref(h, 1) + ref(l, 1)), 0, tr);
diz := sum(dmz, 13)/(sum(dmz, 13) + sum(dmf, 13));
dif := sum(dmf, 13)/(sum(dmf, 13) + sum(dmz, 13));
ddi : diz - dif;
fillrgn(1, ddi, 0), transparency6;
addi : sma(ddi, 30, 10);
ad   : ma(addi, 5); 

reverse(x)
x: a single number or an array of numbers
Function reverse(x) returns -x.

Example: what follows is the MFI (Money Flow Index) indicator. Because the variable rvs is assigned as reverse(mfi), it displays the opposite shadow of mfi in the chart.

# MFI
polyline(1, 0), colorblack;
n    := 20;
l_va := if(h > l, (2*c-(h+l))/(h-l)*v, 0);
mfi  : sum(l_va, n)/sum(v, n);
rvs  : reverse(mfi); 

sgn(x)
x: a single number or an array of numbers.
Function sgn(x) returns 1, 0 or -1 if x > 0, x = 0 or x < 0, respectively.

Example: this is the MFI indicator again, with the sign of mfi displayed in the chart.

# MFI
polyline(1, 0), colorred;
n    := 20;
l_va := if(h > l, (2*c-(h+l))/(h-l)*v, 0);
mfi  : sum(l_va, n)/sum(v, n)*3; # MFI
sign : sgn(mfi); 

intpart(x)
ceiling(x)
floor(x)
x: a single number or an array of numbers.
All the three functions return integer numbers. The function intpart(x) returns the integer part of x, ceiling(x) rounds x up, and floor(x) rounds x down. For example, if x is an array of (12.3, 0.6, -3.5), then intpart(x) returns (12, 0, -3), ceiling(x) returns (13, 1, -3), and floor(x) returns (12, 0, -4).

mod(x, divisor)
x: a single number or an array of numbers.
divisor: a single number or an array of numbers.
The function mod(x,divisor) returns the remainder of x divided by divisor. Note that when calculating, x and divisor are first casted to integer numbers. For example, if x is an array of (12.3, 0.6, -3.5), then intpart(x,10) will return (2, 0, -3).

Example:

a := intpart(c*10);
b := mod(c*100, 10);
d := if(b < 5, 0, 1);
close_price : c;
rounded : (a+d)*0.1;

pow(x, n)
x: a single number or an array of numbers.
n: a single number.
The function pow(x,n) returns the value of x raised to the power of n. For example,
pow(c, 0.5);

log(x)
ln(x)
x: a single number or an array of numbers.
The function log(x) returns the common logarithm (10-based) of x, ln(x) returns the natural logarithm of x. For example,
log : log(c);
ln  : ln(c)/2; 
Statistic Functions
In technical analysis, sometimes we need static functions to analyze the price movement in the past. This section describes statistic functions provided by TSFS.
avedev(x, n)
x: an array of numbers,
n: period number
Function avedev(x,n) calculate the average of the absolute deviations of x in n periods.

Example: The Commodity Channel Index (CCI) uses avedev to measure the position of price in relation to its moving average. This can be used to highlight when the market is overbought/oversold or to signal when a trend is weakening. The indicator is similar in concept to Bollinger Bands but is presented as an indicator line rather than as overbought/oversold levels. CCI was developed by Donald Lambert.

n   := 20;
typ := (high + low + close)/3;
cci := (typ-ma(typ,n))/(0.015*avedev(typ,n));
fillrgn(cci, 100), color963264;
fillrgn(-100, cci), color963264;
cci, colorf05a5a; 

std(x, n)
x: an array of numbers,
n: period number
Function std(x,n) estimates the standard deviation of x in n periods.

Example: The well known Bollinger Bands indicator (BOLL) uses std to calculate the upper and lower bands.

n := 20;
p := 2; 
l_mid   := ma(close, n); 
l_upper := l_mid + p*std(close, n); 
l_lower := l_mid - p*std(close, n); 
fillrgn(l_upper, l_lower), color6464fa, transparency9; 
polyline(1, l_mid), linestyle2, color9494fa; 
polyline(1, l_upper), color9494fa; 
polyline(1, l_lower), color9494fa; 

Example: you can also use std to define your custom boll-like indicators.

n := 10;
p := 3;
bbi:(ma(close,3)+ma(close,6)+ma(close,12)+ma(close,24))/4;
upr:bbi+p*std(bbi,n);
dwn:bbi-p*std(bbi,n);
var(x, n)
x: an array of numbers,
n: period number
Function var(x,n) estimates the variance of x in n periods. The root of var(x,n) is very close to std(x,n).

Example: this example compares the root of var(x,n) and std(x,n) and save the difference in diff. You can see that diff is very close to the 0-line.

# STD(X, N) is very close to (VAR(X, N))^(1/2)
n    := 5;
a    := pow(var(c, n), 0.5);
b    := std(c, n);
diff : a-b, colorred;

slope(x, n)
x: an array of numbers,
n: period number
The function slope(x,n) uses the least squares method to find the best fitting line of x in n periods and returns the slope of the line.

Example: the following formula displays the slope of the 20-day best fitting line of close. For a more meaningful example of slope, refer to the discussion of TREND.

0, colorred;
slp : slope(close, 20); 

relate(x, n)
relate(x, y, n)
x: an array of numbers,
y: an array of numbers,
n: period number
The function relate calculates the correlation coefficient. The correlation coefficient is used to measure "goodness-of-fit" in pattern-fitting procedures. relate(x,n) checks the match of x with ascending straight lines in n periods, while relate(x,y,n) measures the match of x with y in n periods. The returning value of relate ranges between 1 and -1, where a value of 1 indicates a perfect match, i.e., the two patterns are identical. A value of -1 indicate that an exact match had been found, but that it is "upside-down". Values near zero mean there is no match at all.

In practice it has been found that values of 0.8 or more correspond to patterns in the data that are easily discerned as "good matches" by the human eye.

Example: an interesting use of relate is that you can let TSFS find all the stocks whose price trend closely matches that of the Nasdaq Composite Index in the last 20 days. In the example, ochl is a formula predefined in TSFS, "^ixic$ochl.c" is the close price of Nasdaq Composite Index. If you save this formula in your account, TSFS will report matching stocks to you by generating match signals everyday!

n := 20;
sigline : 0.8, colorred;
market_c := "^ixic$ochl.c";
rel : relate(c, market_c, n); # generate "match" signals
signal_match : rel > sigline;

Example: with little changes the above formula becomes even more interesting and can be used to track down stocks who followed the nasdaq index trend closely until yesterday (because of the use of ref(c,m) with m = 1). By watching out the index move today, you can "foresee" where your stocks are going to head to tomorrow.

n := 20;
m := 1;
sigline : 0.8, colorred;
market_c := "^ixic$ochl.c";
rel : relate(ref(c, m), market_c, n);
signal_match : rel > sigline;
Indicator Functions
To protect you from writing some tedious but useful formulas you often need, TSFS provides a list of indicator functions that you can use directly in your formulas.
zig(x, n)
zig(n)
x: an array of numbers,
n: a percentage number
The function zig(x,n) returns results representing zigzag lines which reverse when the change of x is more than n%. The function zig(n) is a short form of zig(x,n) and uses high and low prices to return zigzag lines. Function zig filters out random noise and daily price fluctuations and can be used to identify waves for Elliott Wave counts. Please note that zig uses future data to determine when to reverse zigzag lines. Because of this, zig should not be used in any formulas generating trading signals.

Example: this example shows the difference between zig(x,n) and zig(n). Notice that peaks and troughs of zig(3) appear at high and low prices, while zig(low, 3) always uses the low price.

z1 : zig(3), linethick2;
z2 : zig(low, 3); 

Example: function zig(x,n) is flexible so that you do not have to always apply it to price data. In the following formula, it is applied to 5-day moving averages.

ma: ma(close, 5);
zig(ma, 2);

peak(x, n, m)
peakbars(x, n, m)
peak(n, m)
peakbars(n, m)
x: an array of numbers,
n: a percentage number
m: an integer number.td>
These four functions are all about peaks of zigzag lines and are thus closely related to functions zig(x,n) and zig(n). The function peak(x,n,m) returns the mth last peak value of zig(x,n), while peakbars(x,n,m) returns the period number between the current period and the mth last peak of zig(x,n). peak(n,m) and peakbars(n,m) are short forms of the above two functions and use high and low prices to return the mth last peak value and its period number of zig(n).

Example: this example includes two formulas. In the main chart, zig(p) is plotted with the brown color. peak(p,1) (the green line) changes its value when a new peak is encountered. peak(p,2) (the red line) follows the green line because peak(p,2) returns the second last peak value of zig(p). The period numbers are plotted by the second formula in the subchart below. Colors in the subchart matches that in the main chart. So you see how the period numbers are changing.

p := 3.5; # percentage
polyline(1,zig(p)), colorlightbrown;
peak_value_1 : peak(p,1), colorgreen;
peak_value_2 : peak(p,2), colorred;
p := 3.5; # percentage
peak_period_1 : peakbars(p,1), colorgreen;
peak_period_2 : peakbars(p,2), colorred;

Example: the formula below draws two bold lines from the third last peak to the second last peak and from second last peak to the last peak.

p  := 3.5;
zig(p), colorlightbrown;
a1 := backset(islastbar, peakbars(p, 1) + 1);
a2 := a1 > ref(a1, 1);
b1 := backset(islastbar, peakbars(p, 2) + 1);
b2 := b1 > ref(b1, 1);
d1 := backset(islastbar, peakbars(p, 3) + 1);
d2 := d1 > ref(d1, 1);
drawline(d2, h, b2, h, 1), linethick2;
drawline(b2, h, a2, h, 1), linethick2;

trough(x, n, m)
troughbars(x, n, m)
trough(n, m)
troughbars(n, m)
x: an array of numbers,
n: a percentage number
m: an integer number.
These four functions are the opposite of that of peak and peakbars. The function trough(x,n,m) returns the mth last trough value of zig(x,n), while troughbars(x,n,m) returns the period number between the current period and the mth last trough of zig(x,n). trough(n,m) and troughbars(n,m) are short forms of the above two functions and use high and low prices to return the mth last trough value and its period number of zig(n).

Example: this example includes two formulas. In the main chart, zig(p) is plotted with the brown color. trough(p,1) (the green line) changes its value when a new trough is encountered. trough(p,2) (the red line) follows the green line because trough(p,2) returns the second last trough value of zig(p). The period numbers are plotted by the second formula in the subchart below. Colors in the subchart matches that in the main chart. So you see how the period numbers are changing.

p := 3.5; # percentage
polyline(1,zig(p)), colorlightbrown;
trough_value_1 : trough(p,1), colorgreen;
trough_value_2 : trough(p,2), colorred;
p := 3.5; # percentage
trough_period_1 : troughbars(p,1), colorgreen;
trough_period_2 : troughbars(p,2), colorred;

Example: the formula below draws two bold lines from the third last trough to the second last trough and from second last trough to the last trough.

p  := 3.5;
zig(p), colorlightbrown;
a1 := backset(islastbar, troughbars(p, 1) + 1);
a2 := a1 > ref(a1, 1);
b1 := backset(islastbar, troughbars(p, 2) + 1);
b2 := b1 > ref(b1, 1);
d1 := backset(islastbar, troughbars(p, 3) + 1);
d2 := d1 > ref(d1, 1);
drawline(d2, l, b2, l, 1), linethick2;
drawline(b2, l, a2, l, 1), linethick2;

peaktrough(x, n, m)
peaktroughbars(x, n, m)
peaktrough(n, m)
peaktroughbars(n, m)
x: an array of numbers,
n: a percentage number
m: an integer number.
These four functions are the combination of functions peak, trough and peakbars, troughbars. peaktrough(x,n,m) returns the mth last peak/trough value of zig(x,n), while peaktroughbars(x,n,m) returns the period number between the current period and the mth last peak/trough of zig(x,n). peaktrough(n,m) and peaktroughbars(n,m) are short forms of the above two functions and use high and low prices to return the mth last peak/trough value and its period number of zig(n).

Example: this shows both zig(p) and peaktrough(p,1) in the main chart. peaktrough(p,1) (the green line) changes its value when a new peak or trough is encountered.

p := 3.5;
polyline(1,zig(p)), colorlightbrown;
h1:peaktrough(p,1), colorgreen;

sar(n, s, m)
sarturn(n, s, m)
n: period number,
s: a single number representing step,
m: a single number representing step limit, s < m
sar stands for parabolic stop-and-reverse. It returns trailing stop positions that follow a prevailing trend. sar was developed by J. Welles Wilder. Basically, if the stock is trading below sar, you should sell. If the stock price is above sar, you should buy or stay long.

Function sar(n,s,m) uses hhv(high,n) and llv(low,n) in the computation. The initial and incremental step is s%, the maximum step is m%. In practice, s and m are often set to 2 and 20.

Function sarturn(n,s,m) returns 1, when sar(n,s,m) turns to long; -1, when sar(n,s,m) turns to short; 0, otherwise.

Example: sar is always displayed in the main chart. The following formula plots small blue balls when sar(10,2,20) turns to long and small red balls when it turns to short.

s := sar(10, 2, 20);
st := sarturn(10, 2, 20);
s, pointdot, linethick4;
drawicon(st = 1, s, 10), align1;
drawicon(st = -1, s, 11), align2;

Example: this is another example using sar with a different period number and colors.

# color sar
csar : sar(3, 2, 20), linethick0;
drawicon(csar >= h, csar, 11);
drawicon(csar <= l, csar, 10);
cond1 := backset(sarturn(3, 2, 20)<>0, 2);
cond2 := ref(cond1, 1) < cond1;
drawicon(cond2, csar, 12);

cost(n)
winner(x)
n: a percentage number,
x: a single number or an array of numbers.
The calculation of functions cost and winner are based on the cumulative total volume in the indicator of CYQ. The function cost(n) returns the cost price under that there are n% of the shares displayed in the CYQ chart. The function winner(x) returns numbers from 0-1 indicating the percentage of shares displayed in the CYQ chart that are held below the price x.

For example, if cost(50) returns the price p, then 50% of total volume in CYQ were accumulated under the price p, and another 50% of the total volume were trading above p. Obviously, winner(p) = 0.5.

Because CYQ is dependent on capital, functions cost and winner are available only if capital is present for the given stock.

Example: Function cost is directly related to trading volumes. Therefore, price changes with light volumes have little effect on cost. The following example shows cost with different parameters in the main chart.

cost20 : cost(20);
cost50 : cost(50);
cost80 : cost(80);

Example: Crowded trading areas often serve as resistance or support. When the price penetrates a crowded trading area, it is expected to see a high volume. If the volume is light, it is suspected that the stock is under control of some big investors or market makers. The formula below exams if the trading range penetrates a 15% volume interval in the CYQ chart with a trading volume relatively small. If this is the case, it displays a small blue ball on the top of the penetrating volume avol.

polyline(1, 20), linestyle2;
pvol := vol/capital*100;
avol : abs(winner(close)-winner(open))*100, volstick;
control := avol >= 15 and pvol/ma(pvol, 30) < 2 and pvol < 2;
drawicon(control, avol, 10), align2;
Drawing Functions
This set of functions are provided to draw calculation results in charts. You can use them to draw lines, icons, candlesticks, text strings etc. in different parts of charts based on your conditions. Together with drawing descriptors that will be covered in the next section, you have all the freedom to produce clear, informative and colorful charts for your custom indicators.
drawicon(cond, pos, icon_idx)
cond: a single number or an array of numbers,
pos: a single number or an array of numbers,
icon_idx: integer number from 0-12.
The function drawicon draws an icon specified by icon_idx at the position indicated by pos when the condition cond holds, i.e., cond != 0. The number of icon_idx has the following meaning:
  • 0: happy face;
  • 1: happy face;
  • 2: sad face;
  • 3: face;
  • 4: blue up arrow;
  • 5: red down arrow;
  • 6: blue flag;
  • 7: red flag;
  • 8: blue up triangle:
  • 9: red down triangle;
  • 10: blue boll;
  • 11: red boll;
  • 12: green boll;

Example: this is to draw all the different icons on trading days from 2005-10-11 to 2005-10-27 at the position 10% higher than the high price.

drawicon(date=1051011, 1.01*high, 0);
drawicon(date=1051012, 1.01*high, 1);
drawicon(date=1051013, 1.01*high, 2);
drawicon(date=1051014, 1.01*high, 3);
drawicon(date=1051017, 1.01*high, 4);
drawicon(date=1051018, 1.01*high, 5);
drawicon(date=1051019, 1.01*high, 6);
drawicon(date=1051020, 1.01*high, 7);
drawicon(date=1051021, 1.01*high, 8);
drawicon(date=1051024, 1.01*high, 9);
drawicon(date=1051025, 1.01*high, 10);
drawicon(date=1051026, 1.01*high, 11);
drawicon(date=1051027, 1.01*high, 12);

Example: The next formula calls the predefined formula KDJ and marks buy signals with happy faces and sell signals with sad faces. The descriptor align1 indicates that icons are to be drawn at the position a bit lower than the low price.

k := "KDJ.K"(20, 3, 3);
d := "KDJ.D"(20, 3, 3);
j := "KDJ.J"(20, 3, 3); 
buy  := d < 20 && cross(k, d);
sell := d > 80 && cross(d, k);
drawicon(buy, low, 1), align1;
drawicon(sell, low, 2), align1;

drawline(cond1, pos1, cond2, pos2, expand)
cond1: a single number or an array of numbers,
pos1: a single number or an array of numbers,
cond2: a single number or an array of numbers,
pos2: a single number or an array of numbers.
expand: integer number 0 or 1.
The function drawline draws line segments starting at position pos1 where the condition cond1 holds and terminating at position pos2 where the condition cond2 holds. The two positions have to be in different periods. If expand != 0, the function expands line segments from position pos2 to the chart board.

Example: the following formula draws an expanded trend line. The line starts from the position where the high price is the highest high in the last 80 days. The starting position is marked with a small blue ball using the drawing function drawicon(cond1, h, 10). The line then goes through the second position where the high price is the highest high in the last 40 days. The second position is marked with a small red ball using the function drawicon(cond2, h, 10).

n := 80; m := 40;
a1 := backset(islastbar, hhvbars(h, n)+1);
a2 := backset(islastbar, hhvbars(h, m)+1);
cond1 := count(a1, 2) = 1;
cond2 := count(a2, 2) = 1;
drawicon(cond1, h, 10), align2; # mark the first position with a blue ball
drawicon(cond2, h, 11), align2; # mark the first position with a red ball
drawline(cond1, h, cond2, h, 1); 

Example: if you loose conditions for cond1 and cond2, you will see a lot of line segments drawn in the chart.

cond1 := c > ref(c, 1);
cond2 := c < ref(c, 1);
drawicon(cond1, h, 10), align2; # mark the first position with a blue ball
drawicon(cond2, h, 11), align2; # mark the first position with a red ball
drawline(cond1, h, cond2, h, 1); 

polyline(cond, pos)
cond: a single number or an array of numbers,
pos: a single number or an array of numbers.
The function polyline draws connected line segments using pos as vertices where the condition cond holds.

Example: the formula in this example first identifies high prices that occur 2 days ago and are the 5-day highest high. The identified high prices are marked with small blue balls and then the function polyline is called to draw line segments passing through these vertices.

cond := backset(ref(high, 2) = hhv(high, 5), 3);
drawicon(cond > ref(cond, 1), h, 10), align2;
polyline(cond > ref(cond, 1), h), colorblue, linestyle1;

Example: this example draws a set of different moving averages of ma(close,5). Because the constant number 1 is used as the condition, the condition holds in every period and polyline draws all the moving averages in all periods.

ma(close, 5), linethick2, colorgreen;
polyline(1, ma(ma(close, 5), 2)), colorgreen;
polyline(1, ma(ma(close, 5), 4)), colorgreen;
polyline(1, ma(ma(close, 5), 6)), colorgreen;
polyline(1, ma(ma(close, 5), 8)), colorgreen;
polyline(1, ma(ma(close, 5), 10)), colorgreen;
polyline(1, ma(ma(close, 5), 12)), colorgreen;
polyline(1, ma(ma(close, 5), 14)), colorgreen;
polyline(1, ma(ma(close, 5), 16)), colorgreen;
polyline(1, ma(ma(close, 5), 18)), colorgreen;
polyline(1, ma(ma(close, 5), 20)), colorgreen;
polyline(1, ma(ma(close, 5), 22)), colorgreen;
polyline(1, ma(ma(close, 5), 24)), colorgreen;
polyline(1, ma(ma(close, 5), 26)), colorgreen;
polyline(1, ma(ma(close, 5), 28)), colorgreen;
polyline(1, ma(ma(close, 5), 30)), colorgreen;
polyline(1, ma(ma(close, 5), 32)), colorgreen;

Example: this example demonstrates how to use polyline to draw horizontal lines. It highlights the recent trading range with two horizontal lines using the last 40-day high and 40-day low prices in the chart. Because of the use of backset, the condition count(a1, 2)>0 always holds since the last occurrence of hhv(h,n) and so is the condition count(a2, 2)>0 since the last occurrence of llv(l,n). Because ref(h,bars1) and ref(h,bars2) will always return the same hhv(h,n) and llv(l,n), respectively, polyline displays two horizontal lines in the chart.

# draw horizontal lines without using drawline
n     := 40;
a1    := backset(islastbar, hhvbars(h, n)+1);
cond1 := count(a1, 2) = 1;
bars1 := barslast(cond1);
a2    := backset(islastbar, llvbars(l, n)+1);
cond2 := count(a2, 2) = 1;
bars2 := barslast(cond2);
drawicon(cond1, h, 10), align2;
drawicon(cond2, l, 11), align1;
polyline(count(a1, 2) > 0, ref(h, bars1)); 
polyline(count(a2, 2) > 0, ref(l, bars2));

stickline(cond, pos1, pos2, width, empty)
cond: a single number or an array of numbers,
pos1: a single number or an array of numbers,
pos2: a single number or an array of numbers,
width: a single positive integer number,
empty: a single integer number that is either 0 or 1.
When the condition cond holds, function stickline draws line sticks or rectangles from position pos1 to position pos2 depending on whether width = 0 or not. If width = 1, it draws rectangles with the same width as volume bars. If width > 1, the rectangle width will be that of candlesticks in the main chart. When drawing rectangles, if empty = 0, rectangles are filled with the color specified by color descriptors (we will cover this topic in the next section), or the default color will be used. If empty = 1, rectangle are remained unfilled. Function stickline can be used to produce impressive and colorful charts.

Example: by using stickline, you can redraw all the candlesticks in the main chart using different colors. In the formula below, if the price is closed above the previous close price, we use the blue color, otherwise, use the green color. The stick width is set to 2 when drawing from close to open. If is_up is true, draw sticks with empty=1, otherwise, set empty=0.

is_up   := isequal | isup;
is_down := isdown;

stickline(is_up & c >= ref(c, 1), h, c, 0, 0), colorblue;
stickline(is_up & c >= ref(c, 1), l, o, 0, 0), colorblue;
stickline(is_up & c >= ref(c, 1), c, o, 2, 1), colorblue;

stickline(is_up & c < ref(c, 1), h, c, 0, 0), colorgreen;
stickline(is_up & c < ref(c, 1), l, o, 0, 0), colorgreen;
stickline(is_up & c < ref(c, 1), c, o, 2, 1), colorgreen;

stickline(is_down & c >= ref(c, 1), h, c, 0, 0), colorblue;
stickline(is_down & c >= ref(c, 1), l, o, 0, 0), colorblue;
stickline(is_down & c >= ref(c, 1), c, o, 2, 0), colorblue;

stickline(is_down & c < ref(c, 1), h, c, 0, 0), colorgreen;
stickline(is_down & c < ref(c, 1), l, o, 0, 0), colorgreen;
stickline(is_down & c < ref(c, 1), c, o, 2, 0), colorgreen;

Example: this example goes further and draws four different colors within a single candlestick using stickline.

a := c-o;
stickline(isup, o, o+a/4, 2, 0), colorff99ff;
stickline(isup, o+a/4, o+a*2/4, 2, 0), colorff00ff;
stickline(isup, o+a*2/4, o+a*3/4, 2, 0), color9900ff;
stickline(isup, o+a*3/4, o+a, 2, 0), color0000ff;

Example: you can also use stickline to highlight candlestick patterns in charts. The following formula highlights candlesticks in a monochrome chart when price is closed higher in 3 consecutive days.

cond := backset(every(c > ref(c, 1), 3), 3);
stickline(cond, h, c, 0, 0), colorred;
stickline(cond, l, o, 0, 0), colorred;
stickline(cond & isup, c, o, 2, 1), colorred;
stickline(cond & isdown, c, o, 2, 0), colorred;

fillrgn(cond, pos1, pos2)
fillrgn(pos1, pos2)
cond: a single number or an array of numbers,
pos1: a single number or an array of numbers,
pos2: a single number or an array of numbers.
When the condition cond holds, i.e., cond != 0, function fillrgn(cond,pos1,pos2) fills the region formed by positions pos1 and pos2 with the default color if the color is not specified. fillrgn(pos1,pos2) is a short form for fillrgn(pos1>pos2, pos1,pos2), i.e., the region is filled only if pos1 > pos2.

Example: in this example, the region bounded by ma(c,5) and ma(c,10) is filled by the blue color when ma(c,5) is above ma(c,10). Otherwise, it is filled by the color ff99cc. In the formula, transparency5 is a color blending descriptor indicating that 50% of the background color is allowed to shine through the drawing color.

a := ma(c, 5);
b := ma(c, 10);
fillrgn(a >= b, a, b), colorblue, transparency5;
fillrgn(a < b, a, b), colorff99cc, transparency5;
ma5: a, colorblue;
ma10: b, colorred;

Example: this example fills the region bounded by ma(c,5) and ma(c,10) when ma(c,5) is above ma(c,10).

a := ma(c, 5);
b := ma(c, 10);
fillrgn(a, b), colorblue, transparency5;
ma5: a, colorblue;
ma10: b, colorred;

partline(cond, pos)
cond: a single number or an array of numbers,
pos: a single number or an array of numbers.
The function partline draws line segments using pos as its endpoints when the condition cond holds.

Example: the formula in this example implements the KDJ indicator. It draws the k line using the red color when k is above the d line.

n   := 9;
rsv := (close - llv(low, n))/(hhv(high, n) - llv(low, n))*100;
k:sma(rsv, 3, 1);
d:sma(k, 6, 1), colorgreen;
partline(k > d, k), colorred, linethick2;

Example: the next formula draws both ma(c,10) and ma(c,20) using the red color. It redraws moving averages using the green color when they are climbing.

ma10 : ma(c, 10), colorred;
partline(ma10 > ref(ma10, 1), ma10), colorgreen;
ma20 : ma(c, 20), colorred;
partline(ma20 > ref(ma20, 1), ma20), colorgreen;

vertline(cond)
cond: a single number or an array of numbers.
The function vertline draws vertical lines from the top to the bottom in charts when the condition cond holds, i.e., cond != 0.

Example: the formula in this example draws vertical lines in the main chart when it is the first trading day in a new month.

a := month != ref(month, 1);
vertline(a), color999999;

Example: you can also use vertline to mark certain conditions in the chart when debugging formulas. The following formula marks candlesticks when the price is closed up in 3 consecutive days and the close price is above its 20-day moving averages.

ma(c, 20);
vertline(every(c > ref(c, 1), 3) and c > ma(c, 20)), colorgreen;

drawnumber(cond, pos, n)
drawnumber(cond, pos, n, precision)
cond: a single number or an array of numbers,
pos: a single number or an array of numbers,
n: a single number or an array of numbers to be displayed,
precision: an integer number from 0-3, representing digits number after decimal point.
The function drawnumber draws the given number n at the position pos when the condition cond holds. If precision is not provided, the precision used in the current chart will be used.

Example: this example displays the value of peaks and troughs in the main chart.

a := peak(3, 1);
b := trough(3, 1);
drawnumber(a != ref(a, 1), h, h, 1), align2;
drawnumber(b != ref(b, 1), l, l, 1), align1;

drawtext(cond, pos, "text")
cond: a single number or an array of numbers,
pos: a single number or an array of numbers,
text: a text string to be drawn.
The function drawtext draws the given text string text at the position pos when the condition cond holds.

Example: this example displays text strings "PEAK" and "TROUGH" above peaks and below troughs in the main chart, respectively.

a := peak(3, 1);
b := trough(3, 1);
drawtext(a != ref(a, 1), h, "PEAK"), align2;
drawtext(b != ref(b, 1), l, "TROUGH"), align1;

Example: the formula below draws the string "Rally started?" slightly below the low price when yesterday's low price is the lowest low price in the last 40 days and the current close price is 10% higher than the open price.

drawtext(ref(l, 1) = llv(l, 40) & c/o > 1.01, l, "Rally started?"), align1, colorgreen;
Drawing Descriptors
TSFS provides a few drawing descriptors to enhance the flexibility when drawing charts and to improve the chart quality. All drawing descriptors have to be used in conjunction either with expressions returning data, or with specific drawing functions. The general syntax of using drawing descriptors is
expression, descriptor1, descriptor2, ...;
drawingfunction, descriptor1, descriptor2, ...;
stick
colorstick
linestick
All the three descriptors are related to drawing sticks in charts. The stick draws sticks from the 0-line; colorstick draws sticks from the 0-line using two different colors depending on if the value returned by its expression is above or below the 0-line; the linestick does the same thing as stick does, plus it also draws line segments connecting all the values returned by its expression in charts.

Example: this is to demonstrate the use of stick and colorstick in two separate subcharts.

close - ref(close, 1), stick;
close - ref(close, 1), colorstick;

Example: the next example shows the use of linestick.

close - ref(close, 1), linestick;

volstick
The volstick descriptor is provided specially for drawing volume bars. For example
volume, volstick;

crossdot
circledot
pointdot
These three descriptors are provided to draw indicated type of dots in charts. The crossdot descriptor draws crosses, circledot draws small circles and the pointdot descriptor draws small point dots.

Example: this is to show how you can use these descriptors in formulas and how they look in charts.

a : ma(c, 20), crossdot;
b : ma(c, 30), circledot;
d : ma(c, 60), pointdot, linethick3;
ma(c, 90), crossdot;
ma(c, 90), circledot, colormagenta; 

align[0-5]
The align descriptor takes an integer number from 0-5 as part of its name to indicate the position alignment when drawing icons using the function drawicon. The number has the following meaning.
  • align0: draw at the desired position;
  • align1: draw slightly below the position;
  • align2: draw slightly above the position;
  • align3: draw at the center of the chart;
  • align4: draw at the top of the chart;
  • align5: draw at the bottom of the chart.

Example: the following formula draws different icons on the day 2005-10-17 at different positions.

cond := date=1051017; # if it is 2005-10-17
drawicon(cond, c, 10), align0;  # draw a small blue ball at the close price position
drawicon(cond, c, 11), align1;  # draw a small red ball below the close price
drawicon(cond, c, 12), align2;  # draw a small green ball above the close price
drawicon(cond, c, 3),  align3;  # draw a face at the chart center
drawicon(cond, c, 4),  align4;  # draw an blue arrow at the chart top
drawicon(cond, c, 5),  align5;  # draw an red arrow at the chart bottom

linethick[0-7]
The linethick descriptor takes an integer number from 0-7 as part of its name to specify the thickness of lines to be drawn in charts. Note that when linethick0 is used, the line thickness is supposed to be 0, i.e., no lines will be drawn.

Example:

1, linethick1, colorred;
2, linethick2, colorred;
3, linethick3, colorred;
4, linethick4, colorred;
5, linethick5, colorred;
6, linethick6, colorred;
7, linethick7, colorred;
8, linethick0, colorred; # no line will be drawn, the constant 8 is still displayed

linestyle[0-2]
The linestyle descriptor takes an integer number from 0-2 as part of its name to indicate the line style to be used when drawing lines. The integer number has the following meaning.
  • linestyle0: use solid line style;
  • linestyle1: use dashed line style;
  • linestyle2: use dotted line style.

Example:

polyline(1, 0), linestyle0, colorred;
polyline(1, 1), linestyle1, colorred;
polyline(1, 2), linestyle2, colorred; 

color[..]
The color descriptor specifies which color should be used when drawing in charts. The word color has to be followed either by a predefined color name or 6 color hex code from 000000-ffffff. For example color0000ff. The predefined color names are those listed below.
  • colorblack
  • colorred
  • colorgreen
  • colorblue
  • colorwhite
  • colorgray
  • coloryellow
  • colorcyan
  • colormagenta

Example:

ma10 : ma(close, 10), colorff9999;
ma30 : ma(close, 30), colorgreen;
ma50 : ma(close, 50), pointdot, colorred;

transparency[0-9]
The transparency descriptor takes an integer number from 0-9 as part of its name to indicate the percentage of how much the underlying color can be allowed to shine through the drawing color (percentage of color transparency). For example, transparency9 means 90% color transparency, transparency0 means 0% color transparency, i.e., no color transparency at all and the drawing color is used literally. transparency is effective only when it is applied to the function fillrgn.

Example: the following formula draws the BOLL (Bollinger Bands) indicator in the chart. The region formed by the boll upper line the boll lower line is filled with color 6464fa using 80% color transparency.

n := 20;
p := 2; 
l_mid   := ma(close, n); 
l_upper := l_mid + p*std(close, n); 
l_lower := l_mid - p*std(close, n); 
fillrgn(l_upper, l_lower), color6464fa, transparency9; 
polyline(1, l_mid), linestyle2, color9494fa; 
polyline(1, l_upper), color9494fa; 
polyline(1, l_lower), color9494fa; 

Miscellaneous Functions
sigperform(x, signal)
x: an array of numbers representing, in most cases, close prices,
signal: an array of numbers containing 1, -1, 0, where 1 stands for buy signals, -1 for sell signals and 0 for no trading activity.
sigperform uses x as the price and emulates buy/sell activities according to the numbers in signal. It returns gain/loss percentage numbers as the performance of signal.

sigperform begins calculation with holding cash and waiting for the first buy signal. It uses all cash to buy when seeing a buy signal and sells all shares when meeting a sell signal. Therefor, sell signals before the first buy signal are ignored, consecutive buy or sell signals are ignored except the first one.

Suppose x and signal are given as (11, 12, 13, 14, 15, 16, 17), (-1, -1, 1, 1, 0, -1, -1), respectively. sigperform starts with holding 100 cash and will ignore the first 2 sell signals as it has nothing to sell. sigperform will use all cash to buy 100/13 = 7.6923 shares when it sees the first buy signal. The second buy signal is ignored because there is no cash to buy anything. The number 0 means no activity and will simply be skipped. When the following sell signal is seen, sigperform will sell all the 7.6923 shares and hold 7.6923*16 = 123.0769 cash. The next sell signal is ignored because there is nothing to sell. Thus the market value of the initial 100 cash during the process will be (100, 100, 100, 107.6923, 115.3846, 123.0769, 123.0769), the signal performance in percentage number will be (0, 0, 0, 7.6923, 15.3846, 23.0769, 23.0769).

Example: sigperform can be used to compare performance of different indices and stocks. In the following formula, we compare IBM's performance with that of S&P 500 Index. In the formula, sig is set to 1 for all the periods on the chart. According to what we have explained above, we buy in the first period and hold in the rest of entire time frame on the chart. This is effectively the so-called buy-and-hold strategy: buy once and hold forever. In the formula, ochl is a predefined formula in TSFS, "^gspc$ochl.c" returns the close price of S&P 500 Index and sp500 is the S&P 500 Index performance in the time frame on the chart.

polyline(1, 0), colorblack;
valid := sgn(barssince(isfirstbar)+1);
sig := valid;
performance : sigperform(close, sig), linethick2;
sp500: sigperform("^gspc$ochl.c", sig);

Example: Some investment advisors suggust a simple strategy for small investors: buy when the price is closed above ma(close,10) and sell when the price is below ma(close,10). This strategy works well when markets go straight up and down. We can check how good this strategy really is by comparing its performance with that of the buy-and-hold strategy(see the above example). We take IBM as example. Since IBM is not as volatile as small-cap stocks, in the following formula, instead of using ma(close,10), we use ma(close,30).

polyline(1, 0), colorblack;
v1 := ma(close, 30);
valid := sgn(barssince(isfirstbar)+1);
buy_hold_signal := valid;
signal := if(close > v1, 1, if(close < v1, -1, 0)) * valid;
performance : sigperform(close, signal);
buy_hold: sigperform(close, buy_hold_signal);

Predefined Formulas

TSFS has predefined a big number of formulas. Most of them are used as public indicators and can be found in the expandable list on the left side of the page editing and testing formulas. If you click on an indicator name in the list, you should see the description and the implementation of that indicator on the right side.

Some of the predefined formulas are used to scan markets everyday after market close. The formula code and their explanations are available on Scan Descriptions.

TSFS has also implemented a few formulas that are initially for the internal use. In this chapter, we are going to go through these formulas. You may find that some of them are interesting to you. Examples of using these formulas can be found in Scan Descriptions. In addition, this chapter will also discuss some special indicators in detail that are only available on TecStock.

BASIC_COND
BASIC_COND serves as the basic condition TecStock.com uses when scanning markets. BASIC_COND helps filter out stocks that either are closed below $0.30 or were trading with a volume of less than 20,000 in the last 20 days.

Stocks at this level tend to produce a high degree of misleading signals, which simply results in a waste of the traders time.

BASIC_COND is implemented in the following way in TSFS.

m := 20;    # one month
cond1 := every(vol >= 20000, m);
cond2 := close > 0.3;
cond1 and cond2;

You can call BASIC_COND in your formulas as well, for example,

my_cond := ...               # my condition for picking stocks
signal_mysigname : my_cond and "basic_cond";
                             # call formula "basic_cond" and generate signals
                             # when both my_cond and basic_cond are true

OCHL
TSFS has predefined the following formula with the name OCHL.
o: open;
c: close;
h: high;
l: low;
v: vol;

OCHL is provided to help you easily refer to other stocks open, closing prices etc. from your formulas. To get closing and open prices of IBM, for example, you simply call

ibm_o := "ibm$ochl.o";
ibm_c := "ibm$ochl.c";
...
TREND
TREND is a formula provided to identify price trends that are clearly discernible by the human eye.

TREND takes four parameters. The parameters and the formula itself are listed below

#        name     default  min   max
Parm:    P        4,       1,    6;
Parm:    N        20,      3,    50;
Parm:    S        10,      1,    100;
Parm:    CORREL   0.8,     0,    1;
price := if(p=1,close,if(p=2,open,if(p=3,high,if(p=4,low,if(p=5,
         max(open,close),min(open,close))))));
slp   : slope(price, n) * n / ref(close, n) * 100;
rlt   : relate(price, n);
up    : rlt > correl and slp > s;
down  : rlt < -correl and slp < -s;
The parameter P is to select which price the formula should use. If P = 4, for instance, the formula will use low. The parameter N is the period number. slope(price,n) returns the slope of price in the last n periods, slope(price,n)*n is an estimation of the price change in the last n periods. Thus, the variable slp is the estimated percentage change of price from the closing price n periods ago. relate(price,n) evaluates how good the price matches ascending straight lines. Recall that if price is in downtrend, the return value of relate(price,n) will be less than 0. Parameter S is the given percentage number, CORREL is the input correlation coefficient.

If you call the formula using "trend.up"(4, 20, 10, 0.8), the formula set variable up to 1 (true) if the low price was moving straight up (rlt > 0.8) and has increased more than 10% (approximately) compared to the close price 20 periods ago. If you call the formula using "trend.down"(3, 20, 10, 0.8), it sets the variable down to 1 (true) if the high price moved straight down (rlt < -0.8) and lost more than 10% (approximately) compared to the close price 20 periods ago.

You can find many examples of TREND in Scan Descriptions.

CYQ
The CYQ indicator is one of the tools only available on TecStock. It provides many raw market information that are important but have been so far ignored by a lot of investors.

CYQ is a dynamic version of the PBV indicator (Price By Volume). PBV combines both price and volume into one tool and plots horizontal volume bars onto the chart at price intervals. The length of each bar is determined by the cumulative total of all volume for which the closing price fell within the price interval.

In PBV charts, the volume bar length will never be decreased even if the stock has been trading above or below the bar for long time. From this point of view, PBV is a static indicator. CYQ, on the other hand, takes account of the fact that the cumulative volume in each price interval will be consumed when the stock is not trading in their price ranges. CYQ uses an emulation algorithm to estimate the volume to be taken off in each volume bar. The estimation may not always be precise. With the time, however, it gives a relatively accurate picture of the cost structure of the stock. CYQ is thus also referred to as the dynamic cost chart.

CYQ is a very useful indicator. Like PBV, CYQ can be used to identify crowded trading areas and the related support, resistance levels. In addition to this, big institutional positions can not hide in the CYQ chart. CYQ can be used to estimate average holding cost of big investors' positions. The animated CYQ can even show how the cost structure is changing.

What follows are the CYQ label you often see in the CYQ chart and the explanation of the label.

CYQ(60) indicates that the CYQ chart is consisting of 60 horizontal volume bars. The more volume bars we use, the more precise and slower the chart will be. We have tested many other numbers and believe that 60 is suitable for most of users. This number is currently not configurable.

(5.5B) is the capital, the number of shares held by the public and available for trading (often referred to as shares float). This is different from the number of shares outstanding. 5.5B stands for 5.5 billion shares.

CYQ is calculated based on capital. Unfortunately, this number is not always available for all stocks. This is also why for some stocks there is no CYQ chart.

Ideally, CYQ would be calculated from the stock inception date. This is, however, sometimes neither possible nor necessary. Whenever possible, Tecstock.com calculates CYQ at least from the date earlier than the current date 2 years ago.

93.06% is the percentage of the cumulative total from capital, i.e., the total cumulative volume in the CYQ chart is 5.5B * 93.06% = 5.12B. For active stocks with small capitals this number is often 100%.

Cost(50):21.01 shows that cost(50) = 21.01. It is useful to know the price of cost(50) as it can help figure out approximately how expansive or cheap the current price is.

Winner:87.2% means that 87.2% of the volume in the CYQ chart were trading under the last close price displayed in the chart. In other words, owners of 87.2% of the shares displayed in the CYQ chart are winners.

CYC
CYC_INFINITY
CYC_N
You have seen in the previous chapter the concept of holding cost cyc when we were discussing function dma. Because of its usefulness, TSFS has predefined a formula CYC. CYC is consisting of two intermediate formulas CYC_INFINITY and CYC_N that are explained separately below.

Formula CYC_INFINITY returns the same overall holding cost cyc as we discussed before with the exceptions that it uses (open+close)/2 instead of close and returns an empty result if capital is not available. The code of the formula is listed below. Notice that the function cyc(...) used inside the formula is an internal function that you should not use in your formulas.

cyc: cyc((open+close)/2, 0);

In contrast to the overall holding cost calculated by CYC_INFINITY, formula CYC_N considers only n-period data and returns the average holding cost in the past n-period. CYC_N takes one parameter N and uses (open+close)/2 as well. If capital is not available, it makes use of sum(vol,n) and returns an approximate holding cost that is accurate as long as the volume is not volatile.

#        name     default  min   max
Parm:    N        5,       2,    100;
var_price := (open+close)/2;
var_sum   := sum(vol, n);
cyc1      := cyc(var_price, n);
cyc2      := dma(var_price, if(var_sum > 0, vol/var_sum, 0));
cyc: if(capital > 0, cyc1, cyc2);

The formula CYC itself is simply a combination of CYC_N and CYC_INFINITY. It takes 3 parameters and returns by default the 5-day, 10-day, 30-day average holding costs and the overall average holding cost when used in daily charts. Here is an example using CYC.

#        name     default  min   max
Parm:    P1       5,       2,    50;
Parm:    P2       10,      2,    50;
Parm:    P3       30,      2,    50;
cyc1: "CYC_N"(p1);
cyc2: "CYC_N"(p2);
cyc3: "CYC_N"(p3);
cyc:  "CYC_INFINITY";

A direct use of CYC is to check whether stocks are under bullish condition. If the stock is trading above cyc, it indicates that the price is higher than the overall average holding cost, most investors are winning and the stock is bullish; if the price is moving below cyc, it implies that most investors bought the stock at more expansive prices and are losing money, the stock is bearish.

You can do the same analysis in terms of n-period. If, for example, the stock is trading above cyc3, then by default parameters, most investors bought the stock in the last 30 days are winning; if the price is below cyc3, then most investors bought the stock in the last 30 days are losing.

If you are a cautious investor and are not willing to take too much risk, you can choose stocks trading above the overall average holding cost cyc. If you are a bit aggressive and are seeking for highly profitable opportunities, you can pick stocks that are moving below cyc.

CYC is a lagging indicator as long as the trading volume is light. Daily price fluctuations with light volumes have little effect on CYC. Consequently, the relative position of different holding costs in CYC is stable. This is very different from moving averages, where different moving averages are often crossing up and down when the price movement is volatile. You can see this by comparing two charts here with one have different average holding costs and the other different moving averages.

CYS
The formula CYS calculates the bias of the n-period average holding cost. The only parameter the formula takes is N, the period number. By default, N is 10. Here is an example using CYS.
#        name     default  min   max
Parm:    N        10,      2,    50; 
polyline(1, 0), linestyle1, color4f4f4f; 
polyline(1, 5), color4f4f4f; 
polyline(1, -5), color4f4f4f; 
 
cyc := "CYC_N"(n); 
cys: (close - cyc) / cyc * 100, linethick2, color4f4f4f; 

CYS is an indicator for checking the oversold condition. Unlike other commonly used oversold indicators that check if the price has fallen sharply to a certain degree over a brief period of time, CYS is based on the holding cost and takes trading volume into account. Basically, CYS considers a stock oversold if its price rapidly declines on light volume. The condition of light volume is essential as it indicates that only a small portion of people are in panic selling. The most investors still believe the value of the stock and are holding. This implies that the stock is undervalued (at least temporarily) and may represent a good buying opportunity for investors.

For big cap stocks, daily price changes are often limited to 2%, the CYS value are usually ranging from -5 to 5. If you bought a big cap stock when its 5-day CYS was -10, it means that sellers sold their shares to you with an average loss of 10% in the past 5 days. This is a big loss and the stock may be deemed to be oversold (recall only a minority of investors are in panic selling). The following formula can be used to signal such buying opportunities.

market_cap := finance("shares_outstanding")*close / (1000*1000*1000); # returns number in billion
n    := 5;
cys  := "cys"(n);
cond := market_cap > 1 and cys < -10; # market_cap at least 1 billion, cys less than -10
signal_cys_oversold: cond;            # generate cys_oversold signals

 

Function Index
abs | align | avedev | backset | barscount | barslast | barssince | between | capital | ceiling | circledot | close | color | colorstick | cost | count | cross | crossdot | date | datediff | day | dma | drawicon | drawline | drawnumber | drawtext | ema | every | exist | fillrgn | filter | finance | floor | hhv | hhvbars | high | if | intpart | iscamarket | isdown | isequal | islastbar | isup | isusmarket | last | linestick | linestyle | linethick | llv | llvbars | ln | log | longcross | low | ma | max | min | mod | month | not | open | partline | peak | peakbars | peaktrough | peaktroughbars | pointdot | polyline | pow | range | ref | relate | reverse | sar | sarturn | sgn | sigperform | slope | sma | std | stick | stickline | sum | sumbars | transparency | trough | troughbars | var | vertline | vol | volstick | weekday | winner | year | zig