The script
From the last bullish EMA cross: the close at the cross, the close at the cross before it, the bars elapsed, the move since, and the highest high reached. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.
//@version=6
indicator("Since the last signal", overlay = true)
fastLen = input.int(20, "Fast EMA", minval = 1)
slowLen = input.int(50, "Slow EMA", minval = 2)
fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
bullCross = ta.crossover(fast, slow)
// Close at the most recent cross, and at the one before it
float lastCrossClose = ta.valuewhen(bullCross, close, 0)
float prevCrossClose = ta.valuewhen(bullCross, close, 1)
// Bars since the most recent cross: 0 on the cross bar, na before the first one
int barsSince = ta.barssince(bullCross)
// Highest high since the cross, kept with var instead of a lookback
var float highSince = na
if bullCross
highSince := high
else if not na(highSince)
highSince := math.max(highSince, high)
float movePct = (close - lastCrossClose) / lastCrossClose * 100
bool higherCross = bullCross and lastCrossClose > prevCrossClose
plot(lastCrossClose, "Close at the last cross", color.blue, style = plot.style_stepline)
plot(highSince, "Highest high since the cross", color.teal, style = plot.style_stepline)
plotshape(bullCross, "Bullish cross", shape.triangleup, location.belowbar, higherCross ? color.teal : color.gray, size = size.small)
plot(barsSince, "Bars since the cross", display = display.data_window)
plot(movePct, "% move since the cross", display = display.data_window)How it works
ta.valuewhen(bullCross, close, 0). On a cross bar it returns that bar's close; on later bars it keeps returning it until the next cross. Occurrence1is the cross before that,2the one before, and so on.higherCrosscompares the two: on a cross bar,lastCrossClose > prevCrossClosemeans this cross happened at a higher price than the previous one, which colors the triangle.ta.barssince(bullCross)is 0 on the cross bar and counts up afterwards. It'snauntil the first cross, so it's shown in the Data Window rather than tested directly.highSincewithvar. The highest high since the cross could also beta.highest(high, barsSince + 1), but a length that keeps growing makes the script read further and further back. Avarvariable reset on the cross and raised withmath.max()needs no history at all; the max_bars_back example explains why that matters.- Data Window plots.
display = display.data_windowkeeps the bars-since count and the move off the chart's scale while still showing them when you hover a bar.
Pitfalls
- Calling them inside an if block.
ta.valuewhen()andta.barssince()track every bar's condition; insideif, they only see the bars where the block runs (should be called on each calculation). Assign them at the top level. - na before the first event.
ta.barssince()returnsnauntil the condition has happened once, and any comparison withnais false, sobarsSince > 10andbarsSince <= 10are both false at first. Decide which waynashould count:na(barsSince) or barsSince > 10. - A number as the condition.
ta.valuewhen(ph, ph, 0)withph = ta.pivothigh(...)doesn't compile in v6, which no longer treats numbers as conditions (if condition must be bool). Writeta.valuewhen(not na(ph), ph, 0). - [1] isn't the previous occurrence.
lastCrossClose[1]is the value on the previous bar, which is the same aslastCrossCloseexcept on cross bars. To compare a cross with the one before it, use occurrence1, as the script does. - Conditions from the live bar. If the condition can flicker during the bar, so can the value returned. Add
barstate.isconfirmedto the condition when the value feeds alerts or orders.
Variations
Bars since a strategy's entry. Strategies don't need ta.barssince() for this: strategy.opentrades.entry_bar_index(0) is the bar index where the open trade was filled, so a time-based exit is one subtraction.
//@version=6
strategy("Exit after N bars", overlay = true, initial_capital = 10000, commission_type = strategy.commission.percent, commission_value = 0.05, slippage = 2)
holdBars = input.int(10, "Bars to hold", minval = 1)
fast = ta.ema(close, 20)
slow = ta.ema(close, 50)
bullCross = ta.crossover(fast, slow)
if bullCross and strategy.position_size == 0
strategy.entry("Long", strategy.long)
// Bars since the entry filled, read from the open trade
if strategy.position_size > 0 and bar_index - strategy.opentrades.entry_bar_index(0) >= holdBars
strategy.close("Long", comment = "Time exit")
plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)
Any series at the event. The source can be anything calculated at the top level: with atr = ta.atr(14), ta.valuewhen(bullCross, atr, 0) freezes the ATR of the cross bar, a common base for a stop that doesn't move.
Questions
How do I get the value of a variable when a condition was last true in Pine Script?
Use ta.valuewhen(condition, variable, 0) at the top level of the script. It returns the variable's value from the most recent bar where the condition was true, and keeps it until the condition is true again.
What does the occurrence parameter of ta.valuewhen mean?
Which past event to read: 0 is the most recent bar where the condition was true, 1 the one before it, 2 the one before that. Write it as a number.
Why does ta.barssince return na?
Because the condition hasn't been true yet on any bar the script has seen. That is common on the first bars of the chart or with rare conditions. Test na(x) before comparing, or use nz(x, value) to substitute a value.
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.