fix: evaluate conditionally-reached security ta.* lazily to match Pine short-circuit semantics - #110
Merged
Merged
Conversation
Pine evaluates `ta.*` LAZILY. A call sitting in an untaken ternary branch or
in a short-circuited `and`/`or` operand is not evaluated on that bar, so its
series state does not advance. `_emit_security_evaluators` hoisted EVERY TA
site collected from a `request.security` expression into an unconditional
prologue:
auto _secval_9 = slot_is_new ? _sec0__ta_ema_10.compute(bar.close) : ...
auto _secval_10 = ...
...
_req_sec_0 = (_secval_9 > _secval_10 && close > _secval_11) ? 1 : ...
so every site advanced on every HTF bar regardless of whether Pine reached
it. On the common MTF-trend shape
request.security(sym, tf,
(ta.ema(close,20) > ta.ema(close,50) and close > ta.ema(close,200)) ? 1
: (ta.ema(close,20) < ta.ema(close,50) and close < ta.ema(close,200)) ? -1 : 0)
the four conditionally-reached EMAs desynchronised against TradingView within
the first weeks and never recovered.
`_security_lazy_ta_keys` classifies each `(ta_index, binding_signature)` by
where Pine actually reaches it, and conditionally-reached sites are dropped
from the eager prologue. `_build_security_expr` already had an inline
`(security_series_slot_is_new(N) ? m.compute(a) : m.recompute(a))` fallback for
sites absent from `ta_results`; emitting there puts the advance inside the
expression, where C++'s own `&&` / `||` / `?:` short-circuit fires it on
exactly the bars Pine does. The relational lowering's `_pna_l` / `_pna_r`
temporaries keep operand evaluation left-to-right, and `recompute()` restores
the pre-advance snapshot before recomputing, so a slot first reached on a
later chart bar still advances exactly once.
The classifier is deliberately conservative — anything not provably
single-reach AND conditional keeps the eager hoist:
* A site reached more than once stays hoisted. Sharing one `_secval_*` is
what bounds it to a single advance per bar; two inline copies would
advance it twice. (Distinct helper call sites already get distinct TA
variant members, so each variant is classified on its own.)
* Laziness does not propagate through a global binding. In the requested
context that global is its own unconditional top-level statement and
evaluates on every HTF bar however it is read — this is what keeps
`t0`-shaped chart code exact.
* History-offset sites (`ta.ema(close,55)[1]`) keep the committed
`_secval_*` their per-bar Series push reads.
* Securities that rebind mutable globals, and multi-statement helper calls,
bail entirely: both lower through statement emitters that consume
`ta_results` outside this expression.
The chart-context path is untouched — it never hoisted, so its `?:` / `&&`
already short-circuit the compute inline.
Evidence: on mylivingedge-mylivingedgefx-panel the regenerated evaluator is
semantically identical to a hand-patched lazy reference and produces a
byte-identical tape; against the TV deep-backtest tape (1432 entries, 13
months, +8h) it goes from 1236/1432 matched with count delta +165 to
1432/1432 with count delta 0. Regenerating all 416 standard-corpus slugs
old-vs-new: 413 byte-identical (including every one of the 51 other slugs
with conditional CHART ta.*, and security-only slugs such as
thulashimohanr-prev-day-week-levels-or-vwap-strategy); the 3 that change are
the only ones with ta.* reachable only through a security-expression
conditional. The two besides mylivingedge were re-run end to end and their
tapes are byte-identical to the pre-fix tapes (heneralmomo25-selda-97ma
19/19, remarkablefreddy-scale-strat-trade-plan-wedge-trendlines 8396/8396).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gate
pr-gatePASS — engine corpus zero regressions, scraped entering excellent+strong >= leaving.Verdict:
/Users/haoliangwen/code/pineforge-lab/pr-gate-verdict.json🤖 Generated with Claude Code