Language reference
Everything an indicator script can say — its layout, values and operators, clocks and sources, inputs, plots, marks, statements, arrays and every built-in function.
An indicator script is plain text, one declaration or statement per line, read from top to bottom. This page describes what TapeHawk's checker accepts today. Where the chart does less with something than the language allows, the section says so.
# A price channel from the highest high and lowest low.
input len = 20, min 5, max 100
upper = highest(high, len)
lower = lowest(low, len)
plot top = upper, domain price
plot bottom = lower, domain price
plot middle = (upper + lower) / 2, domain priceLayout of a script
A script has up to three parts, in this order:
- Directives — lines starting with
@, such as@print. All of them come before anything else. - The body — inputs, lines, intermediate values, statements and marks.
- A text table —
@text, which must be the last thing in the file.
Every script must declare at least one plot or mark.
- Order matters. A name can be used only below the line that declares it.
inputlines must come before the first statement (var,ifor:=); plots and marks can appear anywhere after the directives. - One per line. A line may be continued on the next one only after a comma, which lets a long list of clauses or arguments spread over several lines.
- Comments start with
#and run to the end of the line. There are no block comments. Blank lines and comment lines can go anywhere. - Indentation is only for the bodies of
ifandelse, and for the rows of@text. Use spaces; a tab at the start of a line is refused.
Names
Names are ASCII letters, digits and _, not starting with a digit, up to 40 characters, and case-sensitive. The same rule
applies to the name you save an indicator under.
var,if,else,and,orandnotare never names.- A line starting with
input,plotoralertis always that declaration, so none of the three can name a value. - The names of built-in functions and of the sources of the script's clock are taken.
- Inputs, plots, marks and intermediate values share one set of names: each name is declared once.
trueandfalseexist only afterinput … = bool. In an expression, write1and0.
Numbers and text
- Numbers:
20,0.5,.5,1e3,2.5e-4.nanis the number meaning "no value", andinfis infinity. There are no hexadecimal numbers. - A minus sign in front of a number is part of it wherever an input takes a number, so
input shift = -2, min -10, max 10declares a default and bounds below zero. Anything more than a sign and a number there is refused. - Text in double quotes is allowed only where the language asks for words or a colour — titles, labels, the text table,
mark messages and colours such as
"#26a69a". Colours must be quoted, because#starts a comment. Inside text,\"is a quote and\\a backslash. Text cannot be used in an expression.
Values
Every value is either a series — one number per bar, like close or ema(close, 20) — or a single number, like
20 or an input. An operation between a series and a number applies the number to every bar.
- True and false are numbers. A comparison gives
1or0, and so doand,orandnot. A number counts as true when it is not0and notnan. nanmeans "no value yet". Warm-up bars arenan, and a line is not drawn where its value isnan. Every comparison withnanis false except!=, sox != xis true exactly whenxhas no value.notis not arithmetic:not 5is0, andnot nanis1.
Operators
From loosest to tightest:
| Operators | Meaning |
|---|---|
or | Either side true |
and | Both sides true |
not | The opposite |
< <= > >= == != | Comparison |
+ - | Addition, subtraction |
* / | Multiplication, division |
- in front of a value | Negation |
not a > bmeansnot (a > b), anda or b and cmeansa or (b and c). Use brackets when in doubt.- Comparisons do not chain:
a < b < cis refused. Writea < b and b < c. - Both sides of
andandorare always worked out. - There is no
%,^,? :,&&or||. To choose between two values, useselect.
Clocks
A script runs on one clock, which decides how often it runs and which sources — the built-in series — it can read. The default is the bar clock; one of three directives at the top changes it.
| Clock | Runs once per | Sources |
|---|---|---|
| Bar (no directive) | bar | open high low close volume delta cvd vol day t |
@print | trade | t price qty aggressor tick sample |
@book | order-book update | t mid spread bestbid bestask tick sample |
@cell | closed footprint column | close t tick sample |
| Source | What it is |
|---|---|
open high low close | The bar's prices |
volume | The bar's volume |
delta | Buying minus selling volume in the bar — aggressive buys minus aggressive sells |
cvd | The running total of delta: restarting at 00:00 UTC on candles and footprint panels, and on a heatmap panel counted from the start of the data the panel loaded |
vol | Buying plus selling volume in the bar, from the trade record; nan where the bar reports none |
day | The bar's day, as a whole number of days since 1 January 1970, UTC |
t | The time: the bar's opening time, or the event's, in milliseconds since 1970 |
price, qty | The trade's price and size |
aggressor | 1 when the buyer took the trade, -1 when the seller did |
tick | The symbol's price step |
sample | 1 on the extra reading the chart takes at the edge of each column, 0 on a real trade or update |
mid, spread | The middle of the best bid and ask, and the distance between them |
bestbid, bestask | The best bid and the best ask |
On the event clocks — @print and @book — a length in a series function counts events, not bars or seconds.
What the chart does with each clock. A saved bar-clock or @cell script can be added to any panel, and a @print or
@book script to a heatmap panel. The other menus list the script by name with a note — see
Your indicator on a chart and
What the preview can draw.
@cell scripts have no memory from one column to the next: statements (var, if, :=), arrays and the
series functions are refused on that clock.
Inputs
input len = 20, min 2, max 200An input is a setting of the indicator, with a default. Its value is fixed while the script runs, so it can set a window
length.
| Kind | Written | Value in the script |
|---|---|---|
| Number | input len = 20 | The number |
| Switch | input show = bool true | 1 or 0 |
| List of words | input mode = enum(line, histogram) | The position of the choice: 0, 1, … |
| List of numbers | input hl = enum(15, 30, 60) | The number chosen |
| Colour | input up = color "#26a69a" | Not a number: usable only where a colour is |
Clauses follow the default, separated by commas:
| Clause | For | Meaning |
|---|---|---|
min n, max n | numbers | The range the setting may take. Declare max on any input used as a window length: the warm-up the editor reports, and the save's limit on it, are worked out from it. |
def member | lists | The default choice, instead of the first |
role compute / role display | all but colours | display marks a setting that changes how the indicator looks but not its numbers. compute is the default. |
step n | numbers | The step of the setting's arrows |
label t, help t | all | A title and a help sentence for the setting |
labels t | lists | Words to show for each choice |
- A list needs at least two members, all words or all numbers, with no repeats — at most 20 in a saved indicator.
- A word from a list is a value only when compared with its own input:
mode == histogram. Anywhere else,histogramis an unknown name. - A default must lie between
minandmax, and defaults and bounds cannot be larger, either way, than a limit TapeHawk sets; the checker names it when it refuses one. - On a panel, a saved indicator's settings show number, switch and list inputs, named by their input names;
step,label,helpandlabelsare not used there. A colour input is a colour control.
Lines and intermediate values
Intermediate values
spread = high - lowA name bound with = holds a value — usually a series — for the lines below it. It is not a line on the chart. A plot
name is not a value: to use what a plot draws, give it a name first and plot that name, as upper in the example at the
top of this page.
A value can refer to its own previous bar through prev, which makes running calculations possible without statements:
# The highest close since the first bar loaded. On that bar prev(top) has no value, and max returns close.
top = max(close, prev(top))
plot line = top, domain price- Its own name may appear only as
prev(name), outside any series function. - Several names may refer to each other through
prev. Within one bar, a name may read another's current value only if that other name is declared above it.
plot
plot line = ema(close, len), domain price, color "#26a69a", style line, shift 0A plot draws a line. The clauses are optional:
| Clause | Meaning |
|---|---|
domain d | What kind of number the line holds — see below. Usually worked out from the expression. |
color c | "#rrggbb", or the name of a colour input |
style s | line, histogram (bars from zero) or signed (bars coloured by sign) |
shift n | Draws the line n bars to the right — or to the left when negative. A number input may be used. |
| Domain | For |
|---|---|
price | Prices and price levels |
count | Quantities on a scale of their own — volume, a count of bars, a distance |
osc100 | Values on a fixed 0–100 scale |
zero | Values centred on zero, like delta |
The domain decides how the line is drawn: an indicator whose lines are all price is drawn over the price, and anything
else in a band. When the checker cannot tell the domain — a constant, a comparison, a value only a statement sets — the
plot must declare it. Declare count, not price, for a distance measured in price, such as a range.
fill and hline
fill top bottom, alpha 15, when above, color "#4aa8e0"
hline 70fill a bshades between two plots, named without a comma.alphais the opacity in percent;when aboveorwhen belowshades only where the first plot is above or below the second.hline ndraws a dashed horizontal line at a level, a number or a number input. A script may have several. A line set by a number input is left out while that input is 0, so 0 is how a reader switches it off.
On a panel, a saved indicator draws its lines with the color, style, shift, fill and hline its script declares,
as the editor's preview does. See Custom indicators.
Statements
Statements hold values that carry from one bar to the next — a trailing stop, a count, a state.
# How many bars in a row have closed higher.
var run = 0
if close > prev(close)
run := run + 1
else
run := 0
plot line = run, domain countvar x = edeclares a value whose starting expression runs only once — on the first bar — and which then keeps whatever it was last given.x := egives a new value to a name declared withvaror inside the statements.x = einside the statements is worked out again on every bar.if condition, then an indented body, optionally followed byelse if conditionorelseand their own indented bodies. A condition that isnancounts as false. Each body needs at least one line; there is no one-lineif.
The first var, if or := starts the script's statements, and from there every plain name = e belongs to them. The
rules:
- Every
inputmust be above the first statement. - A name cannot be declared for the first time inside
iforelse: declare it above withvar, then give it a value with:=. - Reading a name before its first assignment in the current bar gives its value from the previous bar.
- A series function cannot read a name the statements set, except through
prev.prev(run)works;sma(run, 5)does not. input,plotandmarklines cannot be inside anif.ifbodies can nest.
Arrays
# Keeps the last few closes in a ring, and plots the highest of them.
input size = 5, min 2, max 50
var ring[size]
var slot = 0
ring[slot] = close
slot := select(slot + 1 >= size, 0, slot + 1)
top = reduce_max(ring)
plot line = top, domain price-
var name[size]declares an array inside the statements. The size is a whole number known before the script runs — a number or a number input withmax. Every slot starts at0. -
name[i] = ewrites one slot, andname[i]reads one. The index is worked out as the script runs; an index outside the array stops the script. -
Arrays can be read and written only inside the statements. To draw something from one, give it a name there and plot the name.
-
There are no loops, so a bar writes the slots it names and no more.
-
All arrays in a script hold at most 4,000 slots between them.
-
Reductions, on the bar clock only:
reduce_sum(a)— the total of every slot;reduce_max(a)— the largest slot;reduce_argmax(a)— the index of the largest slot, the lowest index on a tie.
Each takes the name of an array, and gives
nanif any slot isnan.
Functions
There are no user-defined functions. Calling an unknown function, or with the wrong number of arguments, is refused.
Series functions
These read a series and give a series. They are not available on the @cell clock.
| Function | Gives, over the last len values |
|---|---|
sma(src, len) | The simple average |
ema(src, len) | The exponential moving average: starts from the simple average of the first len values, then weighs each new value by 2 ÷ (len + 1) |
rma(src, len) | Wilder's moving average, the smoothing RSI and ATR use: each new value is weighted 1 ÷ len |
wma(src, len) | The weighted average, where the newest value counts len times, the one before len − 1 times, and so on |
stdev(src, len) | The standard deviation, dividing by len |
highest(src, len), lowest(src, len) | The highest and lowest value |
sum(src, len) | The total |
| Function | Gives |
|---|---|
cum(src) | The running total since the first bar. A bar without a value adds nothing and reads nan; the total carries on. |
prev(src) | The previous bar's value; nan on the first bar |
change(src) | src − prev(src) |
- The length is known before the script runs: a number, an input with a
max, or arithmetic on them —len * 2,floor(len / 2). It is rounded to the nearest whole number and must be between 1 and 4,000. - Warm-up. A window function has no value until it has seen
lenvalues: its firstlen− 1 bars arenan, and a function over another function adds their warm-ups up. A missing value in the middle starts the window again. Inhighestandlowestit does not blank the line, but the values before it are forgotten. - On the bar clock, a saved script may need at most 1,000 bars of warm-up, counted at the largest value each input may take.
- On the event clocks, a series function cannot be written inside another's argument — give the inner one a name first — and each window needs memory, so long windows are refused with the amount they would need.
Element-wise functions
These work on each value on its own, on every clock. Given a series they give a series.
| Function | Gives |
|---|---|
abs(x) | The value without its sign |
sqrt(x), ln(x) | The square root and the natural logarithm |
max(a, b), min(a, b) | The larger and the smaller — a whenever either is nan |
select(c, a, b) | a where c is true, otherwise b. Both a and b are always worked out. |
floor(x) | x rounded down — for numbers known before the script runs only, such as a window length |
decay(dt, halfLife) | The share left after dt milliseconds of a quantity that halves every halfLife milliseconds: 1 when dt is 0 or less. The script stops if either is nan or halfLife is not above zero — guard it with if, not select. |
Trade-clock functions
Available under @print only.
| Function | Gives |
|---|---|
flow_delta(halfLife) | Aggressive buying minus aggressive selling, fading over time, so a trade halfLife seconds ago counts half as much as one now |
flow_buy(halfLife), flow_sell(halfLife) | One side of it |
flow_warm(halfLife) | 1 once that measure has run long enough to be trusted, 0 before |
win_buy(ms), win_sell(ms) | The value bought and sold in the last ms milliseconds — 0 when nothing traded |
win_range(ms) | The price range over the last ms milliseconds — nan when nothing traded |
run_levels(gap) | When a run — consecutive aggressive trades on one side, each no more than gap milliseconds after the last — has just ended, the number of price levels it crossed; nan on every other trade |
run_vol(gap), run_duration(gap), run_start(gap), run_price(gap), run_side(gap) | The same run's traded value, its length and start time in milliseconds, its last price, and its side (1 buy, -1 sell) |
halfLifeis in seconds and must be one of 15, 30, 60, 120 or 300 — written as a number, or as a list input whose every member is one of them.msandgapmust each lie within a range the checker gives when it refuses a value. A script uses onemsfor all itswin_calls and onegapfor all itsrun_calls.- Until every
flow_measure the script reads is warm, every line of the script stays blank — not only the lines that read one.
Order-book functions
Available under @book only.
| Function | Gives |
|---|---|
book_imb(from, to, weighted, halfLife) | Which side has more size resting, from +100 (all on the bid) to -100 (all on the ask), smoothed over time. from and to are distances from the middle price in ticks, 0 to 4,000, where 0 includes the best prices; weighted 1 makes levels near the price count more; halfLife as for flow_delta. |
liq_scan(from, to, trigger, mode) | How many levels between from and to ticks meet trigger, in the symbol's units: with mode 0, resting size that has just reached it; with mode 1, size that has just changed by at least that much |
liq_change(kind, minSize) | How many large walls, judged against the order book's own sizes, have just been confirmed (kind 0) or pulled (kind 1), ignoring walls smaller than minSize |
- A script may read at most eight different
from–toranges withbook_imb. - The levels
liq_scanandliq_changecount are read in a mark witheach. - Reading far from the price costs more: a
liq_scanrange too wide for the work one order-book update may do is refused.
Footprint-column functions
Available under @cell only. step groups the column's prices into rows of that many ticks, up to 1,000; a script uses one
step.
| Function | Gives |
|---|---|
cell_poc(step) | The price of the row with the most volume in the column |
cell_imb_scan(step, ratio, minLen, dustPct, minVolPct) | How many stacks of diagonal imbalance the column holds: at least minLen rows in a row where one side outweighs the other by ratio, ignoring rows below dustPct and stacks below minVolPct of the column's volume |
cell_abs_scan(step, ratio, minLen, dustPct, minVolPct, depth) | The same for absorption, where price also closed at least depth ticks away from the stack |
The stacks are read in a mark with each, through cell_imb_from(), cell_imb_to() and cell_imb_side() — the stack's
bottom and top prices, and its side.
mark
@print
input minValue = 50000, min 1, max 900000
notional = qty * price
mark big = notional >= minValue, side aggressor, value notional, cap 10, glyph circleA mark draws a symbol on the event where its condition is true, instead of a line. It exists on the event clocks only —
@print, @book and @cell — and a script may have up to eight.
| Clause | Meaning | When left out |
|---|---|---|
time e | The moment the mark points to, in milliseconds — it can point back to where something began | The event's own time |
at e | The price the mark sits at | The trade price (@print) or the middle price (@book) |
side e | Negative for the bid side, anything else for the ask side | Ask side |
value e | The one number the mark carries and labels | 0 |
hollow e | When true, draws the mark as an outline with no label | Never hollow |
each n | Draws one mark per element: n is how many, read from liq_scan, liq_change or a cell_ scan | One mark |
cap n | The most marks per minute, a whole number up to 100 | A default of the chart |
glyph g | The shape: arrow, arrowNotch, triangle, chevron, chevronBar, bracket, level, circle or square | arrow with a side, square without |
color c, colorDown c | The colour; with both, color is the ask side's and colorDown the bid side's | The chart's colours |
title t, msg t | The headline and sentence of an alert on this mark | The indicator's and the mark's names; the mark's value |
- A condition that is a constant — the same on every event — is refused.
- Under
@cell, a mark must give its price withat— a footprint column has no single price — and the check refuses a mark without it. - Under
each,liq_price(),liq_size(),liq_side()andliq_time()— or thecell_imb_readers — give the current element, and may be used only intime,at,side,valueandhollow. titleandmsgmay use{{symbol}},{{source}},{{price}},{{value}},{{side}},{{name}}and{{time}}(UTC), up to eight of them in at most 512 bytes.{{tf}}is refused on every clock a mark can use. One of these names written with a single opening brace, such as{value}, would be printed as it is, so saving or previewing a script that does so is refused.- Where marks are drawn. A
@printor@bookscript's marks are drawn on a heatmap panel, and a@cellscript's on a candles, footprint or heatmap panel — in the editor's preview and once the saved script is added, in the shape and colours the script declares. A@cellscript marks a column once it has closed, never the one still trading. On a candles or footprint panel a column is a bar; on a heatmap panel it is one of the heatmap's own columns, whose width follows the range you pick — so the marks there are not the bars an alert on the script reads. A rule on a mark notifies you on every clock that can carry one — see Custom indicators.
alert
alert breakout = close > prev(highest(high, 20))The language still reads alert name = comparison, but TapeHawk refuses a script that declares one, both in the
editor's check and on save, naming the line. On the trade or footprint-column clock the message suggests the mark to
write instead; on the bar and order-book clocks it says what announcing needs instead. Scripts saved
before this keep their alert lines, keep compiling and keep drawing; their conditions cannot be chosen for a rule. See
Custom indicators.
Directives
| Directive | Meaning | For a saved indicator |
|---|---|---|
@print, @book, @cell | The clock — at most one | Decides which panels it can be added to |
@title t, @desc t | A name and a description | Not shown: the chart uses the name you save under |
@group g, @kinds k, …, @indicator id | Where a built-in indicator is listed | Ignored |
@render mode, … | How a built-in indicator is drawn | Ignored: over the price when every line is price, in a band otherwise |
@caption input, … | Which inputs a built-in indicator's name shows | Ignored: the name shows every number input |
Each directive may appear once, at the top of the script.
Text table
@text
name en "Pressure" vi "Áp lực"
modes en ("Line", "Bars") vi ("Đường", "Cột")@text gathers the script's words, in one or more languages, at the end of the file. Each indented row is a name, then a
language code and text — or a bracketed list of texts, for labels — for each language. Refer to a row with text(name)
where a text is expected; plain "…" works there too and reads the same in every language.
- Every row must be used, and every row must give the same set of languages.
- A
text(name)with a misspelt row name is refused, with the likely row suggested. - The rows reach users only through a mark's
titleandmsg, in the alert it would send.
Limits
The check refuses, with a message, a script that nests expressions or if bodies more than 64 deep, or that asks for more
work in one run — over its candles, or on one trade, order-book update or footprint column — than one script may do — shorten windows, draw fewer lines or read less of the order book. Saving
also refuses a script that would push your account past the work all its indicators may do together, or past the number of
indicators it may hold.
A saved indicator stops drawing when a panel holds more bars than its script can read at once — the more lines, windows, sources and array slots a script has, the fewer. Its row in the info bar then shows a ⚠ that says to scroll forward or zoom in. See Limits and troubleshooting.