Skip to content

Releases: PyneSys/pynecore

PyneCore v6.5.6

Choose a tag to compare

@wallneradam wallneradam released this 08 Jul 15:58

This release focuses on TradingView parity: strategy margin-call and exit-leg sizing, session/date-string handling, request.* graceful degradation, cross-symbol request.security() correctness, and several series-history and rolling-ta.* fixes.

Strategy / broker emulator

  • Sub-lot margin calls now trim exactly one whole contract capped by the current position size (min(1.0, quantity)), both at the bar open and intrabar, matching TradingView (the Gaussian Channel corpus shows 43 such single-contract trims on 8+ contract positions).
  • strategy.exit qty/qty_percent legs bound to one entry are capped at the unreserved entry remainder (min(requested, init_size - sibling_reservations)), and create no order when that remainder is <= 0, matching TradingView's first-come-first-served leg reservation.
  • strategy.close/strategy.close_all(immediately=true) fills are deferred to after the script body: the position settles at the bar close but stays script-visible until the next bar, so position_size-gated plots/expressions after the close call no longer read the flat position one bar early. Exit price, P&L and cumulative stats are bit-identical; only the timing moves.
  • Below 1e7 order money, a market entry snaps up to the next lot when the raw money-tick count reaches the 0.05-tick floor cell (measured [1e8, 1.16e8] cost band); the snapped size still faces the ordinary creation-time margin check at the placement close.

request.security() / cross-symbol

  • Gap-compact a same-TF cross-symbol security child (e.g. TVC:US10Y@30 on a 24/7 BINANCE:BTCUSDT@30 chart): stateful indicators (ta.ema, ta.atr) inside the child no longer run over synthetic weekend/overnight fill bars, so indicator state freezes between real bars and forward-fills (gaps_off) / emits na (gaps_on) like TradingView.

Builtins / lib parity

  • label.set_textcolor keyword parameter renamed color -> textcolor (Pine v6 name); previously raised TypeError on the named call.
  • str.tostring second parameter renamed fmt -> format (Pine v6 name); previously named-argument calls (str.tostring(x, format=format.percent)) raised TypeError.
  • request.seed() returns na instead of raising (seed data lives in TradingView-hosted community repositories PyneCore cannot access), mirroring request.footprint().
  • request.financial() gains explicit named parameters and returns na when ignore_invalid_symbol is set, mirroring request.earnings().
  • string.format / str.tostring propagate na when the format pattern is na.
  • math.sum periodically resyncs its sliding window from the buffer so residual Kahan rounding no longer breaks TradingView-preserved identities (flat windows now sum exactly to n*v).
  • array.covariance uses the Welford online co-moment to match TradingView bit-for-bit on the unbiased path.

Sessions / date parsing

  • session.islastbar_regular/islastbar now fire on 24/7 markets: a session end at midnight (00:00 = 24:00) is rolled forward one day so the closing-bar window matches; strategies gating on it (e.g. a Friday-close hard exit) no longer strand positions.
  • A "0000-0000" session (start equals end, the input.session default) spans the full 24 hours: every bar on an allowed weekday now qualifies instead of being reported out of session (which previously placed zero trades).
  • parse_datestring accepts ISO datetimes without seconds ("2021-01-01 00:00", both space and T separators) and space-separated numeric dates ("05 12 2000" is May 12, month-first; day-first "31 1 2000" stays rejected), verified live on TradingView.

Series / transformers

  • Tuple-unpacked series variables (a, b = f()) now record per-bar history: previously the slot buffer never advanced and a[1] was permanently na.
  • A parent-scope series indexed only from inside a nested function is no longer pruned to a scalar (which crashed with 'float' object is not subscriptable); index usage propagates up the scope chain.
  • Rolling ta.* (sma, wma, cog, stdev, variance, bb, bbw, kc, correlation, change, roc) no longer collapse to na for length > 500: the indexed buffer grows with max_bars_back(series, length); length > 5000 now raises, matching Pine's own ceiling.
  • DynamicDefaultTransformer evaluates lib.*-referencing parameter defaults (e.g. source = hl2) per call rather than at def time, so anchored library call sites no longer freeze the first bar's value.

v6.5.5 — TradingView parity: strategy sizing, request.security multi-context, builtin compatibility

Choose a tag to compare

@wallneradam wallneradam released this 06 Jul 13:15

This release focuses on TradingView parity — closing strategy-sizing and request.security() multi-context edge cases — alongside several builtin/lib compatibility fixes and two robustness fixes that stop runaway CPU/memory.

Strategy / broker emulation

  • Same-direction market entries that clear their own placement-time 100%-equity check but over-margin at bar open only because an earlier same-bar entry already grew the position are now filled (both legs pass their own bar-start check, and the bar-open margin call trims the aggregate), matching TradingView. The sub-1e7 fill-margin tolerance was tightened from 7.5e-10 to 1e-11 so real overages are rejected while float noise still fills.
  • A default-sized (percent_of_equity / cash) price-based entry that re-resolves to a larger quantity at the fill price now has its no-qty bracket exit grow with it, so one exit closes the whole fill instead of stranding a sliver that surfaced as a spurious extra trade.

request.security() / multi-context correctness

  • timeframe.main_period now reports the chart's main-series timeframe inside a request.security() child. Previously it fell back to the child's own period, so a timeframe.change(timeframe.main_period) reset key (the canonical up/down-volume accumulator) reset every intrabar instead of once per chart bar.
  • Fixed three bugs blocking nested request.security() scripts: timeframe.main_period used as a security timeframe argument at module-exec time no longer crashes; string.match now translates Pine's \z regex escape to Python's \Z; and the security protocol is now injected into imported library modules, so a library that itself calls request.security() no longer raises NameError.
  • --security path resolution no longer mangles symbols containing a dot (e.g. a perpetual BINANCE:BTCUSDT.P); the dotted stem previously resolved to a non-existent .ohlcv file and aborted the run with "Security data not found".
  • last_bar_time is now anchored to the chart's final historical bar (Pine's whole-history-known semantics), with live runs still tracking the realtime bar. chart.left_visible_bar_time / right_visible_bar_time are rebased on it so right-edge viewport comparisons fire correctly.

Builtins / lib compatibility

  • array.max(id, nth) and array.min(id, nth) now accept Pine v6's optional 0-based nth rank (na elements ignored); array.max(id, 0) no longer raises a TypeError.
  • lib_math.sum (and through it ta.sma / ta.rsi / ta.mfi) now supports a varying series length (e.g. ta.sma(src, ta.barssince(cond))); a per-bar-changing window previously produced garbage. The constant-length O(1) fast path is unchanged.
  • Bare ta.vwap now works as the Pine built-in variable form (session-anchored VWAP of hlc3), not only the ta.vwap(source) function form.
  • request.footprint() now returns na (with a working not na(fp) guard and a new Footprint.total_volume() accessor) instead of raising, so footprint scripts that guard their reads keep running on data without tick-level order flow.

Robustness

  • Iterating an NA or a Series through the sequence protocol (str.join(na), list(na), tuple unpacking) no longer spins forever at 100% CPU: NA.__iter__ now raises loudly, SeriesImpl iterates newest-to-oldest and terminates, and string.repeat / string.replace guard na inputs.
  • An orphaned request.security() child process now self-terminates within ~2s when its parent is hard-killed (SIGKILL or a subprocess timeout). On macOS such an orphan previously spun at 100% CPU and pinned its OHLCV + interpreter memory, enough to exhaust RAM and freeze the host.
  • Volume is restored to feed precision on read via _round_volume, removing the ~1e-7 float32 dust that accumulated in every volume sum.

v6.5.4 — TradingView strategy parity: big-money sizing, margin calls, trailing stops, Heikin Ashi security

Choose a tag to compare

@wallneradam wallneradam released this 04 Jul 15:27

PyneCore v6.5.4 continues the TradingView backtest-parity work: the strategy broker emulator now reproduces TradingView's large-equity sizing, margin-call and trailing-stop behaviour, its two-ledger FIFO trade attribution and its fill-time default sizing. It also adds Heikin Ashi support to request.security(), honours calc_bars_count, fixes builtin method dispatch on na and compiled-script builtin resolution, and removes a macOS request.security() latency bottleneck.

Features

Heikin Ashi in request.security()

request.security(ticker.heikinashi(symbol), tf, expr) now returns expr evaluated on Heikin Ashi candles instead of raising NotImplementedError. The chart-type helper marks the ticker with an internal separator; the security machinery strips it to the base symbol and transforms the feed to Heikin Ashi bars once up front, so the existing request.security() child consumes the transformed feed unchanged. On a higher-timeframe context the bars are aggregated to the period first and then transformed, matching TradingView, which builds Heikin Ashi from the requested timeframe's candles. Backtest (file-backed) only: live streaming and lower-timeframe chart-type requests raise a clear error, and renko / pointfigure / kagi / linebreak remain unsupported (path-dependent, need tick data).

calc_bars_count

The runner now honours calc_bars_count, restricting calculation to the last N chart bars. Bars before that window advance bar_index (kept absolute) but feed no series, run no main, process no orders and emit no output; series start fresh at the first calculated bar while last_bar_index is unchanged. A value of 0, or one covering the whole history, calculates every bar.

Strategy / backtest parity

Large-equity sizing, margin calls and trailing-stop timing

Extends the backtest broker emulator to reproduce TradingView's exact behaviour at large equity and on trailing exits (verified on the BINANCE:BTCUSDT 30m corpus with entries, exits and plots all matching TradingView):

  • Big-money entry sizing and margin gate: from 1e7 account-currency units of order money upward, TradingView re-judges the floor-sized quantity through a tick-grid margin gate instead of a plain float comparison. This gate is wired into entry(), order(), deferred-quantity resolution and the creation-time and fill-time margin checks (the fill-time check switches to an integer-tick comparison at ≥1e7 equity as well).
  • Margin-call trigger and cover at ≥1e7 equity: fires on an integer-tick comparison of truncated equity against the required margin rounded to a tick — half-up below 1e10 margin ticks, nearest multiple of ten ticks above — and sizes the cover from the resulting tick-shadow shortfall; the sub-1e10 float path is unchanged.
  • Close-time margin check: TradingView evaluates margin at every bar close and books the liquidation on that bar at the close price. The position is now trimmed on the correct bar and price instead of one bar late at the next open.
  • Reversal nofill keeps its closing leg: a market entry whose big-money sizing judges nofill no longer cancels a reversal outright — the opposite position still closes at the next open while the opening leg stays suppressed and re-issues a bar later.
  • Trailing-stop two-phase intrabar walk: the walk is split so the open tick and the legs up to the second extreme run before the intrabar margin-call checkpoints and the closing (extreme → close) leg resumes after them, matching TradingView's chronology when a partial margin call at the adverse extreme precedes the trailing fill.
  • Trailing re-issue semantics: a re-issue with changed trailing parameters is a cancel+replace that drops the carried water mark and re-arms from the issue bar's close, while an identical re-issue still inherits the ratcheted stop. A fractional trail_offset tick count is truncated to whole ticks the way TradingView does.

Fill-time sizing, margin cancellation and closing leg

  • Default-sized (percent_of_equity / cash) price-based entry and order quantities now resolve at the actual fill price instead of the placement close, matching TradingView. A marketable limit filling at the open therefore uses equity/open, not equity/limit. Market entries keep placement-close sizing (rejected at the next open if unaffordable), so only limit/stop orders defer.
  • The creation-time margin check now extends to price-based entries, plus a per-bar sweep that cancels pending entries whose required margin at the current price exceeds equity. This reproduces TradingView's asymmetry where a resting buy limit below the market never opens at 100% percent_of_equity sizing, while a resting sell limit above it survives.
  • The intrabar walk gains its closing leg (low → close and high → close) so an exit activated mid-bar by its own entry fill can fill on the same bar at its trigger price.

Two-ledger FIFO trade close

TradingView keeps two independent ledgers: closed trade rows are attributed FIFO across the whole position, while each exit/close order still settles against its own from_entry quantity. The simulator previously only closed per-entry, so pyramided closes split trade rows and cancelled brackets incorrectly. A per-entry open-quantity ledger now drives the exit-order lifecycle: a bracket survives its entry's rows being consumed FIFO by another entry's close and is cancelled only once its own bound quantity is spent; a from_entry-bound exit leg stays dormant until its entry fills (no triggering, no OCA-sibling cancellation, no fill-cap counting while the entry is pending, cancelled or rejected); and strategy.close(id) sizes off the bound entry's open quantity instead of the whole position.

Fixes

Builtin method dispatch on na

A builtin method invoked on an na builtin object (e.g. naLine.delete() on a line that was never drawn) now dispatches to that builtin's namespace, which treats na as a no-op the way Pine does. An na of a UDT or untyped value matches nothing and falls through to user-method dispatch.

Compiled-script builtin resolution (__ren__ images)

Two runtime paths matched user-source names verbatim and missed PyneComp's canonical __ren__ renamed images, regressing previously-working compiled scripts:

  • BuiltinShadowTransformer keyed the alias-shadows-builtin fallback on the emitted alias name, so a compiled import ... as ta__ren__ no longer matched and non-exported members (ta.crossover) raised AttributeError. The __ren__ suffix is now stripped from the alias, and an unserved member retries the builtin registry with the bare name.
  • The overload dispatcher did not adapt bare Pine keyword arguments to a callee's canonically renamed parameter (position vs position__ren__), raising "No matching implementation". Such kwargs are now renamed before selection.

Performance

request.security() on macOS

macOS has no sem_timedwait, so a timed multiprocessing wait falls back to CPython's sem_trywait + select() polling emulation, adding roughly 20 ms of wake latency per wait. The per-bar chart↔security handshake used a 0.5 s liveness-poll timeout on every wait, so that quantization dominated security-context runs (over 80% of wall time). Child-death detection is moved out of the wait onto a daemon thread that blocks on the process sentinel (no polling), letting the per-bar waits become plain untimed waits. Output is byte-identical; security-context runs are several times faster.

v6.5.3 — TradingView-parity fixes: na handling, standard library, holiday-aware D/W/M aggregation

Choose a tag to compare

@wallneradam wallneradam released this 02 Jul 10:23

Patch release on the 6.5 line: TradingView-parity fixes across na handling, the array/color/ta standard library, holiday-aware D/W/M aggregation, and compiled-library method dispatch.

Fixes

  • Holiday-aware nD/nW/nM aggregation — Intraday→nD request.security / timeframe.change over-counted early-close holiday sessions on exchange-listed futures, shifting the multi-period grid relative to TradingView (NQ1! 15m→3D was off by one around Labor Day). Early closes are now detected from the data alone and folded into the adjacent day, matching TradingView's daily feed (#71).
  • int(na) returns nacast_int was the only cast helper not propagating na (it returned 0). Scripts deriving an array size or loop bound from an na-valued int() on early bars no longer crash; the loop now runs zero iterations, matching TradingView.
  • Array reductions ignore naarray.avg/sum/min/max/range/median/mode/variance/covariance/stdev now reduce over the non-na subset and return na only when no non-na element remains. covariance drops na index pairs pairwise, variance gains a len < 2 → 0.0 guard.
  • Array percentiles match TradingViewarray.percentile_linear_interpolation, array.percentile_nearest_rank and array.percentrank use TradingView's unified position formula and na handling (also correcting some no-na fractional-position results), and now return float | NA[float].
  • ta.wma / ta.cog / ta.linreg survive internal na gaps — a source with an internal na gap no longer poisons the rolling sum to na forever; the oldest window value is indexed past the gap. ta.hma is fixed transitively. Clean sources stay byte-identical.
  • color.from_gradient na handling — no longer crashes on an na endpoint; it keeps the solid endpoint's RGB and fades transparency toward the na side, returning na at the na endpoint, matching TradingView. color.t() now returns transparency (0–100) instead of the raw 0–255 alpha, and the color.new / from_gradient signatures widen to their Type | NA[Type] domain.
  • Color is hashable — a value-based __hash__ consistent with __eq__ lets Color be a UDT field default (compiled UDTs use dataclass with slots) and a valid dict key / set member.
  • Compiled-library method dispatch — runtime string method dispatch now retries the canonical rename suffix, so an untyped receiver reaches a library-exported method whose Pine name cannot live verbatim in Python; keyword arguments are remapped to the target's renamed parameters, leaving correct calls untouched.

v6.5.2

Choose a tag to compare

@wallneradam wallneradam released this 29 Jun 21:15

PyneCore 6.5.2 is a correctness release on the 6.5 line. It sharpens request.security / higher-timeframe accuracy, adds an opt-in effective-dated session-schedule history, fixes a PyPI/Linux install regression that broke stateful ta.* calls, and adds a request.security fast path.

request.security / higher-timeframe

  • Multi-day and intraday HTF feeds are aggregated to the security timeframe, so each period exposes its last confirmed value instead of the still-developing one (#70).
  • Session-gapped intraday request.security is corrected across the session break.
  • request.security_lower_tf collects intrabars from the bar's own period [T, T+tf), on intraday charts and on D/W/M, weekly and monthly charts.

Session schedules (new, opt-in)

  • SymInfo can carry an effective-dated session_schedules history. A backtest spanning a session-hours change (for example a night session end moving 23:30 -> 23:00) confirms each intraday HTF bar against the schedule in effect on its trading day, closing the residual divergence around such changes (#69). Symbols without a history are unaffected.

Strategy simulation

  • A same-bar reversal fills and trims the new leg instead of being rejected.
  • strategy.opentrades.size() returns 0.0 for a negative or out-of-range trade index instead of raising.

Standard library

  • map.get() returns na for a missing key instead of raising, matching TradingView.
  • ta.percentile_*, ta.percentrank and ta.rci grow their source buffer to fit history reads.
  • timeframe.in_seconds("") treats an empty timeframe string as the chart timeframe.
  • Added ta.max_bars_back.

Core / compiler

  • Stateful library calls (ta.sma and friends) isolate correctly on PyPI/Linux installs again; a non-editable install no longer misclassifies PyneCore as part of the standard library (#68).
  • Method and function state persists across per-bar redefinition on the uniform binding path.
  • Nested-function series subscripts resolve through the scope chain.
  • The import hook detects a single-line """@pyne""" module marker.
  • A leaked shared-memory block is recreated when the result block grows.

Performance

  • A plain-OHLCV request.security is served without re-running the script's main() on every bar.

v6.5.1

Choose a tag to compare

@wallneradam wallneradam released this 22 Jun 17:20

PyneCore 6.5.1 is a correctness and performance release on the 6.5 line, fixing TradingView-parity divergences across the strategy simulator, request.security, the standard library, time/timeframe handling and the compiler, with hot-path speedups on the drawing, array and overload code.

Strategy simulation

  • Same-bar close_all after a partial close no longer overshoots into a phantom opposite trade.
  • Multiple same-bar partial closes for one entry id now all fill instead of evicting each other.
  • A sub-lot margin-call shortfall closes a single contract, not the whole position.
  • immediately=True closes now book their cumulative stats correctly.
  • Added strategy.netprofit_percent, strategy.openprofit_percent and strategy.max_drawdown_percent.

request.security / higher-timeframe

  • Sparse single-period D/W/M feeds keep their gaps_off forward-fill instead of writing na.
  • HTF barmerge.lookahead_off confirms on the period's own last chart bar, not one bar late (#66).
  • Tuple request.security_lower_tf returns one array per element again.
  • The per-chart-bar LTF handshake is skipped before the feed begins (faster idle prefix).

Standard library

  • array.new_*(na) creates an empty array; empty-array reducers return na instead of raising.
  • Array methods dispatch on array.slice() (SequenceView) receivers.
  • str.contains(na, ...) no longer hangs; str.format_time(na) and out-of-range timestamp(...) follow Pine.
  • ta.correlation, ta.crossover and ta.crossunder stay valid across na gaps in the window.
  • An na series subscript resolves to the current bar (high[na] == high[0]), matching TradingView.

Time, timeframe and bar aggregation

  • Bars close at the trading-day end; daily bars anchor to the session open.
  • Multi-period D/W/M aggregation (2D, 3W...) is trading-day- and holiday-aware (#65).
  • last_bar_index stays fixed at the dataset tail on history, so last-N-bar guards work.

Compiler / transformers

  • Emitted builtin calls are shadow-proof against user names like len or getattr.
  • inline_series history is materialized every bar in short-circuited (ternary, and/or) positions.
  • Overload instance state (ta.*, Persistent) survives the per-bar swap in library mains.
  • A library alias shadowing a built-in namespace falls back to the built-in for unexported names.

Performance

  • O(1) identity-keyed drawing registries that enforce Pine's max_*_count caps.
  • Overload dispatch memoizes implementation selection per call shape.
  • pine_range returns a native range for integer for-loop bounds; array.get drops a runtime cast.

v6.5.0 — state-engine rewrite, strategy & request.security() TradingView parity

Choose a tag to compare

@wallneradam wallneradam released this 11 Jun 14:01

The largest release since the 6.4 line. The state engine has been rewritten end-to-end, the strategy simulator and request.security() reach trade-for-trade parity with TradingView on the previously diverging corpus strategies, and several Pine v6 surfaces are completed. 148 files changed.

State engine rewrite (slot-based per-instance state)

The module-global state scheme — mangled global names plus the FunctionType-keyed isolation cache — is replaced with compile-time slot layouts and runtime root state vectors:

  • Series / Persistent / varip variables now live in compile-assigned slots of their scope's state vector; reads resolve through the scope chain.
  • Call sites are classified at transform time into fast / direct / uniform routes; carrier status is a fixpoint over the module call graph.
  • Overload dispatch, Pine method dispatch and inline_series are anchored per call site, so a site whose argument types vary across bars keeps separate state per implementation.
  • Both runners (chart and request.security() children) drive every entry point — script main and registered library mains — through its own root vector, keyed per qualified function so library registrations can't collide.

Correctness fixes that fell out of the rewrite:

  • A str / bool persistent with a non-literal += no longer emits float arithmetic and crashes.
  • varip Kahan compensation is carried with its companion across var rollback, so the pair can't desynchronize.
  • Deterministic call routing, independent of sys.modules import order.
  • Stateful lib functions (fixnan, math.random, math.sum, timeframe.change) ported to @pyne submodules.

Module property routing

CallableModule and the hasattr-based runtime fallback are gone; module-property routing is now pure AST rewriting backed by an auto-generated, exhaustively tested registry (224 -> 1012 entries). Unknown names of known lib modules raise at transform time instead of failing at runtime, and a freshness test keeps the registry from going stale.

Performance

Per-bar hot-loop overhead cut in the runner and strategy engine: -18..-32% wall-clock on the benchmark strategies, -21% on the demo indicator. Behavior unchanged (suites pass, TV trades match bar-for-bar). Includes an idle fast path in process_orders, per-bar memoized time_tradingday, and a single-call timezone conversion in _set_lib_properties.

Strategy simulator — TradingView parity

Both previously diverging pyne-in-the-wild corpus strategies now match TV trade-for-trade.

  • Trailing stops rewritten as an intrabar O-H-L-C / O-L-H-C path walk: same-bar activation, ride and rebound fill at the extreme -/+ offset; hard-stop and limit legs reached earlier on the path win.
  • strategy.exit(from_entry=) covers every same-ID entry, recomputing the leg reservation on each (re-)issue instead of freezing the first size.
  • Margin calls on fractional-lot symbols size the liquidation in lot units (was always whole contracts), so crypto backtests no longer force-close the whole position and halve the trade count vs TV. The bar-open margin call keeps TV's whole-contract sizing.
  • Margin overshoot now depends on lot granularity: fractional-lot symbols fill-then-trim, whole-lot symbols reject the entry so the signal re-fires later.
  • Offset-0 trailing leg: trailing arguments alongside stop/limit are legal and arm at 0 ticks; trail_price-only exits are no longer dropped; trailing arguments without trail_offset are ignored per Pine docs.
  • Position-flat close: the flattening trade size is snapped to 0.0 so it is removed, fixing an NA-propagation TypeError in percent-of-equity sizing.

request.security()

  • Deferred and mixed contexts unblocked: a deferred context resolving to the chart's own symbol+timeframe now gets result blocks (no deadlock); static subprocesses in scripts mixing static and deferred contexts are now spawned; double-spawn is guarded.
  • Intraday HTF bars anchored to the session open (e.g. equities at 09:30 / 10:30 ...) instead of a pure UTC clock-floor, matching TV; zero overhead for 24/7, on-hour and session-aligned markets.
  • Library mains run before main() in security children, so scripts calling imported library functions no longer crash with "Exported proxy has not been initialized".
  • Config safety: security children no longer rewrite the script's .toml on import — multiple contexts could race and truncate the user's config (now disabled via PYNE_SAVE_SCRIPT_TOML=0).
  • request.security contexts from imported libraries are merged; deferred / same-context timeframe handling is more reliable.

Pine v6 built-ins

  • syminfo.mincontract plus the full Pine v6 syminfo namespace (main_tickerid, expiration_date, current_contract, isin, the recommendations_* family, and more). Order sizes are floored to the symbol's real lot grid (BINANCE:BTCUSDT -> 1e-5). The CCXT provider derives the step from precision, and pyne data download refines it from analyzed volume precision.
  • chart.point namespace (new, now, from_index, from_time, copy) and Pine keyword params for drawings: line.new first_point/second_point, box.new top_left/bottom_right, label.new point. Drawing coordinates widened to int | NA / float | NA.
  • Month-first date strings in timestamp(): "03-04-2023" resolves to March 4 with -, / or . separators and an optional HH:MM[:SS] part; day-first dates are still rejected, as on TV.
  • time / time_close overload handling extended for bars_back and chart-timeframe defaults; math.round precision guard fixed.

Transformer fixes

  • Parameter default values are rewritten in the enclosing scope, fixing a NameError when a parameter shadows a same-named lib module used in its own default.
  • PersistentTransformer rewrites persistent references in attribute and subscript assignment targets (persistent_udt.field = ...).
  • Overload dispatch treats Any (unannotated params, threaded closure vars) as a wildcard instead of crashing on isinstance(..., typing.Any).

CI

  • release.yml checkout / setup-python upgraded to Node 24 actions.

v6.4.11 — import-cache integrity, OHLC precision, timezone & number-format fixes

Choose a tag to compare

@wallneradam wallneradam released this 08 Jun 21:57

What's new

A correctness release. A foreign bytecode recompile can no longer leave a stale, untransformed .pyc that crashes Pyne code at import; high-priced OHLC values keep their mintick-aligned precision instead of collapsing to six significant digits; color.new propagates na and stops mutating shared color constants; the exchange-timezone fallback is no longer cached across runs in the same process; and Pine number-format strings gain sign subpatterns and literal prefix/suffix affixes.

Import & bytecode cache

Stale or foreign .pyc is now detected and healed

Pyne modules are AST-transformed at import time, but a foreign bytecode recompile (pip's post-install compileall, an IDE, or a packaging step) can overwrite the cached .pyc with untransformed bytecode. CPython validates the cache only against source mtime and size, so the stale .pyc was accepted and builtin price series (high, low, ...) stayed raw Source objects, crashing with unsupported operand type(s) for -: 'Source' and 'Source'. A content sentinel (a transform-pipeline hash) is now baked into the transformed AST and marshaled into the .pyc; on load, any Pyne cache whose sentinel is missing or whose pipeline hash differs is dropped and re-transformed. If the stale cache cannot be removed (read-only cache dir), the module is compiled straight from source so the correct bytecode still runs. This replaces the previous mtime-based marker, which could not detect a foreign recompile that landed after a heal. (Reported in #64.)

Data fidelity

OHLC mintick precision preserved for high-priced symbols

The float32 .ohlcv storage adds sub-tick error to OHLC prices, which the runner cleans before a script sees open/high/low/close. The previous clean-up rounded to six significant digits, which for high-priced assets (e.g. BTC around 94000) reaches only one decimal (93898.05 -> 93898.1) and discarded the real mintick-aligned precision, flipping threshold/hysteresis indicators by a bar. Rounding now keeps the finer of six significant digits and the symbol's mintick decimals: large prices recover their exact tick-aligned value, while low prices that carry genuine sub-mintick precision on TradingView are left untouched. The same rounding is applied in request.security() processing.

Time & timezones

Exchange-timezone fallback no longer cached across runs

When a timezone argument is empty, parse_timezone falls back to the exchange timezone (syminfo.timezone), but that result was cached on the empty key. In a process that runs more than one script (e.g. parallel runs, or a long-lived service), the first run's timezone leaked into later runs even after the active symbol changed. Concrete timezone strings are still cached; the exchange-timezone fallback is now resolved fresh on every call.

Library

color.new propagates na and no longer mutates its input

color.new now returns an na color when either the source color or the transparency is na, matching Pine's behavior during warmup. For valid inputs it builds a fresh color, so passing a color constant (or any caller-owned Color) no longer mutates that object when only the transparency should change.

Number-format patterns support sign subpatterns and affixes

Pine number patterns follow Java DecimalFormat. The formatter now parses an optional negative subpattern after ; and preserves literal prefix/suffix affixes (e.g. $, +, R) around the digit pattern, with sign handling kept separate from zero padding — so patterns like "$#,##0.00" or one with a distinct negative subpattern format as they do on TradingView.

v6.4.10 — bid/ask & time_tradingday built-ins, strategy simulator fidelity

Choose a tag to compare

@wallneradam wallneradam released this 08 Jun 06:51

What's new

A feature-and-correctness release. Two Pine v6 built-ins land (bid/ask source series and time_tradingday), a per-bar time-isolation regression that silently froze time_close and timenow is fixed, and the strategy backtest simulator gets a large fidelity pass — rewritten trailing stops, full strategy.risk.* enforcement, an intraday filled-order cap, corrected gap-open and same-bar fill ordering, sticky-bracket / partial-exit correctness, and a lot-size rounding fix — bringing strategy results closer to TradingView.

Built-ins

bid and ask source series

Pine v6's bid and ask built-in float sources are now available, so Pyne code referencing them (including history access like bid[1]) compiles and runs. As on TradingView, they only carry real values on the unsupported 1-tick (1T) feed and resolve to na on every bar timeframe — PyneCore has no tick data, so they are always na. Any bid/ask columns present in the input data's extra fields are intentionally ignored for the same reason.

time_tradingday

New Pine v6 built-in returning 00:00 UTC of the bar's trading-day date in the exchange timezone. For symbols whose session crosses midnight (forex/futures overnight sessions), a bar whose window contains the session open is assigned to the new trading day — matching TradingView and session.isfirstbar_regular — even when the open does not land on a bar boundary (e.g. a 17:00–18:00 bar with a 17:05 open). Intraday-only and 24/7 symbols, and sessions opening exactly at midnight, use the bar's own date.

Time & sessions

time_close and timenow no longer frozen at the first bar

The function-isolation transform substitutes a snapshot of the module globals captured at a function's first call. time_close and timenow were being wrapped by that pass, so the per-bar updates to the underlying time state never reached them and both returned the bar-0 value on every subsequent bar. They are now marked non-transformable and read live per-bar state. (time was already exempt, so only time_close and timenow were affected.)

Strategy / backtest simulator

A large set of simulator fixes that change trade lists, exit prices/bars, trade counts, and halt behavior for affected strategies, bringing them in line with TradingView.

Trailing stops rewritten

The trailing-stop engine now re-evaluates once per bar from the prior high/low-water mark instead of riding the stop to the current bar's extreme, with a trail level persisted per order. An offset-0 trailing stop fills on its activation bar at the activation level; a carried stop fills at the open when a no-wick bar gaps past the water mark; it defers to a hard stop= reached earlier in intra-bar time; and the trail is seeded on the entry-fill bar so the water mark is not one bar behind TradingView. A re-issued live trailing leg inherits its ratcheted level instead of re-arming at the bare activation level each bar.

strategy.risk.* rules enforced

max_drawdown, max_intraday_loss, and max_cons_loss_days are now actually enforced via a post-bar risk pass. Max-drawdown is anchored to running peak equity (for percent_of_equity), max-intraday-loss to start-of-day equity, and the consecutive-loss-day counter rolls over on a session-aware trading day (driven by time_tradingday, not the calendar day) so overnight forex/futures sessions count correctly. A breach fires a synthetic Risk management close trade with a rule-identifying comment and latches a trading halt.

Intraday filled-order cap

max_intraday_filled_orders now counts a position reversal as a single filled order (not two), flattens the position on the reversal/flip path as well as the plain-fill path (tagged Close Position (Max number of filled orders in one day)), and blocks new strategy.entry placement — not just the fill — until the next trading day, preventing a phantom entry at the new day's open.

Sticky-bracket / partial-exit correctness

Re-issuing strategy.exit() on every open bar (TradingView's sticky-bracket pattern) no longer re-arms already-filled partial-exit legs at a shrinking size. Each exit leg reserves a fixed slice of the entry's original filled size — a qty_percent leg off the original size, a no-qty "rest" leg off the remainder after its siblings — and fires at most once; a re-issue only refreshes its limit/stop prices. While the bound entry is still pending, the exit tracks the entry's current pending size. This restores exact partial-exit trade counts and sizes against TradingView (e.g. 132 vs a prior 444 on a 50% partial-take-profit strategy).

Fill ordering

When two brackets sharing one entry gap through the open together, both now fill on the gap bar instead of one evicting the other. Same-bar exits walk the open→low / open→high price path so the fill nearest the open executes first, matching TradingView's intrabar order.

Other strategy fixes

  • strategy.exit() with limit, stop, profit, loss, and trail all na is now a no-op instead of becoming a level-less same-bar market close.
  • Lot-size rounding snaps a value within a few ULPs of a lot boundary up before flooring, so an exact lot multiple is no longer truncated a whole lot down while a genuine sub-lot fraction still floors.
  • strategy.order(limit=…, stop=…) documentation corrected: supplying both creates two OCA legs (a separate limit and a separate stop order), not a single stop-limit order.

v6.4.9 — Bytecode cache, timezone errors & inline_series isolation

Choose a tag to compare

@wallneradam wallneradam released this 04 Jun 13:37

What's new

Patch release with four runtime-correctness fixes. Three resolve reported issues (#58, #59, #61) that could silently break scripts after an upgrade, on Windows without tzdata, or when inline_series() ran at more than one call site; the fourth turns a confusing data-load error into a self-explanatory one.

Core

Stale Pyne bytecode invalidated after transform-pipeline upgrades (#58)

CPython validates a cached .pyc only against its source .py mtime and size, so a PyneCore upgrade that changes the AST transform output keeps executing obsolete bytecode while the unchanged user source masks the staleness — e.g. a builtin that moved from a module property (emitted as a call) to a plain variable then raised 'bool' object is not callable. PyneLoader.get_code now drops a Pyne module's cached bytecode whenever it is older than the transform pipeline itself (the newest mtime among import_hook.py and the transformers package, including module_properties.json, which shapes the output but has no bytecode of its own), forcing a recompile from source. Invalidation delegates to importlib.util.cache_from_source, so it targets the exact file CPython reads back and honours PYTHONPYCACHEPREFIX and the -O / -OO optimization level.

inline_series() buffer isolated per call site (#61)

inline_series() registered its series buffer under the registry key 'create_series', but the runtime isolator keys isolation on the function qualname ('inline_series'). The lookup missed, so isolation was skipped and every inline_series call site shared one module-global buffer. A second, unguarded inline_series in a script body overwrote the slot a request.security() expression's inline_series had written, silently corrupting the security delivery for unrelated contexts. The registry key is now aligned with the qualname so each call site gets its own buffer.

This also affects compiled strategies: PyneComp emits inline_series() for any subscripted expression whose base is not a bare identifier (e.g. ta.atr(14)[1], (high - low)[1]), not only hand-written code. New regression tests cover call-site isolation and the request.security() interaction.

Time & sessions

Missing timezone now raises an actionable error instead of returning silent na (#59)

time(timeframe.period, session, "America/Chicago") returned na on every bar when the IANA timezone database was unavailable (e.g. Windows without tzdata), so session-gated strategies produced 0 trades with no error. parse_timezone now raises TimezoneNotFoundError with a platform-aware, actionable message for any unresolved IANA name (not just a handful of short names) and reports a genuinely unknown name distinctly from a missing database. time() and time_close() re-raise this configuration error past the broad session-validation guard, while genuine out-of-session bars still return na.

Data

Clearer error on duplicate-timestamp rows during load

OHLCVWriter raised a generic Invalid interval: 0 when consecutive input rows shared a timestamp — typical for Databento exports carrying multiple publisher_id rows per bar — which hid the real cause. The check is now split: duplicate timestamps raise a duplicate-specific message that names the offending timestamp and points to pre-filtering the source, while strictly-decreasing timestamps keep the chronological-order message. This is the Invalid interval: 0 case flagged in the v6.4.8 notes.