Evaluate Contract
Shared request/response evaluate contract across Flask Pro API, pyne-worker, and in-process Runtime.run (Python + PyneTS).
This page
Evaluate Contract
Abstract
The evaluate contract is the JSON dialect that lets AXIS, HOOX, CLI tools, and edge workers call any PYNE-compatible host interchangeably. Flask POST /run is the reference implementation; Python pyne-worker aims for the same HTTP fields. PyneTS speaks the in-process form (RuntimeResult).
This page freezes the semantic envelope — not transport auth headers.
Conceptual model
Rendering…
Invariant: given the same script + OHLCV + mode, hosts should agree on plot series and strategy events within known parity limits (see numerical validation and PyneTS parity). AXIS engines call Python hosts today, not PyneTS.
Interface surface
Request (single evaluate)
| Field | Type | Required | Description |
|---|---|---|---|
script | string | yes | Full Pine source |
data | Bar[] | yes | Chronological OHLCV (ohlcv alias on pyne-worker) |
symbol | string | no | Default CHART / host-specific |
mode | "interpret" | "compile" | "auto" | no | Runtime.run default when omitted: PYNE_RUNTIME_MODE or interpret. Flask RUN_SCHEMA default auto |
inputs | object | no | input.* overrides keyed by title (forces interpret under auto) |
libraries | {namespace, name, version, source}[] | no | In-process import ns/Name/ver. Flask /run and /run/batch accept this (max 32). |
profiler | bool | no | Per-line interpret timings (forces interpret) |
timeout_seconds | number | no | Optional wall-clock interpret budget (checked every 32 bars; sets timed_out). Flask /run and /run/batch accept it; omit / null / ≤ 0 → no timeout. |
webhook_url | string | no | Flask L2 alert webhook (overrides ALERT_WEBHOOK_URL) |
data_source | string | no | Optional external history provider id (free paths: chart / mock only) |
data_options | object | no | Provider configuration |
Bar
| Field | Type | Required | Notes |
|---|---|---|---|
time | number | recommended | Unix ms preferred |
open | number | yes | |
high | number | yes | |
low | number | yes | |
close | number | yes | |
volume | number | no | Default host-dependent |
bid / ask | number | no | Runtime bid/ask hooks |
Response (success)
| Field | Type | Description |
|---|---|---|
status | "success" | Flask sets this; workers may omit and use HTTP 200 only — prefer including it |
plots | (number|null)[] | Primary series (plot 0) |
series | Record<string, (number|null)[]> | Named multi-series |
plot_meta | Record<string, PlotMeta> | Display metadata |
events | StrategyEvent[] | Strategy / trade-like events |
drawings | Drawing[] | line/label/box export |
alerts | AlertEvent[] | alert() / true alertcondition() firings (interpret; [] on compile) |
alert_conditions | object[] | optional condition evaluations |
alert_forward | object | optional L2 webhook delivery summary |
count | number | Bars evaluated |
script_id | string | Stable hash prefix of source |
run_id | string | Per-invocation id |
mode | string | Effective mode (interpret | compile) |
auto_backend | string | auto only: which path ran |
compile_fallback_reason | string | auto only: why compile was skipped or failed |
object_mode | bool | Compile path: pure-Python bar loop (no njit) |
timed_out | bool | Interpret budget exceeded (Runtime.run(timeout_seconds=…)) |
data_source | string | Flask echo of resolved source label |
PlotMeta (Flask)
{
"title": "C",
"color": "#2196F3",
"linewidth": 1,
"index": 0,
"kind": "plot"
}
kind is plot | hline | fill | bgcolor | plotshape | plotchar | plotarrow. Fill bands add plot1 / plot2 title refs; hline may add price. Extra style keys (style, linestyle, location, text, char, size) appear when the interpret collector has them.
StrategyEvent (minimum)
Hosts should preserve:
{
"type": "string",
"bar_index": 0,
"script_id": "…",
"run_id": "…"
}
Additional fields follow evaluator to_dict() / TS port parity — treat unknown keys as forward-compatible.
AlertEvent (minimum)
{
"message": "string",
"freq": "once_per_bar",
"bar_index": 0,
"time": 0,
"source": "alert",
"script_id": "…",
"run_id": "…"
}
See Alerts for frequency rules and webhooks.
Response (failure)
Flask /run:
{
"status": "error",
"code": "EXECUTION_ERROR",
"message": "Runtime Error at bar …",
"error_kind": "runtime"
}
Optional error_type, error_bar, logs, profile, meta pass through when Runtime set them. error_kind is parse | compile | runtime | data | order | mode.
Workers may use equivalent { error: string } or HTTP 4xx/5xx with message body. Clients should accept:
- Envelope
status === "error"withmessage, or - Presence of top-level
errorstring (rawRuntime.runshape).
Batch evaluate (Flask extension)
POST /run/batch:
Request: scripts: (string | {id, script})[] (max 8) + shared data + optional symbol / data_source / data_options / mode / profiler / libraries / timeout_seconds / webhook fields. Not on the batch schema: inputs.
Response:
{
"status": "success" | "partial",
"results": [ /* per-script success or error object with id */ ],
"count": 0,
"ok": 0,
"data_source": "chart"
}
Workers may implement batch as N single evaluates; AXIS should not assume batch exists on every host.
Internals — reference mapping
| Contract field | Flask source |
|---|---|
plots / series / plot_meta | pynescript.runtime.host.Runtime.run packing loop |
events | evaluator._strategy_state.drain_events() → to_dict() (compile: __events) |
drawings | DrawingRegistry.export_for_api (compile: __drawings) |
alerts | export_alerts_from_evaluator (interpret; compile returns []) |
alert_forward | backend/alert_forwarder.maybe_forward_run_alerts |
script_id | sha256(source)[:16] |
run_id | Runtime._run_id |
compile_fallback_reason | Runtime._run_auto |
timed_out | Interpret bar loop vs timeout_seconds (Flask /run and /run/batch when the field is set and > 0) |
Schema enforcement on Flask free tier: RUN_SCHEMA / RUN_BATCH_SCHEMA in backend/middleware/schemas.py (strict, reject extras). Webhook fields: webhook_url, forward_alerts, alert_last_bar, alert_batch. libraries and timeout_seconds are on both /run and /run/batch.
Tests: tests/test_backend.py (test_run_success, test_run_exports_alerts, webhook cases), tests/test_alert_forwarder.py.
Divergences to remember
| Surface | Divergence |
|---|---|
| Preview / backtest | Columnar data dict, not Bar[]; not the evaluate contract |
| Compile mode | Extra keys object_mode, compile_cached, compile_ms, nopython_fallback_reason. generated_code only if PYNESCRIPT_RETURN_GENERATED_CODE=1 |
timeout_seconds | In-process Runtime.run, edge workers, and Flask /run / /run/batch (optional; omit = no budget) |
| Health | Flask GET / and GET /health (CORS-free); not part of evaluate |
| Auth | Flask free /run open (bar/rate/concurrency caps); workers may require HOOX mTLS / API gateways |
| Error codes | Flask uses code enums; workers should map to stable strings when possible |
Invariants and edge cases
- Bar order is chronological ascending — hosts do not sort for you.
na/ missing appear as JSONnullin series arrays.- Duplicate plot titles get suffixes (
_2, …) on Flask; clients should key on returned map keys, not assumed titles. - Idempotent script_id for identical source text across hosts.
- run_id is not stable across retries — use for log correlation only.
- Parity is best-effort on floating edges; pin fixtures when testing hosts against each other.
Worked example — host-agnostic client sketch
type EvaluateRequest = {
script: string;
data: Array<{
time: number;
open: number;
high: number;
low: number;
close: number;
volume?: number;
}>;
symbol?: string;
mode?: "interpret" | "compile" | "auto";
libraries?: Array<{
namespace: string;
name: string;
version: number;
source: string;
}>;
};
type EvaluateSuccess = {
plots: Array<number | null>;
series: Record<string, Array<number | null>>;
plot_meta: Record<string, Record<string, unknown>>;
events: Array<Record<string, unknown>>;
drawings: Array<Record<string, unknown>>;
alerts: Array<Record<string, unknown>>;
count: number;
script_id: string;
run_id: string;
mode: string;
auto_backend?: "compile" | "interpret";
compile_fallback_reason?: string;
};
POST that body to Flask /run or the worker’s evaluate route; branch on status / error.
Failure modes
| Client bug | Symptom |
|---|---|
Sending columnar preview data to /run | Schema data must be a list |
| Assuming batch on worker | 404 / unknown field |
Ignoring series and only reading plots | Multi-plot scripts look single-series |