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.
//@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:
calc_on_every_tick=falsein thestrategy()declaration, so the script only recalculates on bar closes.barstate.isconfirmedappended 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
| Setting | What it controls | Why it matters |
|---|---|---|
| calc_on_every_tick=false | When the strategy engine recalculates | Stops signals from evaluating against an incomplete, still-forming bar |
| barstate.isconfirmed | Whether the current bar has finalized | Explicit boolean gate — entries can only fire on confirmed closes, never mid-bar |
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
- Find the
strategy()call at the top of your script. - Add
calc_on_every_tick=falseto it — for example:strategy("My Strategy", overlay=true, calc_on_every_tick=false). - Append
and barstate.isconfirmedto every long/short entry condition. - Re-run the backtest. If the results shift noticeably, the original version was repainting — the new numbers are the honest historical performance.