A strategy that backtests cleanly can still misbehave the moment its alerts start posting to a webhook. The failures are mostly plumbing: a payload that is not valid JSON, an alert that fires on every tick, a session guard that does not pin its clock to IST. Each one is cheap to catch before the first live order and expensive after.

This page is the checklist Sarathi's own house strategies go through. Work it top to bottom. Every row has a pass condition you can observe, not an opinion.

## The checklist

| # | Check | Pass condition | Where to look |
| --- | --- | --- | --- |
| 1 | Compiles on Pine v6 | `//@version=6` at the top, no compiler warnings | Pine Editor |
| 2 | Executes on confirmed closes only | `calc_on_every_tick = false`, `process_orders_on_close = true`, entries gated on `barstate.isconfirmed` | Strategy header and entry conditions |
| 3 | No lookahead | `[1]` offset with `lookahead_on`, or completed prior-period values with `lookahead_off` and the one-period lag documented | Every `request.security()` call |
| 4 | Alert fires once per bar close | `alert.freq_once_per_bar_close` in code; Once Per Bar Close in the dialog for `alertcondition()` | `alert()` calls and the alert dialog |
| 5 | Payload is valid JSON | The raw body from the alert log parses without edits | Alert log, webhook status column |
| 6 | Numbers are numbers | No thousands separators, no `NaN`, no `1e+05` | `str.tostring()` calls in `alert_message` |
| 7 | Session and square-off are in IST | Entry window and flat-by time computed with `"Asia/Kolkata"` | Clock block |
| 8 | Size and risk per trade are explicit | `qty` in the payload is a lot multiple; stop distance times quantity is a number you accept | Sizing block, order card |
| 9 | Paper trade first | Cards confirmed on the paper route, fills and slippage visible in the journal | [Paper route](/brokers/paper), journal |
| 10 | Journal reviewed after a week | Expiries, skips, rejections and slippage reviewed against the backtest | `/dashboard/journal` |

The rest of this page walks through each row.

## 1. Compile on v6

Pine v6 changed several defaults that quietly alter a strategy's behaviour: implicit bool conversion is gone, `and` and `or` evaluate lazily, and timeframe strings such as `"D"` need a multiplier. TradingView publishes the full list in its [migration guide](https://www.tradingview.com/pine-script-docs/migration-guides/to-pine-version-6/). A v5 script that still runs is not a passed check; convert it, fix what the compiler flags, and re-run the backtest. If the trade list changed, find out why before moving on. The Studio writes v6 from the start, and [this page](/learn/pine-v5-to-v6-migration) covers the breaking items one by one.

## 2. Confirmed closes, not ticks

A strategy recalculates on every realtime update when `calc_on_every_tick` is true, which means an entry can appear mid-bar and disappear before the close. Historical bars never behave that way, so the backtest and the live chart stop describing the same thing. Set it false, set `process_orders_on_close = true`, and put `barstate.isconfirmed` in the entry condition.

```pine
//@version=6
strategy("ORB 15m", overlay = true,
     calc_on_every_tick = false,
     process_orders_on_close = true,
     commission_type = strategy.commission.cash_per_order,
     commission_value = 350,
     slippage = 2)

longSig = barstate.isconfirmed and close > orHigh + buffer
```

The house rule adds ₹350 per order and two ticks of slippage to every backtest. Neither number is a forecast; they are a stress test so a strategy that only works at zero cost fails in the editor rather than in an account.

## 3. Prove it does not repaint

Repainting has four common causes, and [this page](/learn/pine-repainting-lookahead-calc-on-every-tick) covers them. The one that survives most reviews is `request.security()` with `barmerge.lookahead_on` on an incomplete higher-timeframe bar, which lets a 15-minute script see today's daily close at 09:30. Use a `[1]` offset with `lookahead_on`, or completed prior-period values with `lookahead_off` and the one-period lag documented.

The test is mechanical. Leave the strategy on a live chart for a few sessions and export the trade list. Then reload the chart so every bar is historical and export again. The two lists should match trade for trade. TradingView's [repainting guide](https://www.tradingview.com/pine-script-docs/concepts/repainting/) explains what each mismatch usually means.

## 4. Alert frequency

For `alert()` calls the frequency is the `freq` argument in code, and a strategy without `calc_on_every_tick` fires at bar close whatever you pass. For `alertcondition()` alerts it is the dialog's Once Per Bar Close setting. "Every time" can post the same order several times while the bar is still forming. For `alertcondition()` the choice lives in the dialog, not in the script, so it is easy to forget when recreating an alert. The [alert() versus alertcondition() page](/learn/alert-vs-alertcondition-strategy-vs-indicator) covers how `alert_message` rides on `strategy.entry()`.

## 5 and 6. A payload that parses

The webhook receiver only sees the body TradingView posts. If it is valid JSON the request arrives with an `application/json` header; anything else is sent as `text/plain`, per TradingView's [webhook documentation](https://www.tradingview.com/support/solutions/43000529348-about-webhooks/). Sarathi accepts both, but a body that was meant to be JSON and is not will be rejected with a 400 that echoes what was received.

Numbers are the usual culprit. `str.format('{0}', x)` groups thousands by default and emits `57,530`; `str.tostring(x)` can carry floating-point noise. Give every number an explicit pattern: `{0,number,#.##}` or `str.tostring(x, "#.##")`. The [webhook alerts page](/learn/tradingview-webhook-alerts) has the worked example.

The body the receiver expects to see:

```json
{
  "sym": "BANKNIFTY",
  "side": "BUY",
  "type": "ORB",
  "price": 57530,
  "sl": 57400,
  "qty": 30,
  "time": "2026-08-27T09:30:00+0530"
}
```

Two more traps. A stop computed from `na` becomes the string `NaN`, which JSON does not allow; guard the division that produced it. And an exit order with no `alert_message` posts the unexpanded placeholder `{{strategy.order.alert_message}}`; Sarathi logs that as ignorable and does not raise a card. The full field list, aliases and error codes are on the [webhook page](/learn/tradingview-webhook-alerts) and under [/developers](/developers).

## 7. Session and square-off in IST

Without a timezone argument, `hour()`, `minute()` and `time()` use `syminfo.timezone`, the exchange timezone. For NSE and MCX that is already IST, but pass `"Asia/Kolkata"` explicitly so the script still reads correctly on a non-Indian symbol or a copied chart. Express both the last-entry cut-off and the flat-by time in minutes since midnight.

```pine
hh = hour(time, "Asia/Kolkata")
mm = minute(time, "Asia/Kolkata")
tMins    = hh * 60 + mm
canEnter = tMins < 14 * 60 + 30     // no new entries after 14:30
isEOD    = tMins >= 15 * 60 + 15    // flat by 15:15
```

The [NSE F&O page](/learn/nse-fo-in-pine-sessions-lots-expiries) has the session strings for NSE and MCX and the reasoning behind a 14:30 cut-off on a 15:30 close.

## 8. Size and risk per trade

The order card shows entry, stop, quantity, risk in rupees and notional. Those numbers come from the payload, so the payload has to carry a quantity that is a whole number of lots for the instrument, and a stop that is on the correct side of the entry. Work out the rupee risk on the card before the first live session: stop distance in points times quantity. If that figure surprises you, fix the script, not the tap.

Sarathi adds one more knob at decision time. Confirm carries ½, 1× or 2× of the payload quantity, rounded to a whole number of lots for the instrument and never below one lot, so a ½ tap on a one-lot signal places one lot. The [Telegram page](/connectors/telegram) has the full wording.

## 9. Paper trade first

The paper route is a broker adapter that fills at the price hint, records the fill and the slippage, and never touches an exchange. It is never metered. Run the strategy on paper until every branch has fired at least once: long, short, stop, target, square-off, and a card you deliberately let expire. Each of these lands in the journal with its route, side, quantity, entry, fill and IST timestamp, so you can compare against the backtest line by line. The [paper route page](/brokers/paper) lists what it does and does not simulate.

## 10. Read the journal after a week

After five sessions, filter the journal by strategy and count four things.

| Count | What it tells you |
| --- | --- |
| Expired cards | Alerts fired when you could not respond. Either move the cut-off or accept the miss rate. |
| Skipped cards | Signals you chose not to take. If most are skips, the script and your judgement disagree; decide which is right. |
| Rejected fills | Quantity, symbol or session errors that a live broker would also refuse. |
| Slippage vs signal | Fill minus price hint, in points. Compare against the two ticks the backtest charged. |

A strategy that passes all ten rows is not guaranteed to make money. It is guaranteed to post what you think it posts, when you think it posts it, at the size you intended, and that is what this checklist is for.


  Sarathi is an execution bridge, not an investment adviser, and is not
  registered with SEBI as a Research Analyst. Nothing on this page is advice or
  a recommendation to trade. Derivatives trading carries substantial risk of
  loss.


## Try it in Sarathi

The [Studio](/dashboard/studio) writes Pine v6 with the house header from row 2 already in place. Point the alert at your webhook, leave the account on the paper route, and every card that arrives in Telegram and on the web dashboard waits for your Confirm or Skip. Nothing is placed until you tap.
