Pine Script Bar-Close Entry Guard

Forces every entry to fire only on a confirmed bar close, removing the repainting that makes backtests look nothing like live automated trading.

Pine Script v5 Free · TradingView free plan
Pine Script
//@version=5
// calc_on_every_tick=false is the setting that matters here — it forces
// the strategy to recalculate only on confirmed bar closes, so a backtest
// signal can never fire earlier than the same signal would in live trading.
strategy("Target Filled - Bar-Close Entry Example", overlay=true, calc_on_every_tick=false)

fastLen = input.int(9, "Fast EMA Length")
slowLen = input.int(21, "Slow EMA Length")

fastEMA = ta.ema(close, fastLen)
slowEMA = ta.ema(close, slowLen)

// barstate.isconfirmed is true only on the final tick of a closed bar —
// a second, explicit gate on top of calc_on_every_tick=false.
barConfirmed = barstate.isconfirmed
crossUp   = ta.crossover(fastEMA, slowEMA)  and barConfirmed
crossDown = ta.crossunder(fastEMA, slowEMA) and barConfirmed

if crossUp
    strategy.entry("Long", strategy.long)
    strategy.exit("Long Exit", "Long", profit=20, loss=10)

if crossDown
    strategy.entry("Short", strategy.short)
    strategy.exit("Short Exit", "Short", profit=20, loss=10)

plot(fastEMA, color=color.new(color.blue, 20), linewidth=1)
plot(slowEMA, color=color.new(color.orange, 20), linewidth=1)

Why repainting destroys automation results

The single most common complaint from traders who automate a Pine Script strategy is that live performance bears no resemblance to the backtest. Repainting is almost always the reason.

The default strategy setting on TradingView is calc_on_every_tick=true, which recalculates the script on every price tick inside a bar, not just at the close. That means an EMA crossover can appear, register as a fired signal in the Strategy Tester, and then quietly disappear before the bar actually finishes — the backtest keeps the trade, but that trade never happened at a real, confirmed point in time.

The fix is two lines:

  1. calc_on_every_tick=false in the strategy() declaration, so the script only recalculates on bar closes.
  2. barstate.isconfirmed appended to every entry condition, an explicit flag that is only true on a bar's final tick.

With both in place, every signal your strategy fires in backtesting corresponds to a real, finalized bar. That's the same moment the live alert fires and, if you automate through CrossTrade, the moment the order is sent to NinjaTrader 8 or Tradovate.

How the two-layer protection works

SettingWhat it controlsWhy it matters
calc_on_every_tick=falseWhen the strategy engine recalculatesStops signals from evaluating against an incomplete, still-forming bar
barstate.isconfirmedWhether the current bar has finalizedExplicit boolean gate — entries can only fire on confirmed closes, never mid-bar
Trading a webhook off a minute chart? calc_on_every_tick=false is the setting that matters most. Without it, one bar can send the same alert several times as price oscillates back and forth through a crossover level.

How to add this to your existing strategy

  1. Find the strategy() call at the top of your script.
  2. Add calc_on_every_tick=false to it — for example: strategy("My Strategy", overlay=true, calc_on_every_tick=false).
  3. Append and barstate.isconfirmed to every long/short entry condition.
  4. Re-run the backtest. If the results shift noticeably, the original version was repainting — the new numbers are the honest historical performance.

Frequently Asked Questions

What is repainting in Pine Script?
Repainting is when a strategy throws an entry signal mid-bar that then changes or vanishes before the bar actually closes. During a backtest, TradingView can already see the whole bar, so a crossover can look like it fired and get counted — but live, that exact crossover never happened on a finished bar. You end up with a backtest that looks strong and live behavior that has nothing to do with it.
What does calc_on_every_tick=false actually do?
It tells the strategy engine to recalculate only when a bar closes, not on every incoming tick inside that bar. That single setting is the main fix for repainting, because from then on a signal can only appear at the same moment it would appear live — once the bar's OHLC is final.
Do I need both calc_on_every_tick=false and barstate.isconfirmed?
Running both gives two independent layers of protection. calc_on_every_tick=false stops the mid-bar recalculation, which is the core of the fix. barstate.isconfirmed is a belt-and-suspenders boolean gate that is only true once a bar is fully closed. Together they cover edge cases where platform behavior might otherwise differ, so the strategy stays resistant to repainting in both backtest and live automation.

TradingView tools built to automate.

Indicators from $39/mo, strategies in Pro. Cancel anytime. Every update included.