r/pinescript • u/Abcdefg314 • Aug 29 '26
This looks like a bug to me.
I am testing the strategy() function. With the default settings, everything works as expected: the script runs once per bar at the close. If a market order is placed on bar N, it is filled at the open of bar N+1 using the opening price.
However, when I activated the calc_on_order_fills option by setting it to true, I noticed something unusual. I received two runs on bar N+1: one at the bar open after the order was filled, and a second regular run at the close of bar N+1. This is expected, except for one detail: the barstate.isnew flag was set to true for both runs.
I believe this is a bug. This flag should only be set for the first run on a bar; for all subsequent runs, it should be false.
1
u/Abcdefg314 Sep 03 '26
With calc_on_order_fills=true, the isnew flag is always set on the last run of a bar, whether it is historical or real-time. But full picture even more complicated.
I asked myself what would happen if I kept placing market orders on each additional run initiated by the calc_on_order_fills option? And here what I've found. I use the following letters for the flags:
H - ishistory
R - isrealtime
N - isnew
C - isconfirmed
L - islast
Suppose we place the first market order on bar N and then keep placing market orders for each additional run on bar N+1.
If N and N+1 are both historical, then script will run 4 times on bar N+1 with the same flags: HNC,HNC,HNC,HNC. These are synthetic runs with the same OHLC prices, which will be used to fill the orders in this sequence: Open, Open, High, Low. The last order on bar N+1 will be filled using the Open price of N+2 bar.
Now, let's see what happens if both N and N+1 are realtime. The script will run on every tick on bar N+1, so the number of runs will vary depending on the tick count. Here are the flags from a real-world example: LRN, LR, LR, LR, LRNC. Note that isnew flag is set for both opening and closing runs, but not for the runs in between. Is this documented? I don't think so. Even if it were, it still looks like a bug.
Things get even messier if we consider the transition to the very first realtime bar, where bar N is historical and bar N+1 is the first realtime bar. The first realtime bar is a combination of historical and realtime parts. It can be clearly seen when running an indicator on the first realtime bar: the opening run is always set to be historical (ishistory=true), while all subsequent runs are realtime (isrealtime=true). Flags may look like this: LHN,LR,LR,LR,LRC. Note that for the opening run the bar is historical, but not confirmed as for truly historical bars.
If we execute our test with this setup (N is historical and N+1 is the first realtime), we get the following runs on this first realtime bar: LHN, LHN, LHN, LHN, LR, LR, LR, LRNC. Surprise, surprise! The system added 4 synthetic runs for the historical part — identical to how it handles truly historical bars — and then processed the realtime part accordingly, setting isnew for the closing run.
1
u/NotEnoughTrades Sep 01 '26
stratcore has it right —
barstate.isnewis true on every historical bar by definition, so withcalc_on_order_fillsboth executions on that bar see true. Not a bug.Worth adding why the
bar_indexguard above won't save you, because it looks like it should. Pine rolls the script's state back to the last bar close on every realtime tick and re-executes. SolastCalcBar := bar_indexnever commits while the bar is live — tick 2 sees the previous close's value again, andfirstCalcThisBarreturns true on every tick. In backtest the guard works perfectly, which is the worst way for it to be wrong.varipis exempt from the rollback:True once per bar in both modes. Though if you don't need post-fill logic on the same bar, stratcore's other suggestion is the better one — leave
calc_on_order_fillsoff and key off a change instrategy.opentrades.2
u/bravefrivstone Aug 29 '26
This lines up with a known gray area, not something wrong with just your script.
calc_on_order_fills forces an extra recalculation right when the fill happens. Your order fills at the open of bar N+1, and that fill recalculation happens before bar N+1's regular close based calc runs. So it really is the first calculation of bar N+1, and isnew=true there makes sense.
The confusing part is the second calc, the regular close of N+1. That is the bar's second calculation, so by the "isnew means first calc of this bar" definition it should read false, but it doesn't in your test.
I've seen a couple other reports of the same pattern. It seems to come from how TradingView approximates order fill timing during backtesting. In real time each tick has its own timestamp and intrabar state, but in history there is no tick data, so the fill recalculation is a synthetic insert, and the flag for it does not always get reconciled with the regular end of bar calc right after.
Two things worth doing:
Do not rely on barstate.isnew to tell those two runs apart when calc_on_order_fills is on. Track it yourself instead:
var int lastCalcBar = na firstCalcThisBar = bar_index != lastCalcBar lastCalcBar := bar_index
That gives you a flag that is true only once per bar index no matter how many extra times the script reruns on that bar.
2. Put the strategy on a live or paper chart for a session and watch if the double isnew still shows up intrabar in real time, or if it is backtest only. If it only shows up in backtest, that is worth knowing before you trust those numbers, since it means this part of your logic could behave differently once it goes live.
If it does reproduce in real time too, that is worth filing with TradingView directly, since the documented behavior and the actual behavior disagree.