Repository navigation
fix(codegen): resolve input-derived TA lengths and input.timeframe request.security tf - #29
Merged
Conversation
…quest.security tf
Two related resolution gaps where a structural argument derived from an input
(not a literal or direct input alias) silently degraded:
- TA constructor length that is arithmetic-of-input (e.g. wilderLen = rsiLen*2-1,
wp = sf*2-1) or a function parameter (f(src, len) => ta.sma(src, len)) fell back
to period 1, yielding an unsmoothed indicator. Now folded to the compile-time
value / expanded to the real runtime length expression; genuinely unresolvable
lengths raise a guardrail instead of silently emitting 1. Function per-call-site
TA clones get globally-unique member names so a helper reached via multiple
enclosing functions no longer emits duplicate member declarations.
- request.security timeframe backed by input.timeframe() (tf = input.timeframe("60"))
was emitted as a bare variable, uninitialized at evaluator-registration time, so
the HTF series degraded to the chart timeframe. Now resolved to its runtime input
value.
Validated against TradingView deep-backtest on the scraped strategy set
(BINANCE:ETHUSDT.P @ 15m): recovers the 3commas DCA cohort (tao/dual-rsi 0->100%,
bb-rsi 23->99%, sol 7->93%) and the QQE pair (qqe-trend 1.4->99.8%, qqe-channel
18->84%). Corpus parity intact: verify_corpus --all = 251 excellent / 1 documented
TV anomaly; the only two corpus strategies whose generated C++ changes
(mtf-htf-confluence-manual-trail / -static-bracket) move moderate/weak -> excellent,
realigning codegen with the committed validated output. Full pytest: 1372 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Sep 23, 2026
…ound spelling (C2 W2) Root cause. 6545234 (#29, first shipped in 0.8.1) added the TA-constructor guard in _emit_constructor: a ctor arg must fold to a literal or expand, through _runtime_ctor_arg_for_reset, to a runtime expression whose every identifier token is input-backed, a known constant or structural. Ctor args travel as Pine source spellings, and the token scan read an inline call's own argument text -- `input`, `int`, the title `fast` -- as unknown names, so the length was refused. Before that commit 0.7.5-0.8.0 "accepted" it by sizing both EMAs at 1 with no runtime reset (ta::EMA(1): fast == slow == close, a strategy that can never trade). 8390364 later let the bare word `input` through, which admitted argument-free spellings such as input(15) but not a title; and the analyzer's _expr_to_str dropped keyword arguments, so `input.int(defval=9, title="fast")` reached codegen as `input.int()` and silently became ta::EMA(get_input_int("", 0)). Fix: * pine_spelling.py: string-literal-aware helpers -- the Pine literal of a string, identifier substitution that leaves string contents alone, the extent of each inline input call, and a full spelling of an input call. * analyzer _expr_to_str spells an input call with its keyword arguments, escapes string literals and spells list literals (options=[...]). * codegen: an inline input call is admitted exactly when its bound spelling `v = <call>` would make v input-backed (_is_stable_inline_input: not a source input, constant defval; _input_default_value is now the one rule for both). The reset gate masks each admitted call to the bare word `input`; the placeholder, precalc and prepare_script_run sizes read its defval (_fold_inline_input_defaults), as a bound name does through _known_vars; derived-length tracking (`n = input.int(9, "fast") * 2`, `var n = input.int(9, "fast")`) spells it in full and counts it as an input reference; _expand_derived no longer rewrites a title that happens to spell a derived name. * The input manifest lists every global-scope input call, inline ones too, one entry per call site in source order, titled with the key the C++ reads the input by. The inline spelling now emits the bound spelling's C++ except for the bound twin's own member and its source-line call-site ids. A length that is otherwise a series (a bar field, bar_index, a source input) is still refused with the same diagnostic. Evidence: tests/test_e2e_inline_input_ta_length.py 79 passed (was 74 failed); all 312 corpus/validation generated.cpp re-transpile byte-identical; the manifest of two corpus probes gains the inputs their C++ already reads (analyzer-anvil-percent-costs-01: "Stop %", "Target %"; na-ledger-history-anchor-01: "Entry Deviation %"). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Sep 23, 2026
…the member and the manifest (C4 W4)
TradingView: "If not specified, the variable name is used as the input's
title" (Pine v6 reference, input.int title). The manifest and the member
followed that rule, but several getters re-derived the key from a
re-spelled call or from whichever name they were handed, so an override
set the member and never reached the TA size or the security feed:
- tf = input.timeframe("60") as a request.security timeframe registered the
evaluator with get_input_string("", ...) -- since 6545234 (#29), which
renders the bound call with no enclosing name;
- len = input.int(9) with a stable `len := ...` reassignment: the TA reset
read the folded `(cond ? 30 : input.int(9))` under the default's text "9"
from 8390364 on, under "" since C2's spelling;
- var n = input.int(9) * 2 (or math.max(input.int(9), 2), or a float
feeding ta.bb): the reset re-read the nested call under "" -- 097f499 (#54);
- a = input.int(9) then b = a: the reset read it under "b" -- since the
initial public release 5b92828.
One key per input, from the call node itself:
pine_spelling.input_binding_names() maps each global-scope input call a
declaration names -- the call bound straight to `v = input.*()` and any call
in a var initializer, the manifest's rule -- to that name.
_get_input_title() consults it before the name its caller passes, so the
member, the TA reset, a security timeframe and an alias's read all agree
with the manifest; and a call re-spelled for a derived length (codegen
_arith_expr_to_str, analyzer _expr_to_str) carries the name as title=, since
the re-parsed node has lost its declaration. The manifest walker moves to
pine_spelling.global_input_calls() so both share one definition of "global
scope".
E2E (tests/test_e2e_untitled_input_keys.py): each untitled shape runs next
to the same strategy with an explicit title, at the defaults and under the
same override keyed by each one's manifest title; the trades must be
identical and the override must move them. Before (on d0ae5be): 7 failed --
security_timeframe(_alias), stable_reassignment, var_nested(_call,_float),
alias -- each "override left the trades unchanged"; the direct binding,
input.source, a security length and a ta.change length through an alias
passed. After: 11 passed (e.g. security_timeframe override {"tf": "240"}
5508 trades == the "TF"-titled twin).
Re-transpile: no C++ or manifest change in the 312 probes, the corpus or
the 101 closed strategies -- none uses these shapes.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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.
Summary
Two related transpiler resolution gaps where a structural argument derived from an input (not a literal or direct input alias) silently degraded the generated indicator:
ta.*length that is arithmetic-of-input (wilderLen = rsiLen*2-1,wp = sf*2-1) or a function parameter (f(src, len) => ta.sma(src, len)) fell back to period 1 (unsmoothed). Now folded to the compile-time value / expanded to the real runtime length expression; genuinely unresolvable lengths raise a guardrail instead of silently emitting1. Function per-call-site TA clones now get globally-unique member names, so a helper reached via multiple enclosing functions no longer emits duplicate member declarations.input.timeframe()-backedrequest.securitytimeframe → bare/empty.tf = input.timeframe("60")thenrequest.security(sym, tf, …)emittedtfas a bare variable, uninitialized at evaluator-registration time, so the HTF series degraded to the chart timeframe. Now resolved to its runtime input value.Validation (TradingView deep-backtest, BINANCE:ETHUSDT.P @ 15m)
verify_corpustiers: excellent+strong 42→57 / 90.Corpus parity (hard gate) — intact
verify_corpus.py --all= 251 excellent / 1 documented TV anomaly, 0 weak/moderate/minimal.mtf-htf-confluence-manual-trail,…-static-bracket): both moderate/weak → excellent, realigning codegen with the committed validated output (HEAD had drifted). The other 252 are byte-identical.🤖 Generated with Claude Code