Edgoro
Indicator · Signals and price structure

Pine Script crossover and crossunder conditions (v6)

How ta.crossover() and ta.crossunder() work in Pine Script v6, the difference with >, crossing a fixed level, a cross within the last N bars, in one script.

Short answer

ta.crossover(a, b) is true only on the bar where a ends above b after being at or below it on the previous bar, and ta.crossunder(a, b) is the mirror image. To test whether one line is above another on every bar, use a > b instead.

The script

Five kinds of condition side by side: the cross event, the above/below state, a fixed level, a recent cross and a combination of them. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.

Pine Script v6
//@version=6
indicator("Crossover conditions", overlay = true)

fastLen = input.int(10, "Fast SMA", minval = 1)
slowLen = input.int(30, "Slow SMA", minval = 2)
rsiLen  = input.int(14, "RSI length", minval = 2)
within  = input.int(5, "Recent cross = within N bars", minval = 1)

fast = ta.sma(close, fastLen)
slow = ta.sma(close, slowLen)
rsi  = ta.rsi(close, rsiLen)

// 1. Events: true on the single bar of the cross
bullCross = ta.crossover(fast, slow)
bearCross = ta.crossunder(fast, slow)
anyCross  = ta.cross(fast, slow)

// 2. State: true on every bar while the fast average is above
fastAbove = fast > slow

// 3. A line crossing a fixed level: RSI back above 30
rsiUp30 = ta.crossover(rsi, 30)

// 4. Recency: bars since the last bullish cross (na before the first one)
sinceBull  = ta.barssince(bullCross)
recentBull = not na(sinceBull) and sinceBull <= within

// 5. Combined: RSI leaves oversold shortly after a bullish cross, while it still holds
setup = rsiUp30 and recentBull and fastAbove

plot(fast, "Fast SMA", color.teal)
plot(slow, "Slow SMA", color.orange)
bgcolor(fastAbove ? color.new(color.teal, 93) : color.new(color.red, 93), title = "State")
plotshape(bullCross, "Bullish cross", shape.triangleup, location.belowbar, color.teal, size = size.small)
plotshape(bearCross, "Bearish cross", shape.triangledown, location.abovebar, color.red, size = size.small)
plotshape(setup, "Setup", shape.diamond, location.bottom, color.blue, size = size.small)
plot(anyCross ? 1 : 0, "Any cross", display = display.data_window)
alertcondition(setup, "Setup", "RSI left oversold after a recent bullish SMA cross on {{ticker}}")

How it works

  • What a crossover is. ta.crossover(fast, slow) equals fast > slow and fast[1] <= slow[1]: the fast line is above now and wasn't on the previous bar. ta.crossunder() is fast < slow and fast[1] >= slow[1], and ta.cross() is either one.
  • Event or state. bullCross is true on one bar; fastAbove is true for as long as the trend lasts. The background shows the state and the triangles show the events, so the difference is visible on the chart.
  • Crossing a number. The second argument can be a constant: ta.crossover(rsi, 30) fires when RSI moves from 30 or below to above 30.
  • ta.barssince(bullCross) returns 0 on the cross bar, 1 on the next, and na until the first cross ever. The not na(...) check keeps recentBull false until there is a cross to count from.
  • Every condition has its own line. All the ta.* calls run at the top level on every bar, and the combinations only use their results. That keeps v6's lazy and from skipping them (see the pitfalls).

Pitfalls

  • Entering on a state. In a strategy, if fast > slow followed by strategy.entry() fires on every bar of the trend: after a stop-out the strategy re-enters on the next bar. ta.crossover() waits for a new cross. The EMA crossover strategy puts this into a complete strategy.
  • A cross inside and/or. In v6, and and or stop at the first operand that decides the result, so in close > open and ta.crossover(fast, slow) the crossover isn't evaluated on every bar and its result can be wrong. TradingView warns should be called on each calculation. Assign the cross to a variable first, as the script does.
  • Touching from one side. The previous bar only needs a <= b. If the lines are below, equal for one bar, then below again, ta.crossunder() fires although the fast line was never above. On flat markets, require a gap (fast > slow * 1.001) or a cross that holds for a few bars.
  • Crosses on the live bar. During the live bar a cross can exist at one tick and be gone at the close. Add barstate.isconfirmed for markers and alerts that must not vanish (barstate.isconfirmed example).

Variations

A cross that has held for N bars, and a price level crossed by the close or only by the wick. ta.barssince(not fastAbove) == holdBars is true once, on the bar where the fast line has been above for exactly holdBars bars.

//@version=6
indicator("Cross variations", overlay = true)
holdBars = input.int(3, "Bars the cross must hold", minval = 1)
level    = input.float(0.0, "Price level (0 = previous 20-bar high)", minval = 0)
fast = ta.ema(close, 10)
slow = ta.ema(close, 30)
hi20 = ta.highest(high, 20)[1]
fastAbove = fast > slow
// Fires once, on the holdBars-th bar in a row with the fast EMA above
heldBull = ta.barssince(not fastAbove) == holdBars
float lvl = level > 0 ? level : hi20
closeAbove = ta.crossover(close, lvl)
// The wick reached the level, whether or not the bar closed above it
wickTouch = high >= lvl and high[1] < lvl[1]
plot(lvl, "Level", color.gray, style = plot.style_stepline)
plotshape(heldBull, "Cross held", shape.labelup, location.belowbar, color.teal, text = "held", textcolor = color.white, size = size.tiny)
plotshape(closeAbove, "Close above level", shape.triangleup, location.belowbar, color.blue, size = size.small)
plotshape(wickTouch and not closeAbove, "Wick only", shape.xcross, location.abovebar, color.gray, size = size.tiny)

Questions

What is the difference between crossover and crossunder in Pine Script?

ta.crossover(a, b) is true when a moves from at or below b to above it; ta.crossunder(a, b) when a moves from at or above b to below it. Both are true on that one bar only. ta.cross(a, b) is true for either direction.

How do I check if a crossover happened in the last N bars?

Count the bars since it with ta.barssince(): recent = not na(ta.barssince(cross)) and ta.barssince(cross) <= 5. Better, store ta.barssince(cross) in a variable on its own line and test the variable.

How do I detect price crossing a horizontal line?

Use the level as the second argument: ta.crossover(close, level), where level is a number, an input or a series. For a line drawn with line.new(), read its price on the current bar with myLine.get_price(bar_index).

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.

Need a different version of this script?

Describe the change in plain English and Edgoro writes the Pine Script v6 for you, checked with Pine Check. For example:

“Signal when RSI crosses back above 30 within 5 bars after the 10 SMA crossed above the 30 SMA, and send an alert once the bar closes.”