CODESCRIPT

Drawing lines

A calculation you never see is a calculation you cannot trust. plot turns a value into a line bar by bar, hline pins a fixed level, and fill shades the space between two lines. This page walks you through all three.

You have written an indicator and it produces numbers. A number that never reaches the screen is a number you cannot check. Drawing it is the quickest way to inspect your own math. That makes the drawing functions a debugging tool, not decoration.

Drawing a value with plot

plot takes one number per bar and joins them end to end into an unbroken line. It is the tool for anything that runs continuously: a moving average, momentum, volume, position direction.

CODESCRIPT
// the 20-bar simple average is recalculated on every bar
average = sma(close, 20)
plot(average)

A single line tracking the 20-bar average appears on the price panel. With no title or color given, the defaults apply.

plot returns nothing; it only draws. So you cannot assign its result to anything. Compute into a variable first and draw afterwards — it reads better, and the same value stays available elsewhere in the code.

plot(series, title?, color?, pane?, style?, linewidth?, linestyle?, offset?, display?)

Returns nothing; draws the series onto the chart.

series
series
The series to draw. It must be numeric; passing text raises an error.
title
text
Optional. The line's name, which is what shows up in the chart legend.
color
color
Optional. A hex color code such as "#26a69a".
pane
text
Optional. Which panel the line goes to. Every line sharing a name lands in the same panel. Leave it out and the panel is picked from the value range.
style
text
Optional. How the value is drawn: line, stepline, histogram, columns, area, circles, cross. Defaults to line.
linewidth
number
Optional. Thickness, from 1 to 4.
linestyle
text
Optional. Dash pattern: solid, dashed or dotted.
offset
number
Optional. Shifts the line N bars right or left. Only the drawing moves; the calculation stays put.
BTCUSDT5m62,522-0.28%plot + crossover markSMA2062,29862,40862,51862,62862,737VOLUME62,522

Giving a title and a color

The second argument is the title, the third is the color. The moment you put two lines in one panel you need both: an unnamed line is unidentifiable in the legend, and two lines in the same color blur together.

CODESCRIPT
fast = ema(close, 12)
slow = ema(close, 26)
// the second argument is the title, the third is the color
plot(fast, "EMA 12", "#26a69a")
plot(slow, "EMA 26", "#ef5350")

A green EMA 12 and a red EMA 26 on the price panel, each listed under its own name in the legend.

Colors are hex: #rrggbb. Two extra characters set transparency, so #2962ff22 is a faint blue. If you would rather work in channels, rgb(38, 166, 154) gets you the same result.

Placing a line in a lower panel

Not every series belongs next to price. RSI travels between 0 and 100 while price is measured in thousands. Put them in one panel and the candles collapse into a thin strip. The fix is the fourth argument: you give the series a panel name.

CODESCRIPT
// RSI moves between 0 and 100 and does not fit the price pane
plot(rsi(close, 14), "RSI 14", "#7e57c2", "rsi")
// every line given the same pane name gathers in that sub-pane
plot(sma(rsi(close, 14), 9), "RSI average", "#ff9800", "rsi")

A panel named "rsi" opens below the price panel. RSI and its own 9-bar average are drawn together there, leaving the candles untouched.

Leave the panel name out and one is chosen for you. That usually works, but it is a guess: a series whose range happens to sit near price ends up on the price panel. Name the panel yourself when you want to be sure.

Changing how the line is drawn

plot does not have to produce a smooth line. Volume reads more honestly as bars, and a higher-timeframe value looks truer as a stepped line. The fifth argument sets the shape, the sixth the thickness, the seventh the dash pattern.

CODESCRIPT
// draw volume as columns and lay its average over them as a dashed line
plot(volume, "Volume", "#78909c", "volume", "histogram")
plot(sma(volume, 20), "Volume average", "#ff9800", "volume", "line", 2, "dashed")

The "hacim" panel shows grey volume bars with a 2-pixel orange dashed average running across them. Bars that overshoot the average stand out at a glance.

A fixed level with hline

Some levels never move. The 70 and 30 thresholds on RSI, a target price you have in mind, a round number. You do not need plot for these; plot expects a fresh value each bar, and you only have one number. hline exists for exactly this.

hline(price, title?, color?, style?, width?, display?)

Returns nothing; draws a horizontal level spanning the chart.

price
number
The level the line sits at. A single constant number.
title
text
Optional. A name for the level.
color
color
Optional. The line color.
style
text
Optional. solid, dashed or dotted. Defaults to dashed.
width
number
Optional. Thickness in pixels. Defaults to 1.
CODESCRIPT
plot(rsi(close, 14), "RSI 14", "#7e57c2", "rsi")
// these thresholds do not change bar by bar, they stay as fixed levels
hline(70, "Overbought", "#ef5350")
hline(50, "Middle", "#90a4ae", "dotted")
hline(30, "Oversold", "#26a69a")

Three horizontal levels in the RSI panel: a red dashed line at 70, a grey dotted one at 50, a green dashed one at 30. The RSI line now has something to be read against.

Do not hand hline an indicator series. Write sma(close, 50) and nothing breaks, but nothing works either: only the last bar's value is taken and the line freezes there. Use plot for an average that moves. This is the single most common beginner trap.

Shading between two lines with fill

Often the gap between two lines says more than the lines do. A widening band means volatility is picking up; a narrowing one means things are calming down. Shade that gap and you see it in a single glance.

fill(a, b, color)

Returns nothing; shades the area between a and b.

a
series
One edge of the band. A series, a constant number, or an hline().
b
series
The other edge of the band.
color
color
Optional. The fill color. Give it a transparent code so the candles underneath stay readable.
CODESCRIPT
band = bb(close, 20, 2)
plot(band.upper, "Upper band", "#2962ff")
plot(band.lower, "Lower band", "#2962ff")
// the trailing 22 is transparency; the candles stay visible under the fill
fill(band.upper, band.lower, "#2962ff22")

Two blue band lines on the price panel with a translucent blue body between them. The body swells as volatility rises and pinches in when things go quiet.

The edges can be fixed levels too. Pass hline() calls straight into fill and the region between them gets shaded. This is how you mark the neutral zone between overbought and oversold.

CODESCRIPT
plot(rsi(close, 14), "RSI 14", "#7e57c2", "rsi")
// fill the neutral zone between the two fixed levels
fill(hline(30), hline(70), "#7e57c222")

The area between 30 and 70 in the RSI panel fills with translucent purple. The moment the line leaves that band, it catches your eye.

fill does not draw the edges, only the space between them. If you want the boundaries visible, draw them separately with plot or hline. And keep the color transparent — a solid fill hides the candles beneath it.

When plot is not enough

plot is for a value that runs continuously. When you do not have a number on every bar, or you want separate marks rather than a line, you switch tools. The list below points you to the right one.

  • To mark only the bars where a signal fires, use plotshape; it drops a triangle, circle or flag.
  • When a single character is enough, plotchar gives a cleaner mark.
  • To show direction and strength at once, plotarrow draws an arrow that grows with the value.
  • To attach free text at a specific price, reach for label.
  • For a slanted line between two points use trendline; plot cannot do it, because it ties one value to every bar.
  • To frame a region with a rectangle, box is the fit.
  • To color a whole bar's background use bgcolor; fill only shades between two series, never the backdrop.
  • For a summary pinned in a corner, infoTable writes your rows there.

You can still bend plot into a marker. The line breaks on na bars, so if iff only produces a value on signal bars the rest stay empty. When you want the same thing as a discrete shape, plotshape looks tidier.

CODESCRIPT
// produce a value only on crossover bars, leave the rest as na
signal = crossover(close, sma(close, 20))
plot(iff(signal, low, na), "Crossover point", "#26a69a", "price", "circles")
// if you want to mark the same signal in a separate style
plotshape(signal, "Crossover", "triangleup", "belowbar", "#26a69a")

Green dots at the low on up-cross bars, with green triangles under those same bars. Nothing at all is drawn on the na bars.

The line breaking on na bars is a feature, not a flaw: it shows you which bars the calculation was not ready for. sma(close, 200), for instance, stays empty over the first 199 bars. When you see a gap, check that first.

Common mistakes

  • Handing plot a piece of text. The series must be numeric, and text stops the code with a clear error.
  • Drawing a series on a completely different scale from price onto the price panel. The candles get crushed. Name the panel.
  • Trying to draw a moving average with hline. The line freezes at the last bar's value and misleads you.
  • Calling fill without ever drawing the edges. The band shows up, but its boundaries stay vague.
  • Leaving the fill color solid. The candles disappear underneath it.
  • Piling fifteen lines into one panel. The chart turns to noise and none of them can be read.

To line a series up against an earlier stretch of the chart, use the offset argument. It slides the drawing N bars either way and leaves the calculation alone. Never take an entry off a shifted line — what you are looking at is an older value.