Evaluation map

How AXIS runs Pine: Flask Pro API, Worker proxy, in-browser Pyodide. AXIS does not import PyneTS and does not place HOOX orders.

This page

Abstract

AXIS is a charting PWA, not a language rewrite. Every Pine evaluation goes through an engine plugin. Built-ins are server (HTTP/WS to PYNE) and pyodide (in-browser Python wheel). Optional HOOX pyne-worker is just another Backend URL.

AXIS does not import @hoox-sh/pynets. AXIS does not call trade-worker. Drawing a strategy on the chart is research, not execution.

Conceptual model

Diagram

Rendering…

Interface surface

PathImplementationNotes
Engine serversrc/engines/catalog.tsPrefer WS ws(s)://{endpoint}/ws/run (preferWs default true), else POST {endpoint}/run?mode= with { script, data, mode, inputs?, profiler?, libraries? }. Modes interpret | compile | auto. Timeout scales with bar count (45–180s). Default endpoint http://localhost:5002. Live sessions (Pro API 0.6.9+, interpret only): genuine live ticks (EngineOpts.liveTick) go through src/engines/live-sessions.ts — POST /session, /tick (O(1)), /append (O(delta)) — with silent fallback to the full run on any miss. Only engaged for interpret runs: the configured mode must be interpret, or the backend must have reported interpret for the last full run of the same script (auto). Scripts whose inputs hold expanded input.source series stay on /run — a session freezes its inputs at create.
Engine pyodidesame fileSelf-hosted Pyodide 0.29.5 (/pyodide/v0.29.5/, Python 3.13), wheels /vendor/pynescript-0.6.29-py3-none-any.whl + antlr4_python3_runtime-4.13.2, runtime /pyodide/pynescript_runtime.py. Numba is not in Wasm; compile / auto is object-mode. Cold load ~20–30s.
AXIS Worker /api/runworker/src/runtime.tsPYODIDE_IN_WORKER=enabled scaffold; else proxy EXTERNAL_BACKEND/run; else 503 NO_BACKEND. Caps: 512 KiB script, 50k bars, 30 req/min/IP, 60s upstream.
Engine pyne-workersrc/engines/catalog.tsBuilt-in (builtIn: true). POST /run to the HOOX edge worker. Default origin https://pyne-worker.cryptolinx.workers.dev. Distinct from engine server and from the AXIS Worker (worker-axis).
PyneTS—TypeScript library. PYNE docs. Not referenced in AXIS source.

Payloads follow the PYNE evaluate contract. Defaults: Flask POST /run uses mode auto. See modes.

Internals

PathRole
src/engines/catalog.tsserver, pyne-worker, pyodide
src/engines/live-sessions.tsO(delta) live sessions (/session, /tick, /append); interpret-only, silent fallback to /run
src/workers/catalog.tsWorkers page catalog entries
worker/src/runtime.tsEdge /api/run
public/pyodide/Wasm runtime + wheel
scripts/sync-pyne-wheel.shRefresh vendored wheel

Invariants & edge cases

  1. AXIS ≠ engine (ADR-002). The shell never embeds a closed interpreter.
  2. AXIS ≠ execution. Strategy events on the Results panel are not HOOX orders. See AXIS and HOOX.
  3. No none stream plugin in src/streams/catalog.ts. Pausing live data is stopLive(), not a plugin id.
  4. The vendored wheel is pynescript-0.6.29-py3-none-any.whl, built from the sister pyne repo with scripts/sync-pyne-wheel.sh. That script also prints a warning if the hard-coded path in src/engines/catalog.ts drifts from the wheel filename — update it when the version changes.
  5. CORS: Worker allows localhost, *.hoox.sh, *.pynescript.online, *.axis.pages.dev (plus legacy *.pynescript-axis.pages.dev) only.
  6. A live tick never changes evaluation semantics. Live sessions (src/engines/live-sessions.ts) always evaluate in interpret, so the O(delta) path engages only for a run the backend itself resolved to interpret (the client records the mode each full run reports), and a session freezes its inputs at create — scripts whose inputs hold expanded input.source series stay on POST /run. Any miss falls back to the full run, and a response that is not a /run-shaped result is a miss, never an empty success.

Worked examples

Local desk

bash
# terminal 1 — PYNE Pro API
cd ../pyne && make run    # :5002 — sister repo hoox-sh/pyne; some checkouts are still named pynescript

# terminal 2 — AXIS
cd axis && bun run dev

Manager → engine server, Backend URL http://localhost:5002.

Offline

Switch engine to pyodide. First run downloads/caches the self-hosted runtime. Do not expect Numba.

Edge proxy

Deploy AXIS Worker with EXTERNAL_BACKEND pointing at Flask or pyne-worker. Engine stays server; endpoint is the Worker origin. Still no trade-worker hop unless that backend forwards events.

Pyodide in Worker (scaffold — not production yet)

Goal: a fully-CF deployment (no Flask) with the Worker evaluating Pine itself.

toml
# worker/wrangler.toml
PYODIDE_IN_WORKER = "enabled"   # default "disabled" — leave it there for now

Status: Pyodide boots per isolate, but the wheel install + run_script bridge are stubbed, so every run returns an error envelope (HTTP 200) and does not fall through to EXTERNAL_BACKEND. Enabling the flag today breaks evaluation. Completion checklist (R2 wheel, BUNDLES binding, micropip install, bridge, fail-open semantics) lives in worker/RUNTIME.md. Production recommendation stays EXTERNAL_BACKEND.

Failure modes

SymptomCauseFix
503 NO_BACKENDWorker has no EXTERNAL_BACKEND and Pyodide-in-worker offSet backend or use browser Pyodide
503 body { status: 'error', code: 'NO_BACKEND' } to API consumersSame as above, machine shapeDo not retry — the backend is absent, not busy. Fix config, then resend. (PYNE Agent treats this as skipped-with-note after 1 attempt.)
Compile "slow / different" in PyodideNo NumbaUse Flask for numeric compile
Expecting pynets in the PWANot wiredImport @hoox-sh/pynets in your own app
Strategy PnL on chart, no CEX fillAXIS does not executepyne-worker live trading

See also