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 — the exact same moment a webhook bridge like TradersPost would receive and act on that alert live.

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.

Get a Pine Script tuned to your prop firm.

Invite-only on TradingView within 24 hours. From $19/mo, cancel anytime.