Your RSI or MACD indicator starts producing results that look slightly off. The values on individual bars do not match what the standalone built-in indicator shows. You have not changed the formula, only moved a function call inside a condition. In Pine Script v6, that can silently corrupt the calculation.
This post explains why, shows the exact pattern that triggers it, and gives a simple rule that prevents it completely. It is one of the most common sources of subtle incorrect behavior in AI-generated and hand-written Pine Script v6 code alike.
What lazy evaluation means in Pine Script v6
In Pine Script v6, the and and or operators evaluate lazily, also called short-circuit evaluation. For and: if the left side is false, the result is already known to befalse regardless of the right side, so the right side is not evaluated at all. For or: if the left side is true, the result is already known to be true, so the right side is skipped.
This is standard behavior in most programming languages. In Pine Script it creates a specific problem because of how built-in technical analysis functions work.
Why ta.* functions must run on every bar
Functions like ta.rsi(), ta.ema(), ta.crossover(), and ta.macd() are not stateless calculators. They maintain internal history buffers. ta.rsi() tracks a running average gain and average loss that it updates on every bar. ta.crossover() tracks whether the crossover event occurred on exactly this bar versus the previous bar. ta.ema() carries a running EMA value forward from each bar.
If one of these functions is skipped on even a single bar — because the lazy-evaluated left side was already decisive — its internal state falls one bar behind. On subsequent bars, the function calculates from a stale starting point. The output is wrong, and nothing in TradingView will warn you. The script compiles, it runs, and it plots values that are subtly but consistently incorrect.
//@version=6
indicator("Lazy evaluation bug — RSI history corrupted", overlay=false)
fastMa = ta.ema(close, 9)
slowMa = ta.ema(close, 21)
// BROKEN: ta.rsi() is on the right side of 'and'.
// When fastMa <= slowMa, the 'and' short-circuits and ta.rsi() is never called.
// ta.rsi() maintains a running history buffer internally. If it skips even
// one bar, its calculation on subsequent bars is wrong — silently wrong,
// because the script still compiles and plots something.
signal = fastMa > slowMa and ta.rsi(close, 14) > 50
plotshape(signal, "Signal", shape.circle, location.abovebar, color.green)The fix: hoist every ta.* call to its own line
The solution is straightforward: evaluate any ta.* function that maintains internal state at the global scope, outside of anyand or or expression. Assign its result to a variable. Then use that variable inside the condition. The function now runs unconditionally on every bar, its state stays correct, and the boolean result participates in the compound condition without risk.
//@version=6
indicator("Lazy evaluation fix — hoist ta.* calls", overlay=false)
fastMa = ta.ema(close, 9)
slowMa = ta.ema(close, 21)
// FIXED: evaluate ta.rsi() unconditionally at the global scope first.
// It now runs on every single bar, maintaining its internal history correctly.
// Only the boolean result participates in the 'and' expression.
rsiValue = ta.rsi(close, 14)
signal = fastMa > slowMa and rsiValue > 50
plotshape(signal, "Signal", shape.circle, location.abovebar, color.green)This is not a workaround — it is the documented correct pattern for Pine Script v6. The same fix is used throughout the code examples in the multi-timeframe indicator guide and the indicator to strategy guide for exactly this reason.
The same applies to or
The or operator short-circuits in the opposite direction: if the left side is true, the right side is skipped. The fix is identical: hoist any ta.* call on the right side to its own variable before the condition.
//@version=6
indicator("'or' has the same problem", overlay=false)
// 'or' also short-circuits: if the left side is true, the right side is skipped.
// If ta.crossunder() is on the right side of 'or', it may be skipped on bars
// where the left condition is already true, corrupting its history.
macdLine = ta.macd(close, 12, 26, 9)[0]
// BROKEN
exitSignal = close < ta.sma(close, 50) or ta.crossunder(macdLine, 0)
// FIXED: hoist ta.crossunder first
crossUnder = ta.crossunder(macdLine, 0)
exitSignalFixed = close < ta.sma(close, 50) or crossUnderThe general rule
Any ta.* function that maintains internal state — which is essentially all of them — must be called at the global scope of the script on every bar. Never place such a call directly inside an and or or expression, inside a ternary operator, or inside an if condition where it might not be reached on every bar.
//@version=6
indicator("Nested conditions — hoist everything", overlay=false)
rsi = ta.rsi(close, 14)
macd = ta.macd(close, 12, 26, 9)[0]
signal = ta.macd(close, 12, 26, 9)[1]
// If you have multiple ta.* calls in a compound condition, hoist all of them.
// The rule: any ta.* function that maintains internal state must run every bar.
crossUp = ta.crossover(macd, signal)
rsiOversold = rsi < 30
// Now combine the pre-computed booleans safely.
longEntry = crossUp and rsiOversoldA simple mental check: if you are about to type ta. inside a compound boolean expression, stop and move it to its own line first.
Why this shows up in AI-generated code
General AI models trained on Pine Script code from across the internet often produce compound conditions with ta.* calls inline, because much of the older Pine Script v5 and v4 code written that way was syntactically valid and not visibly broken. In v5, and and or evaluated both sides regardless of the left side's value, so the problem did not exist. The v6 change was silent from a compilation standpoint, which means old patterns imported into v6 scripts can produce wrong results that the compiler does not flag.
This is one of the changes covered in the v5 to v6 migration guide. When PineScripter generates Pine Script v6 code, it applies the hoisting pattern by default, so the output does not inherit this class of bug. When you ask it to add a condition to an existing script, it edits only the relevant lines while keeping all ta.* calls at the correct scope.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.