A strategy that shows a clean backtest and then trades differently live is not necessarily wrong. It is usually repainting, which TradingView defines as "script behavior causing historical vs realtime calculations or plots to behave differently" ([Pine Script docs: Repainting](https://www.tradingview.com/pine-script-docs/concepts/repainting/)). The definition is broad on purpose. By the docs' own account most indicators repaint in some form, and most of that is harmless. The question for anyone sending alerts to a webhook is narrower: does the signal that fires live match the signal the backtest counted?

## What repainting is

Pine runs a script once per historical bar, with the bar's final open, high, low and close already known. On the live bar it runs again on every price update, and only the closing run is committed to history. Historical data has no record of the path price took inside a bar, so anything that depended on that path cannot be reproduced when the chart reloads ([TradingView support: different results after refreshing](https://in.tradingview.com/support/solutions/43000478429-script-or-strategy-gives-different-results-after-refreshing-the-page-repainting/)).

The docs group the causes into four tiers. Two are worth acting on:

| Tier | Examples | Verdict |
| --- | --- | --- |
| Widespread, often acceptable | Using `close` on the open bar; higher timeframe `request.security()` tracking an unconfirmed period | Fine if you know it and gate alerts on bar close |
| Potentially misleading | Plotting into the past, `calc_on_every_tick = true`, `varip`, `timenow`, pivots that confirm late | Remove from anything that fires orders |
| Unacceptable | Leaking future data into history, strategies on non standard charts, alerts from intrabar ticks | Never in a strategy that feeds a webhook |
| Unavoidable | Data revisions, the chart's starting bar shifting | Accept and document |

## `barmerge.lookahead_on` and `lookahead_off`

`request.security()` takes a `lookahead` argument. With `barmerge.lookahead_off`, the default, a historical chart bar only sees a higher timeframe value once that higher timeframe bar has closed. On the live bar, the same call returns the higher timeframe bar that is still forming. The two do not match, and the mismatch is the most common source of "it worked in the backtest" ([Pine Script docs: Other timeframes and data](https://www.tradingview.com/pine-script-docs/concepts/other-timeframes-and-data/)).

With `barmerge.lookahead_on` and no offset, the reverse happens: every historical chart bar inside a daily period sees that day's final close before the day has finished. The backtest knows the future. That is the "unacceptable" tier, and it produces equity curves that cannot be reproduced live.

The documented fix is the two together: offset the expression by one bar and turn lookahead on.

```pine
// Same value on historical and live bars: yesterday's confirmed daily close.
dailyClose = request.security(syminfo.tickerid, "1D", close[1],
     lookahead = barmerge.lookahead_on)
```

`close[1]` inside the request refers to the previous daily bar, which is complete on every chart bar, and `lookahead_on` lets historical bars read it at the start of the day rather than at the end. Live bars see the same thing. The price is that the value is always one daily bar late, by design ([Pine Script docs FAQ: avoiding repainting with request.security()](https://www.tradingview.com/pine-script-docs/faq/other-data-and-timeframes/)).

### Why `x[1]` alone lags on historical bars

People often add the `[1]` and keep `lookahead_off`, thinking the offset is the safe part. It is not. With lookahead off, the historical series only advances when the higher timeframe bar closes, so `close[1]` on a historical chart bar is the close of the day before the one that most recently finished. Live, the same expression returns yesterday's close. The result is a series that is one period late in real time and two periods late in history, and it still does not line up. The offset and the lookahead flag are one technique, not two options.


  Sarathi's house strategies take a stricter line: they avoid higher timeframe
  requests where they can, and where they cannot they read only completed prior
  period values with `lookahead_off` and state the lag in the strategy notes.
  That gives up some responsiveness in exchange for a backtest that cannot see
  ahead under any setting.


## `calc_on_every_tick`

By default a strategy executes once per bar, on the closing tick. `calc_on_every_tick = true` makes it recalculate on every price update of the live bar, so orders and `alert()` calls can happen mid bar ([Pine Script docs: Bar states](https://www.tradingview.com/pine-script-docs/concepts/bar-states/)). Historical bars have no ticks, so the backtest still evaluates once per bar with the final close. The strategy is now two different strategies, one for the past and one for the present, and TradingView lists it under potentially misleading repainting.

Leave it `false` for anything that fires a webhook. The signal arrives a few seconds after the bar closes instead of somewhere inside it, and the backtest counted exactly those signals.

## `barstate.isconfirmed`

`barstate.isconfirmed` is `true` on every historical bar and on the closing update of the live bar. Put it in the entry condition and the condition can only become `true` at the moment the bar is final, which is the same moment the backtest evaluated it.

```pine
fired = ta.crossover(close, orHigh) and barstate.isconfirmed
if fired
    strategy.entry("L", strategy.long, alert_message = body)
    alert(body, alert.freq_once_per_bar_close)
```

Two limits. It does not work inside a `request.security()` expression, so it cannot fix a higher timeframe leak. And in an indicator it is the only thing standing between you and an `alert()` that fires on the first tick that satisfies the condition, because `alert()` defaults to once per bar, not once per bar close ([Pine Script docs: Alerts](https://www.tradingview.com/pine-script-docs/concepts/alerts/)). Use both the gate and `alert.freq_once_per_bar_close`.

## `process_orders_on_close`

A strategy that decides on bar close normally fills on the next bar's open. With `process_orders_on_close = true` in the declaration, the broker emulator fills at the close of the bar that produced the signal. That is the fill the backtest should assume when the live path is "alert fires at bar close, human confirms, order placed at market", because the price the trader sees on the card is the close the strategy acted on. Pair it with a realistic per order commission and a couple of ticks of slippage so the assumption does not flatter the result.

The one place `process_orders_on_close` can mislead is stop and target exits. Those fill inside a bar in the emulator, before the script runs on that bar, so an exit alert driven by `strategy.exit()` alone may not carry the state you expect. Derive exit alerts from the confirmed position change instead:

```pine
exitedLong  = strategy.position_size[1] > 0 and strategy.position_size == 0
exitedShort = strategy.position_size[1] < 0 and strategy.position_size == 0
if barstate.isconfirmed and (exitedLong or exitedShort)
    alert(makeMsg(exitedLong ? "SELL" : "BUY"), alert.freq_once_per_bar_close)
```

## How to test for it

None of this needs a special tool. It needs patience and a notebook.

1. **Reload test.** Add the strategy to a chart during market hours, let it run for twenty or thirty bars, note the markers, then reload the page. Every marker that moves or vanishes is a repaint.
2. **Bar replay.** Step through the same session with Bar Replay and compare the signals it produces against the ones the full backtest shows for that day. They should be identical.
3. **Historical versus live count.** Keep a strategy alert running for a week and compare the alert log against the Strategy Tester's trade list for the same week. Mismatched counts point at `calc_on_every_tick`, a missing `isconfirmed` gate, or a higher timeframe request.
4. **Search the source.** `lookahead_on` without a `[1]` on the expression, `calc_on_every_tick = true`, `varip`, `timenow` in a condition, negative `offset` values in plots, and `request.security()` on a lower timeframe are all reasons to stop and read.
5. **Shift the start.** Change the chart's date range so the first bar moves. Functions with a memory, such as `ta.ema()`, `ta.barssince()` and `ta.valuewhen()`, change slightly with the starting point. Small drift is expected; changed trade direction is not.

## Checklist

| Check | Pass condition |
| --- | --- |
| `calc_on_every_tick` | Absent or `false` |
| Entry and exit conditions | Include `barstate.isconfirmed` |
| `alert()` frequency | `alert.freq_once_per_bar_close` |
| `request.security()` higher timeframe | `[1]` offset with `lookahead_on`, or completed values with `lookahead_off` and the lag documented |
| `request.security()` lower timeframe | Not used for signals |
| `varip`, `timenow` | Not in any condition that places an order |
| Plot offsets | None negative on signal markers |
| Exit alerts | Driven by position change, not by the exit call |
| Reload test | Markers unchanged after refresh |
| Bar replay | Same signals as the backtest for the same session |

## Try it in Sarathi

The 13 house strategies in the Studio act on confirmed closes only, keep `calc_on_every_tick` off, and were tested with ₹350 per order and two ticks of slippage charged. Strategies the Studio writes for you follow the same rules and carry the webhook payload in both `alert_message` and `alert()`. (live) Each alert becomes an order card in Telegram and on the web dashboard; you tap Confirm at ½, 1× or 2× size, or Skip, and nothing is placed without the tap. The paper broker fills today; Dhan and Zerodha adapters are planned. (planned) [Open the Studio](/dashboard/studio).
