Home / Learn / Make Pine Script Use Only Closed Bars (S
Pine Script

Make Pine Script Use Only Closed Bars (Stop Getting Faked by Unclosed Candles)

Unclosed candles produce signals that vanish when the bar closes — here is the exact Pine Script fix.

The Unclosed-Bar Trap

You watch a signal fire on your chart. You act. The candle closes and the signal disappears. You were not reading a broken indicator — you were reading an unclosed bar. Pine Script recalculates your indicator on every incoming tick by default. Any signal built on current-bar values (close, RSI, MACD) can appear mid-candle and evaporate before the bar finishes.

This is one of the most common reasons a strategy looks good in backtesting and loses money live: the backtest used closed bars, but your live script did not. Closing that gap takes two or three lines of code.

Fix 1 — barstate.isconfirmed

The cleanest solution is barstate.isconfirmed. This built-in boolean is true only when the current bar has fully closed and a new bar has not yet opened. Gate your signal logic on it and nothing fires until price is locked in.

//@version=5
indicator("Closed-Bar RSI", overlay=false)

rsiVal = ta.rsi(close, 14)

// Only show value on a confirmed, fully closed bar
confirmedRSI = barstate.isconfirmed ? rsiVal : na
plot(confirmedRSI, color=color.blue)

// Alert fires only on bar close, never mid-candle
if barstate.isconfirmed and rsiVal < 30
    alert("RSI oversold", alert.freq_once_per_bar_close)

On historical bars, barstate.isconfirmed is true for every bar by definition — every historical bar is already closed. Adding it leaves your backtest results unchanged and only affects live execution. One gate, no phantom signals.

Fix 2 — The [1] History Operator

If you want to base your logic on the previous confirmed candle rather than the one that just closed, use the history offset operator. close[1] is the close of the bar before the current one — always fully closed, always stable, regardless of where the current bar is in its lifecycle.

//@version=5
indicator("Closed-Bar Signal", overlay=true)

// These values are always from a fully closed bar
prevClose = close[1]
prevOpen  = open[1]

bullBar = prevClose > prevOpen
plotshape(bullBar, style=shape.triangleup,
          location=location.belowbar, color=color.green)

Use [1] when you need a series value — for example, passing a closed-bar reading into another function that expects a series rather than a gated conditional block.

Fix 3 — request.security() and Higher-Timeframe Repaint

A separate but equally common source of unclosed-bar bugs is request.security(). When you pull a higher-timeframe value, Pine Script gives you the in-progress value of the current HTF bar by default — not the last closed one. That causes the same phantom-signal problem, sourced from a different timeframe.

The safe pattern is to shift the series by one bar inside the call and use barmerge.lookahead_on so the shift is applied correctly on historical bars too:

//@version=5
indicator("HTF Closed Close", overlay=true)

// Always the last CLOSED daily bar — no repaint
dailyClose = request.security(
    syminfo.tickerid, "D",
    close[1],
    lookahead = barmerge.lookahead_on
)

plot(dailyClose)

Using close without [1] and with the default barmerge.lookahead_off gives you the in-progress daily bar value on every real-time tick — the repaint you are trying to stop.

Fixing Script Errors After You Add the Fix

When TradingView shows a red error banner after you add closed-bar logic, the cause is almost always one of three things.

1. Pine v4 vs v5 mismatch. If your script opens with //@version=4 but uses v5 syntax (or vice versa), you will get type errors. Check the version declaration at the top and make sure it matches every function you call. Replace bare security() calls with request.security() when upgrading to v5.

2. Using barstate as a series or integer. barstate.isconfirmed is a simple boolean, not a length parameter. Passing it as ta.rsi(close, barstate.isconfirmed) will error. Use it only as a gate: if barstate.isconfirmed or in a ternary expression.

3. Strategy orders executing mid-bar. For strategies, add process_orders_on_close = true to your strategy() declaration. Without it, the engine can place orders mid-bar during live execution, which does not match how a closed-bar backtest ran.

//@version=5
strategy(
    "My Strategy",
    overlay = true,
    process_orders_on_close = true
)

FAQ

Questions, answered

Does barstate.isconfirmed change my backtest results?

No. On all historical bars, <code>barstate.isconfirmed</code> is <code>true</code> by definition because every historical bar is already closed. Adding it does not change your backtest signal history or P&amp;L. What changes is that your live script now matches those conditions instead of firing mid-candle.

Are IndicatorEdge backtest results hypothetical — not real trading?

Yes, plainly. All 660,005 backtests across 903 assets are <strong>hypothetical, simulated results</strong> — not live trading performance and not financial advice. Realistic transaction costs are factored into the calculations, but past simulated results do not predict future returns. Nothing on this site is a recommendation to buy or sell any asset. The timeframes tested are 1-Hour, 4-Hour, Daily, and Weekly.

Will forcing closed-bar logic reduce my win rate?

Often yes, and usually downward — which is the correct direction. A high win rate on unclosed bars contains a phantom component: the strategy reacts to price data that changes before the bar finishes. Once you force closed-bar logic, the signal count drops and the win rate converges toward what is actually achievable. A lower real number is more useful than a high phantom one.

My alert still fires mid-bar even after I fixed the script. Why?

TradingView alerts fire based on when the condition first becomes true, independent of your Pine Script logic — unless you choose the right frequency in the alert creation dialog. Even with <code>barstate.isconfirmed</code> in your code, the wrong alert setting triggers mid-bar. Open the alert dialog and set the frequency to <strong>Once Per Bar Close</strong>. That setting is separate from anything in your Pine Script source.

Honest by default

Every figure here comes from our own out-of-sample backtests, costs included — not a course or a guess. Educational information only — not investment advice. Hypothetical backtested results; past performance does not guarantee future results. Trading involves risk of loss.

Keep reading

Free · no spam

Get the weekly edge report

The best-performing indicator per asset, what changed this week, and the honest caveats — straight to your inbox.