Formula GuideYou can also download this guide from formulaguide.zipTable of Contents An Overview of TecStock's Formula System (TSFS) Getting StartedTecStock'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 TSFSTecstock.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:
What Can TSFS Not DoEven 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 TSFSThe 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.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. 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. About BacktestingThe 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.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 InsensitivityTSFS 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 SemicolonsIn 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 NumbersTSFS 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 ParametersWe 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.VariablesIn 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 variableInternal 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 variableNote 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 redefinedUnless 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 SignalsThere is a special case about variable names. When TSFS monitors markets using your custom formulas, variable names in your formulas prefixed with signal_ likesignal_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 FormulasIt 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
Arithmetic OperatorsAll 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 OperatorsTSFS also supports the following comparison and logical operators:
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_4In 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:
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 DataThese 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.
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-dayExample: 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;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. 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.
...
market_cap := finance("shares_outstanding")*close / (1000*1000); # returns number in million
...
Date FunctionsThis section explains functions that can be used to process date related calculations. The function day returns an array of numbers of the day in month from 1-31. For exampled : 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;Function date returns an array of integers representing dates. For dates before 2000-01-01, it returns integers in the format YYMMDD. For any other dates, the returned integers have the format 1YYMMDD. For example, for dates 1999-12-30, 2000-01-01, 2001-12-30, date returns 991230, 1000101, 1011230 respectively. Example: the following formula plots vertical lines on the chart when the date is between 2008-12-16 and 2008-12-26. vertline(date >= 1081216 and date <= 1081226);If you have a set of well defined formulas and want to sell your formulas in an encrypted format, you can use date to restrict the use of your formulas to a predefined time frame. Example: the next formula is a variation of the KDJ indicator. It has a restriction date defined inside and works properly only until 2008-12-01. valid := if(date < 1081201, 1, 0); # valid becomes 0 after 2008-12-01 rsv := (close - llv(low, 9))/(hhv(high, 9) - llv(low, 9)) * 100; k : sma(rsv, 3, 1) * valid; d : sma(k, 3, 1) * valid;
0, colorred; diff : datediff(date, 1081101);Notice that datediff(date1, date2) returns the difference number between date1, date2 in terms of calendar day, i.e., numbers returned by datediff(date1, date2) will include days when markets were closed. Example: if you want to get the difference number in terms of trading day, you can use the built-in function barslast. The formula below shows the difference between calendar day diff and trading day diff, where the function barslast(close > ma(close, 30)) returns the trading day number since the last occurrence of close > ma(close, 30). 0, colorred; trading_day_diff : barslast(close > ma(close, 30)); calendar_day_diff : datediff(date, ref(date, trading_day_diff)); Reference FunctionsReference 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.
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;
# 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;
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;
v1 : ma(c, 10); v2 : ma(v1, 10); v3 : ma(v2, 10);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(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) 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.05That 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;
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);
# william's R hh : hhvbars(high, 20); ll : llvbars(low, 20);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;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. 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));
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;
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 FunctionsLogical 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).
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;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;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));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;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 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;
a : between(close, ma(close, 5), ma(close, 30)); b : range(close, ma(close, 5), ma(close, 30)) * 2;
a : exist(c < ref(c, 1), 3); b : every(c < ref(c, 1), 3) * 2;
last(isup, 3, 2);
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; |