Placing marks and shapes
An indicator line carries a value on every bar. Most of the time, though, what you are after is the event, not the value: the bar where the cross happened, the bar where volume spiked. This page shows you how to stamp those events onto the chart.
Why a separate tool?
plot stitches a series into one unbroken line, bar after bar. Events, on the other hand, are sparse. Try to show a condition that holds on three bars out of a hundred with a line and nothing comes through.
Marking tools do the opposite. Where the condition holds they stamp one thing on that bar; where it does not, they draw nothing at all. There are four of them: plotshape stamps a shape, plotchar stamps a character, plotarrow draws an arrow scaled to a value, and label leaves free text.
All four draw on the price panel. None of them opens a lower panel; only plot can do that.
Before you place a mark, ask yourself one question: does this condition hold on every bar, or only now and then? If it holds on every bar, what you really want is a line.
plotshape: stamping a shape on the signal bar
This is the one you will reach for most. Its first argument is a series that produces true or false. Every bar where it comes out true gets a shape.
// 20 barlik ortalamayi yukari kesen barlari isaretle ortalama = sma(close, 20) alSinyali = crossover(close, ortalama) plot(ortalama, "SMA20", "#787b86") plotshape(alSinyali, "al", style="triangleup", location="belowbar", color="#26a69a")
A grey SMA20 line, with green triangles under the bars where price crossed above it. Every other bar stays clean.
crossover is true only on the bar where the cross actually happens, not on the bars that follow. That is why the triangles appear one at a time instead of in clusters.
Shape options
style sets the form of the shape. Leave it out and you get circle.
- arrowup and arrowdown — a strong claim about direction, good for entry and exit signals.
- triangleup and triangledown — readable even when small, the cleanest pick for crosses.
- circle, square, diamond — neutral forms that flag a bar without claiming a direction.
- cross and xcross — for negative events such as a cancel, an invalidation, or a stop-loss.
- flag — for a standalone event that has to catch the eye.
- labelup and labeldown — boxed shapes that can hold a short piece of text inside.
Both spellings are valid and give the same result: style="triangleup" or style=shape.triangleup.
Location options
location tells the shape where to sit relative to the bar. Leave it out and you get abovebar.
- abovebar — just above the bar's high.
- belowbar — just below the bar's low.
- top and bottom — a fixed strip at the top or bottom edge of the panel, staying level wherever price goes.
- absolute — treats the number you feed the series as a price level and puts the shape exactly there.
hizli = sma(close, 10) yavas = sma(close, 30) // yukari kesisim: isaret barin ALTINDA ve yukari bakiyor plotshape(crossover(hizli, yavas), "al", style="triangleup", location="belowbar", color="#26a69a") // asagi kesisim: isaret barin USTUNDE ve asagi bakiyor plotshape(crossunder(hizli, yavas), "sat", style="triangledown", location="abovebar", color="#ef5350")
A green triangle below the bar when the fast average crosses above the slow one, and a red triangle above the bar when it crosses back below.
Match the directions: an up signal belongs belowbar with an upward shape, a down signal abovebar with a downward shape. Pair them the other way round and the chart stops making sense at a glance.
absolute works a little differently. Instead of true or false you pass a number, and the shape lands at that price. Bars where you pass na get nothing, so a single series carries both the position and the condition.
sinyal = crossover(close, sma(close, 20)) // sinyal barinda dip seviyenin yuzde bir altini hedefle, digerlerinde bos birak seviye = iff(sinyal, low * 0.99, na) plotshape(seviye, "al", style="triangleup", location="absolute", color="#26a69a")
Green triangles sitting one percent below the low of each cross bar. Bars without a cross are left empty.
Text, size and shift
You can put a short piece of text next to the shape. text is the text itself and textcolor is its color. size controls both the shape and the text beside it.
- Named sizes: tiny, small, normal, large, huge. Leave it out and you get normal.
- You can also pass a number; it is read as pixels, between 7 and 40.
- offset shifts the mark along the bar axis. offset=-1 moves the shape one bar back.
guc = rsi(close, 14) // asiri alim barina kutu sekil ve iki harflik yazi koy plotshape(guc > 70, "asiri alim", style="labeldown", location="abovebar", color="#ef5350", text="AA", textcolor="#ffffff", size="small")
Small red boxes reading AA in white, above every bar where the momentum reading pushes past 70.
text is meant to stay short: two or three letters, one word at most. Longer text makes the marks overlap and hides the bars behind them. If you need a sentence, use label.
plotchar: placing a character
plotshape's list of shapes is fixed. When you want something that is not on it, plotchar stamps the character you choose. It works like plotshape, except it takes char instead of style.
ortHacim = sma(volume, 20) // hacim ortalamanin iki katini asarsa panelin ust kenarina H harfi koy plotchar(volume > ortHacim * 2, "hacim", char="H", location="top", color="#ff9800")
Orange H letters along the top edge of the panel, on the bars where volume ran to more than twice its twenty-bar average.
Note the location="top" here. The letter sits at the top edge of the panel rather than on the price, so it never covers the candles and the reading order stays intact.
char holds a single character; leave it out and you get a star glyph. For more than one word, label is the right tool, not plotchar.
plotarrow: showing direction and size together
plotshape and plotchar always draw at the same size; they say nothing about how strong a signal is. plotarrow takes its direction from the sign of your number and its length from the magnitude. One series answers both which way and how much.
// govde buyudukce ok uzar; yon govdenin isaretinden gelir govde = close - open plotarrow(govde, "govde", colorup="#26a69a", colordown="#ef5350", minheight=6, maxheight=60)
One arrow per bar: green and up on bullish bars, red and down on bearish ones, with arrow length tracking the size of the body.
Bars whose value is 0 or na get no arrow at all. Use that as a filter: turn the weak signals into na and only the strong ones remain on the chart.
momentum = rsi(close, 14) - 50 // zayif momentumu na yap; na olan barda ok cizilmez secilmis = iff(abs(momentum) > 15, momentum, na) plotarrow(secilmis, "momentum", colorup="#26a69a", colordown="#ef5350")
Arrows only on the bars where the momentum reading strays more than 15 from its midpoint; everything in between stays empty, which thins the arrows out considerably.
Never hand plotarrow an absolute value. A series wrapped in abs is always positive, so every arrow points up and the direction is lost entirely.
label: leaving text on a bar
A shape or a character carries one fixed meaning. When you want to show a computed number, a level, or a sentence, you need label. It takes a price level and a text, and puts a boxed note right there.
hizli = ema(close, 12) yavas = ema(close, 26) // grafigin son barina tek bir ozet notu birak if barstate.islast { label(close, "Fark: " + str(round(hizli - yavas, 2)), color="#2962ff", style="labeldown", size="small") }
A single blue label on the last bar of the chart, spelling out the gap between the two exponential averages to two decimals.
If you do not pass label a bar, the label always lands on the last bar of the chart. Even when the condition holds a hundred times, all hundred labels pile onto the same spot and any bar-by-bar reading is gone. When you want text on every signal, plotshape's text argument is the right tool, not label.
That is why label does its best work as a one-off summary: a note, a level, or a result resting on the last bar. The barstate.islast condition gives you exactly that.
mark: the one-line shortcut
mark is a pared-down plotshape. It takes a condition, a form, a location, a color, and a short text. It has none of the finer controls such as offset or textcolor, but one line is all it takes to make a signal visible.
// tek satirda sinyal isareti: ince ayar yok, hiz var mark(crossover(close, sma(close, 50)), style="triangleup", location="belowbar", color="#26a69a", text="AL") mark(crossunder(close, sma(close, 50)), style="triangledown", location="abovebar", color="#ef5350", text="SAT")
A green triangle reading AL below the bars where price crosses above the fifty-bar average, and a red one reading SAT above the bars where it crosses below.
A practical habit is to start with mark, confirm the signal really fires where you expect, then move to plotshape. Both accept the same form names, so the switch costs you one line.
Which one, and when
The decision comes down to a single question: what are you trying to show?
- A continuous value that updates on every bar — plot.
- A sparse event with one fixed meaning — plotshape.
- A symbol that is not among the built-in shapes — plotchar.
- Direction and intensity together — plotarrow.
- Changing text or a computed number — label.
- A quick mark with minimal code — mark.
hizli = ema(close, 12) yavas = ema(close, 26) al = crossover(hizli, yavas) sat = crossunder(hizli, yavas) hacimli = volume > sma(volume, 20) * 1.5 // surekli deger -> cizgi plot(hizli, "EMA12", "#26a69a") plot(yavas, "EMA26", "#ef5350") // seyrek olay -> sekil plotshape(al, "al", style="triangleup", location="belowbar", color="#26a69a") plotshape(sat, "sat", style="triangledown", location="abovebar", color="#ef5350") // ikinci kosul -> harf plotchar(hacimli, "hacim", char="H", location="top", color="#ff9800") // tek seferlik ozet -> etiket if barstate.islast { label(close, "Fark: " + str(round(hizli - yavas, 2)), color="#2962ff", style="labeldown", size="small") }
Two exponential average lines, green and red triangles on the cross bars, H letters along the top of the panel on high-volume bars, and one summary label on the last bar.
All four tools appear in this script, and not one of them is doing another's job. That is how you keep a chart rich without letting it get crowded.
Common mistakes
- Passing a number where a condition belongs. plotshape counts any non-zero value as true, so an accidental close - open puts a shape on nearly every bar.
- Misspelling a style or location name. The script raises no error, yet the mark does not come out the way you expected. Copy the names exactly as listed.
- Attaching a mark to a condition that holds on every bar. Tighten the condition first; if you are past a hundred marks, you are probably marking the wrong thing.
- Calling label inside a condition without giving it a bar. The labels stack up on the last bar and none of them stays readable.
- Using plotarrow without a filter. One arrow per bar means price disappears between them.
Give every mark a title. Unnamed ones end up as shape0, shape1 and so on, and once you have three or four of them you will lose track of which rule is which.
Function reference
plotshape(series, title?, style?, location?, color?, offset?, text?, textcolor?, size?)
Returns nothing. It stamps a shape on the bars where the condition holds.
plotchar(series, title?, char?, location?, color?, offset?, text?, textcolor?, size?)
Returns nothing. It stamps a single character on the bars where the condition holds.
plotarrow(series, title?, colorup?, colordown?, offset?, minheight?, maxheight?)
Returns nothing. It draws up or down arrows according to a signed series.
label(price, text, bar?, color?, style?, size?)
Returns nothing. It places a written box at the given price level.
mark(condition, style?, location?, color?, text?, size?)
Returns nothing. It stamps a plain mark on the bars where the condition holds.