Skip to content
GetProfitable
Search

plotshape, plotchar and bgcolor

Lesson 11 · about 10 min

A line is the right visual for a level. A signal is an event, and events want a marker on the bar where they happened. Pine has three lightweight functions for that: plotshape() draws an icon, plotchar() draws a character, and bgcolor() paints the background. They share the same plot budget as plot() and the same global-scope rule, and they take a boolean series that says on which bars to draw.

plotshape

//@version=6
indicator("Signals on chart", overlay=true)
ema = ta.ema(close, 21)
rsi = ta.rsi(close, 14)
bull = ta.crossover(close, ema) and rsi > 50
bear = ta.crossunder(close, ema) and rsi < 50

plot(ema, "EMA 21", color=color.orange)
plotshape(bull, "Bull", style=shape.labelup, location=location.belowbar,
     color=color.green, textcolor=color.white, text="B", size=size.small)
plotshape(bear, "Bear", style=shape.labeldown, location=location.abovebar,
     color=color.red, textcolor=color.white, text="S", size=size.small)

The first argument is the condition. When it is true on a bar, the shape is drawn; when false, nothing. Options:

  • style: shape.triangleup, shape.triangledown, shape.arrowup, shape.arrowdown, shape.circle, shape.square, shape.diamond, shape.cross, shape.xcross, shape.flag, shape.labelup, shape.labeldown.
  • location: location.abovebar, location.belowbar, location.top, location.bottom (edges of the pane), location.absolute (at the value of the series passed as the first argument instead of a boolean).
  • text and textcolor: a short caption; keep it to one or two characters or it clutters fast.
  • size: size.auto, size.tiny, size.small, size.normal, size.large, size.huge.
  • offset: shift the shape by n bars; negative moves it left, which looks like foresight and should be avoided on signals.

location.absolute is the way to mark a price rather than a bar: plotshape(stopLevel, "Stop", style=shape.cross, location=location.absolute, color=color.red) draws a cross at the stop price on every bar where stopLevel is not na.

plotchar

plotchar() is plotshape() with any single Unicode character instead of a built-in shape:

plotchar(rsi > 70, "Overbought", char="!", location=location.abovebar, color=color.red, size=size.tiny)
plotchar(rsi < 30, "Oversold", char="•", location=location.belowbar, color=color.green, size=size.tiny)

Its second use is debugging. plotchar(someValue, "debug", char="", location=location.top) with an empty character draws nothing on the chart but puts the value in the data window, so you can inspect a series bar by bar without cluttering the pane. That is the Pine equivalent of a print statement.

bgcolor and barcolor

bgcolor(color) paints the background of the bar. Pass na to leave it alone:

bgcolor(rsi > 70 ? color.new(color.red, 90) : rsi < 30 ? color.new(color.green, 90) : na)

High transparency (85 to 95) is essential; anything more opaque hides the candles. bgcolor is the natural way to show regimes: in session, trend up, volatility high.

barcolor(color) recolours the candles themselves. It is effective for a one-condition signal (barcolor(close > ema ? color.teal : color.gray)) and confusing if you stack several, because only one colour can win per bar.

Keeping signals honest

The condition you pass is evaluated on every update of the live bar. A crossover that is true at 10:03 can be false at 10:05 when price dips back, and the shape appears and disappears. Historical bars never show that because they only have their final values. If a marker must mean "this happened on a closed bar", combine the condition with barstate.isconfirmed, at the cost of the marker appearing only when the bar ends. Module 5 covers when this matters and when it does not.

Key idea: plotshape and plotchar mark bars where a boolean is true; bgcolor paints regimes. All three must be in the global scope and count toward the 64-output limit.

A small style guide

  • One shape per signal type, consistent colours: green for long, red for short, gray for neutral.
  • Put entries below the bar and exits above, or the reverse, and stick to it.
  • Use size.tiny or size.small; large shapes hide the price action you are trying to read.
  • Give every call a title. The style dialog lists them, and users can turn individual ones off.
  • Prefer bgcolor for conditions that persist for many bars and shapes for conditions that are true on single bars.

Try it: Mark bars where the close is the highest of the last 20 with a tiny triangle above and bars where it is the lowest with a triangle below. Paint the background when ATR as a percent of close is above its 50-bar average. Then add a debug plotchar with an empty character for the ATR percent and read it in the data window.

Recap

  • plotshape(cond, title, style, location, color, text, size) draws an icon on bars where cond is true.
  • plotchar draws any character; with char="" it exports a value to the data window for debugging.
  • bgcolor paints regimes with high transparency; barcolor recolours candles.
  • Conditions on the live bar can flicker; guard with barstate.isconfirmed when a marker must be final.
  • Keep shapes small, titled, and consistent in colour and placement.

See it drawn

Original diagrams for the ideas on this page. Illustrative, not real market data.

A fast and a slow moving average crossingA jagged price line with two smoother average lines through it; the fast average dips below the slow one on the left and cuts back above it in the middle, where a circle marks the crossing.pricefast averageslow averagefast crosses belowfast crosses abovethe slow averageAverages of recent closes; the fast one reacts sooner than the slow one.
Fast and slow moving averages crossing. A moving average is the average of the last few closing prices, redrawn each period. An average over fewer periods turns sooner than one over many, so the two lines cross whenever the recent pace of the market changes.
Bearish divergence between price and RSIA price line whose second peak is higher than its first, drawn above an RSI panel whose second peak is lower than its first, with the two peaks joined by sloping dashed lines.PRICEhigher highRSI (14)70overbought30oversoldlower high
Divergence between price and RSI. RSI measures how one-sided recent price moves have been on a 0–100 scale. Here price sets a higher peak while RSI sets a lower one, so the second push carried less momentum than the first.
Ascending, descending and symmetrical trianglesThree small charts in which price swings get smaller until the range runs out of room.Ascendingflat highsrising lowsDescendingfalling highsflat lowsSymmetricalfalling highsrising lowsEach squeezes price into a narrowing range.
Three triangles. Three ways a market can coil up: a flat ceiling with rising lows, a flat floor with falling highs, or both edges closing in on each other. The swings get smaller, and traders watch whichever edge price leaves first.