Releases: PyneSys/pynecore
Release list
PyneCore v6.5.6
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.exitqty/qty_percentlegs 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, soposition_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
1e7order money, a market entry snaps up to the next lot when the raw money-tick count reaches the0.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@30on a 24/7BINANCE:BTCUSDT@30chart): 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) / emitsna(gaps_on) like TradingView.
Builtins / lib parity
label.set_textcolorkeyword parameter renamedcolor->textcolor(Pine v6 name); previously raisedTypeErroron the named call.str.tostringsecond parameter renamedfmt->format(Pine v6 name); previously named-argument calls (str.tostring(x, format=format.percent)) raisedTypeError.request.seed()returnsnainstead of raising (seed data lives in TradingView-hosted community repositories PyneCore cannot access), mirroringrequest.footprint().request.financial()gains explicit named parameters and returnsnawhenignore_invalid_symbolis set, mirroringrequest.earnings().string.format/str.tostringpropagatenawhen the format pattern isna.math.sumperiodically resyncs its sliding window from the buffer so residual Kahan rounding no longer breaks TradingView-preserved identities (flat windows now sum exactly ton*v).array.covarianceuses the Welford online co-moment to match TradingView bit-for-bit on the unbiased path.
Sessions / date parsing
session.islastbar_regular/islastbarnow 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, theinput.sessiondefault) 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_datestringaccepts ISO datetimes without seconds ("2021-01-01 00:00", both space andTseparators) 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 anda[1]was permanentlyna. - 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 tonaforlength > 500: the indexed buffer grows withmax_bars_back(series, length);length > 5000now raises, matching Pine's own ceiling. DynamicDefaultTransformerevaluateslib.*-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
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-
1e7fill-margin tolerance was tightened from7.5e-10to1e-11so 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_periodnow reports the chart's main-series timeframe inside arequest.security()child. Previously it fell back to the child's own period, so atimeframe.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_periodused as a security timeframe argument at module-exec time no longer crashes;string.matchnow translates Pine's\zregex escape to Python's\Z; and the security protocol is now injected into imported library modules, so a library that itself callsrequest.security()no longer raisesNameError. --securitypath resolution no longer mangles symbols containing a dot (e.g. a perpetualBINANCE:BTCUSDT.P); the dotted stem previously resolved to a non-existent.ohlcvfile and aborted the run with "Security data not found".last_bar_timeis 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_timeare rebased on it so right-edge viewport comparisons fire correctly.
Builtins / lib compatibility
array.max(id, nth)andarray.min(id, nth)now accept Pine v6's optional 0-basednthrank (na elements ignored);array.max(id, 0)no longer raises aTypeError.lib_math.sum(and through itta.sma/ta.rsi/ta.mfi) now supports a varying serieslength(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.vwapnow works as the Pine built-in variable form (session-anchored VWAP ofhlc3), not only theta.vwap(source)function form. request.footprint()now returnsna(with a workingnot na(fp)guard and a newFootprint.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
NAor aSeriesthrough the sequence protocol (str.join(na),list(na), tuple unpacking) no longer spins forever at 100% CPU:NA.__iter__now raises loudly,SeriesImpliterates newest-to-oldest and terminates, andstring.repeat/string.replaceguard 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-7float32 dust that accumulated in every volume sum.
v6.5.4 — TradingView strategy parity: big-money sizing, margin calls, trailing stops, Heikin Ashi security
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
nofillkeeps its closing leg: a market entry whose big-money sizing judgesnofillno 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_offsettick 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_equitysizing, 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:
BuiltinShadowTransformerkeyed the alias-shadows-builtin fallback on the emitted alias name, so a compiledimport ... as ta__ren__no longer matched and non-exported members (ta.crossover) raisedAttributeError. 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 (
positionvsposition__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
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.changeover-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)returnsna—cast_intwas the only cast helper not propagatingna(it returned0). Scripts deriving an array size or loop bound from anna-valuedint()on early bars no longer crash; the loop now runs zero iterations, matching TradingView.- Array reductions ignore
na—array.avg/sum/min/max/range/median/mode/variance/covariance/stdevnow reduce over the non-nasubset and returnnaonly when no non-naelement remains.covariancedropsnaindex pairs pairwise,variancegains alen < 2 → 0.0guard. - Array percentiles match TradingView —
array.percentile_linear_interpolation,array.percentile_nearest_rankandarray.percentrankuse TradingView's unified position formula andnahandling (also correcting some no-nafractional-position results), and now returnfloat | NA[float]. ta.wma/ta.cog/ta.linregsurvive internalnagaps — a source with an internalnagap no longer poisons the rolling sum tonaforever; the oldest window value is indexed past the gap.ta.hmais fixed transitively. Clean sources stay byte-identical.color.from_gradientnahandling — no longer crashes on annaendpoint; it keeps the solid endpoint's RGB and fades transparency toward thenaside, returningnaat thenaendpoint, matching TradingView.color.t()now returns transparency (0–100) instead of the raw 0–255 alpha, and thecolor.new/from_gradientsignatures widen to theirType | NA[Type]domain.Coloris hashable — a value-based__hash__consistent with__eq__letsColorbe a UDT field default (compiled UDTs usedataclasswith 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
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.securityis corrected across the session break. request.security_lower_tfcollects 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)
SymInfocan carry an effective-datedsession_scheduleshistory. 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()returns0.0for a negative or out-of-range trade index instead of raising.
Standard library
map.get()returnsnafor a missing key instead of raising, matching TradingView.ta.percentile_*,ta.percentrankandta.rcigrow 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.smaand 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.securityis served without re-running the script'smain()on every bar.
v6.5.1
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_allafter 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=Truecloses now book their cumulative stats correctly.- Added
strategy.netprofit_percent,strategy.openprofit_percentandstrategy.max_drawdown_percent.
request.security / higher-timeframe
- Sparse single-period D/W/M feeds keep their
gaps_offforward-fill instead of writingna. - HTF
barmerge.lookahead_offconfirms on the period's own last chart bar, not one bar late (#66). - Tuple
request.security_lower_tfreturns 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 returnnainstead of raising.- Array methods dispatch on
array.slice()(SequenceView) receivers. str.contains(na, ...)no longer hangs;str.format_time(na)and out-of-rangetimestamp(...)follow Pine.ta.correlation,ta.crossoverandta.crossunderstay valid acrossnagaps in the window.- An
naseries 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_indexstays 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
lenorgetattr. inline_serieshistory 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_*_countcaps. - Overload dispatch memoizes implementation selection per call shape.
pine_rangereturns a nativerangefor integer for-loop bounds;array.getdrops a runtime cast.
v6.5.0 — state-engine rewrite, strategy & request.security() TradingView parity
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/varipvariables 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_seriesare 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 — scriptmainand 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/boolpersistent with a non-literal+=no longer emits float arithmetic and crashes. varipKahan compensation is carried with its companion across var rollback, so the pair can't desynchronize.- Deterministic call routing, independent of
sys.modulesimport order. - Stateful lib functions (
fixnan,math.random,math.sum,timeframe.change) ported to@pynesubmodules.
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 withouttrail_offsetare ignored per Pine docs. - Position-flat close: the flattening trade size is snapped to
0.0so it is removed, fixing anNA-propagationTypeErrorin 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
.tomlon import — multiple contexts could race and truncate the user's config (now disabled viaPYNE_SAVE_SCRIPT_TOML=0). request.securitycontexts from imported libraries are merged; deferred / same-context timeframe handling is more reliable.
Pine v6 built-ins
syminfo.mincontractplus the full Pine v6syminfonamespace (main_tickerid,expiration_date,current_contract,isin, therecommendations_*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, andpyne data downloadrefines it from analyzed volume precision.chart.pointnamespace (new,now,from_index,from_time,copy) and Pine keyword params for drawings:line.newfirst_point/second_point,box.newtop_left/bottom_right,label.newpoint. Drawing coordinates widened toint | NA/float | NA.- Month-first date strings in
timestamp():"03-04-2023"resolves to March 4 with-,/or.separators and an optionalHH:MM[:SS]part; day-first dates are still rejected, as on TV. time/time_closeoverload handling extended forbars_backand chart-timeframe defaults;math.roundprecision guard fixed.
Transformer fixes
- Parameter default values are rewritten in the enclosing scope, fixing a
NameErrorwhen a parameter shadows a same-named lib module used in its own default. PersistentTransformerrewrites 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 onisinstance(..., typing.Any).
CI
release.ymlcheckout / setup-python upgraded to Node 24 actions.
v6.4.11 — import-cache integrity, OHLC precision, timezone & number-format fixes
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
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()withlimit,stop,profit,loss, andtrailallnais 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
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.