v6.4.4 — Strategy correctness, security safety & numerical stability
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:
- Compile-time:
SecurityTransformerraisesSyntaxErrorwhen any of the 13 forbiddenstrategy.*state attributes appears directly as the expression argument ofrequest.security()/request.security_lower_tf(). Direct references only — local-alias paths still pass. - Runtime: All 13
@module_propertyaccessors (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 withlib._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.