Troubleshooting · Pine Script

Why Pine Script Alerts Don't Remember Anything
(and why that breaks naive automation)

2026-07-22·10 min read

Your strategy fires an alert, the webhook goes out, and moments later a second, near-identical order lands on the same candle. Or the opposite happens: you tweak an entry condition, save the script, and the live alert keeps trading exactly like it did last week — as if the edit never happened. Neither is a random glitch.

Both trace back to the same root cause: a Pine Script alert has no memory of what it already sent, and — this is the part people miss — it isn't even running the script you're currently looking at.

If an AI wrote the strategy for you, none of this would have come up. It isn't a syntax problem, so the code compiles and the backtest looks fine; the failure lives in how TradingView executes the script and how the alert system stores it. So this post isn't here to teach you Pine. It's here to give you the four mechanisms behind those failures, the exact wording to hand an AI so it doesn't build them in, and the questions to ask when you paste generated code back for review.

TL;DR: the frequency controls around alert() and alertcondition() only throttle how often a condition is allowed to fire inside one bar — for alert() that's the freq argument in your code, for alertcondition() it's the Frequency dropdown in the Create Alert dialog, since that function has no freq parameter of its own. They don't confirm delivery, they don't deduplicate, and they don't know what happened on the previous bar unless you store it yourself with var/varip. Even then, that memory belongs to one specific alert instance running on TradingView's servers — not to the script sitting in your chart editor.

Why the script forgets: it reruns from scratch, bar by bar

Pine doesn't run your script once over the whole chart. TradingView's own execution-model documentation describes it as running the script "repeatedly on the sequence of historical bars and realtime ticks... performing separate calculations for each bar as it progresses." On closed, historical bars that's simple: one execution per bar, in order, from the oldest bar to the newest.

The bar still forming on the right edge of your chart — the realtime bar — is different. Its high/low/close aren't final yet, so the script reruns on every incoming tick, recalculating with whatever price just arrived. Before each rerun, Pine resets ("rolls back") that bar's variables to the state they had before the tick, so a stale intermediate result doesn't leak into the next calculation. Only the values from the bar's final, closing tick get written into permanent history.

Strategies are a partial exception: by default a strategy — unlike an indicator — executes only once per bar, including the realtime bar, and only after that bar closes. Tick-by-tick recalculation only happens if you turn on calc_on_every_tick or calc_on_order_fills.

What this means when you're prompting. Ask for "a strategy that only enters once per setup" and the AI has to invent memory from somewhere, because the language doesn't hand it any. Whatever it reaches for — a flag, a counter, a stored bar index — is a workaround for this execution model, and the next section is how you recognize which workaround you got.

If the AI's code uses varip, here's what it's after

Two declaration keywords control whether a variable's value carries over between executions, and the difference between them is exactly the rollback behavior above. You're not learning these to write them — you're learning them so that when they turn up in generated code, you know what the AI was trying to solve.

DeclarationFirst setSurvives to next barSurvives a mid-bar edit
(none)Every executionNo — reinitialized every timeNo
varFirst execution on a bar's closing tickYesNo — rolled back to the last confirmed close
varipFirst execution, even mid-barYesYes — immune to rollback

Reference table — you don't need to memorize it. Use it to read what you were handed.

On historical bars this distinction is invisible, since every bar only executes once anyway — var and varip look identical there. It only shows up on the realtime bar, where a var flag you set mid-bar quietly reverts the moment the next tick rolls in; only the version that existed at bar close actually sticks. And for strategies specifically, since they default to one execution per bar (even in realtime), varip behaves just like var unless you've enabled calc_on_every_tick or calc_on_order_fills — a detail that trips people who assume varip is always "more realtime" than var.

So what do you do with that? Seeing varip in generated code usually means the AI is trying to remember something across ticks — and in an alert setup, remembering things across ticks is often the wrong answer to the wrong question. Ask it directly: "Is this state still reliable after the alert fires? What is it worth on the first bar after I delete the alert and create a new one?" If the answer doesn't mention that the value resets, you've found something to push back on. The same question is worth asking about any var flag being used as a "don't fire twice" guard.

If declarations and executions are still new territory, our Pine Script for Beginners guide covers the fundamentals — though you can get through the rest of this post without it.

The prompt that keeps most of this out of the code

Paste this as the requirements block on top of your own strategy description. It's written to force the AI to answer for each item rather than quietly do whatever it was going to do:

text
Write a Pine Script v6 strategy for TradingView. It will drive a webhook that
places real orders, so treat reliability as more important than elegance.

Strategy logic: [describe your entries and exits in plain language]

Hard requirements. Apply all of them, then report back on each one by number:

1. Declare it with strategy(), not indicator().

2. Every entry and exit condition must be evaluated only on a confirmed bar.
   Gate them on barstate.isconfirmed. Then list any condition left in the
   script that still reads close, high or low of the bar still forming.

3. Do not use var or varip to remember "I already sent this signal".
   If you think the logic needs that kind of memory, stop and explain what
   that flag is worth on the first bar after I delete the alert and create
   a new one.

4. The alert message must be one single line of valid JSON, no line breaks,
   with exactly these fields: [paste the fields your receiver expects]

5. At the end, list every line where the script behaves differently on the
   realtime bar than it does on historical bars.

Item 3 is the one that earns its keep. A useful answer explains that the flag starts from zero on a fresh alert; an answer that just says "yes, var persists" is technically true and practically misleading, and tells you the dedup logic needs to live somewhere other than the script.

Item 4: so where does that JSON actually go?
Three different places, and picking the wrong one is how you end up with a configured alert firing an empty payload:
One: the alert() call in the code. The message is assembled by the script itself, so you don't retype it in the dialog.
Two: the alert_message= argument on an order function. That text only surfaces when an order-fill event fires and the message uses {{strategy.order.alert_message}}.
Three: the Message field in TradingView's Create Alert dialog. This is the only place {{strategy.*}} placeholders work, and only when the alert was created from a strategy.
The click-by-click version — chart top-right clock, Add Alert, Condition = your strategy, Notifications tab, Webhook URL — is walked through in Writing Pine Script With Claude.

The alert isn't running the code you're looking at

Here's the part that surprises people who assume "the alert IS my script." It isn't. When you click Create Alert, TradingView takes a snapshot — script, inputs, chart symbol, timeframe — and runs that snapshot independently on its own servers from then on. The documentation is explicit: "Subsequent changes to your script's inputs or the chart will thus not affect running alerts previously created from them." The FAQ is even more direct when asked whether editing a script updates a live alert: "No, not without creating a new alert... To update an alert after making changes, delete the existing alert and create a new one."

That has a direct consequence for anything you store in var or varip to track state — a "have I already fired this" flag, say. That memory belongs to the frozen snapshot the alert is running, not to the script you keep editing in the chart panel. Edit the script all you want — the live alert's internal counters keep running on the old logic until you delete and recreate it. And when you do recreate it, that's a brand-new snapshot: its var/varip state starts from zero, recalculated from historical data, with no memory of anything the old alert instance ever tracked. (TradingView doesn't spell this last consequence out in one sentence — it follows from combining the snapshot behavior above with the restart behavior described in the next section.)

What you changeDoes the running alert follow?Official basis
Script code in the chart editorNo"Subsequent changes to your script's inputs or the chart will thus not affect running alerts previously created from them."
Script inputs / chart symbol or timeframeNoSame sentence — inputs and chart are part of the snapshot taken at creation.
Delete the alert and create a new oneYes — but as a brand-new snapshot"To update an alert after making changes, delete the existing alert and create a new one." Its var/varip state starts from zero.

Quotes are TradingView's own wording from its alerts documentation and FAQ.

This is where people burn the most time with an AI. You paste the script back, it spots something, you apply the fix, you watch live — and nothing changes. Not because the fix was wrong, but because the fix never reached the thing that's actually running. No amount of prompting reaches a running alert. The only path is: edit the script, then delete the alert and create a new one. Worth writing on a sticky note, because it looks exactly like "the AI didn't fix it."

"It fired, then the signal vanished" — what to check first

TradingView defines repainting plainly: "script behavior causing historical vs realtime calculations or plots to behave differently." It isn't automatically a bug — the docs treat some repainting (like close shifting on the still-forming bar) as "widespread but often acceptable," while other patterns are flagged "potentially misleading" or outright "unacceptable" (firing an alert or order off realtime intrabar data falls in that last bucket).

The mechanic behind the "it fired, then vanished" complaint is what the docs call fluid data: historical bars only ever store the final OHLC, but a realtime bar's high/low/close move with every tick until it closes. If your condition checks close or high right now, it can be true for one tick and false again by the time the bar actually closes — the alert already fired, and on the next refresh the condition looks like it was never met.

The documented fix is to require the bar to close before evaluating: set the alert frequency to Once Per Bar Close, or gate the condition on barstate.isconfirmed in code. TradingView is upfront that this has a cost: "while they prevent repainting, they will also trigger signals later than repainting scripts... you can't have your cake and eat it too." That same close-vs-realtime tension is also a big part of why the same alert can look near-instant in one test and noticeably slower in the next — our webhook delay breakdown covers what's actually configurable there versus what's TradingView's own infrastructure.

The practical version: the Frequency dropdown is yours to set in the browser, and it takes ten seconds. Everything else is an instruction to whoever writes the code — "gate every condition on barstate.isconfirmed, and tell me which ones you changed" — which is exactly why it's item 2 in the prompt above.

Paste the code back and ask these four questions

Whether the script came from an AI, a forum, or your own past self, this is the review pass. The point of holding back the rewrite is that you get to see whether it actually understood the setup before it starts changing lines:

text
Here is a Pine Script strategy. I am going to run it as a live TradingView
alert into a webhook. Do not rewrite it yet — answer these first:

1. Which conditions in this script can be true mid-bar and false again by the
   time the bar closes? List them by line.

2. Does anything here rely on a var or varip variable to avoid firing twice?
   If yes: what value does that variable hold on the very first bar after I
   delete the alert and create a new one?

3. If I set the alert Frequency to "Once Per Bar Close", which parts of this
   logic change behaviour and which do not?

4. Is there anything in this script that assumes my server received the
   previous alert? Answer yes or no, and point at the line.

Then, and only then, give me a version that fixes what you found.

[paste your script here]

Question 4 is a trap on purpose, and the correct answer is no — a Pine script has no way to know that. If the reply claims otherwise, or proposes a "confirmation" mechanism inside the script, treat everything else in that reply with more suspicion. The last section of this post explains why nothing in Pine can answer that question.

Match your symptom to the mechanism

Put the pieces above together — bar-by-bar execution, var/varip scoping, frozen alert snapshots, repainting — and most "my automation went wrong for no reason" reports map to one of a handful of mechanisms.

1
Getting duplicate orders on the same candle?
Frequency is set to All (alert.freq_all)That setting is meant to fire on every tick the condition holds, so several sends inside one bar are expected behavior. Once Per Bar is documented as "only the first call per realtime bar triggers the alert" — it caps you at one send per bar, and Once Per Bar Close waits for the confirmed close.
The alert comes from a strategy's order fillsOne bar can genuinely produce several order events — an entry plus a stop or take-profit exit filling on the same candle — and each fill sends its own message. Nothing is being duplicated; the bar really did generate multiple orders.
Your endpoint returned a 5xxTradingView resends the same notification 5 seconds later, up to 3 resends — a maximum of 4 sends for one trigger (status 504 is excluded from this). A receiver that accepts the payload but still returns 500 will get the same signal four times. Return 2xx as soon as you've accepted it and do the slow work asynchronously.
2
A signal you expected just never showed up?
Alert was recreated recentlyAny var/varip counter tracking "has this already fired" reset to zero when the new snapshot was created — that isn't broken, it's a fresh instance recalculating from history.
Script was edited after the alert was createdCompare the Condition shown in Alert Manager against your current script. A mismatch means the live alert is still running the old snapshot — delete and recreate it.
3
Live behavior doesn't match what the editor shows you right now?
Confirmed mismatchExpected, not a bug. Editing a chart script never propagates to alerts already created from it — see the snapshot section above.
4
Backtest looked clean, live signals are messier?
Condition contains repainting elementsSome repainting is expected in backtests but produces extra realtime-only triggers before a bar confirms. Switch to Once Per Bar Close and re-observe a few sessions.

Your pre-flight check, and the one thing no script can do

Before you point any of this at real money, walk this list. Every item is something you can verify yourself, in the browser or by asking the AI a direct question — none of it requires writing Pine:

  • Alert Frequency is set to Once Per Bar Close, not just Once Per Bar — this alone removes most repaint-driven duplicates.
  • Any condition touching close/high/low is gated on barstate.isconfirmed.
  • You know when the alert was last recreated, and can compare that timestamp against your last script edit.
  • A var/varip flag used as a "don't fire twice" guard is understood to reset on alert recreation — not just on chart refresh.
  • Your receiving/execution side keeps its own log of every payload actually received, since TradingView doesn't.

One thing Pine genuinely can't give you: confirmation that a webhook you sent was received and processed downstream. Both alert() and alertcondition() are fire-and-forget by design — TradingView's documentation describes frequency and triggering mechanics in detail, but nowhere states that the alert engine tracks or confirms what a receiving server did with the message. Whether the same signal got processed twice downstream isn't something the Pine runtime is built to know.

That's really an execution-side problem, not a Pine problem. TVSBot handles it by processing incoming signals asynchronously in the background and logging every one with its original payload and full processing detail, replayable per signal — so you can check whether TradingView sent something twice or a duplicate happened somewhere else in the chain.

To be clear about what that is: it makes duplicates visible after the fact, not impossible. Fixing unreliable alert logic itself still starts in the Pine script, with the checklist above.

FAQ

Why did my alert fire mid-bar, and then the condition disappeared?
Because Frequency is Once Per Bar (or Once Per Bar Close isn't enabled) while the condition checks a value like close or high that can still change before the bar closes — a form of repainting. The condition looks true mid-bar, fires, and then no longer holds once the final tick lands. Once Per Bar doesn't send twice for this; TradingView documents it as "only the first call per realtime bar triggers the alert." The problem is that the one send was based on a value that didn't survive the close. Setting Frequency to Once Per Bar Close, or gating on barstate.isconfirmed in the script, forces evaluation to wait for the confirmed close.
I edited my Pine script — why is the live alert still behaving the old way?
TradingView saves alerts as a snapshot of the script, inputs, and chart context at the moment you create them. Per TradingView's own FAQ, changes to the script or chart afterward "do not affect that created alert" — you have to delete it and create a new one for edits to take effect.
Does a var variable keep a counter safe permanently?
Only for as long as that specific script run — and, for a running alert, that specific alert snapshot — stays alive. It survives across historical and realtime bars within one run, but a script restart (recompiling after an input change, or deleting and recreating the alert) reinitializes var from scratch against the historical dataset.
The AI put varip in my strategy — should I be worried?
Not automatically, but it's worth asking what it's there for, because the two keywords differ in exactly one way. var updates only take effect on a bar's closing tick — a change made mid-bar on the still-forming realtime bar gets rolled back before the next tick. varip keeps whatever value it's set to immediately, tick by tick, with no rollback. On historical bars there's no visible difference, since every bar there executes only once. So varip in generated code is usually an attempt to carry state across ticks — ask what state, and what it's worth after the alert is recreated.
Can I know for sure TradingView delivered my webhook only once?
No — and in one documented case it explicitly doesn't. TradingView's own webhook documentation states that if your receiver returns a status between 500 and 599 (504 excepted), the notification is sent again after 5 seconds, up to 3 resends, for a maximum of 4 sends per trigger. There is no delivery-confirmation mechanism going the other way, and the Frequency settings only control how often a condition may trigger, not whether the receiving server processed the message once. Logging every payload you actually receive on your own execution side is the practical way to check this.

Get started

Ready to ship what you just learned?

TVSBot turns TradingView alerts into orders across 7 exchanges — non-custodial, your own API keys — with asynchronous signal processing and a full per-signal replay log so you can see exactly what was received.

Start free