Every backtest is a simulation running on aggregated data, and the gap between it and live results is mostly the sum of the assumptions that simulation had to make. That is a more useful framing than treating the difference as bad luck, because each assumption is identifiable, most are adjustable, and a few are cheap to eliminate entirely. This article works through them in rough order of how much damage they do.
Short answer
The main causes are repainting, which makes a backtest act on information that did not exist yet; intrabar ambiguity, because a bar’s four prices cannot establish the order in which levels were reached; unmodelled costs such as commission, spread, and slippage; survivorship and selection effects from testing on the instrument and period you already know worked; and overfitting from tuning parameters against the same data used to judge them. Repainting and cost modelling are fixable in code and settings. The others are addressed by how you test, not by what you code.
Key facts
- A repainting strategy uses data that was not available at the time, which is the largest and most common source of a backtest that cannot be reproduced live.
- A single bar’s open, high, low, and close cannot establish the sequence in which prices occurred inside that bar.
- When a stop and a target both fall within one bar’s range, the outcome is decided by the platform’s fill assumptions rather than by your rules.
- TradingView’s Bar Magnifier, available on higher account tiers, uses lower-timeframe data to resolve intrabar sequence during a backtest.
- Commission and slippage are configured in the strategy declaration or the Strategy Tester properties, and default settings do not model your actual costs.
- process_orders_on_close changes fills from the next bar’s open to the signal bar’s close, which changes results and is a modelling choice rather than a fix.
- Pine v6 defaults strategy margin to 100 and enforces position-size limits, so a strategy can no longer open a position it cannot afford.
- A backtest reports the worst drawdown that happened to occur in the window tested, which is a single observation rather than a bound on future declines.
| Cause | What it does | Fixable in code? | How to check |
|---|---|---|---|
| Repainting | Acts on data not yet available | Yes | Compare live signals against history |
| Intrabar ambiguity | Guesses the order of fills | Partly | Widen levels, or use Bar Magnifier |
| Costs not modelled | Overstates every result | Yes, in settings | Set commission and slippage |
| Order timing | Fills at a price you would not get | Yes | Check fill prices in the trade list |
| Overfitting | Measures the tuning, not the rule | No | Test on data you did not tune on |
| Selection effects | Measures a symbol you chose in hindsight | No | Test across instruments and periods |
Repainting, which is the big one
If a backtest cannot be reproduced live and you only investigate one thing, investigate this. A repainting strategy makes decisions using information that did not exist at the moment it claims to have decided. On historical data that produces results ranging from flattering to spectacular, and in real time it produces nothing resembling them, because the information genuinely is not there.
The most common form is acting on a developing bar. A condition evaluated before a bar closes can be true at one moment and false by the close, so a strategy that enters on it is entering on a value that later changes. On historical data every bar is already closed, so this failure is completely invisible. It is not a subtle bug; it is a categorically different strategy being tested from the one that will run.
The second form comes from higher-timeframe data. A request for a higher timeframe returns a value that is only final once the higher-timeframe bar completes, so using it before then means using a partially formed value. The lookahead setting on those requests can make this dramatically worse by returning data from the future outright, which produces backtests that look extraordinary and cannot be reproduced by anyone.
The check is straightforward and takes patience rather than skill. Note where the strategy’s signals sit on the chart now. Come back after a session and look again. Signals that moved or disappeared were never real. Our guide to repainting covers the five distinct varieties and how each presents.
What a bar cannot tell anyone
A bar is four numbers: open, high, low, close. From those, nobody can determine the order in which price visited the high and the low. The information is not in the data, so it is not a matter of the platform trying harder. This single fact is responsible for a large share of the remaining gap once repainting is dealt with.
It matters most when both your stop and your target fall inside one bar’s range. On such a bar, whether the trade was a win or a loss is decided by the simulator’s assumption about sequence, not by your rules. The consequence follows directly: the tighter your levels relative to typical bar size, the more of your results are produced by assumptions. A strategy whose stop and target both sit comfortably inside an average bar is not really being tested at all.
There are three mitigations and it is worth knowing what each costs. Testing on a lower timeframe makes each bar smaller relative to your levels, so fewer bars contain both, at the cost of more bars to process and a shorter history within platform limits. TradingView’s Bar Magnifier, on higher account tiers, uses lower-timeframe data to resolve the sequence during the backtest, which addresses the cause directly. And widening the distance between your levels reduces how often the ambiguity applies, though that is a change to the strategy rather than to the test.
A related point that catches people: this affects trailing stops more than fixed ones. A trailing stop follows the best price reached and can then be triggered by a move back, and both events can happen inside one bar. Trailing backtests therefore carry more uncertainty in their exit prices than fixed-stop backtests do, which is worth knowing before comparing the two.
Costs, which are boring and decisive
A backtest with no commission and no slippage configured is measuring a market that does not exist. The defaults are not your costs, and the effect is not a small haircut applied at the end. Costs are subtracted from every trade, so their impact scales with trade frequency, and a strategy that trades often can go from apparently viable to clearly not on cost alone.
Set commission explicitly in the strategy declaration or in the Strategy Tester properties, in whichever form matches how you are actually charged, whether that is per contract, per order, or as a percentage. Then set slippage, which is the harder number because it is not a fixed fee but a function of how much price moves between decision and fill. Being approximately right matters far more than being precise; the difference between zero and a plausible estimate is much larger than the difference between two plausible estimates.
The diagnostic worth running is a sensitivity check rather than a single figure. Run the strategy at your best estimate of costs, then at roughly double that, and see what happens. A strategy whose results survive both is telling you something. A strategy that only works at zero cost has told you something too, and it is better to learn it from the Strategy Tester than from an account statement.
Do not forget the costs that are not per-trade. Financing on held positions, currency conversion, and anything charged on a schedule rather than on a fill are outside what a standard backtest models. For a strategy that holds positions for long periods, these can matter more than commission.
When orders actually fill
By default a market order generated by a signal on one bar fills at the next bar’s open. That is a conservative and generally realistic assumption, and it is also why a strategy can appear to enter at a price some distance from where the signal occurred, particularly around a gap. If fill prices in the trade list look wrong, this is usually the explanation rather than a bug.
The process_orders_on_close setting changes this so orders fill at the close of the signal bar instead. It is available and it is a modelling choice, not a correction. It assumes you could transact at the closing price of the bar that produced the signal, which is sometimes reasonable and sometimes not. What matters is that turning it on will change your results, so change it deliberately, one setting at a time, and know which change produced which difference.
There are also settings governing when the strategy recalculates, including on every tick and after order fills. Each changes behaviour, and each can turn a stable backtest into something that behaves differently live, particularly the tick-based one, since historical data does not have ticks in the same way live data does. The safe default is to leave these alone unless you have a specific reason and have tested the consequence.
One more timing effect that catches people migrating from v5: Pine v6 defaults strategy margin to 100 and enforces position-size limits, so a strategy can no longer open a position the account cannot afford. This means a migrated strategy can produce fewer or different trades with no change to its entry rules. That is more realistic rather than broken, and our position sizing guide covers how to reason about it.
The sample you tested is not a sample of the future
Everything so far is a modelling problem with a technical fix. What follows is not, and it is the part that persists after the technical work is done. A backtest measures one strategy on one instrument over one period, and the reason you chose that instrument and period is that you already know something about how it behaved. That knowledge is in the result whether or not you intended it.
Overfitting is the sharpest version. Adjusting parameters until a backtest looks good, then reading that backtest as evidence, means you have measured how well the parameters fit the data rather than how well the rule describes the market. The number of adjustments you make is roughly the number of times you should discount the result. This has nothing to do with code quality and cannot be fixed by writing better Pine Script.
The standard defence is to hold data back. Tune on one period, judge on another you have not looked at, and accept the second number rather than the first. It is uncomfortable precisely because the held-back result is usually worse, which is the information you wanted. Testing across several instruments serves a similar purpose: a rule that only works on one symbol has told you about that symbol.
The drawdown figure deserves specific scepticism. A backtest reports the worst decline that happened to occur in the window tested. It is a single extreme observation, not a limit. There is no principle that says a future window cannot be worse, and treating the historical maximum as a ceiling is one of the more expensive misreadings available. Our max drawdown calculator measures the figure and is equally clear about what it does not mean.
None of this is advice about whether to trade anything. It is a description of what a number produced by a simulation can and cannot support. Every decision that follows is entirely yours.
A practical order to work through
Eliminate repainting first, because until that is done nothing else you measure means anything. Confirm every condition uses closed bars, confirm any higher-timeframe request is not looking ahead, then watch the strategy forward for a session and check the signals stayed put.
Then set costs. Commission and slippage at plausible values, then again at roughly double, so you know how sensitive the result is. This is fast and frequently decisive, and doing it early avoids refining a strategy that costs alone will rule out.
Then look at the intrabar exposure. How large are your stop and target relative to a typical bar? If both usually fit inside one, either widen them, drop to a lower timeframe, or accept that the results carry substantial assumption-driven uncertainty. Reconcile a handful of individual trades from the List of Trades by hand while you are here; a single trade checked properly is worth more than an equity curve glanced at.
Then, and only then, worry about parameters. Tune on one period, judge on another, and read the second number. If you reach this point and the strategy still looks reasonable, you have a result that is at least measuring your rules rather than the simulator’s assumptions, which is the most a backtest can honestly offer.
//@version=6
// A declaration that states its assumptions rather than inheriting them.
strategy("Assumptions made explicit", overlay = true,
initial_capital = 25000,
// Costs, set deliberately. Adjust to match how you are charged.
commission_type = strategy.commission.percent,
commission_value = 0.05,
slippage = 2,
// v6 defaults margin to 100. State it either way, with a reason.
margin_long = 100,
margin_short = 100,
// Sizing as a share of equity, so the test compounds.
default_qty_type = strategy.percent_of_equity,
default_qty_value = 10)
// Every condition uses the CLOSED bar. barstate.isconfirmed makes that
// explicit for anything evaluated on the developing bar.
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)
longSignal = ta.crossover(fast, slow) and barstate.isconfirmed
if longSignal
strategy.entry("Long", strategy.long)
if strategy.position_size > 0
atrValue = ta.atr(14)
strategy.exit("Exit", from_entry = "Long",
stop = strategy.position_avg_price - atrValue * 3,
limit = strategy.position_avg_price + atrValue * 6)The point of that declaration is not the specific numbers, which are yours to choose. It is that every assumption is written down. A declaration that sets nothing inherits defaults that were not chosen for your situation, and six months later there is no way to tell which values were decisions and which were accidents.
Where a Pine-focused workflow helps
Most of what closes the backtest-to-live gap is careful, unglamorous work in the declaration and in the conditions: costs set explicitly, conditions confirmed against closed bars, exits that reference the same levels the sizing assumed. It is also work that touches several parts of a script at once, which makes it a poor fit for asking a general chat assistant to fix and accepting a regenerated strategy in return. You cannot tell whether the numbers moved because the assumption was fixed or because five other things changed with it.
PineScripter is the product we build, and the relevant part is the shape of the edit. Because it works from the retrieved Pine Script manual, a suggestion about lookahead or the v6 margin default references documented behaviour rather than a guess, and because its edits are line-level diffs, you can change one assumption at a time and see exactly what moved. That sequencing is the whole method here: one change, one measurement.
What no tool can do is tell you whether a result is real. Repainting is detectable, costs are configurable, and intrabar ambiguity is reducible, but overfitting and selection effects live in how you chose what to test, and no amount of correct code addresses them. The honest use of a backtest is as a way to rule things out, and that is a genuinely valuable thing for it to be.
Frequently asked questions
Why are my backtest results better than my live results?
In rough order of likelihood: the strategy repaints, so it acted on data that did not exist yet; costs are not modelled, so every trade is flattered; the stop and target both fall inside typical bars, so fills are decided by assumptions; or parameters were tuned against the same data used to judge them. Check repainting first, because until that is ruled out nothing else you measure is meaningful.
What is repainting and how do I know if my strategy does it?
Repainting is when a script uses information that was not available at the moment it claims to have acted, usually by evaluating a condition on a developing bar or by requesting higher-timeframe data with lookahead. To check, note where signals sit on the chart, come back after a session, and look again. Signals that moved or vanished were never real.
Why did both my stop loss and take profit trigger on the same bar?
A bar’s open, high, low, and close cannot establish the order in which price visited levels inside it. When both levels fall within one bar’s range, the result is decided by the simulator’s fill assumptions. Testing on a lower timeframe or using TradingView’s Bar Magnifier reduces how often this arises.
How much slippage and commission should I use in a backtest?
Match commission to how you are actually charged, whether per contract, per order, or as a percentage. Slippage is an estimate rather than a fee, so being approximately right matters more than precision. Run the strategy at your best estimate and again at roughly double it: if the result only survives at zero cost, that is the useful finding.
Does process_orders_on_close make a backtest more accurate?
It makes it different, not more accurate. By default a signal fills at the next bar’s open, which is conservative. Turning this on fills at the close of the signal bar, which assumes you could transact at that price. Both are assumptions; choose deliberately and change one setting at a time so you know what caused a difference.
Can I trust the max drawdown figure from a backtest?
Treat it as the worst decline that happened to occur in the window you tested, not as a limit. It is a single extreme observation from one sample, and nothing prevents a future window from being worse. It is useful for comparing strategies on the same data and unreliable as a forecast of future declines.
The practical takeaway
The gap between a backtest and live results is mostly a list of assumptions, and the list is short. Rule out repainting first, set costs deliberately and test their sensitivity, understand how much of your result depends on intrabar guesswork, and then judge parameters on data you did not tune against. What remains after that is a result that measures your rules, which is the most a simulation can honestly give you.
Because closing this gap means changing one assumption at a time across the declaration, the conditions, and the exits, PineScripter is our product and shows each change as a reviewable diff so you always know what moved the number. It cannot tell you whether a result is real, and the forward test remains the only check that counts.
Sources
- TradingView Pine Script documentation: strategies, fills and costs
- TradingView Help Center: strategy properties, commission and slippage
- TradingView Help Center: the Bar Magnifier
- TradingView Pine Script v6 migration guide: margin defaults
Related reading: how TradingView backtesting works, repainting explained, stop loss and take profit in Pine Script, position sizing in Pine Script, the max drawdown calculator, the profit factor calculator.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.