Performance
Load-time code transformation
- Trivial builtin wrappers are inlined into the call site. A new call
inlining pass rewrites calls of stateless one-expression builtins —math.abs,
pow,sqrt,sign,floor,ceil,exp,log,log10, the
trigonometric functions,todegrees/toradians, two- and three-operand
max/min, andarray.get/array.size— as the wrapper's own body,
written out as a single expression. The expression is derived from the
wrapper's source AST, so it runs the same operations in the same order and
stays bit-identical; a wrapper whose body leaves the derivable shape simply
keeps its call. Arguments are bound once, evaluation order is preserved, and
call sites where the callee is not provably the library function are left
alone. typing.castcalls andTYPE_CHECKINGblocks are erased at load time. A
type erasure pass replacestyping.cast(T, x)withxand resolves
if TYPE_CHECKING:to its else body. It runs in the@pynepipeline and, as
the only pass, over PyneCore's own plain modules, where the hot-path casts
live. A name is trusted only when a module-leveltypingimport is its sole
binding in the module, so a rebound name orctypes.castis untouched.
Number formatting
str.tostringandstr.formatno longer go throughDecimalon the
common paths. A fast path handles a native finite double with a digit mask
andstr.tostringwithformat.mintick; the mask is parsed once per pattern
string. The previous implementation stays as the semantic reference and the
fallback for exponential reprs, negative zero, non-float input, the other
format types and malformed subpatterns, so the output string and the
exception type are unchanged for every input. Per call:str.tostring
2.2 us -> 0.7-0.9 us,str.format2.3 us -> 0.9 us.
Call-site routing and rollback
- Plain calls for stateless library functions. The function isolation
whitelist missed most plain library functions, so their call sites paid an
anchor slot and an identity check, and inside a loop a bind plus a builtin
rollback on every iteration. Allmatrix.*functions, thecast_*helpers,
theline/label/box/table/linefill/polyline/chart.point/ticker
functions, the drawing array constructors, therequestfunctions that open
no context, the strategy risk rules, the footprint and volume row accessors,
runtime.error,color.tandplotbarare now routed as plain calls. The
array.percentile_nearest_rankentry was listed by its bare name and never
matched;timestamp, an overload dispatcher that has to stay bound, and a
color.aentry that names no function were removed. - Cheaper drawing and loop-site rollback. The drawing snapshot saves fields
with a cached per-type attrgetter and restores only the objects whose fields
changed. Loop-site builtin snapshots carry a state-vector epoch, so while no
state vector was created since the snapshot the restore reuses the snapshot's
machine list instead of re-walking the callee subtree.
strategy.exit
- A resting exit leg is kept in place when the call would rebuild it
unchanged, refreshing only its placement stamps. The shortcut applies to
legs without trailing or tick fields that sit alone on each of their price
levels, so a leg sharing a level is still re-queued behind its neighbours.
The per-leg logic moved out of closures created on every call into
module-level functions, and the na conversions and sibling reservation sums
are now plain inline loops. Trade output is unchanged.
Bulk OHLCV writes
- A bulk append publishes once instead of once per record.
OHLCVWriter
fsynced twice per record — the record bytes, then the header naming them —
which is the right granularity for a live bar and far too fine for a feed
being built or fetched in bulk. On Linux, where an fsync is a real device
barrier, a script with threerequest.security()contexts over a 23 272-bar
60m feed spent 58.3 s in the barriers alone. The append protocol is
unchanged, only its granularity: a newbatched()context manager defers
publication to the end of a block and is taken byaggregate_ohlcv, the
CSV/JSON converter, the demo generator and each providersave_ohlcv_data()
call, so a download page is fsynced once while a live bar still publishes on
its own. The same three-context script: 58.3 s -> 1.95 s, output
byte-identical.
Fixes
Security child processes start on a pinned method
Security children were started with a bare multiprocessing.Process, so the
start method was the platform default: fork on Linux up to Python 3.13,
spawn on macOS, forkserver on Linux 3.14+. fork is the unsafe one here —
the live runner starts its provider thread and waits for the connection before
warmup, and children start lazily during the run, so every live security child
was forked from a multi-threaded parent and inherited locks no surviving thread
would release. Children now start on forkserver where the platform offers it
and spawn otherwise, and every primitive handed to a child comes from that one
context.
Two consequences of not forking from the runner any more are fixed with it: the
orphan watchdog compared os.getppid() against the spawning runner, which a
forkserver child never has, so a hard-killed runner went unnoticed — it now
waits on the runner's own process sentinel. And because a spawn carries no
environment, per-run PYNE* switches reached the chart but not the children on
a second run in one interpreter; each spawn now carries a snapshot of the
runner's PYNE* environment, replacing the prefix wholesale so a cleared switch
propagates too.
One table per chart position
table.new and table.set_position now evict the table already at the target
position, as TradingView does; the arriving table survives and an evicted handle
stays usable. The registry was unbounded before, so a script calling
table.new on every bar without var added one table per bar and every drawing
snapshot walked all of them, making the run cost grow quadratically with the bar
count. The drawing snapshot also rolls back table cells now: the cells dict and
the cell fields are changed in place, so a discarded calc_on_order_fills or
live re-execution used to leave its table edits behind.
Exits are deferred for a parked parent entry
The order sync engine no longer dispatches or amends an exit while the parent
entry's disposition is unresolved. Exits proceed as before once a fill has been
recorded.
Interrupts from the live runner reach the CLI
A KeyboardInterrupt is now propagated after streaming teardown, so an
interrupted live run is no longer reported as completed.
Durability fixes found along the way
- A failed header fsync left the OHLCV file naming a record the caller then
disowned. The header write and its fsync now share the rollback to the last
published header, and if that rollback fails too the writer refuses further
appends instead of trimming a sidecar row the on-disk header still counts. - Sidecar rows were only flushed, never fsynced, so a crash could commit records
whose extra values were gone — a row-count mismatch the reader rejects. They
are now synced before every publication, before the file replacement that
publishes a schema promotion, and with the directory entry when the sidecar is
created. math.sqrtrejects negative input with a guard instead of catching
ValueError. The result is unchanged except for a negative Python int beyond
the double range, which returnsnainstead of raisingOverflowError.