You did the thing everyone told you to do. The entry condition is wrapped in barstate.isconfirmed, the alert is set to Once Per Bar Close, and the signal arrow no longer jumps around on the right edge of the chart for non-repainting strategy, Then you check the account: two fills, eight seconds apart, on one crossover.
Table of Contents
- barstate.isconfirmed does nothing in a default non-repainting strategy
- Which alert triggers can your guard actually reach?
- Why does one signal produce two fills?
- The explicit reversal sends two orders
- The single-entry reversal sends one
- Brackets and calc_on_order_fills add more
- What in the Properties tab overrides your code?
- How does the Create Alert dropdown double your signal?
- Where does the guard fail outright rather than idle?
- Repainting and double-firing are two different bugs
- How does the bridge decide whether two alerts become two positions?
- Two templates, two stacking defaults
- Two flags that block the repeat
- What the risk settings can’t catch
- How do you find which stage sent the second order?
- Fix it in this order
- Frequently Asked Questions
- Test it somewhere it can’t cost you
So the guard didn’t work? It worked exactly as designed. The uncomfortable part is that in a default strategy it was never doing anything at all. The thing that sent your second webhook lives in a part of the pipeline Pine Script can’t reach.
This is the article for people who already applied the fix. Still stuck on the earlier question of why a signal repaints mid-bar? Start with our guide to repainting versus non-repainting indicators and come back.
Key Takeaways
- Strategies run once per closed bar by default, so
barstate.isconfirmedis true on 100% of executions.- Order fill alerts ignore every script execution setting. No Pine guard reaches them.
strategy.close()plusstrategy.entry()is two orders, two fills, two webhooks.- Your alert JSON, not Pine, decides whether a repeat alert stacks into a doubled position.
barstate.isconfirmed does nothing in a default non-repainting strategy
A default strategy executes your code exactly once per bar, on the closing tick, so your guard is checking a variable that’s true 100% of the time. A non-repainting strategy is one whose signals, once printed, never change on a closed bar, and a plain strategy() script already behaves that way out of the box. Nothing you add changes that until you change a setting.
barstate.isconfirmed is a built-in boolean that returns true on all historical bars and on the last, closing update of a realtime bar. Read that against the default execution model and the arithmetic falls out. The script only ever runs on the closing update, so the guard never gets a chance to say no. if myCondition and barstate.isconfirmed is just if myCondition wearing a costume.
What we found: the guard is not wrong. It’s inert. Adding it to a default strategy and watching the duplicates continue is not evidence that the fix failed. It’s evidence that the duplicate was never coming from mid-bar evaluation.

The guard earns its keep in exactly two situations: an indicator() script, which genuinely does run once per tick on the realtime bar, and a strategy where somebody has ticked On realtime bar tick in the settings. Outside those two, you’ve added a line that costs nothing and does nothing. Is that harmful? Not at all. It’s just not the fix.
That’s also why copying the pattern from an indicator tutorial into a strategy causes so much confusion. The advice is sound; the context isn’t. We’ve covered the wider family of bar-state tricks, and where each one belongs, in the barstate.islast pattern and its traps.
So if the guard isn’t the problem, what is? Follow the second order backwards. Something generated an event, something turned that event into an HTTP request, and something turned that request into a position. Three stages, and only the first is written in Pine.
Which alert triggers can your guard actually reach?
A script alert built on a strategy can fire on three different things: alert() function calls, order fill events, or both. Your Pine guard has authority over exactly one of them. That split is the most useful thing to understand about duplicate orders, and it rarely gets drawn explicitly.
An order fill event is any event the broker emulator generates that causes a simulated order to execute. It’s the software equivalent of a broker confirming a trade. Once your script hands an order to the emulator, the emulator owns it: the script isn’t told when the order fills, and it can’t intervene. That’s exactly why alert() calls can’t be coded to trigger on fills.
Which leaves an awkward conclusion. Alerts from order fill events execute immediately, regardless of the script’s execution settings. Not once per bar close. Not when barstate.isconfirmed says so. Immediately, on the tick the fill happens, whatever your code contains.
So which path is your webhook on? If its body carries {{strategy.order.action}} and {{strategy.order.contracts}}, it’s the order-fill path, because fills are what supply those values. Our guide to setting up TradingView alerts for automated trading shows where that message gets pasted. Your guard is guarding a door nobody walks through.
There’s a second irony on top of that. In a strategy, alert() calls are forced to the alert.freq_once_per_bar_close frequency regardless of the freq argument you pass. The only exception is when tick-level calculation is on. So on the one trigger type your guard can reach, TradingView had already applied the same restriction for you.
Why does one signal produce two fills?
Two fills on one bar isn’t a malfunction. It’s what the code asked for. When we analyzed the order-placement commands against the fill events each one generates, the most ordinary-looking reversal in Pine turned out to produce two of them.
The explicit reversal sends two orders
Consider a reversal written the explicit way:
if longCondition
strategy.close("Short")
strategy.entry("Long", strategy.long, qty = 1)That’s two orders. Two orders fill separately, so the emulator generates two fill events. The alert fires twice, and your endpoint receives two POSTs. Nothing repainted, and nothing evaluated mid-bar. The script did precisely what those three lines told it to do, and the account has two fills to prove it.
The single-entry reversal sends one
Now the same reversal written the other way:
if longCondition
strategy.entry("Long", strategy.long, qty = 1)A single strategy.entry() in the opposite direction closes the open position and opens the new one as one order. One order, one fill, one webhook. The contract count arrives as the combined size, because the order has to flatten the old position before it can establish the new one. Ever seen a quantity of 2 on an alert when your size is 1? That’s why. Which placeholder you put in quantity changes what the bridge receives, and we compare the options in position size versus market position placeholders.
Brackets and calc_on_order_fills add more
Brackets add a third shape. An entry fills at the open, price runs to the take-profit inside the same bar, and the exit fills too. That’s two fill events on one bar from a script with one entry condition. The behaviour is perfectly correct. You still get two webhooks, and the bridge has to work out whether the second is a new trade or a close.
One more multiplier is worth knowing. calc_on_order_fills is off by default. When it’s on, the script runs an extra time on every tick where an order fills, and it can place new orders during that run. Turn it on with a condition that’s still true and you’ve built a small machine for generating orders.
What in the Properties tab overrides your code?
Four settings in the Properties tab can override your strategy() declaration, and the override wins every time. So before you edit a single line, open the script’s Settings and read that tab. Whoever last touched the chart chose those values. That includes you, six months ago, debugging something.
| Properties setting | Code parameter | Default | What changes when it’s on |
|---|---|---|---|
| On realtime bar tick | calc_on_every_tick | off | Script runs on every incoming tick of the open bar |
| On order fill | calc_on_order_fills | off | Extra execution on each fill tick; new orders can be placed there |
| On history bar tick | calc_on_every_history_tick | off | Historical bars are re-run tick by tick to match realtime behaviour |
| Order execution delay: None | process_orders_on_close | one tick | Orders created on the close can fill on that same tick |
The first row is the one that matters here. Tick On realtime bar tick and your strategy starts behaving like an indicator on the open bar, evaluating on every price update. That’s the moment barstate.isconfirmed stops being decorative and starts being load-bearing. It’s also the moment a condition that flips true, false, then true again can hand the emulator more than one order. Guess which checkbox gets ticked during debugging and never unticked?

Changing these settings can also introduce repainting in the strategy’s own calculations and results. Historical and realtime bars carry different levels of intrabar detail, so the two stop behaving alike. That touches order placement, alerts, and anything declared with varip. The checkbox that makes a forward test feel more responsive is the same one that makes it stop matching the backtest.
Two practical notes. These settings live with the saved chart layout rather than the published script, so the same strategy can behave differently on two of your own tabs. And when you add a published strategy, you get the defaults written in its code, which aren’t necessarily the settings its author tested with. Our rundown of what breaks when Pine v6 hits live automation covers the version-upgrade half of this problem.
How does the Create Alert dropdown double your signal?
Create an alert on a strategy and TradingView asks what should trigger it. The Condition section offers three choices: order fills and alert() function calls, order fills only, or alert() function calls only. It’s easy to accept whatever’s preselected and never open that menu again.
Now picture a script that does both. It calls strategy.entry() on a crossover. Because some tutorial suggested it, it also calls alert() on the same crossover to push a message to Discord. Leave the condition on “order fills and alert() function calls” and one crossover produces two alert events: the alert() call at the bar’s close and the fill at the next bar’s open. One condition, two outbound requests moments apart, and both look completely legitimate in the log.
The other version is even simpler: two saved alerts on the same script. Alerts survive script edits. You tweak the strategy, create a fresh alert to test it, and the original is still sitting in the alert manager, still pointed at the same webhook URL. Nothing in TradingView warns you that two alerts share a destination. When did you last open the alert manager and read the whole list?
Driving one endpoint from several indicators is its own coordination problem, and we walk through that setup in combining two indicators into one alert.
Where does the guard fail outright rather than idle?
There’s one context where barstate.isconfirmed isn’t inert. It’s broken: inside a request.security() call, where the variable simply doesn’t work. Move it outside the call and it works again, but it now describes the chart’s bar, not the timeframe you requested.

That matters for any multi-timeframe setup. Say you’re on a 5-minute chart pulling a 1-hour trend filter. You add a confirmation check, expecting it to wait for the hourly bar to close. What you get is the 5-minute bar’s state, which confirms twelve times an hour while the hourly value keeps moving underneath it. The filter you thought was gating the signal is releasing it on every chart bar.
The fix isn’t a better guard. It’s requesting a value that’s already settled, an offset series rather than a live one, so there’s nothing left to confirm. That’s a different technique with a different failure mode, and it belongs with the rest of the common Pine Script mistakes that break live strategies.
Repainting and double-firing are two different bugs
Repainting and double-firing produce the same complaint, an unexpected trade, from two different causes. They get conflated all the time. That’s how a repainting fix ends up applied to a duplication problem, and why automation then looks broken.
Repainting is a history problem: a value or signal changes its appearance on a bar that has already closed, so what you backtest isn’t what you traded. Double-firing is a count problem. The signal itself is stable and correct, and the pipeline simply emitted it more than once.
The two overlap in one direction only. A condition that recalculates mid-bar can produce both symptoms, which is where the confusion starts. But a perfectly stable, confirmed-bar-only signal can still arrive at your broker twice. No amount of extra confirmation logic touches that, because the duplication happened after the signal was final.
Diagnose them differently. Did the arrow move, or did it stay put? If it moved, it’s repainting, so check your data sources and your confirmation logic. If the arrow stayed put and the fill count is wrong, it’s duplication, so check the three stages after Pine. Our guide to TradingView alert automation covers the setup side of that pipeline end to end.
How does the bridge decide whether two alerts become two positions?
Two webhooks are not automatically two positions. The JSON flags in the alert body decide what the second request does, and the template you copied chose those flags for you. So what does the second request actually do when it lands?
Two templates, two stacking defaults
The default depends on which template you started from. When we compared PickMyTrade’s documented pairings, we discovered they ship opposite stacking defaults. The order pair sends {{strategy.order.action}} and {{strategy.order.contracts}} with "pyramid": true, so a repeat buy stacks on the first. The position pair in the Rithmic JSON guide sends {{strategy.market_position}} and {{strategy.market_position_size}} instead, and ships with "pyramid": false, "reverse_order_close": true and "duplicate_position_allow": true.
Neither default is a mistake. Each is paired with a different set of placeholders. What matters is that the second request inherits whatever flags your alert body carries, so a template you copied months ago decides how today’s double-fire lands. Open the alert, read the flags, and match them to the rows in the chart. Which pairing are you actually on?

Two flags that block the repeat
Two flags exist specifically to stop a repeat. duplicate_position_allow is the one the Rithmic guide documents: set it to false and duplicate trades in the same direction are blocked. Both of that guide’s templates ship it as true, so blocking is opt-in. The general JSON reference documents a second flag, same_direction_ignore. It blocks a same-direction alert while an order or position is already active, and still lets the first opposite signal through.
The trade-off is real, so decide deliberately. Either filter also blocks genuine scaling-in, because the bridge can’t tell a deliberate second entry from an accidental one. If your strategy legitimately pyramids, fix the duplicate upstream in Pine rather than filtering it downstream. For one-position-at-a-time logic, a duplicate filter turns a code bug into a logged rejection instead of a doubled position.
reverse_order_close rounds this out. On an opposite signal, true closes the open position and opens the reverse one, while false only closes it. Indicator alerts lean on this because an indicator has no idea what you’re holding. For example, a LuxAlgo-style buy signal doesn’t know you’re short, so the bridge has to flatten first.
What the risk settings can’t catch
One honest limitation. The account risk settings cover Daily Loss, Daily Profit, Weekly Loss and Weekly Profit. There’s no maximum-trades-per-day counter and no cooldown timer between orders, so a rate limit can’t catch a duplicate. Deduplication has to happen by direction or not at all.
How do you find which stage sent the second order?
It takes about five minutes to find which of the three stages produced the extra order, and you need that answer before you can fix anything. The instinct is to start rewriting Pine. Start by counting instead.
- Open the TradingView alert log and read the Webhook status column. It records delivery for each request. Two rows for one bar means TradingView sent two; one row means the duplication happened downstream, at the bridge or the broker.
- Count the active alerts pointed at that webhook URL. Open the alert manager and look for a second alert on the same script. It’s the fastest check. Do it first.
- Open the Create Alert dialog on the live alert and read the Condition section. If it says order fills and
alert()function calls, and your script contains both, that’s your duplicate. - Open Settings, then the Properties tab, and read the Script execution checkboxes. On realtime bar tick and On order fill both change how many orders get placed, whatever the code says.
- Check the bridge’s own alert log for two received alerts versus one. Two received means the requests genuinely arrived twice. One received with two fills points at the broker or the account.
Only after all five of those checks come back clean should you open the Pine editor and start reading code. When you do, the question isn’t “where do I add a guard.” It’s “how many orders does this block actually place?” Count the strategy.* calls inside the if, not the number of conditions.
One note on delivery. TradingView cancels a webhook request if the receiving server takes longer than three seconds, and it only sends to ports 80 and 443. A cancelled request is a missing order, not a doubled one. But a trader who sees a failure in the log may re-fire manually, and that’s a duplicate with an entirely human cause. If failures are what you’re seeing, our write-ups on webhook 403 and 401 errors and allowlisting TradingView’s four webhook IPs cover the usual causes.
Fix it in this order
- Delete the duplicate alert, if there is one. Free, instant, no code.
- Set the Create Alert condition to order fills only, unless you actively need
alert()events. - Uncheck On realtime bar tick and On order fill in Properties unless you know why they’re on.
- Collapse
strategy.close()plusstrategy.entry()into a singlestrategy.entry()reversal. - Add a duplicate filter to the alert JSON as a backstop, if your logic never scales in.
Want the backstop without rebuilding your script? PickMyTrade routes TradingView alerts to Tradovate, Rithmic, Interactive Brokers, TradeStation and the major prop firms, with per-alert JSON control over stacking, reversals and duplicate handling. Plans run $50/month, $120 per quarter or $350 a year, and there’s a 7-day free trial with no card required. Wiring up a strategy for the first time? The TradingView automation overview is the place to start.
Frequently Asked Questions
Only when tick-level calculation is on. A strategy executes once per closed bar by default, so the variable is already true on 100% of runs. Enable On realtime bar tick in Properties and the guard starts to matter, because the script then evaluates on every incoming tick of the open bar.
No. Order fill alerts fire the moment the broker emulator fills an order, regardless of the script’s execution settings or the frequency you picked. A bar with 2 fills sends 2 alerts. The frequency control governs alert() calls only, so if your webhook carries {{strategy.order.action}}, it doesn’t apply.
Because a single strategy.entry() in the opposite direction closes the open position and opens the new one as one order. Flattening 1 contract and establishing 1 more is 2 contracts of work, so {{strategy.order.contracts}} reports 2. That’s a correct reversal, not a duplicate. Splitting it into two orders is what creates duplicates.
No retry behaviour is documented. Webhooks can occasionally fail to arrive, a request is cancelled if your server takes longer than 3 seconds, and the Webhook status column in the alert log is your delivery record. Treat each request as fire-and-forget, and confirm fills at the broker rather than assuming delivery.
Fix the script first if you can identify the cause, because a JSON filter hides the symptom without correcting the 2 orders your backtest still shows. Then add duplicate_position_allow: false or same_direction_ignore: true as a backstop, as long as your logic never scales in. Running both layers is what we’d suggest for a funded account.
Test it somewhere it can’t cost you
A duplicate order is cheap to reproduce and expensive to discover live. Run the strategy on a demo connection for ten signals, then count rows in three places: the TradingView alert log, the bridge’s alert log, and the broker’s order history. If the three numbers match, you’re clean. If they don’t, the stage where they diverge is your bug, and you now know which of the causes above lives there.
The pattern underneath all of this is worth keeping. Pine controls whether a signal exists, the alert configuration controls how many times that signal gets announced, and the bridge controls what it finally becomes. A guard written in the first stage can’t fix a fault in the second or third. That’s exactly why a genuinely non-repainting strategy can still hand you two fills on one bar.
Working through a first live deployment? Paper trading an automated strategy first and taking Pine Script to live trading both cover the handover in more depth. You can see which brokers and prop firms are supported on the supported prop firms page, or read more about the team behind the platform.
Disclaimer:
This content is for informational purposes only and does not constitute financial, investment, or trading advice. Trading and investing in financial markets involve risk, and it is possible to lose some or all of your capital. Always perform your own research and consult with a licensed financial advisor before making any trading decisions. The mention of any proprietary trading firms, brokers, does not constitute an endorsement or partnership. Ensure you understand all terms, conditions, and compliance requirements of the firms and platforms you use.
Also Checkout: Rithmic vs Tradovate vs CQG: Fees & Latency Measured (2026)
Connect your alerts with PickMyTrade — automated trade execution, no coding required. Start free →
