TapeHawk Docs
Custom indicators

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.

channel.ind
# 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 price

Layout of a script

A script has up to three parts, in this order:

  1. Directives — lines starting with @, such as @print. All of them come before anything else.
  2. The body — inputs, lines, intermediate values, statements and marks.
  3. 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. input lines must come before the first statement (var, if or :=); 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 if and else, 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, or and not are never names.
  • A line starting with input, plot or alert is 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.
  • true and false exist only after input … = bool. In an expression, write 1 and 0.

Numbers and text

  • Numbers: 20, 0.5, .5, 1e3, 2.5e-4. nan is the number meaning "no value", and inf is 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 10 declares 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 1 or 0, and so do and, or and not. A number counts as true when it is not 0 and not nan.
  • nan means "no value yet". Warm-up bars are nan, and a line is not drawn where its value is nan. Every comparison with nan is false except !=, so x != x is true exactly when x has no value.
  • not is not arithmetic: not 5 is 0, and not nan is 1.

Operators

From loosest to tightest:

OperatorsMeaning
orEither side true
andBoth sides true
notThe opposite
< <= > >= == !=Comparison
+ -Addition, subtraction
* /Multiplication, division
- in front of a valueNegation
  • not a > b means not (a > b), and a or b and c means a or (b and c). Use brackets when in doubt.
  • Comparisons do not chain: a < b < c is refused. Write a < b and b < c.
  • Both sides of and and or are always worked out.
  • There is no %, ^, ? :, && or ||. To choose between two values, use select.

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.

ClockRuns once perSources
Bar (no directive)baropen high low close volume delta cvd vol day t
@printtradet price qty aggressor tick sample
@bookorder-book updatet mid spread bestbid bestask tick sample
@cellclosed footprint columnclose t tick sample
SourceWhat it is
open high low closeThe bar's prices
volumeThe bar's volume
deltaBuying minus selling volume in the bar — aggressive buys minus aggressive sells
cvdThe 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
volBuying plus selling volume in the bar, from the trade record; nan where the bar reports none
dayThe bar's day, as a whole number of days since 1 January 1970, UTC
tThe time: the bar's opening time, or the event's, in milliseconds since 1970
price, qtyThe trade's price and size
aggressor1 when the buyer took the trade, -1 when the seller did
tickThe symbol's price step
sample1 on the extra reading the chart takes at the edge of each column, 0 on a real trade or update
mid, spreadThe middle of the best bid and ask, and the distance between them
bestbid, bestaskThe 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 200

An 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.

KindWrittenValue in the script
Numberinput len = 20The number
Switchinput show = bool true1 or 0
List of wordsinput mode = enum(line, histogram)The position of the choice: 0, 1, …
List of numbersinput hl = enum(15, 30, 60)The number chosen
Colourinput up = color "#26a69a"Not a number: usable only where a colour is

Clauses follow the default, separated by commas:

ClauseForMeaning
min n, max nnumbersThe 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 memberlistsThe default choice, instead of the first
role compute / role displayall but coloursdisplay marks a setting that changes how the indicator looks but not its numbers. compute is the default.
step nnumbersThe step of the setting's arrows
label t, help tallA title and a help sentence for the setting
labels tlistsWords 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, histogram is an unknown name.
  • A default must lie between min and max, 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, help and labels are not used there. A colour input is a colour control.

Lines and intermediate values

Intermediate values

spread = high - low

A 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:

running-high.ind
# 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 0

A plot draws a line. The clauses are optional:

ClauseMeaning
domain dWhat 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 sline, histogram (bars from zero) or signed (bars coloured by sign)
shift nDraws the line n bars to the right — or to the left when negative. A number input may be used.
DomainFor
pricePrices and price levels
countQuantities on a scale of their own — volume, a count of bars, a distance
osc100Values on a fixed 0–100 scale
zeroValues 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 70
  • fill a b shades between two plots, named without a comma. alpha is the opacity in percent; when above or when below shades only where the first plot is above or below the second.
  • hline n draws 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.

up-run.ind
# 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 count
  • var x = e declares a value whose starting expression runs only once — on the first bar — and which then keeps whatever it was last given.
  • x := e gives a new value to a name declared with var or inside the statements.
  • x = e inside the statements is worked out again on every bar.
  • if condition, then an indented body, optionally followed by else if condition or else and their own indented bodies. A condition that is nan counts as false. Each body needs at least one line; there is no one-line if.

The first var, if or := starts the script's statements, and from there every plain name = e belongs to them. The rules:

  • Every input must be above the first statement.
  • A name cannot be declared for the first time inside if or else: declare it above with var, 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, plot and mark lines cannot be inside an if.
  • if bodies can nest.

Arrays

close-log.ind
# 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 with max. Every slot starts at 0.

  • name[i] = e writes one slot, and name[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 nan if any slot is nan.

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.

FunctionGives, 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
FunctionGives
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 len values: its first len − 1 bars are nan, and a function over another function adds their warm-ups up. A missing value in the middle starts the window again. In highest and lowest it 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.

FunctionGives
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.

FunctionGives
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)
  • halfLife is 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.
  • ms and gap must each lie within a range the checker gives when it refuses a value. A script uses one ms for all its win_ calls and one gap for all its run_ 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.

FunctionGives
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–to ranges with book_imb.
  • The levels liq_scan and liq_change count are read in a mark with each.
  • Reading far from the price costs more: a liq_scan range 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.

FunctionGives
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

big-trade.ind
@print
input minValue = 50000, min 1, max 900000

notional = qty * price
mark big = notional >= minValue, side aggressor, value notional, cap 10, glyph circle

A 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.

ClauseMeaningWhen left out
time eThe moment the mark points to, in milliseconds — it can point back to where something beganThe event's own time
at eThe price the mark sits atThe trade price (@print) or the middle price (@book)
side eNegative for the bid side, anything else for the ask sideAsk side
value eThe one number the mark carries and labels0
hollow eWhen true, draws the mark as an outline with no labelNever hollow
each nDraws one mark per element: n is how many, read from liq_scan, liq_change or a cell_ scanOne mark
cap nThe most marks per minute, a whole number up to 100A default of the chart
glyph gThe shape: arrow, arrowNotch, triangle, chevron, chevronBar, bracket, level, circle or squarearrow with a side, square without
color c, colorDown cThe colour; with both, color is the ask side's and colorDown the bid side'sThe chart's colours
title t, msg tThe headline and sentence of an alert on this markThe 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 with at — a footprint column has no single price — and the check refuses a mark without it.
  • Under each, liq_price(), liq_size(), liq_side() and liq_time() — or the cell_imb_ readers — give the current element, and may be used only in time, at, side, value and hollow.
  • title and msg may 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 @print or @book script's marks are drawn on a heatmap panel, and a @cell script'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 @cell script 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

DirectiveMeaningFor a saved indicator
@print, @book, @cellThe clock — at most oneDecides which panels it can be added to
@title t, @desc tA name and a descriptionNot shown: the chart uses the name you save under
@group g, @kinds k, …, @indicator idWhere a built-in indicator is listedIgnored
@render mode, …How a built-in indicator is drawnIgnored: over the price when every line is price, in a band otherwise
@caption input, …Which inputs a built-in indicator's name showsIgnored: 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 title and msg, 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.

On this page