OptiNod Academy

Look-Ahead Bias and Repainting: Why Perfect Chart Signals Didn’t Exist in Real Time

Why signals that look perfect on a chart often did not exist in real time, explained through `request.security` and bar-confirmation mechanics.

Repainting is when an indicator redraws past signals after a candle has closed. Look-ahead bias is a backtesting error where signals are calculated using future data. The causes are different, but the result is the same. On the historical chart, the signal appears in the perfect location. If you had been watching the chart live at that time, the signal either would not have existed or would have appeared somewhere else.

The usual takeaway is simple: if an indicator gives a signal, enter on that candle. The arrow is printed exactly at the bottom, and the backtest win rate is above 70%, so traders trust it. That belief breaks down in live trading. When you run the same indicator on a live chart, an arrow that appeared before the candle closed may disappear the moment the candle closes, or shift one bar to the side. The signal shown in the backtest was drawn after the fact.

The key point is this: many TradingView strategies with strong backtest results only look good because the code has seen the future. The strategy itself may have no real edge. One line in the request.security() lookahead setting, one option that calculates on an unfinished candle, or one inherently repainting tool can create the difference. Once you understand the mechanism, you can decide which backtests to reject and which live signals must be taken one bar later.

How a signal's position differs between the after-the-fact chart and the live view

Signal Confirmation and Trade Entry Happen at Different Times

The starting point for understanding repainting and look-ahead bias is the two states of a candle. A closed historical candle has fixed open, high, low, and close values. They do not change. A real-time candle in progress can change its high, low, and close many times before it closes. TradingView code behaves differently in these two states, but most users do not notice the difference.

Suppose an indicator prints a “buy on this candle” arrow. If that candle is still forming, the arrow is only a temporary value calculated from the close so far. If price changes direction before the candle closes, the condition can fail and the arrow disappears. If you enter based on a signal from an active candle, you may end up in a trade at a place where the signal did not exist when the candle finally closed.

The only signals you can use in live trading are signals confirmed after the candle closes and the close is fixed. The signal becomes confirmed when the candle closes. The earliest you can enter after seeing that signal is when the next candle opens. If a backtest assumes execution at the signal candle’s close, it already contains a small amount of look-ahead. In practice, getting filled exactly at the closing price at the exact moment the candle closes is close to impossible. Ignoring this one-bar gap between signal confirmation and possible entry makes backtests look better than live trading.

request.security With lookahead_on Pulls Future Final Values

request.security() is the function used to fetch higher-timeframe data. Traders use it to reference the weekly close on a daily chart, or to bring a 1-hour trend into a 5-minute chart. The most common place look-ahead bias enters is the function’s lookahead parameter.

If you call request.security(syminfo.tickerid, "1W", high, lookahead=barmerge.lookahead_on) on a daily chart, TradingView pulls the final high of the still-forming weekly candle into the chart from the first day of that week. The official documentation also warns that this call can produce dangerously misleading results on historical bars because it fetches future data. It looks perfect on the chart, but that perfection comes from seeing the future.

Take the BTC weekly candle that opened during the week of March 11, 2024. During that week, BTC printed a high of $73,777 on March 14 and closed at $68,393. If you reference that weekly high from the daily chart with lookahead_on, the value 73,777 appears on the chart starting Monday, March 11. That is three days before the high actually occurred. If you run a backtest that shorts near the weekly high using that value, the strategy effectively knew on March 11 that 73,777 would be the weekly high. The win rate becomes unrealistically high. In live trading, there was no way to know that value on March 11. The perfect short shown on the chart was created from information that did not exist at the time.

lookahead_on pulls a higher-timeframe candle's final high into bars before it forms

lookahead_off Is the Safe Default, and Future-Referencing Code Leaks by Design

The correct default pattern is barmerge.lookahead_off. With this setting, higher-timeframe candle data is only delivered to the lower timeframe after that higher-timeframe candle has actually closed. The weekly high and close are reflected on the daily chart only after the weekly candle closes on Sunday, so historical bars cannot see the future. If the setting is omitted, the default is lookahead_off, but many scripts copied from the internet explicitly include lookahead_on, so you need to check the code yourself.

If you want to use confirmed higher-timeframe values safely while receiving the signal as early as possible, the official documentation recommends a specific pattern: request.security(syminfo.tickerid, "1W", expression[1], lookahead=barmerge.lookahead_on). The [1] offset references the previously closed higher-timeframe value. This behaves the same on historical and real-time bars. The signal arrives one higher-timeframe bar later, but that delay is a delay you must accept in live trading anyway.

Another form of look-ahead bias comes from future references and negative offsets. Referencing the next candle’s close with a negative index such as close[-1] is an obvious future leak. It creates the same result as using lookahead_on inside security() without an offset, only more directly. If a backtest looks abnormally good, the first two things to check are negative indexing and lookahead_on.

Indicators Calculated Before Bar Close Create Signals That Appear and Disappear

If an indicator or strategy has calc_on_every_tick=true, it recalculates on every tick while the candle is forming. On a live chart, this makes signals appear and disappear repeatedly. An arrow appears the moment the assumed close satisfies the condition, then disappears when price moves again and the assumed close changes.

The problem is the mismatch between backtesting and real time. As the official documentation notes, strategies execute on historical bars using bar-close data, but with calc_on_every_tick=true, they execute on every tick in real time. The execution models are different, so historical performance and live behavior diverge. On the historical section of the chart, every candle is already closed and the signals look clean. That clean look is simply the result of viewing the chart after the candles have closed.

The standard tool for preventing this is barstate.isconfirmed. Adding and barstate.isconfirmed to an entry condition makes the signal fire only at the final moment of the candle, when the close is confirmed. Historical bars are always treated as confirmed, so this condition reproduces the same behavior on historical and real-time bars. If you want to remove live signals that appear and disappear inside the candle, this one line is the simplest fix. Before rushing into a trade based on an arrow on an active candle, first check whether that signal has passed a barstate.isconfirmed gate.

Inherently Repainting Tools Move the Last Swing After the Fact

Some tools repaint by design, and no setting can fix that. ZigZag is the classic example. ZigZag confirms swing highs and lows only after a reversal above a specified percentage has occurred. Until that reversal is confirmed, the last swing keeps moving with price. If price pushes higher, the last high moves higher. Only after the reversal is confirmed does that point become fixed.

Return to the BTC example where price reached $73,777 on March 14. ZigZag did not confirm $73,777 as a swing high on March 14 itself. It was only confirmed later, around March 16 or March 19, after price had dropped enough. On March 19, BTC fell to a low of $61,555. Only when the reversal threshold was met did ZigZag lock the high at $73,777. Before that, the final segment was still moving with price. On the historical chart, a clean high is marked at $73,777, making it look as if anyone could have shorted there. By the time that high was confirmed, price had already fallen by more than $12,000.

The same trap exists in some Supertrend variants and automatic Fibonacci tools. Tools that draw retracement levels from the latest swing can move the entire level set until that swing is confirmed. An automatically drawn 0.618 retracement may look like perfect support on the chart, but that line may only have appeared there after the support had already been confirmed. Do not trust the historical appearance of automatic tools as the basis for an entry.

A ZigZag's last segment trails price and only fixes the swing high once the reversal confirms

Following HTF Signals in Real Time Can Flip Inside the Candle

The first misuse is trusting a backtest win rate based only on arrows from a repainting tool. If a ZigZag-based strategy shows an 80% win rate, that result was likely calculated from swing points confirmed after the fact. In real time, you must make the entry decision before that swing is confirmed, so you cannot enter at the location assumed by the backtest. The win rate itself is a future-looking calculation.

The second misuse is following higher-timeframe signals in real time as if they are final. If you watch a 4-hour or daily signal on a 5-minute chart, that signal can keep changing until the higher-timeframe candle closes. It is common for a buy signal to appear while the daily candle looks likely to close green, then disappear near the daily close as the candle flips red. A higher-timeframe signal should only be considered confirmed after that timeframe’s candle has closed. If you trade by checking the color of an active daily trend every five minutes, you can end up entering and exiting on the same signal repeatedly while fees slowly reduce the account.

The third misuse involves alerts. If a TradingView alert is based on a calc_on_every_tick condition, it can fire at any moment when price briefly touches the condition during the candle. By the time the candle closes, the condition may no longer be true, but the alert has already been sent. Unless the alert is set to “Once Per Bar Close,” a mid-candle alert can send you into a trade at a place that is no longer a valid signal at bar close.

How to Check Whether an Indicator Repaints

You can verify repainting directly on the chart without reading the code. Use this process when testing a suspicious indicator.

  • [ ] Use Bar Replay: In TradingView Bar Replay, choose a historical point and advance one candle at a time while recording where signal arrows appear. If an arrow moves or disappears when the candle closes, the tool repaints.
  • [ ] Check the lookahead setting: Search the indicator source code for request.security and check whether lookahead=barmerge.lookahead_on is used without an offset ([1]). Any unoffset lookahead_on introduces future leakage.
  • [ ] Search for negative indexes: Search the source for [- and check for negative offsets such as close[-1]. Negative indexing is a direct leak from the next candle.
  • [ ] Check tick calculation settings: If it is a strategy, check the value of calc_on_every_tick. If it is true, confirm that the entry condition is also gated by barstate.isconfirmed. Without that gate, live signals can move inside the candle.
  • [ ] Compare historical and live behavior: Run the same indicator on a live chart for several days and record where signals occur in real time. Later, compare those locations against the historical chart. If the locations differ, the indicator’s backtest cannot be trusted.
Comparing live-observed signals against the historical chart to detect repainting

Quick Filters for Repainting Risk

When you do not have time to run the full verification process, there are fast ways to filter risky tools. First, be suspicious of strategies with abnormally high backtest win rates. A realistic win rate for a simple trend-following strategy is usually in the 40-55% range. If the win rate is above 70%, it is likely either a martingale-style system with an extremely poor payoff ratio or a strategy with look-ahead bias. When the numbers look too good, checking the code first is a habit that reduces losses.

Second, do not trust an indicator just because its description says “non-repainting.” Many authors leave lookahead_on in the code unintentionally, or they fix only the multi-timeframe section while leaving the ZigZag calculation repainting. Bar Replay results matter more than the description.

Third, make it your default habit to take signals one candle late. For any indicator, build the setup under the assumption that you skip the signal candle’s close and enter at the next candle’s open. That one-bar buffer is the boundary between a signal that can flip inside the candle and a confirmed signal. It also does the most to close the gap between backtesting and live trading. The more perfect a signal looks on the chart, the more important it is to ask whether that perfection only became visible after the candle closed.