Runtime
Bar-loop evaluator — series caps, incremental TA, alerts, fill export, foreign request.security → na, and interpret↔compile parity.
This page
Runtime
Abstract
PYNE’s runtime is the deterministic bar-loop that turns a parsed ASDL AST into series values, drawing side-effects, strategy events, and alerts. It is deliberately host-agnostic: the same visitor mixins power the library API, the Flask Pro API (Runtime.run), browser Pyodide, and edge workers. A second path—source-to-source compilation into a Numba or object-mode bar loop—trades interpretive flexibility for throughput on long histories; Pro API defaults to mode=auto.
This section documents semantics, not the chart. AXIS consumes plots / fill / drawings; hosts export alerts (optional L2 webhooks); HOOX consumes strategy events. None of those hosts is required to evaluate a script.
Conceptual model
Rendering…
Execution modes share the same parse tree (Pro API / Runtime also expose auto):
| Mode | Entry | Bar loop | Typical use |
|---|---|---|---|
| Interpret | NodeLiteralEvaluator.visit(tree) per bar | Host updates PineSeries / context, re-visits AST | Full language surface, strategy, drawings, alerts, request.* |
| Compile | pynescript.compiler.engine.compile_script | Generated for __bar_idx in range(n) over OHLCV + time_arr | Numeric indicators (Numba) or object-mode subset |
| Auto | Runtime.run(..., mode="auto") | Compile when safe; interpret on fallback | Pro API default — prefer throughput without hard-failing exotic scripts |
Compile inputs are contiguous arrays: open_arr … vol_arr plus time_arr (bar-open Unix ms; synthetic bar_index * 60_000 when the host omits times) so time / calendar builtins match the interpret time series. Disk IR cache + product warm-compile (prewarm API/CLI) cut cold JIT latency. See Compiler overview and Interpret ↔ compile parity.
request.security (landed): interpret resamples same-symbol simple OHLCV and allowlisted ta.sma / ema / rsi / atr on a coarser TF (last completed HTF bucket only). Compile passthrough is same-symbol simple OHLCV only. Foreign symbols and complex / nested expressions resolve to na on both hosts (no inventing chart close as foreign fundamentals). Policy tags land on result["meta"]["request_security"]. Real multi-symbol feeds remain adapter work (roadmap B1).
Series caps / incremental TA: host list trim defaults on (PYNE_SERIES_CAP); bar-mode TA hot path defaults on (PYNE_TA_INCREMENTAL). Drawing GC honors max_*_count on the interpret registry; fill exports series keys + plot_meta for AXIS bands.
Interpret hot path (0.3.10 / Round 9): the AST walker inlines Assign / Expr(Call) after the first bar (function/type/import decls stay locked), skips unused derived OHLCV series (hl2 / hlc3 / ohlc4 / tr unless named, input.source, or ta.vwap), and reuses plot cells by call-site. Same-machine bench @ 2000 bars vs 0.3.9: minimal 2.7×, ta.sma 2.0×, ta_combo 1.45×. PYNE_SERIES_RING stays off.
Host flags and knobs
| Knob | Default | Effect |
|---|---|---|
PYNE_SERIES_CAP | on | Trim chronological current_series lists (max_bars_back / 256) |
PYNE_TA_INCREMENTAL | on | Call-site incremental ta.* in bar mode |
PYNE_SERIES_RING | off | Chronological ring lookback; skip dual list write when on |
PYNE_LIGHT_PLOTS | off | Skip plot columns + input meta (corpus OK/fail only) |
PYNE_RUNTIME_MODE | interpret | Default when Runtime.run(mode=) is omitted (auto is the Pro API body default) |
timeout_seconds= | None | Interpret wall-clock budget; checked every 32 bars → timed_out + error_kind=runtime |
libraries= | [] | {namespace, name, version, source} registered before import (auto forwards into interpret fallback) |
realtime_* | historical | Interpret-only varip / forming-bar simulation (realtime_last_bar / ticks / bars / from_bar) |
Runtime.create_session | interpret | Persistent InterpretSession (0.6.1): delta append_bars, forming-bar update_last_bar; compile has no resumable state |
Interface surface
| Concern | Start here |
|---|---|
Series history, [], var/varip, PYNE_SERIES_CAP, na | Series & history |
| Expressions, statements, control flow | Expressions & statements |
Builtin namespaces (ta.*, strategy.*, …) | Builtins hub |
Drawing / plot / fill / max_*_count GC | Drawing & plotting |
request.* / input.* (foreign → na) | request / input |
Library export / import | Libraries |
StrategyEvent parity contract | Events |
alert() / webhooks | Alerts |
| Numba / object-mode compiler | Compiler overview |
| Interpret vs compile plot parity | Parity testing |
Incremental interpret sessions (create_session) | Evaluate scripts |
Library callers typically use NodeLiteralEvaluator or the package pynescript.runtime.Runtime (Pro API re-export) rather than wiring the bar loop by hand:
from pynescript.ast.evaluator import NodeLiteralEvaluator
from pynescript.runtime import Runtime # package SoT (0.3.4+)
ev = NodeLiteralEvaluator(context={"close": [1.0, 2.0, 3.0], "bar_index": 2})
result = ev.evaluate_script('//@version=6\nindicator("x")\nplot(close)')
# Full bar loop (preferred host API):
# Runtime(symbol="CHART").run(
# source, ohlcv_data=bars, mode="auto",
# timeout_seconds=None, libraries=None,
# )
# Incremental interpret (0.6.1):
# sess = Runtime(symbol="CHART").create_session(source, bars)
# sess.append_bars(new_closed_bars)
# sess.update_last_bar(forming_bar)
Hosts that own OHLCV (Pro API, workers) use the package host: push bar → fill pending orders → visit(tree) → drain events → collect plots. SoT: src/pynescript/runtime/ (host.py, series.py, evaluator.py). backend.runtime / backend.series / backend.evaluator are identity re-export shims for the Pro API.
Internals
| Path | Role |
|---|---|
src/pynescript/runtime/ | Package Runtime SoT — host, series, CustomEvaluator |
src/pynescript/ast/evaluator/ | Mixin evaluators (base, names, expressions, statements, libraries, events) |
src/pynescript/ast/evaluator/builtins/ | Builtin dispatch tables |
src/pynescript/compiler/ | CompilerVisitor, Numba builtins, strategy broker, engine façade |
backend/runtime.py | Compat re-export (sys.modules alias) of pynescript.runtime.host |
tests/test_evaluator.py, tests/test_parity.py, tests/test_strategy_*.py, tests/test_first_party_ta_goldens.py | Semantic oracles + dual-host TA goldens |
Composition of the full interpreter:
BaseEvaluator
+ LiteralEvaluator
+ NameEvaluator
+ ExpressionEvaluator
+ StatementEvaluator
+ BuiltinEvaluator
→ NodeLiteralEvaluator
BuiltinEvaluator itself aggregates mixins (TechnicalAnalysisMixin, StrategyBuiltinsMixin, PlottingFunctionsMixin, …) into one dispatch map built at construction time.
Invariants & edge cases
- Bar re-entrancy. The AST is visited once per bar. Function/type definitions lock after bar 0 (
_pine_defs_locked) so multi-dispatch tables do not grow (O(\text{bars}^2)). - Order of broker steps. Pending limit/stop fills run before script body evaluation on each bar (interpreter and compile object-mode agree). Pending-fill averaging when pyramiding ≤ 0 matches both brokers (F2).
naisNone. Unresolved missing data, OOB history, and arithmetic with missing operands produce PythonNone, not IEEE NaN—except on the Numba path, which usesnp.nanas the array sentinel. Cross-mode plot compares must normalize (scripts/compare_interp_compile.py).- Series caps. Unbounded
current_seriesgrowth is capped (PYNE_SERIES_CAPdefault ON,max_bars_back/_SERIES_MAX) so long histories stay memory-bounded (T1). - Incremental TA. Hot-path kernels (MAs, oscillators,
bb/kama/cmo/stochrsi, volumeobv/wad/cmf/klinger/nvi/pvi, …) use call-site incremental updates in bar mode (PYNE_TA_INCREMENTAL=0to disable). - Strategy state is per-evaluator.
StrategyStatelives on the instance (evaluator._strategy_state), never as a process-global singleton—required for concurrent runs and parity tests. - Drawing/plot registries are side channels. Visual objects do not participate in pure expression values except where
plot()returns a plot id forfill(). Hosts exportfillbands for AXIS via series +plot_meta(and__drawingson compile). Drawing objects honormax_*_countGC (package + Pro API + AXIS Pyodide). - Alerts are a host side-channel.
alert()/alertcondition()firings are not strategy events; dual-host export + L2 webhooks are documented under Alerts. - Host series are not aliased on assign.
last = time(oropen/high/…) copies the current scalar into a fresh series forlast—see Series & history. - Omitted bid/ask stay
na. Hostbid/askupdate only when those keys appear on a bar dict; they are not synthesized from close.
Worked example — minimal bar loop mental model
for bar_index, bar in ohlcv:
open/high/low/close.update(bar)
context[bar_index, time, barstate, …] = …
process_pending_orders(OHLC) # broker sim
evaluator.visit(ast) # script body
events += drain_events()
series.append(plot values)
Compile mode collapses this into one generated function over full arrays (open_arr…vol_arr, time_arr), with __bar_idx standing in for bar_index.
Failure modes
| Symptom | Likely cause |
|---|---|
unexpected type of node: … | AST node not implemented in any mixin’s visit_* |
Unknown built-in function: … | Missing dispatch key or wrong qualified-name resolution |
| Divergent strategy events vs TV | Partial-fill / OCA / risk settings, or fill timing vs bar |
| Numba path raises / falls back | Script uses UDT/map/drawing → object mode; or Numba not installed |
| Plots empty but no error | Host forgot to collect plot_outputs / PlotRegistry after visit; or PYNE_LIGHT_PLOTS=1 |
timed_out + partial series | timeout_seconds elapsed (checked every 32 interpret bars) |
See also
- Language core — grammar, AST, types before evaluation
- Alerts —
alert()engine + L2 webhooks - Pro API runtime bridge
- Interpret ↔ compile parity —
scripts/compare_interp_compile.py - Numerical validation
- Roadmap — H1/H2/T1/T2/F1/F2/L2/P1p official builtins landed; optional set0x unmeasured
- pyne-worker
- AXIS docs — optional AXIS for series/drawings