Interpreter: var persistence, series history, and stateful functions#49
Interpreter: var persistence, series history, and stateful functions#49FellowTraveler wants to merge 1 commit into
Conversation
|
Pushed one follow-up commit (02188cf): |
…tions Implements TradingView Pine semantics for variable state across bars: - var/varip declarations initialize only the FIRST time execution reaches them (once ever, not per bar or per loop iteration), tracked by declaration execution (var_decls_initialized) rather than name existence so 'var close = 10' can shadow a host-injected builtin. New AST field is_var_persistent distinguishes 'var x = e' from plain 'x = e' (re-evaluated every bar); parser threads it and AST JSON snapshots are regenerated. - user_series_history: per-variable history enabling Pine's name[n] lookback on user-computed series. Plain declarations push their old value on re-execution; := reassignment of a var-persistent variable pushes BEFORE evaluating the RHS so acc[1] inside the RHS sees the previous bar's committed value. Variables without tracked history fall through to the existing Series historical provider (builtin series like close[1] behave exactly as before). - Pine functions are stateful: all function-local variables persist across calls (function_local_state), so a 'var' counter inside a function accumulates across bars; locals are restored per function and parameters are always freshly bound. Method-local var declarations survive method calls the same way. Tests: multi-bar state tests (var persists across bars; history pushed before RHS eval; function-local var accumulates across calls), new var_init_once_in_loop.pine pinning once-ever init inside a loop, and nested_while.pine's inner counter made a plain declaration to preserve that test's intent under the new (correct) var semantics.
02188cf to
a46bbc0
Compare
|
Following up on the "happy to split" offer in the original description: I've split this PR into three independent pieces so each can be reviewed on its own — the original 72-file diff was mostly regenerated AST snapshots and made the substantive changes harder to see.
No code changed in the split — the commits were re-partitioned as-is, and the three branches combined reproduce the previous head ( |
|
Hey @FellowTraveler. I did not get notified for the contributions. I will take a look at them. Thank you very much! :) |
Summary
This PR closes the gap between pinecone and TradingView's cross-bar state model, found while running real trading strategies through pinecone bar-by-bar and validated against TradingView's own strategy-tester trade list.
Heads-up on the diff shape: ~60 of the changed files are regenerated AST JSON snapshots in
crates/pine-parser/testdata/— the only change in each is the newis_var_persistentfield. The hand-written changes are confined topine-ast,pine-parser,pine-interpreter, and the tests.feat(interpreter): var persistence, series history, and stateful functionsThree related pieces of TV's cross-bar state model:
var/varipinitialize once. The initializer runs only the first time execution reaches the declaration — once ever, not per bar or per loop iteration (varin the reference). Tracked by declaration execution rather than name existence, sovar close = 10correctly shadows a host-injected builtin series. A new AST fieldis_var_persistentdistinguishesvar x = efrom plainx = e(which re-evaluates every bar); the parser threads it and the AST JSON snapshots are regenerated (the only snapshot change is the new field).name[n]lookback now works on user-computed series via a per-variable history. Plain declarations push their previous value on re-execution;:=reassignment of avarvariable pushes before evaluating the RHS, soacc := acc[1] + 1sees the previous bar's committed value — this ordering was the single root cause of a systematic one-bar lag in every recursive series (Heikin-Ashi etc.) when we compared against TV. Variables without tracked history fall through to the existingSerieshistorical provider, so builtin series indexing (close[1]) behaves exactly as before.varcounter inside a function accumulates across bars ando[1]inside a function sees the previous bar's local value. Locals are kept per function; parameters are always freshly bound.Tests: three multi-bar tests in
tests/lib.rs(avarcounter persists across bars; history is pushed before RHS eval; a function-localvaraccumulates across calls) and a newtestdata/loops/var_init_once_in_loop.pinepinning once-ever initialization inside a loop.Note on
testdata/loops/nested_while.pineThat test used
var j = 0as the inner loop counter. Under correctvarsemantics the initializer runs once ever, sojstays 2 after the first outer iteration and the expected3(which encoded re-initialization per iteration) becomes unreachable. To preserve the test's intent — nested loop mechanics — the inner counter is now a plain declaration (j = 0), and the newvar_init_once_in_loop.pinedocuments the once-ever behavior explicitly with the same shape.Validation
Beyond the tests in this PR, the combined change set (this PR plus #50 and #51) was validated externally: we run full Pine strategies through pinecone one bar at a time and diff the emitted entries/exits against TradingView's own strategy-tester CSV export for the same symbol/timeframe. With all of them in place, two real strategies match TV 100% — 56/56 signals on a 4H strategy and 21/21 on a daily strategy, including exit-reason order comments — over their full comparison windows.
Happy to squash or adjust any of this to fit the project's conventions.