OpenScriptv0.5.0Documentation
GitHub

Visuals overview

Everything an OpenScript study can put on a chart, which call draws each picture, which pane it lands in, and what each one adds to the study.

On this page
  1. One study, one of each surface
  2. The surfaces
  3. If you want this, use that
  4. Where each call may appear
  5. What costs a plot slot
  6. Which pane a thing lands in
  7. Declaration options that shape the picture
  8. The order things are painted in
  9. Two more worked examples
  10. Choosing between a plot, a level and a drawing

A script produces two kinds of output: numbers, and pictures made from numbers. This page is the map of the pictures. It names every call in OpenScript (also called OpenAlgo Script) that draws on a chart, says what each one is good at, and gives you four questions that pick the right one. Read it before the detailed pages, so that when you want a line, a band, a marker or a shaded zone you already know which call to reach for.

The four questions are always the same:

  1. Is the thing one value per bar, or a shape that spans several bars?
  2. Does it have a price to sit at, or is it a fact about the whole bar?
  3. Is it fixed before the first bar runs, or does it appear and disappear as bars arrive?
  4. Which pane should a reader find it in?

Answer those four and the call chooses itself.

One study, one of each surface

Here is a complete overlay study (one drawn over the price candles) that uses most of the drawing calls at once, so you can see them side by side. Save it in the Scripts panel of the /trading page and apply it to any NSE chart.

version 1
study("Surface tour", overlay = true)

len = input(20, "Length", min = 2, max = 500)

basis = sma(close, len)
band  = 2 * stdev(close, len)

// Two named plots, so the fill has two columns to shade between.
pUpper = plot(basis + band, "Upper", aqua)
pLower = plot(basis - band, "Lower", aqua)
plot(basis, "Basis", orange, width = 2)

fill(pUpper, pLower, color = aqua, opacity = 0.08)

// A regime has no price to sit at, so it is painted rather than plotted.
// Absent during warmup, so the candles keep their own colours there.
trendColor = isNone(basis) ? none : (close > basis ? lime : red)
barColor(trendColor)
background(close > basis + band ? fade(orange, 93) : none)

// An event, on the one bar it happened.
if crossUp(close, basis + band)
    signal("BREAKOUT", at = "above", shape = "triangleUp")

Read it as: three plotted lines, one shaded band between two of them, a colour on every candle, a faint wash on the bars where price closed above the upper band, and a marker on the bars where it first crossed out.

The surfaces

Everything a script draws lands on one of seven surfaces.

SurfaceCallsWhat it draws
Plotted columnsplot(), plotCandles()One value per bar, drawn as a line, a step, an area, a histogram, columns or candles
Shaded bandsfill()The region between two plotted columns
Horizontal levelslevel()One line straight across the pane at one price
Bar markerssignal()A named marker on one bar
Per-bar paintbarColor(), background()A colour for the bar's candle, or a shade behind the bar's whole column
Drawing objectsdraw.line(), draw.label(), draw.box(), draw.polyline()Objects anchored to a time and a price, created, moved and deleted over many bars
The pinned gridtable(), cell()A panel fixed to a corner of the pane, showing the current state rather than a history

Plots, fills and markers together look like this on a real chart. This HalfTrend study on a BHEL 15 minute chart draws its trend level as two plots, blue while the trend is up and red while it is down, shades a channel beside the level with two fills, and puts a labelled marker on each flip:

HalfTrend study on a BHEL 15 minute chart: a blue up level and a red down level with shaded channels and Buy and Sell labels
HalfTrend, written in OpenScript: the level holds flat through noise, a channel rides it, and each flip is labelled.

Two more calls produce output that is not drawn at all. People look for them here, so they belong on the map: alert() raises an alert on the bar it runs on, and print() writes a value to the script's log. See Alerts from scripts and Debugging.

If you want this, use that

Keep this table open while you write. The last column names the page that covers the call in full.

If you wantUseExplained in
A moving average drawn over priceplot(value, "Title", colour)Plots
A value that changes once a session or once a dayplot(..., style = "step")Plots
An oscillator histogram coloured by signplot(h, "Hist", color = h > 0 ? lime : red, style = "histogram")Plots
Volume as columns in a pane of its ownplot(volume, "Volume", style = "column")Plots
Candles built from your own four valuesplotCandles(o, h, l, c, "Title")Plots
A plot pushed forward or back along the time axisplot(..., offset = n)Plots
One line of a pane study drawn over priceplot(..., overlay = true)Plots
A second series on the left price axisplot(..., scale = "left")Plots
A shaded band between two linesfill(plotA, plotB, ...)Fills
A band that changes colour with which line leadsfill(a, b, colorUp = ..., colorDown = ...)Fills
Shading between a line and a fixed valueAn invisible second plot, then fillFills
A fixed reference line at 70, 30 or zerolevel(70, "Overbought", ...)Levels
A line at the previous day's highlevel(dayHigh, "Previous day high", ...)Levels
A pane whose scale never rescalesstudy(..., range = [0, 100])Levels
How many decimals a study's own pane showsstudy(..., precision = 2)Levels
A pane axis that reads as a percentage or a volumestudy(..., format = "percent")Levels
A colour that fades, blends or changes per barfade, mix, a ternaryColors
A mark on the one bar something happenedsignal("BUY")Labels and shapes
A plate of text at a pricedraw.label(t, p, "Text")Labels and shapes
The candles recoloured by regimebarColor(colour)Bar colouring and backgrounds
The whole bar column shaded behind everythingbackground(colour)Bar colouring and backgrounds
A trendline between two points in the pastdraw.line(t1, p1, t2, p2)Lines and boxes
A zone that is extended and later deleteddraw.box(...), then the draw settersLines and boxes
A readings panel in a cornertable(...) and cell(...)Tables
An alert when a condition is metalert(...)Alerts from scripts
A value written to the log while debuggingprint(value)Debugging

Three rows of that table are worth their reasons, because they are where people most often reach for the wrong call.

A crossing is not a plot. A crossing happens on one bar. A plotted column carries one value on every bar, so the only way it can show an event is by being absent everywhere else, which spends a whole column on one dot. signal() is the call for an event: one call, one named marker, with at and shape when you care where it sits and what it looks like.

A regime is not a price. "The trend is up", "the opening range is still forming", "volatility is unusually high": none of these has a price to sit at. Drawing one as a line puts a flat series through the price scale saying something that has nothing to do with price. background() shades the bar's whole column and barColor() recolours the candle itself. Both take none to mean "leave this bar alone", which is how a conditional paint switches itself off.

A fixed reference is not a plot either. A line at 70 does not need a column of identical numbers, a value in the legend and a group of rows in the settings dialog. level() draws it with one call and none of those.

Where each call may appear

Some drawing calls declare the fixed shape of the study, and the rest are per-bar events. This rule catches everyone once.

CallsWhere they may appearWhy
plot, plotCandles, fill, level, tableTop level only. Inside an if, a loop or a function it is OS3006They declare the study's fixed shape. The legend, the settings dialog and the pane are built before the first bar runs, so the set of columns, bands, levels and grids must be known when the script compiles
inputTop level only. Inside a block or a function it is OS3007Each input is one row of the settings dialog, built before the first bar
signal, barColor, background, cell, print, alert, everything in drawAnywhereThey are per-bar events and per-bar paint. Nothing about them has to be known in advance

So you never hide a plot by wrapping it in an if:

trending = adx(14, 14)[0] > 25
ema20 = ema(close, 20)

if trending
    plot(ema20, "EMA 20", aqua)

You hide it by giving it the absent value, none, on the bars where you do not want it:

trending = adx(14, 14)[0] > 25
ema20 = ema(close, 20)

plot(trending ? ema20 : none, "EMA 20", aqua)

The same idea works on every surface. Absence reaching a drawing surface is a gap, never a zero, and that one rule is what makes "hide it with none" work everywhere without a second mechanism. Absent values explains where none comes from.

SurfaceWhat an absent value does
A plotted columnThe line breaks. Nothing is drawn on that bar
A shaded bandThe band stops, and resumes where both ends have values again
A levelA level is drawn at its price on the last bar, so an absent price there means no line at all
A markerNo marker on that bar
barColorThe candle keeps its own colour
backgroundThe bar's column is not shaded
A table cellThe cell is left blank

What costs a plot slot

A plot slot is one plotted column. It keeps a value for every bar, shows its current value in the study's legend (the row at the top of the pane with the study's name), and gets its own group in the Style tab of the settings dialog, where the user can change its colour, opacity, thickness, line style and plot style without editing your script. Only two calls create one.

CallPlot slotsKept per bar
plot(...)OneIts value, plus a colour when the colour is worked out bar by bar
plotCandles(...)OneFour values: open, high, low and close
fill(...)None: it names two plots that already existNothing of its own
level(...)NoneIts price, of which the last bar's is drawn
signal(...)NoneIts text, on the bars it fires
barColor(...), background(...)NoneOne colour per bar each
table(...), cell(...)NoneThe cells as last written
draw.*NoneThe objects the script is holding
alert(...)NoneIts condition and its message

Two consequences come up in real scripts.

A function with several outputs costs one slot per output you draw. macd() returns three numbers in one array, from one call and one piece of state. Drawing all three is three plot calls and three slots. Drawing only the histogram is one slot, and the other two outputs cost nothing.

A shaded band needs its two edge plots even when you do not want the lines. When the band is the whole point, keep the plots and make them invisible with a fully transparent colour. fade() takes transparency, and 100 is invisible:

basis = sma(close, 20)
band  = 2 * stdev(close, 20)

pUpper = plot(basis + band, "Upper", fade(aqua, 100))
pLower = plot(basis - band, "Lower", fade(aqua, 100))
fill(pUpper, pLower, color = aqua, opacity = 0.08)

An invisible plot draws nothing, and the legend leaves out its value. Name the band's colour yourself, as here: a band given no colour takes its first plot's colour, and that colour is invisible.

Only plots appear in the Style tab. When you pass a colour input straight to a plot, by the input's own name, the plot's colour in the Style tab and the input are one setting. See Inputs and Settings and style.

Which pane a thing lands in

A study is either drawn over the instrument's candles or given a pane of its own, and the declaration decides which. overlay defaults to false, so a study with no overlay argument gets its own pane below the price.

version 1
study("Relative strength", precision = 2, range = [0, 100])

plot(rsi(close, 14), "RSI", purple, width = 2)
An oscillator study drawn in its own pane below the price chart
A study with overlay = false gets its own pane below the price.

The declaration sets the default for everything the file draws. Two calls can override it item by item, and the rest cannot.

ThingWhere it drawsCan one item be moved?
plot(...)The study's paneYes: overlay = true puts this one column on the price pane
plotCandles(...)The study's paneNo: it has no overlay argument
fill(...)The study's paneYes, with its own overlay argument. Move its two plots with it
level(...)The study's paneNo: a level has no overlay argument
signal(...)Over price, above or below the candle. In a study with its own pane, above or below the study's first plotNo
background(...)The study's pane, the full height of the bar's columnNo
barColor(...)The instrument's own candles on the price pane, even from a study with its own paneNo
table(...)A corner of the study's paneNo, but position picks the corner
draw.*The study's pane, at a time and a price on that pane's scaleNo

barColor is the one that surprises people, and it is deliberate: a trend reading computed in a pane below the chart is most useful painted onto the candles the trader is watching, and forcing the study to be an overlay just to reach them would mean giving up its own axis. The candles are one object, so when two studies both colour them, only one study's colouring is shown, decided by the order of the studies on the chart.

A study with its own pane and no plot at all draws no markers, because its markers have no line to sit on.

Within a pane, a plot's scale argument picks the price axis it maps to:

scaleMeans
"right"The right-hand axis. The default
"left"The left-hand axis, for a second series in different units. It appears when a plot uses it
"none"A hidden scale of its own, fitted to this column alone, so it never stretches the axis the other plots read against

Declaration options that shape the picture

Five options on study(...) decide how the whole study is drawn. Each must be fixed when the script compiles. They are covered in full in Declarations.

OptionDefaultControls
overlayfalsetrue draws on the price pane, false gives the study its own pane
precision4Decimals on the axis and legend of a study with its own pane, a whole number from 0 to 10. Over the price pane the instrument's own formatting is kept
format"price""price", "percent" or "volume": how the study's own pane axis and legend read
rangenone[min, max] fixes the study pane's scale, as in [0, 100]
scale"right"Accepted, but the /trading chart does not apply it in this release. Set scale on each plot instead

The order things are painted in

The /trading chart paints each pane in four layers, bottom to top:

LayerHolds
1, behind the dataShading from background(...) and bands from fill(...)
2The instrument's candles (on the price pane), then plotted columns and candle plots in the order they were added
3Levels, markers from signal(...) and drawing objects from draw
4, on topThe grid from table(...) and cell(...), which covers whatever is under its corner

Four facts are enough to design with:

  • Bands and backgrounds sit behind the candles and lines, so they never hide them. A strong one still drowns their colours, so keep both faint. fade(silver, 92) reads as a tint; silver at full strength reads as a bug.
  • A fill given no colour takes its first plot's colour at twelve percent strength. Name a colour and you get that colour, dimmed only by opacity, which starts at 1.
  • Within one study, plots are painted in the order you declare them, so a plot declared first sits under the ones declared after it. That is the cheapest control you have over what covers what.
  • An absent value removes a layer for that bar rather than painting a zero over what is underneath.

Two more worked examples

An oscillator with its own pane, scale and lines

version 1
study("RSI with a zone", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

r = rsi(close, len)

// Levels, not plots: horizontal lines with no columns and no legend values.
level(70, "Overbought", fade(red, 40))
level(30, "Oversold", fade(lime, 40))

pOsc = plot(r, "RSI", purple, width = 2)

// The midline exists only so the fill has a second column to name.
// Fully transparent, so it draws no line of its own.
pMid = plot(50, "Midline", fade(gray, 100))

fill(pOsc, pMid, colorUp = fade(lime, 86), colorDown = fade(red, 86))

range = [0, 100] fixes the pane's scale, so 70 sits at the same height on every chart you open. That is the reason to fix a range: a reading with real bounds should keep them on screen.

A pane study that reaches onto the price chart

version 1
study("Trend strength", precision = 2, range = [0, 100])

diLen  = input(14, "DI length",  min = 1, max = 100)
adxLen = input(14, "ADX length", min = 1, max = 100)

strength = adx(diLen, adxLen)[0]

// Computed at the top level, on every bar. A stateful call placed inside a
// branch would only advance on the bars that branch ran.
e20 = ema(close, 20)

level(25, "Trending above here", fade(gray, 50))
plot(strength, "ADX", purple, width = 2)

// This one column belongs where the trader is looking, not down here.
plot(strength > 25 ? e20 : none, "EMA 20 while trending", orange, width = 2, overlay = true)

// And the candles themselves carry the regime: greyed out while the market
// is not trending, left alone while it is and during warmup.
barColor(isNone(strength) or strength > 25 ? none : fade(gray, 60))

Three things are worth noticing. The study owns a pane, yet one of its columns is on the price pane and so is its bar colouring. The average is computed on every bar and only the drawing is conditional: computing it inside the ternary or an if would raise warning OS8001 and leave holes in the line. And barColor(none) on the trending bars leaves those candles their own colour, which is a stronger picture than painting them a second colour. The isNone test keeps the warmup bars, where adx() has no value yet, from being greyed out as if they were quiet.

Choosing between a plot, a level and a drawing

These three overlap, and picking the wrong one gives a script that works and then becomes painful to change.

plotleveldraw.line
ShapeOne value per barOne horizontal line across the paneA segment between two anchored points
How manyFixed when the script compiles, one per callFixed when the script compiles, one per callAs many as the script creates, at any time
ValueA different value on every barOne price, read again every bar; the last bar's winsTwo points, movable later
HistoryEvery bar's value is drawn, and the series can be read back with []None: you see one lineEach object stays until deleted
CostA plot slotNo plot slotNo plot slot, but memory per object
Good forAn indicator, a band edge, a stop that moves every barA threshold, the previous day's high, a zero lineA trendline between two pivots, a zone, an annotation

The tie-breaker: if the thing has a value on every bar, plot it. If it has one value that the whole pane should show at a single height, make it a level. If it starts somewhere in the past and ends somewhere else, draw it.

Related: Plots, Levels, Fills, Colors, Bar colouring and backgrounds, Labels and shapes, Lines and boxes, Tables, Plotting reference.