OpenScriptv0.5.0Documentation
GitHub

Tables

table() and cell(), the grid a study pins to a corner of its pane for readings that are one current state rather than a value per bar, with every argument and the rules for writing it.

On this page
  1. How a table works
  2. Declaring a grid
  3. Writing cells
  4. What a table is not for
  5. Related

Most of what a study computes is a value per bar, and a value per bar is a plot(). A table is for the rest: the current RSI, ATR and volume in one glance, the trend on three timeframes, today's range, the symbol and interval. OpenScript (also called OpenAlgo Script) declares a table with table(), a grid pinned to a corner of the pane, and fills it with cell(). The grid stays in its corner while the chart scrolls and zooms, and it sits over whatever candles are behind it.

version 1
study("Readings", overlay = true, precision = 2)

corner = input("bottomRight", "Corner", options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])

// Declared once, at the top level, like a plot.
panel = table("Readings", 4, 2, position = corner, textColor = silver, bgColor = fade(black, 25))

// Every reading is computed on every bar, outside the if below.
r = rsi(close, 14)
a = atr(14)
volumeRatio = volume / sma(volume, 20)

fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

// Written on the newest bar only: the panel shows one state, the current one.
if bar.isLast
    cell(panel, 0, 0, chart.symbol, textColor = white)
    cell(panel, 0, 1, chart.interval, textColor = white, align = "right")
    cell(panel, 1, 0, "RSI 14")
    cell(panel, 1, 1, show(r, 1), textColor = r > 70 ? red : r < 30 ? lime : silver, align = "right")
    cell(panel, 2, 0, "ATR 14")
    cell(panel, 2, 1, show(a, 2), align = "right")
    cell(panel, 3, 0, "Volume against its average")
    cell(panel, 3, 1, show(volumeRatio, 2), align = "right")
A table dashboard fixed to a corner of the chart
A table fixed to a corner of the chart, updated on every bar.

How a table works

table()cell()
Where it may appearTop level onlyAnywhere: inside an if, a loop or a function
When it happensOnce, before the first barOn every bar where the call runs
Its argumentsAll fixed before the first barAll read on the bar it runs
ReturnsA table, the same object on every barNothing

A grid starts every bar empty. What the chart shows is the cells written on the newest bar, and a cell written on an earlier bar does not carry over. That is why a panel is written inside if bar.isLast (bar.isLast is true only on the newest bar): writing it on every bar of a long history is thousands of writes to show the last one. On a moving chart the newest bar runs again on every update and the grid is rewritten each time, so nothing piles up.

Compute at the top level, write inside the if. An indicator such as rsi() keeps state and advances only on the bars where its call runs. Moved inside if bar.isLast, it would see one bar and return none, and the compiler warns with OS8001.

Cells hold text. The text argument is a string, and a number passed there is error OS3011, so convert it with text(). A text that is none leaves the cell blank. Because a condition that is none takes the false branch, b ? "up" : "down" says "down" on every bar before b has a value; test isNone() first and say "warming up".

Addresses start at zero. A grid of 4 rows and 2 columns has rows 0 to 3 and columns 0 to 1. Writing outside the grid is error OS4004, which stops the script on that bar.

Colour has three levels. The grid's textColor and bgColor apply to every cell that says nothing else; a cell's own textColor and bgColor override them for that cell; leave both out and the chart's defaults apply. Give the grid a translucent background, such as fade(black, 25), so the candles behind it stay faintly visible.

On the /trading chart, one grid per study is drawn. A study may declare several grids and the compiler accepts them, but the chart draws only the first one declared; the others compile, are written to and never appear. The grid's title is not shown on the chart either. Declare one grid with the rows you need, and write a second study for a second panel.

Declaring a grid

table()

Top level only

Declares a grid of rows by cols cells pinned to one corner of the study's pane, and returns the table that cell() writes into. The title names the grid; the size and corner are part of the study's fixed shape, which is why the call is top level only and every argument is settled before the first bar.

table(
    title: string,
    rows: number,
    cols: number,
    position?: string = "topRight",
    textColor?: color = none,
    bgColor?: color = none,
    borderWidth?: number = 0
) -> table
ParameterTypeDefaultNotes
titlestringrequiredFixed before the first bar
rowsnumberrequiredA whole number. Fixed before the first bar
colsnumberrequiredA whole number. Fixed before the first bar
positionstring"topRight"One of "topLeft", "topRight", "bottomLeft", "bottomRight". Fixed before the first bar
textColorcolornoneFixed before the first bar
bgColorcolornoneFixed before the first bar
borderWidthnumber0Fixed before the first bar
Returns
table
First value
bar 0
version 1
study("Timeframe bias", overlay = true)

tfA = input("15m", "Timeframe 1", kind = "interval")
tfB = input("1h",  "Timeframe 2", kind = "interval")
tfC = input("1D",  "Timeframe 3", kind = "interval")

grid = table("Bias", 4, 2, position = "topRight", bgColor = fade(black, 20), borderWidth = 1)

// Each row changes when that timeframe's bar closes, and not before.
// A timeframe finer than the chart's stops the study with OS6002, so run
// this on a chart of 15 minutes or less.
biasA = req.timeframe(tfA, ema(close, 20) > ema(close, 50))
biasB = req.timeframe(tfB, ema(close, 20) > ema(close, 50))
biasC = req.timeframe(tfC, ema(close, 20) > ema(close, 50))

fn word(b) => isNone(b) ? "warming up" : (b ? "up" : "down")
fn tint(b) => isNone(b) ? silver : (b ? lime : red)

if bar.isLast
    cell(grid, 0, 0, "Timeframe", textColor = white)
    cell(grid, 0, 1, "Bias", textColor = white, align = "right")
    cell(grid, 1, 0, tfA)
    cell(grid, 1, 1, word(biasA), textColor = tint(biasA), align = "right")
    cell(grid, 2, 0, tfB)
    cell(grid, 2, 1, word(biasB), textColor = tint(biasB), align = "right")
    cell(grid, 3, 0, tfC)
    cell(grid, 3, 1, word(biasC), textColor = tint(biasC), align = "right")

A table() inside an if, a loop or a function is error OS3006:

if bar.isLast
    panel = table("Readings", 2, 2)

Its arguments are fixed before the first bar, so write each one at the call as a literal, a colour such as fade(black, 25), or an input(). An argument that depends on bar data is error OS3003, and so is a size held in a name, such as n = 3 followed by table("Readings", n, 2).

Remarks. rows and cols are whole numbers; a fraction is error OS3004. Make position an input() with the four corners as options, since which corner is free depends on the reader's chart. The table is an ordinary value: you can name it, keep it and pass it to a function of your own, and it is never deleted. Writing no cells leaves every cell blank, but the grid keeps its size, its border and its own bgColor, so a grid with a background still shows as an empty block. For a panel the reader can switch off, leave the grid's bgColor and borderWidth out, set bgColor on the cells instead, and guard the writes with if bar.isLast and showPanel. A table with fifty rows of history works and is unreadable at the size of a chart corner; a list belongs in a report, not on the chart.

See also. cell(), clear(), bar.isLast, Tables

Writing cells

cell()

Writes one cell of the grid t at row and col on this bar, with its own text colour, background and alignment. The text is a string; the colours and the alignment are read on the bar, so a cell can change colour with the reading it shows.

cell(
    t: table,
    row: number,
    col: number,
    text: string,
    textColor?: color = none,
    bgColor?: color = none,
    align?: string = "left"
) -> nothing
ParameterTypeDefaultNotes
ttablerequired
rownumberrequiredA whole number
colnumberrequiredA whole number
textstringrequired
textColorcolornone
bgColorcolornone
alignstring"left"One of "left", "center", "right"
Returns
nothing
First value
bar 0
version 1
study("Average ladder", overlay = true, precision = 2)

ladder = table("Averages", 5, 3, position = "bottomLeft", textColor = silver, bgColor = fade(black, 25))

e9   = ema(close, 9)
e21  = ema(close, 21)
e50  = ema(close, 50)
e200 = ema(close, 200)

// One row of the ladder: labels left, numbers right.
fn row(t, r, name, value) =>
    above = not isNone(value) and close > value
    cell(t, r, 0, name)
    cell(t, r, 1, isNone(value) ? "warming up" : text(value, 2), align = "right")
    cell(t, r, 2, isNone(value) ? "" : (above ? "above" : "below"), textColor = isNone(value) ? silver : (above ? lime : red), align = "right")
    above

if bar.isLast
    // A header: the same background across the row reads as one block.
    header = fade(navy, 40)
    cell(ladder, 0, 0, "Average", textColor = white, bgColor = header)
    cell(ladder, 0, 1, "Value", textColor = white, bgColor = header, align = "right")
    cell(ladder, 0, 2, "Close is", textColor = white, bgColor = header, align = "right")
    row(ladder, 1, "EMA 9", e9)
    row(ladder, 2, "EMA 21", e21)
    row(ladder, 3, "EMA 50", e50)
    row(ladder, 4, "EMA 200", e200)

Because only the cells written on the newest bar show, a grid whose number of rows changes from bar to bar needs no clean-up: write the rows that apply and the rest stay blank.

version 1
study("Averages under the close", overlay = true, precision = 2)

names  = ["EMA 9", "EMA 21", "EMA 50"]
values = [ema(close, 9), ema(close, 21), ema(close, 50)]

board = table("Close is above", 3, 1, position = "topLeft")

if bar.isLast
    slot = 0
    for i = 0 to size(names) - 1
        if not isNone(element(values, i)) and close > element(values, i)
            cell(board, slot, 0, element(names, i))
            slot += 1

Remarks. align is "left", "center" or "right"; put labels left and numbers right, so the digits line up. Writing the same cell twice on a bar keeps the last write. clear() empties every cell written so far on this bar, for a script that builds its grid in more than one pass and wants to start again. There is no merged cell: for a header across a row, write the text in the first column and give every cell in the row the same bgColor. str.padLeft() and str.repeat() help line text up inside a cell, or draw a small meter from characters.

See also. table(), clear(), text(), str.format(), Tables

What a table is not for

You wantUseBecause
A value on every barplot()A table shows one state, not a history
An event on one barsignal()A marker stays on the bar where it happened
A caption on a shapedraw.label(), or a box's own textIt belongs beside the thing it describes
A regime over a stretch of barsbackground()A regime belongs to bars, not to a corner

Tables, Visuals overview, Plotting, Drawing objects, Strings, bar.*, Debugging.