Skip to content

PyneCore v6.8.8 — Per-bar stateful ta builtins and broker restart resilience

Latest

Choose a tag to compare

@wallneradam wallneradam released this 18 Aug 17:29
· 8 commits to main since this release

TradingView compatibility

  • A stateful ta builtin variable is now evaluated once per bar, not once per reference site. Measured on TradingView: a read of ta.accdist, ta.nvi, ta.obv, ta.pvi, ta.pvt, ta.wad or ta.vwap inside an if returns exactly what an unconditional read returns on the same bar — Pine keeps one engine-level series per builtin variable, and only function call sites gate their own state. PyneCore evaluated every reference separately, so a gated read diverged. A new transformer replaces each bare read with a module-global cache and prepends one unconditional evaluation per referenced variable to main, giving the machine a single stable call site per bar.

    • ta.iii and ta.wvad are pure functions of the current bar and need no hoist.
    • ta.sar stays deliberately call-gated: measured, a gated ta.sar is na on TradingView for the entire run, which the per-call window already reproduces.
  • The hoist follows TradingView's measured scope rules. ta.nvi, ta.obv, ta.pvi, ta.pvt and ta.wad stay engine-global inside user functions too, so their reads are rewired everywhere in a script module. ta.vwap and ta.accdist get a per-instance machine inside a function and are rewired only directly in main's body. In a library module the dividing line is export, in both run modes: reads in main's body and in non-exported nested functions behave like in a script, while exported functions and a hand-written library's module-level functions keep their per-call-site gated machines.

  • ta.tr() inside a conditional branch measured the wrong previous close. It kept the previous close in its own per-call-site state, which advances per call, so a tr() sitting in an if compared against the last bar it happened to run on. TradingView reads close[1] there — a global series that advances on every bar — while the accumulating rma of ta.atr() correctly stays per-call-site and call-gated. The runner now publishes that one-deep window and tr() carries no state of its own.

  • array.every() returns false for an empty array instead of Python's vacuously true all([]), matching TradingView. array.some() already agreed at false.

  • Partial date strings are accepted, the ones TradingView fills in with the start of the period: "2025", "2025-06" together with its "2025-6", "2025/06" and "2025.06" spellings, and "Jan 2025" / "January 2025". A leading month ("06 2025") keeps raising, because TradingView rejects it too.

  • A library file run directly as the script executed its main twice per bar. Such a module registers its main as a library entry as well as the script entry; the duplicate run is now suppressed. It was masked until now by within-bar idempotent updaters like ta.nvi and ta.obv.

Runtime

  • A Series parameter of main is no longer value-passed into a nested function. The closure collector recorded type annotations only for annotated assignments, so a Series[T] parameter — what an input(close, ...) source compiles to — was invisible to the series-closure rule. The inner function received it by value and built its own per-call history, so src[i] there raised 'float' object is not subscriptable instead of reading the parent's history through the scope chain. Parameter annotations are now collected the same way, so plain-Series and Persistent parameters both follow the existing rules. A s: Series[T] declared in main's body already worked.

Broker

  • A pyramid parent consumed by its own bracket fill is retired. The FIFO-cleanup's "own entry" exemption skipped every fill labelled with the parent's own Pine id, but a position-attached bracket labels the TP/SL fill exactly that way. A non-flat pyramid parent close therefore left the consumed level's entry and exit intents active, and the next same-name phase diffed the exit as a modify, which the plugin rejects with "no confirmed entry row". Closing legs (TP/SL/TRAILING/CLOSE) are now exempt from the guard; a same-id flatten-and-reopen stays protected by the surviving-open-trades check.

  • A rejected exit modify over a parent that holds no open trade no longer kills the run. The venue has already closed what the replacement bracket would protect, so the stale tracking is retired and the run continues. A reject over a parent that still has an open trade keeps raising — swallowing that would leave real exposure unguarded.

  • Replayed partial bracket legs whose parent was never dispatched in this run are dropped, not fatal. A cross-cycle restart can replay engine-trigger partial leg rows whose parent entry belongs to a previous run (entry ids are re-used over a shared store). The stale-parent adoption branch cancelled the legs correctly but still fell through to the fresh bracket dispatch, where the no-parent guard raised a raw RuntimeError and aborted the whole sync loop. Now the legs are cancelled, the reconstructed exit slot is dropped and the dispatch is skipped entirely — there is no position to protect. At the dispatch site the no-parent refusal became an OrderSkippedByPlugin soft-skip (reason partial_bracket_parent_untracked), so a parentless bracket intent drops per intent instead of taking down the run, and the parent reference is resolved before the envelope build so the skip leaves no half-registered envelope behind.

  • Native fail-safe lifecycle events are logged unconditionally. State transitions, DEGRADED entry-gate engagement and ownership -> UNKNOWN external-edit detection now emit a broker-log warning in addition to the structured event sink. Live runs install no sink, so a degraded fail-safe was previously only visible through the secondary per-signal entry-block warnings.