Skip to content

Sparse ta.atr() uses previous call-site execution close for true range #178

Closed

Description

@awaizkazi1

Summary

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.

Environment

  • PineForge 0.1.20
  • TradingView Pine Script v6
  • BTCUSDT, Binance, 1h

Activity

  1. luisleo526 commented on Sep 5, 2026

    @luisleo526
    Collaborator

    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.

  2. luisleo526 commented on Sep 5, 2026

    @luisleo526
    Collaborator

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions