A practical walkthrough of how TradingView's Strategy Tester runs your code, how to read the metrics it reports, and the structural reasons those numbers can mislead — look-ahead, repaint, overfitting, ignored costs, and tiny samples. This is educational material only, not financial advice; trading involves risk of loss, and a backtest describes the past, not the future.
When you convert a script to a strategy() and add it to a chart, TradingView replays history bar by bar. On each bar it runs your code, evaluates your strategy.entry and strategy.exit calls, simulates fills, and tallies the result into the Strategy Tester panel at the bottom of the screen. That panel has three tabs worth knowing: Overview (the equity curve and headline stats), Performance Summary (net profit, drawdown, profit factor, and the like), and List of Trades (every simulated entry and exit, in order).
Here is a minimal, compilable v6 strategy you can drop on a chart to see the tester populate. It is a plain moving-average cross — not a recommendation, just a mechanism to inspect:
//@version=6
strategy("MA Cross Demo", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
fastLen = input.int(10, "Fast MA")
slowLen = input.int(30, "Slow MA")
fastMa = ta.sma(close, fastLen)
slowMa = ta.sma(close, slowLen)
goLong = ta.crossover(fastMa, slowMa)
goFlat = ta.crossunder(fastMa, slowMa)
if goLong and barstate.isconfirmed
strategy.entry("Long", strategy.long)
if goFlat and barstate.isconfirmed
strategy.close("Long")
plot(fastMa, "Fast", color.aqua)
plot(slowMa, "Slow", color.orange)The barstate.isconfirmed gate matters more than it looks. Without it, your entry logic can act on the still-forming current bar, whose price keeps changing until the bar closes. Gating on a confirmed bar means the test only acts on settled data — the same data you would actually have in live trading. That single habit removes one of the most common sources of inflated results.
The Performance Summary throws a wall of numbers at you. A few carry most of the information, and each has a trap. The whole point of TradingView backtesting is to interrogate these numbers, not to take them at face value.
The List of Trades tab is where honesty lives. Scroll it. If 80% of the net profit came from three trades during one unusual month, you do not have a strategy — you have a description of that month. A believable result has profit spread across many trades and many market conditions, with no single trade carrying the whole equity curve.
None of these numbers predict the future. They summarize how a fixed set of rules would have behaved on data that already happened. That distinction is the entire subject of the rest of this article.
The fastest way to produce a beautiful, fictional backtest is to let your strategy peek at information it could not have had in real time. Two mechanisms cause this: look-ahead bias and repainting.
Look-ahead bias usually enters through request.security when you pull a higher timeframe onto a lower one. If you request daily data on a 5-minute chart without care, the tester can hand your intrabar logic the finished daily value before the day is actually over — knowledge you would never possess live. The fix is to read confirmed, offset data and explicitly disable the look-ahead merge:
//@version=6
indicator("HTF Close, non-repainting", overlay=true)
// Pull the PRIOR completed daily close — never the in-progress one.
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
plot(htfClose, "Prev daily close", color.purple)The close[1] offset plus lookahead=barmerge.lookahead_off is the safe pairing: it guarantees you only ever see a bar that has fully closed. Drop the [1] or flip lookahead on, and your backtest will look dramatically better for entirely fake reasons.
Repainting is the broader cousin: any logic whose historical signals differ from what it would have shown live. Strategies that act on the unconfirmed current bar repaint, which is exactly why the barstate.isconfirmed gate from the first example exists. The brutal tell is to compare a backtest against forward, real-time behavior — if the historical signals are clean and the live ones are messy, the history was lying. Validation tools like the ForexCodes Audit exist largely to flag these look-ahead and repaint patterns before they flatter your results, but you can catch most of them by hand with the two rules above.
Suppose you tweak your fast and slow lengths, your stop distance, and a filter or two until the equity curve points cleanly up and to the right. Congratulations — you may have just memorized the noise in one slice of history. That is overfitting: tuning a strategy so tightly to past data that it captures accidents specific to that data rather than any durable behavior.
The symptoms are recognizable. Oddly specific parameters (a 23-period MA that works where 22 and 24 fail). A profit factor that collapses the moment you change the date range. Many input knobs relative to the number of trades. A curve that is gorgeous in-sample and falls apart the instant you test a different year or a different symbol.
The small-sample problem compounds this. Thirty trades is not enough to trust any win rate or profit factor; the confidence interval around those numbers is enormous. A strategy with 25 trades and a 70% win rate could easily be a 45% strategy that got lucky — you cannot tell them apart from 25 samples. Statistical reliability needs hundreds of trades across varied regimes, not a tidy handful.
The standard defenses are out-of-sample testing (tune on one date range, then test untouched on another) and walk-forward analysis (repeatedly tune on a window, validate on the next, and roll forward). Neither produces a guarantee. They only tell you whether a pattern survived contact with data it was not fitted to — which is the most a backtest can ever honestly offer.
Out of the box, TradingView simulates fills under friendlier conditions than any real market provides. Three costs are easy to forget and large enough to erase an apparent result.
Commission and fees. If you do not set them, the tester assumes free trading. Add realistic values in the strategy properties or directly in code so every simulated round-trip pays what a real one would:
//@version=6
strategy("With Costs", overlay=true,
commission_type=strategy.commission.percent, commission_value=0.04,
slippage=2, initial_capital=10000,
default_qty_type=strategy.percent_of_equity, default_qty_value=10)
len = input.int(20, "Length")
basis = ta.ema(close, len)
if ta.crossover(close, basis) and barstate.isconfirmed
strategy.entry("Long", strategy.long)
if ta.crossunder(close, basis) and barstate.isconfirmed
strategy.close("Long")
plot(basis, "EMA", color.yellow)Slippage. The slippage setting (measured in ticks) models the gap between the price you wanted and the price you got. High-frequency strategies that trade often are hit hardest by slippage precisely because they pay it hundreds of times. A strategy that looks fine at zero slippage and falls apart at two ticks was never robust to begin with.
Liquidity and order size. The tester assumes your order fills at the simulated price regardless of how large it is. In thin markets, a real order moves the price against you. The backtest cannot see this, so it overstates fills for any size that matters. Treat the result as an optimistic ceiling, not an expectation.
The honest summary: a backtest is a hypothesis about a set of rules, generated under idealized conditions, on data that already happened. It can disqualify a bad idea cheaply, and it can show you how an idea behaved historically. It cannot tell you what the market will do next, and it cannot promise profit. Use it to reason carefully, not to manufacture confidence. This article is educational only and not financial advice; trading carries a real risk of loss, and past performance — backtested or live — does not predict future results.
Educational only — not financial advice. Trading involves substantial risk of loss.