Where the gap comes from
"Backtests are optimistic" is true and useless. The gap has specific causes, each of which you can check on your own script, and they sit in three different places in the documentation.
Bar-level execution assumptions
The tester fills orders against bar data. Live, you fill against a book.
| Assumption | What it hides [1] |
|---|---|
process_orders_on_close off | orders fill at the next bar's open, not where your signal fired |
process_orders_on_close on | orders fill at the close of the signal bar — better aligned, but only reachable if you can actually trade the close |
calc_on_order_fills off | the script does not re-evaluate on a fill, so intrabar sequences the live market would produce are not modelled |
pyramiding default of 1 | a second signal is ignored, in the test and live alike |
None of these is wrong. They are modelling choices, and the point is to know which one you made.
The historical/realtime asymmetry
A finished bar holds four numbers. A forming bar's high, low and close are still moving [2]. The tester runs on finished bars; live runs on a bar that is not finished yet.
That single fact produces most of the ordinary divergence. A condition that
compares against high is comparing against a final value in the test and a
provisional one live. A strategy with calc_on_every_tick executes every tick
live and only at bar close historically [2] — the same script, two execution
models.
Costs, which are the largest single factor
Commission and slippage are declaration parameters, and a tester with neither is modelling a market that does not charge you. Slippage is set in ticks [1], so getting the unit wrong scales the error by the instrument's tick size.
This is covered properly in commission, slippage and capital; the short version is that cost settings usually explain more of the gap than execution timing does.
Two that surprise people
Non-standard chart types. TradingView states plainly that they produce unrealistic results by default and that standard charts should be used for realistic backtesting [1]. A Heikin Ashi or Renko backtest cannot be reproduced with real fills — the bars themselves are derived, not traded.
The 9,000-order cap. Beyond it, v6 silently trims the oldest orders rather
than raising an error [3], and trimmed trades return na from
strategy.closedtrades.*. A long high-frequency backtest can therefore be
reporting on a subset of its own trades.
Shrinking the gap, in order of effect
Ordered by how much each step typically moves the result, largest first.
1. Put real costs in. Commission and slippage from your own account, not a number from an article. This usually accounts for more of the gap than everything below it combined.
2. Test on a standard chart. If the backtest was run on Heikin Ashi or Renko, the result is not comparable to live trading at all [1] and nothing else on this list will fix that.
3. Decide the fill model deliberately. Choose process_orders_on_close
knowingly rather than by default, and be honest about whether you can trade the
close of a bar on the timeframe you are testing. On a daily chart, you often
cannot.
4. Check for lookahead. If the script uses request.security(), confirm
there is no future leak — see
repainting and lookahead.
This is a binary: either the historical run had information the live run cannot
have, or it did not.
5. Look at the trade count, not only the return. Costs and execution assumptions scale with frequency, so a high-frequency result is far more sensitive to every item above. A strategy with 40 trades has not been tested, whatever its return says.
6. Re-run at double your cost estimate. A strategy that survives that is interesting. One that does not is telling you its result is the cost assumption.
Then paper trade, and expect it to still differ
Paper trading removes the historical/realtime asymmetry [2] because it runs forward on forming bars, the way live does. What it does not remove is your fill — a paper fill is still a modelled fill, not a queue position.
So the sequence that actually works is: backtest to reject bad ideas cheaply, paper trade to catch the execution assumptions the backtest hid, then live small to find out what your fills really cost.
The honest limit of all of this
None of these steps makes a backtest predictive. A tester with correct costs, standard bars, a deliberate fill model and no lookahead is still a model, and the market is not obliged to resemble it.
What the work buys you is different and worth more: a test that fails honestly. Most strategies that look good with default settings stop looking good once costs are real — and finding that out in the tester costs nothing, while finding it out live costs the account.
Alerts are not orders
The Strategy Tester lists two hundred trades. Your alert log is empty. Nothing is broken, and this is the documented reason:
Alerts only trigger in the realtime bar. [4]
Every fill in the backtest is a replay of history. An alert is a realtime event. Nothing on the historical part of the chart ever fired an alert, and nothing ever will — no setting changes that.
The frequency you did not choose
alert(message, freq)
freq defaults to alert.freq_once_per_bar [4]. There are three values:
| Constant [4] | Fires |
|---|---|
alert.freq_once_per_bar | the first time the condition is true on a bar — the default |
alert.freq_once_per_bar_close | only on the bar's confirmed close |
alert.freq_all | every time the condition is true |
If your strategy decides on bar close, the default is the wrong one. It will notify you on an intrabar condition that has reversed by the time the bar closes — the repainting problem wearing a different hat, and the reason a live alert stream can disagree with the backtest that produced it.
Order-fill alerts and their text
A script alert created from a strategy can be set so that order fill events
also trigger it [4]. Per-order text comes from the alert_message parameter,
which strategy.entry(), strategy.exit(), strategy.close() and
strategy.order() all accept [4]; you read it back in the alert body with the
{{strategy.order.alert_message}} placeholder [4].
Other placeholders available in alert messages include {{ticker}},
{{exchange}}, {{interval}}, {{close}}, {{time}} and {{timenow}} [4].
The part TradingView does not promise
An alert is a notification. It does not place an order with your broker. Getting from a fired alert to a filled position needs a broker integration, and the delay between the two is yours — it is not in the tester, which assumes the fill happened at the price it modelled. That delay is one more part of the gap this page is about, and no tester setting removes it.
The limits that decide your results
Some of what your backtest reports is decided by your account, not by your strategy. These are worth knowing before you spend a weekend debugging one of them.
Your plan changes your backtest
Historical bars available, by plan [3]:
| Plan [3] | Historical bars |
|---|---|
| Ultimate | 40,000 |
| Expert | 25,000 |
| Premium | 20,000 |
| Essential and Plus | 10,000 |
| Other plans | 5,000 |
Read that table again, because the consequence is larger than it looks: two people running an identical script on an identical symbol and timeframe get different backtests. The one with more history tests more market regimes, and the one with less may not reach the drawdown that would have changed their mind.
So a reported backtest figure is not reproducible unless the plan is stated alongside it. If you publish a number, state your plan; if you read one that does not, you cannot check it.
The order cap, again
The 9,000-order cap above rises to 1,000,000 with Deep Backtesting [3]. A frequently-trading script on a long range reaches 9,000 and the equity curve goes flat while the statistics stay plausible, so check the trade count against the cap before concluding the strategy stopped working.
Execution time, which fails a script that used to pass
The time allotted to run across all bars of a dataset is 20 seconds for basic accounts and 40 for others [3].
This one is confusing because your logic does not have to change to hit it. Add
bars, add a request.* call, upgrade to a symbol with more history, and a
script that ran yesterday times out today.
What to actually check, and how often
First, what you do not have to do: keep a machine running. A Pine strategy executes on TradingView's chart, not on your computer, so there is no terminal to restart and no VPS to rent. This is the structural difference from an MT5 Expert Advisor, and most maintenance advice you will find for Pine is an MT5 article with the words swapped — see deploying and maintaining an EA for what that genuinely does require.
What is left is checking that the things your backtest assumed are still true.
After every re-export — the declaration
This is the only mandatory check, because the strategy() declaration is where
your results are decided and a fresh export rewrites it.
| Confirm | Because |
|---|---|
commission_type and commission_value are set | a default export may cost nothing |
slippage is in ticks for this instrument | a tick is not the same size on two symbols |
pyramiding is the number you intended | it defaults to 1 [1]; a higher value stacks positions |
| every input control actually changes behaviour | move a threshold and confirm the trade list changes |
That last row is not paranoia. An input that is declared but never referenced in the logic renders a working control that does nothing, and there is no way to tell by looking at it — see the cost parameters in detail.
Weekly — two spot checks
Pick three trades from the List of Trades and find them on the chart. Does the fill price correspond to a price that bar actually traded at? This one check catches future leak, a non-standard chart type and a misread tick size, and it takes under a minute.
Confirm your session filter still matches the instrument's hours. Daylight saving moves them twice a year, and a session string is interpreted in the exchange's timezone rather than the chart's — the session-filter rules cover why that bites.
Monthly — is it still trading?
A strategy that has stopped opening positions is either in a market regime it was not built for, or has a condition that no longer becomes true. In the equity curve these look identical: a flat line.
Tell them apart by plotting the condition itself rather than the result. If the
condition is firing and no orders follow, look at pyramiding, at the order
cap, or at a filter that is now permanently false. If the condition is not
firing at all, the market changed — and that is a decision to make, not a bug to
fix.
What does not need checking
The code, unless you changed it or the platform did. Version defaults are the exception worth a calendar reminder: the v5 to v6 changes altered behaviour without raising errors, and that class of change is the only one that can break a script you never touched.

