You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sparse ta.atr() uses previous call-site execution close for true range #178
PineForge 0.1.20 evaluates sparse ta.atr() true range using the close from the previous execution of the same call site rather than the previous chart bar's close.
This produces incorrect ATR values whenever a history-dependent ta.* call is inside a conditional branch that executes sparsely.
Minimal reproduction
The reproduction is also saved as .tmp-sparse-atr-investigation/minimal_sparse_atr.pine and is included inline here:
//@version=6
strategy("Sparse ATR previous-close repro", overlay=false, pyramiding=10, process_orders_on_close=true)
// ta.atr(3) is documented as RMA of true range. Keep true range global so
// close[1] means the previous chart bar, then advance both RMAs on the same
// sparse set of red-after-green bars.
chartTR = ta.tr(true)
reach = close[1] > open[1] and close < open
var float builtinSparseATR = na
var float explicitSparseRMA = na
if reach
builtinSparseATR := ta.atr(3)
explicitSparseRMA := ta.rma(chartTR, 3)
if not na(builtinSparseATR)
strategy.entry("BUILTIN_ATR", strategy.long, qty=builtinSparseATR)
if not na(explicitSparseRMA)
strategy.entry("EXPLICIT_RMA", strategy.long, qty=explicitSparseRMA)
strategy.close_all()
On identical synthetic input, the reproduction produces:
ta.atr(3) = 69
ta.rma(ta.tr(true), 3) = 3
The discrepancy occurs because the true-range calculation uses the previous call-site execution's close instead of the previous chart bar's close.
Production reproduction
On BTCUSDT 1h:
PineForge computed 1,161.483311
TradingView computed 519.788674
The values match the faulty PineForge mechanism to six decimal places.
Expected behavior
Sparse evaluation should control only when the RMA state advances. True range must always use the previous chart bar's close (close[1]), regardless of when the conditional call site last executed.
Impact
The incorrect ATR inflates volatility calculations and can change order-block assignments, confirmation counts, and generated BUY/SELL signals.
Thanks for the precise write-up — the mechanism you describe is real, and we checked it against TradingView directly rather than by reasoning about the docs.
What TradingView does. We exported a sensor strategy through TradingView's own strategy report (BINANCE:BTCUSDT, 1h, 2025-04-01 → 2025-07-01). On every bar where close[1] > open[1] and close < open (your reach) it calls ta.atr(3) inside the block and, on the same bar, emits four values as qty-encoded trades: B = ta.atr(3) inside the block; C = ta.rma(chartTR, 3) inside the block with chartTR = ta.tr(true) computed every bar (your model); A = ta.rma(trA, 3) with trA built from the close of the block's previous execution (PineForge's old model); T = ta.rma(ta.tr(true), 3) with ta.tr(true) itself inside the block. Over 398 sparse executions: B == C == T on 398/398, B == A on 0/398. A sparse ta.atr() advances its RMA only on the executions, but its true range always reads the chart's close[1] — exactly what you expected. Same for a sparse ta.tr().
What PineForge did.ta::ATR / ta::TR kept a private previous close updated only when compute() ran, and codegen calls compute() where the Pine call sits — model A. Your diagnosis is correct (we could not check your production numbers without the script, but the mechanism matches).
Fix — merged. Engine and codegen main now pass the previous chart bar's close into ATR/TR::compute(); the engine tracks it at every bar dispatch (and rolls it back with the calc_on_order_fills checkpoint, a case we found on a community script while verifying). Landed via the trade-for-trade parity gate (pineforge-engine #192, pineforge-codegen-oss #124). A release containing it is a separate step, so I can't give a date here.
Closing as fixed on main (pineforge-engine #192, pineforge-codegen-oss #124); it will be in the next release. Please reopen if the released build still disagrees with TradingView on your script.
Activity
engine: the chart-close tracker rolls back with the calc_on_order_fil…
luisleo526 commentedon Sep 5, 2026
Thanks for the precise write-up — the mechanism you describe is real, and we checked it against TradingView directly rather than by reasoning about the docs.
What TradingView does. We exported a sensor strategy through TradingView's own strategy report (BINANCE:BTCUSDT, 1h, 2025-04-01 → 2025-07-01). On every bar where
close[1] > open[1] and close < open(yourreach) it callsta.atr(3)inside the block and, on the same bar, emits four values as qty-encoded trades: B =ta.atr(3)inside the block; C =ta.rma(chartTR, 3)inside the block withchartTR = ta.tr(true)computed every bar (your model); A =ta.rma(trA, 3)withtrAbuilt from the close of the block's previous execution (PineForge's old model); T =ta.rma(ta.tr(true), 3)withta.tr(true)itself inside the block. Over 398 sparse executions: B == C == T on 398/398, B == A on 0/398. A sparseta.atr()advances its RMA only on the executions, but its true range always reads the chart'sclose[1]— exactly what you expected. Same for a sparseta.tr().What PineForge did.
ta::ATR/ta::TRkept a private previous close updated only whencompute()ran, and codegen callscompute()where the Pine call sits — model A. Your diagnosis is correct (we could not check your production numbers without the script, but the mechanism matches).Fix — merged. Engine and codegen
mainnow pass the previous chart bar's close intoATR/TR::compute(); the engine tracks it at every bar dispatch (and rolls it back with the calc_on_order_fills checkpoint, a case we found on a community script while verifying). Landed via the trade-for-trade parity gate (pineforge-engine #192, pineforge-codegen-oss #124). A release containing it is a separate step, so I can't give a date here.luisleo526 commentedon Sep 5, 2026
Closing as fixed on
main(pineforge-engine #192, pineforge-codegen-oss #124); it will be in the next release. Please reopen if the released build still disagrees with TradingView on your script.engine: ta.atr / ta.tr read the true range against the previous CHART…
engine: the chart-close tracker rolls back with the calc_on_order_fil…
tests: the chart-close tracker rollback with a calc_on_order_fills re…
build: pin the corpus regeneration to codegen 1.5.0 (52101f9b)
corpus: move the gitlink to e99bbd9d (generated.cpp from codegen 1.5.0)
build: pin the corpus regeneration to codegen 1.5.0 (52101f9b) and mo…