Tradelyze

Guide

Pine Script repainting and look-ahead bias

Last reviewed: 16 September 2026·Tradelyze

Repainting is when a Pine Script calculates differently on historical bars than it did, or would have, in real time. The costly kind for a backtest is look-ahead bias: past bars read prices that did not exist yet, such as today's closing price at 9:45 a.m. Those trades look well timed but could never have been taken live.

In plain English

A backtest replays old prices one bar at a time; a bar is one candle on the chart, holding its open, high, low and close. A higher timeframe is a longer bar, such as a daily bar seen from a 15-minute chart. If the script can see a price from later in the day, or a higher timeframe's bar before that bar has finished, it can pick trades that already know the outcome. The Strategy Tester report then looks excellent. Live, nobody has those prices yet, so the same script trades differently or not at all.

New to this? Start with how to backtest on TradingView, then read whether TradingView backtesting is accurate.

Which kinds of repainting change backtest trades, and which are harmless?

Repainting matters for a backtest only when it changes which orders a strategy places, or when it places them. TradingView's Pine Script documentation on repainting sorts repainting into four groups. It labels them widespread but often acceptable, potentially misleading, unacceptable, and unavoidable. A line that moves until the bar closes belongs to the first group. A script that leaks future information into the past belongs to the third.

By default a strategy calculates once per bar, when the bar closes. TradingView's execution-model documentation says strategy scripts "execute only once on every bar" unless their settings say otherwise. So a value that wiggles during a live bar usually cannot change a default strategy's orders. The table sorts the common causes by that test.

Common causes of repainting, and whether each one changes a strategy's backtest trades
CauseWhat differs between history and liveChanges backtest trades?Source
request.security() with lookahead = barmerge.lookahead_on and no [1] offsetHistorical bars see a higher-timeframe value before that bar has closedYes. Past trades use future data, so the backtest beats anything tradablePine Script docs, Repainting
request.security() for a higher timeframe, lookahead left at its default (off)Historical bars get the last confirmed value; a live bar gets the still-forming valueNot in history. Live signals can appear and vanish, so live trades can differPine Script docs, Other timeframes and data
calc_on_every_tick = trueLive bars recalculate on every price update; historical bars once per barYes. Live orders will most probably not match the backtest'sPine Script docs, Repainting
calc_on_order_fills = trueThe script runs again when an order fills, historical bars includedYes. On a historical bar the extra run already sees that bar's confirmed close, which TradingView calls a form of lookahead biasPine Script FAQ, Strategies; Pine Script docs, Strategies
varip and timenowValues that exist only across live updates, or depend on the current timeYes, when they feed an entry or exitPine Script docs, Repainting
Indicator values on an unfinished live barThe plotted value moves until the bar closesUsually not, for a strategy that calculates once at the bar closePine Script docs, Repainting and Execution model
Plotting in the past, such as a pivot label drawn back on the pivot barThe chart shows a signal earlier than it was knownUsually changes the chart, not the orders; check the code that places ordersPine Script docs, Repainting
Strategies run on Heikin Ashi, Renko or other non-standard chartsFills use synthetic prices that never tradedYes. See standard OHLC fillsPine Script docs, Repainting; Pine Script FAQ, Strategies
Chart history starting point and data-feed revisionsThe first loaded bar and occasional price corrections change over timeYes. Early trades, and everything calculated after them, can shiftPine Script docs, Repainting; Pine Script FAQ, Strategies

Why this matters for your money: a live account or a prop firm challenge gets sized from the backtest's win rate and drawdown. A future leak raises the first and shrinks the second at the same time, so the size you choose rests on trades nobody could have taken.

How does request.security() lookahead leak future data into past bars?

request.security() fetches data from another symbol or timeframe, for example a daily close on a 15-minute chart. Its lookahead setting decides what a historical chart bar sees of the higher-timeframe bar it sits inside. TradingView's Other timeframes and data page says the default is barmerge.lookahead_off. With barmerge.lookahead_on and no offset, TradingView's repainting page says the call "will return data from the future on historical bars, which is dangerously misleading." It adds that script publications using the technique will be moderated.

Worked example (constructed illustration, not measured data). A US stock closed at $179.20 yesterday. Today it opens at $180.00, the 15-minute bar ending at 9:45 a.m. New York time closes at $180.40, and the day closes at $184.50. A strategy on the 15-minute chart buys when price is below the daily close it requests, then holds to the end of the day.

What a daily-close request returns on the 9:45 a.m. bar of that day. Constructed illustration, not measured data
RequestValue in the backtestValue on a live chartWhat happens
close, lookahead on, no offset$184.50: today's close, more than six hours early$180.40: the daily bar's current priceThe backtest buys near $180.40 and is about $4.10 a share up by the close. Live, the rule never fires
close, lookahead off (default)$179.20: yesterday's confirmed close$180.40, changing with every updateNo leak in the backtest, but the live value moves until the day ends
close[1], lookahead on$179.20: yesterday's close$179.20: yesterday's closeThe same value in the backtest and live, so the result is tradable

In the leaking version, the rule only buys on days that already close above the 9:45 price, so nearly every backtest trade is a winner. That is look-ahead bias doing the work, not an edge. The version with close[1] and lookahead on gives the backtest and a live chart the same number, and the rule's real record appears. TradingView's repainting page calls the [1] offset and lookahead = barmerge.lookahead_on "interdependent": remove either and the leak or the repainting comes back.

This pattern is for timeframes higher than the chart's. For lower-timeframe data, TradingView's Pine Script FAQ on other data and timeframes points to request.security_lower_tf() instead.

Why do strategy results change after refreshing the chart?

Strategy results that change after a chart refresh are usually repainting, not a fault in TradingView. TradingView's Help Center article Script or strategy gives different results after refreshing the page (repainting) says the effect "is not a bug". It comes from the difference between historical bars and live bar information. Four things cause most changes:

What to do: refresh the chart before you export the trade list, and export the same date range you will test. The file then reflects historical calculations, not trades placed while bars were live. The guide to exporting TradingView trades covers the export itself, and the Learn FAQ covers results that change overnight.

How do calc_on_every_tick and intrabar recalculation make live trades differ?

A historical bar holds only four prices: open, high, low and close. A strategy can calculate on it only once, and TradingView's execution-model documentation says strategies run once per bar by default. Setting calc_on_every_tick = true in the strategy() declaration makes the strategy recalculate on every live price update, while history still runs once per bar. TradingView's repainting documentation says such strategies "will most probably not generate the same order executions". It adds that this "invalidates backtesting results", because they no longer represent how the strategy behaves live.

Worked example: a cross that never closed

Constructed illustration, not measured data. A strategy on a 5-minute chart buys when price crosses above 4,000. At 10:02 a.m. price ticks to 4,001, then falls back, and the 10:00 bar closes at 3,996 at 10:05. With calc_on_every_tick on, a live chart sends the buy at 10:02. The historical bar only knows it closed at 3,996, below 4,000, so after a refresh the backtest shows no trade. The live account holds a position the backtest never took.

The related calc_on_order_fills setting runs the script again when an order fills, on historical bars as well as live ones. TradingView's Pine Script FAQ on strategies warns it "might lead to unrealistic results on historical bars", and says why: during that extra execution "the script has access to the confirmed OHLC values for the historical bar, but those values would not be available in the real world until the bar's closing time," which the FAQ calls a form of lookahead bias. The Strategies page of the same manual demonstrates the effect and recommends the separate "On history bar tick" setting instead, which it limits to Premium and Ultimate plans on standard charts. The FAQ also recommends setting calc_on_every_tick to false while backtesting, and elsewhere advises setting calc_on_every_tick, calc_on_order_fills and process_orders_on_close all to false to avoid repainting. For a strategy you plan to trade, test it with all three off, then compare. The gap between the two reports shows how much the result depended on intrabar recalculation. Backtest vs live trading covers the other reasons live results fall short.

How can you test whether your strategy repaints?

You can test a strategy for repainting with TradingView's own tools, in roughly this order, from quickest to most convincing:

  1. Search the code. In the Pine Editor, look for request.security, request.security_lower_tf, lookahead_on, calc_on_every_tick, calc_on_order_fills, varip, timenow and barstate. TradingView's Help Center article on repainting names several of these, including timenow and the bar-state variables.
  2. Fix a copy and compare. Save a copy of the script. Change every higher-timeframe request to a [1] offset with lookahead on, and set calc_on_every_tick, calc_on_order_fills and process_orders_on_close to false, the three TradingView's Pine Script FAQ names together as the way to avoid repainting. Compare net profit, trade count and win rate with the original, on the same chart and date range. A large fall means the original leaned on data it could not have had.
  3. Refresh and compare. Export the trade list, reload the chart and export the same range again. Different trades point to live-bar calculations, a moved starting bar or revised data.
  4. Replay bar by bar. Bar Replay, started from the top panel of the chart, lets you pick a starting point and step forward, according to TradingView's Help Center. Note where signals appear as bars arrive, then compare with the finished chart. That Help Center article does not say how scripts calculate on replayed bars, and it notes that trading orders still run on real-time data during a replay, so treat a clean replay as weaker evidence than the next step.
  5. Watch it live. Leave the strategy on a live chart for several sessions and write down each order as it happens: time, side and price. Then refresh and check each one against the trade list. This costs calendar time, but it is the only step that compares real live behavior with the backtest.

While you work through the list, look at the trades themselves. Entries that sit on the exact high or low of a higher-timeframe bar deserve a close look. So does a win rate far above what the idea should deliver. Is your TradingView backtest too good to be true? collects the other warning signs.

How do you write a non-repainting higher-timeframe request?

A higher-timeframe request stops repainting when it reads only bars that have already closed. TradingView's Other timeframes and data page gives the method: offset the expression by one bar with [1] and set lookahead = barmerge.lookahead_on. That page says such a series "does not repaint its results, because it always uses the last confirmed value from the higher timeframe."

htfTimeframe = input.timeframe("60", "Higher timeframe")
// Non-repainting: the 50-period EMA of the last closed higher-timeframe bar
htfEma = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on)

// Leaks future data on historical bars. Do not trade this version:
leakEma = request.security(syminfo.tickerid, htfTimeframe, ta.ema(close, 50), lookahead = barmerge.lookahead_on)

Check this against the current Pine Script docs

This snippet follows the pattern on TradingView's Other timeframes and data page and Pine Script FAQ as retrieved on 15 September 2026, written for Pine Script version 6. Pine Script changes between versions, so compare it with TradingView's current documentation before you rely on it.

The first request gives the backtest and a live chart the same value all day: the EMA as it stood when the last higher-timeframe bar closed. The second gives historical bars that bar's final EMA before it exists. PineCoders' TradingView publication How to avoid repainting when using security() recommends the same offset-and-lookahead approach. It marks its older technique based on bar states as deprecated.

For Tradelyze, write the timeframe as the snippet does: a plain string such as "60", a variable set to one, or an input.timeframe() default. TradingView's repainting page wraps the request in a helper function that takes the timeframe as a parameter. Tradelyze's upload check cannot read a timeframe passed that way, so it refuses the script and asks for a literal or an input.timeframe() default. When you need a second timeframe file lists the timeframes Tradelyze accepts.

Can repainting show up as TV Only or BT Only trades in Tradelyze?

Repainting can show up as TV Only or BT Only trades, but Tradelyze does not detect repainting and cannot tell you it was the cause. Tradelyze re-runs your Pine Script on the price files you upload, then lines its trades up with your TradingView export. On the Conversion Result card, TV Only counts TradingView trades the re-run did not take. BT Only counts re-run trades that TradingView did not take. Three routes can lead from repainting to those counts:

The reverse matters more for your money. A script that leaks future data on historical bars can leak it the same way in a faithful re-run. A high Match Rate therefore does not clear a script of look-ahead bias, and walk-forward and robustness results built on a leaking script inherit the leak. Fix repainting in TradingView first, refresh, export the trades again, then upload. If trades still do not match, work through why trades do not match TradingView.

Where this appears in Tradelyze

In a Tradelyze report, repainting can surface only indirectly: as TV Only or BT Only trades and a lower Match Rate on the Conversion Result card. Tradelyze does not check scripts for repainting. Tradelyze re-runs an uploaded TradingView Pine Script strategy from your exported trade list and price data. It then runs parameter optimization, walk-forward analysis, a five-check robustness score and prop firm rule checks. It does not place trades, give financial advice or guarantee a challenge pass, and it is in beta.

To judge the whole report, not one tile, use the pre-trade checklist. Common product questions are collected in the Learn FAQ.

Create an account

Learning path · Stage 1: Get a trustworthy backtest · Previous: Is TradingView backtesting accurate? · Next: Win rate and expectancy

Frequently asked questions about Pine Script repainting

What is repainting in TradingView?

Repainting is when a Pine Script calculates or plots differently on historical bars than it did, or would have, in real time. TradingView's Pine Script documentation defines it as script behavior that makes historical and realtime calculations or plots behave differently. Some repainting only moves a line on the chart. The costly kind for a backtest changes orders, for example a strategy reading a higher-timeframe price before that bar has closed.

How can I tell if my Pine Script strategy repaints?

Search the script for request.security(), lookahead_on, calc_on_every_tick, calc_on_order_fills, varip and timenow. Save a copy with each higher-timeframe request offset by [1] and those two settings off, then compare results on the same date range. Export the trade list, refresh the chart and export again. The strongest test is watching the strategy live for a few sessions, noting each order, and checking the trade list afterwards.

What does barmerge.lookahead_on do in request.security()?

barmerge.lookahead_on lets a historical chart bar see the final values of the higher-timeframe bar it sits inside, before that bar has closed. Used on its own, TradingView's repainting documentation says it returns data from the future on historical bars, which it calls dangerously misleading. Paired with a one-bar offset such as close[1], it returns the last closed higher-timeframe value, which is the same in a backtest and live.

Does request.security() repaint with the default settings?

With the default barmerge.lookahead_off, request.security() does not leak future data on historical bars. TradingView's Other timeframes and data page says it returns the last confirmed higher-timeframe value there. On a live bar it returns the current, still-forming value, so plots and signals can change until that bar closes. The backtest stays honest, but live signals can differ from it.

Why do my TradingView strategy results change after I refresh the chart?

After a refresh, bars the strategy calculated while they were live are recalculated from historical data, so a trade that depended on live-only behavior can move or disappear. TradingView's Pine Script FAQ adds that results can change as the chart's starting bar moves forward, and its Help Center lists revised historical data such as stock splits. TradingView's Help Center says this repainting is not a bug.

Does calc_on_every_tick cause repainting?

Yes. With calc_on_every_tick = true a strategy recalculates on every live price update, but historical bars hold no tick data and still calculate once per bar. TradingView's repainting documentation says such strategies will most probably not place the same orders live as in history, which invalidates backtesting results. TradingView's Pine Script FAQ recommends setting calc_on_every_tick to false while backtesting.

Is all repainting bad for a backtest?

No. A plot that moves during a live bar and settles when the bar closes usually cannot change the orders of a strategy that calculates once per bar, which is the default. TradingView's documentation calls some repainting widespread but often acceptable. What ruins a backtest is repainting that changes orders, above all future data leaking into historical bars, which TradingView classes as unacceptable.

How do I request higher-timeframe data without repainting?

Offset the requested expression by one bar and turn lookahead on, for example request.security(syminfo.tickerid, "60", ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on). TradingView's Other timeframes and data page says this kind of series does not repaint because it always uses the last confirmed higher-timeframe value. Keep both parts, which TradingView calls interdependent, and check the pattern against TradingView's current documentation before relying on it.

Does barstate.isconfirmed stop a strategy from repainting?

barstate.isconfirmed is true on a bar's last calculation, when its prices are final, so a condition checked with it waits for the bar to close. TradingView's repainting documentation shows it as one way to avoid acting on unconfirmed live values. It does not fix a request.security() call that leaks future data, and TradingView's Help Center article on repainting lists bar-state variables among the causes.

Why does a repainting strategy look better in a backtest than live?

A backtest runs on finished bars, so a script that reads a value too early, such as a daily close before the day ends, can pick entries that already know the outcome. Those trades raise the win rate and shrink the drawdown on paper. Live, the same values are still forming, so the strategy takes different trades or none. A live account only ever gets the tradable version.

Does Tradelyze detect repainting?

No. Tradelyze does not check Pine Script for repainting, lookahead leaks or calc_on_every_tick. It re-runs your script on the price files you upload and compares its trades with your TradingView export. A script that leaks future data on historical bars can leak it the same way in the re-run, so a high Match Rate does not show the strategy would have taken the same trades live.

Can repainting cause TV Only or BT Only trades in Tradelyze?

It can. Trades your TradingView chart calculated on live bars before you exported may not be reproduced by Tradelyze's re-run on historical price files, and they can show as TV Only. Multi-timeframe scripts can also mismatch because Tradelyze has not yet verified that extra-timeframe price files attach correctly in its backtester. A mismatch alone does not tell you whether repainting was the cause.

Why does Tradelyze refuse my request.security() timeframe?

Tradelyze asks for one extra price file per timeframe your script requests, so it must read each timeframe before the run. A plain string such as "60", a variable set to one, or an input.timeframe() default works. A timeframe passed into a wrapper function as a parameter, or built from a ternary or joined strings, cannot be read in advance, and the upload check asks for a literal or an input.timeframe() default.

Sources