The script
An EMA cross filtered by RSI: labelled markers for the signals the filter keeps, small dots for the crosses it rejects, and alerts for both sides. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.
//@version=6
indicator("Buy and sell markers", overlay = true)
fastLen = input.int(9, "Fast EMA", minval = 1)
slowLen = input.int(21, "Slow EMA", minval = 2)
rsiLen = input.int(14, "RSI length", minval = 2)
fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
rsi = ta.rsi(close, rsiLen)
crossUp = ta.crossover(fast, slow)
crossDown = ta.crossunder(fast, slow)
// Signals the RSI filter keeps, only once the bar has closed
buy = crossUp and rsi < 70 and barstate.isconfirmed
sell = crossDown and rsi > 30 and barstate.isconfirmed
// Crosses the filter rejects
skipBuy = crossUp and rsi >= 70 and barstate.isconfirmed
skipSell = crossDown and rsi <= 30 and barstate.isconfirmed
plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)
// Labels with text: the text must be a fixed string
plotshape(buy, "Buy", shape.labelup, location.belowbar, color.teal, text = "BUY", textcolor = color.white, size = size.small)
plotshape(sell, "Sell", shape.labeldown, location.abovebar, color.red, text = "SELL", textcolor = color.white, size = size.small)
// One character per bar, no text
plotchar(skipBuy, "Rejected buy", "•", location.belowbar, color.gray, size = size.tiny)
plotchar(skipSell, "Rejected sell", "•", location.abovebar, color.gray, size = size.tiny)
alertcondition(buy, "Buy", "Buy signal on {{ticker}} at {{close}}")
alertcondition(sell, "Sell", "Sell signal on {{ticker}} at {{close}}")How it works
plotshape(buy, "Buy", shape.labelup, location.belowbar, color.teal, ...). The first argument is the condition: where it'struethe shape is drawn, where it'sfalsenothing is. The next four positional arguments are the title (the name in the Style tab, where each marker can be hidden), the shape, the position and the color. Everything after that is clearer by name.- Shapes.
shape.labelupandshape.labeldownare the only ones that hold text comfortably; the others aretriangleup,triangledown,arrowup,arrowdown,circle,cross,xcross,diamond,squareandflag.sizegoes fromsize.tinytosize.huge, orsize.auto. - Positions.
location.belowbarandlocation.abovebarfollow each bar's low and high,location.topandlocation.bottomstick to the edges of the pane, andlocation.absoluteputs the shape at the value of the series itself (see the variations). plotchar()draws one character instead of a shape. Any single Unicode character works, which is handy for discreet marks like the dots here.barstate.isconfirmedis part of each signal, so a marker only appears once its bar has closed and never vanishes afterwards. On historical bars it's always true, so the history is unchanged. The barstate.isconfirmed example shows what happens without it.alertcondition()makes both signals available in TradingView's alert dialog;{{ticker}}and{{close}}are filled in when the alert fires. The differences withalert()are in alertcondition() vs alert().
Pitfalls
- Text built from data. The
textofplotshape()must be a fixed string written in the code.text = str.tostring(close)stops the script with series string used but const string expected. To print a price on a marker, draw a label withlabel.new(), as in the labels and lines example. - plotshape() inside an if block.
if buyfollowed by an indentedplotshape(...)gives Cannot use plotshape in local scope. Keep everyplot*()call at the top level and pass the condition as the series. - abovebar and belowbar in a separate pane. With
overlay = false, these two positions still follow the chart's bars, which stretches the pane's scale. In a pane, uselocation.top,location.bottom, orlocation.absolutewith a value on the pane's own scale, such asrsi. - Markers that appear and then vanish. Without
barstate.isconfirmed, a cross that exists for a few ticks of the live bar draws a marker, fires an alert, and is gone when the bar closes. See repainting explained. - Plot limit. Each
plotshape()andplotchar()call counts toward the script's limit of 64 plots, likeplot(). When a script has many signal types, use one call with a conditional color rather than one call per color.
Variations
Only the first signal of each side, at an exact price, with arrows. A var variable remembers the side of the last marker, so repeated buys in a row are skipped. location.absolute puts each triangle half an ATR beyond the bar, and plotarrow() draws an up arrow for positive values and a down arrow for negative ones.
//@version=6
indicator("First signal only", overlay = true)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
atr = ta.atr(14)
buy = ta.crossover(fast, slow) and barstate.isconfirmed
sell = ta.crossunder(fast, slow) and barstate.isconfirmed
// Side of the last marker: 1 after a buy, -1 after a sell
var int side = 0
firstBuy = buy and side != 1
firstSell = sell and side != -1
if firstBuy
side := 1
if firstSell
side := -1
// With location.absolute the series is the price where the shape goes
plotshape(firstBuy ? low - 0.5 * atr : na, "Buy", shape.triangleup, location.absolute, color.teal, size = size.small)
plotshape(firstSell ? high + 0.5 * atr : na, "Sell", shape.triangledown, location.absolute, color.red, size = size.small)
// Positive values draw an up arrow, negative values a down arrow
plotarrow(firstBuy ? 1 : firstSell ? -1 : na, "Arrows", color.teal, color.red)
Questions
How do I show the price on a plotshape marker?
You can't: plotshape() only accepts fixed text. Draw a label instead, for example label.new(bar_index, low, str.tostring(close, format.mintick), style = label.style_label_up). Labels accept text built from data, up to 500 per script with max_labels_count.
What is the difference between plotshape, plotchar and plotarrow?
plotshape() draws a built-in shape (triangle, label, circle, flag...) and can carry fixed text. plotchar() draws any single character. plotarrow() draws an up arrow for positive values and a down arrow for negative ones, with a length that grows with the value.
Why doesn't my plotshape appear on the chart?
Usually the condition is never true (plot it in the Data Window to check), the shape is hidden in the Style tab, or location.absolute is used with a bool: with absolute, the series must be the price where the shape goes, and na where there is no shape.
Related pages
Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.