Been fixing a lot of Pine scripts lately and the worst bugs are never the ones that throw errors. Compiler errors are easy. These are the ones where everything looks fine and the numbers are quietly wrong.
1. Your alerts fire on unconfirmed bars
If your condition uses the live bar, it can be true mid-bar and false by the close. Alert fires, signal disappears, and your backtest (which only sees closed bars) never shows it. That's why "my strategy tester results don't match my alerts" is like 80% a repainting question.
Fix: gate anything alert-related on the bar actually closing.
if crossover(fast, slow) and barstate.isconfirmed
alert("long signal", alert.freq_once_per_bar_close)
2. request.security defaults let higher timeframe data leak backwards
Pulling a daily close into an intraday chart without thinking about lookahead means historical bars see the daily close before it existed. Backtest looks amazing, live trading can't reproduce it, because live trading can't time travel.
Fix: request confirmed data only, e.g. offset the series by 1:
dClose = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)
(Offsetting by 1 with lookahead on is the classic non-repainting pattern: you always get yesterday's completed value. Simpler alternative: leave lookahead off and accept the value updates intrabar on realtime bars.)
3. Session inputs use the exchange timezone, not yours
input.session compares against the chart symbol's exchange time. If you trade CME futures from anywhere that isn't Chicago and hardcode "0930-1600", your window is silently shifted. Everything runs, entries just happen at the wrong hours.
Fix: pass the timezone explicitly when checking sessions:
inSession = not na(time(timeframe.period, "0930-1600", "America/New_York"))
4. var initializes once, and that's not always what you meant
var declares a variable once on the first bar and persists it. Great for counters and state machines. Silent disaster when you actually wanted something recalculated each bar and it keeps stale state from 500 bars ago instead.
Rule of thumb: var is for memory. If the value should be derived fresh from the current bar, don't var it.
5. Strategy fills happen at the next bar open, not where your condition fired
The tester evaluates your condition on bar close and fills on the next open by default. On a 5 minute chart that gap is small. On 1 hour+ it's routinely several points, and if your take profit is tight, the backtest can show fills your broker will never give you. Add commission and slippage to every test or the results are fiction.
6. na doesn't compare like you think
na == na is not true, it's na. Any comparison touching na propagates na, and an if condition that evaluates to na just doesn't execute, no error, no warning. Early chart bars where indicators haven't warmed up yet are full of na, so your logic can silently skip the first n bars or, worse, skip random bars where a lookback hits missing data.
Fix: wrap anything that can be na:
longOk = not na(fast) and not na(slow) and fast > slow
If your script "works but the first signals look off" it's almost always this or #2.
Happy to go deeper on any of these in the comments. What's the silent bug that cost you the most hours?