Pine Script Bar-Close Entry Guard
Forces all entries to fire only on confirmed bar closes — eliminating repainting so your backtest results match live automated trading exactly.
//@version=5
// calc_on_every_tick=false is the key setting — signals only fire on confirmed bar closes.
// This prevents repainting: what you see in backtests is exactly what executes live.
strategy("Bar-Close Entry Guard", overlay=true, calc_on_every_tick=false)
// ── Indicators ───────────────────────────────────────────
ema9 = ta.ema(close, 9)
ema21 = ta.ema(close, 21)
// barstate.isconfirmed = true only on the final tick of a closed bar
longCondition = ta.crossover(ema9, ema21) and barstate.isconfirmed
shortCondition = ta.crossunder(ema9, ema21) and barstate.isconfirmed
// ── Entries ───────────────────────────────────────────────
if longCondition
strategy.entry("Long", strategy.long)
strategy.exit("Long Exit", "Long", profit=20, loss=10)
if shortCondition
strategy.entry("Short", strategy.short)
strategy.exit("Short Exit", "Short", profit=20, loss=10)
// ── Visuals ───────────────────────────────────────────────
plot(ema9, color=color.new(color.blue, 20), linewidth=1)
plot(ema21, color=color.new(color.orange, 20), linewidth=1)
Why repainting destroys automation results
The most common complaint from traders who automate Pine Script strategies is that the live performance looks nothing like the backtest. Repainting is almost always the cause.
Here is how it happens: TradingView's default strategy setting is calc_on_every_tick=true. This means Pine Script recalculates on every price movement within a bar. An EMA crossover can appear, trigger a backtest signal, and then disappear — all before the bar closes. In the Strategy Tester, that signal gets recorded as if it fired, but in live trading, the crossover never actually occurred on a completed bar.
The fix is two lines of code:
calc_on_every_tick=falsein thestrategy()declaration — recalculation only happens on bar closes.barstate.isconfirmedon every entry condition — an explicit gate that is onlytrueon the final tick of a completed bar.
Together, these ensure that every signal your strategy fires in backtesting corresponds to a real, finalized bar close — the same point in time where TradersPost or any other automation bridge would receive and execute the alert.
How the two-layer protection works
| Setting | What it controls | Why it matters |
|---|---|---|
calc_on_every_tick=false | When Pine Script recalculates the strategy | Prevents the strategy engine from evaluating signals on incomplete bar data |
barstate.isconfirmed | Whether the current bar is finalized | Explicit boolean gate — conditions can only fire on confirmed closes, not mid-bar ticks |
calc_on_every_tick=false is the critical setting. Without it, your webhook alert can fire multiple times on the same bar as the price oscillates through a crossover.How to add this to your existing strategy
- Find your
strategy()declaration at the top of your script. - Add
calc_on_every_tick=falseto it:strategy("My Strategy", overlay=true, calc_on_every_tick=false) - On every entry condition (
longCondition,shortCondition), appendand barstate.isconfirmed. - Run a fresh backtest. If your results change significantly, your original strategy was repainting — the new results are the true historical performance.
Frequently Asked Questions
What is repainting in Pine Script?
Repainting happens when a Pine Script strategy generates entry signals mid-bar that disappear or change before the bar closes. In backtesting, TradingView can see the full bar's data, so a crossover might appear and look valid — but in live trading, that same crossover never actually fired on the final tick. The result is a backtest that looks great but performs completely differently when automated live.
What does calc_on_every_tick=false do?
Setting calc_on_every_tick=false in your strategy() declaration tells Pine Script to only recalculate the strategy on completed bar closes, not on every price tick within a bar. This is the most effective way to prevent repainting because signals can only trigger at the same point in time that they would trigger in live trading — when a bar's OHLC data is finalized.
Do I need both calc_on_every_tick=false and barstate.isconfirmed?
Using both provides two layers of protection. calc_on_every_tick=false prevents recalculation mid-bar, which is the core fix. barstate.isconfirmed adds an explicit gate that only evaluates to true on the final tick of a closed bar. Together they handle edge cases where platform behavior might differ, making your strategy maximally resistant to repainting in both backtesting and live automation.
Related
Want a complete non-repainting strategy ready for automation?
Our scripts use bar-close entries, kill switch, session filter, and EOD flatten — pre-wired and verified against 3 years of MES/NQ data. Instant delivery from $50.
View Plans — From $50