Skip to content

v6.4.4 — Strategy correctness, security safety & numerical stability

Choose a tag to compare

@wallneradam wallneradam released this 04 May 17:17
· 498 commits to main since this release

What's new

A correctness-focused release covering strategy bookkeeping, request.security() safety, numerical stability in ta.*, and array drawing constructors. No behavior changes for scripts that were already correct under TradingView semantics — every fix closes a real divergence, race, or crash discovered by external auditors and live trading.

Special thanks to SPYHUNTER on the PyneSys Discord for the detailed bug reports and the precise reproduction hints that made several of these fixes possible. Reports like these are the single most valuable input we receive — keep them coming.

Strategy

Partial take-profit exits no longer collide

Two strategy.exit() calls targeting the same entry on the same bar would silently lose one of the two orders:

strategy.exit('TP1', 'Long', qty=1, limit=110)
strategy.exit('TP2', 'Long', qty=1, limit=120)

Position.exit_orders was keyed by from_entry alone, so TP2 evicted TP1 from both the dict and the orderbook. Multi-target take-profit strategies — a very common Pine pattern — were silently broken.

The exit-order cache is now keyed by the composite (exit_id, from_entry), so TP1 and TP2 coexist as distinct orders. Cancel-cascade behavior on close is preserved (now iterates by value to find all matching orders), and the from_entry=na fan-out path keeps working unchanged.

Regression tests cover both the partial-TP collision case and the previously-existing fan-out path.

strategy.cancel() now matches TradingView semantics

strategy.cancel(id) and strategy.cancel(id, from_entry=...) are now aligned with Pine's behavior for both exit ids and entry ids, including the case where Capital.com sends an exit update against a naturally-closed entry. Two regression tests cover cancel-by-exit-id and the no-cascade entry-id path.

request.security() safety

Strategy state is no longer accepted as a security expression

ps = request.security(syminfo.tickerid, '1D', strategy.position_size)

TradingView rejects this at compile time, and even the transitive form ps = strategy.position_size; request.security(..., ps) fails its data-flow analysis. PyneCore's security children re-run the full main() body in a child context where lib._script is None, so any chart-context strategy.* reference would crash.

Two layers of defense:

  1. Compile-time: SecurityTransformer raises SyntaxError when any of the 13 forbidden strategy.* state attributes appears directly as the expression argument of request.security() / request.security_lower_tf(). Direct references only — local-alias paths still pass.
  2. Runtime: All 13 @module_property accessors (strategy.position_size, position_avg_price, equity, openprofit, netprofit, closedtrades, wintrades, losstrades, eventrades, grossprofit, grossloss, max_drawdown, max_runup) return inert defaults (0.0 / 0) when called with lib._script is None, so any escaped alias path produces zeros instead of a crash.

Tuple-unpacked request.security() now emits tuple defaults

When a security read is bound to a tuple/list target:

high_d, low_d = request.security(syminfo.tickerid, '1D', [high, low])

the transformer now emits an arity-matching tuple of lib.na defaults for the not-yet-computed branch. Coverage added for tuple unpack, list targets, scalar assignment, star unpack, and conditional-expression fallbacks.

Shared-memory result access now lock-protected

Cross-context security result writes/reads were unsynchronized — under contention with mixed payload types this could surface torn reads. Per-slot result locks are now part of the security state, propagated to security processes and protocol helpers, and held around result writes, cross-context reads, and HTF NA writes. A concurrent stress test exercises mixed payload reads/writes.

Numerical stability

ta.variance / ta.stdev / ta.bb / ta.kc no longer drift on long series

The rolling-window variance was a direct sum-of-squared-deviations recomputation, which accumulates catastrophic cancellation over long bar counts and tight value ranges. It has been rewritten to a Welford/Pébay sliding-window update with Kahan compensation, and tiny negative round-off results are clamped to zero before sqrt(). BB/KC tolerances in the test suite were tightened where the new path is now demonstrably more stable, and relaxed where comparison reference data is itself less precise than the new implementation.

Arrays

Drawing constructors return typed arrays

array.new_box, array.new_line, array.new_label, and array.new_linefill previously returned bare lists, breaking type narrowing for downstream code that expected the typed array protocol. They now return properly-typed array objects, with empty / sized / initial-value behavior all covered by tests. The implementation is also significantly smaller (~140 lines deleted) — the typed array constructors already cover the cases the bespoke implementations were duplicating.