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

Diagram

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)

FieldTypeRequiredDescription
scriptstringyesFull Pine source
dataBar[]yesChronological OHLCV (ohlcv alias on pyne-worker)
symbolstringnoDefault CHART / host-specific
mode"interpret" | "compile" | "auto"noRuntime.run default when omitted: PYNE_RUNTIME_MODE or interpret. Flask RUN_SCHEMA default auto
inputsobjectnoinput.* overrides keyed by title (forces interpret under auto)
libraries{namespace, name, version, source}[]noIn-process import ns/Name/ver. Flask /run and /run/batch accept this (max 32).
profilerboolnoPer-line interpret timings (forces interpret)
timeout_secondsnumbernoOptional wall-clock interpret budget (checked every 32 bars; sets timed_out). Flask /run and /run/batch accept it; omit / null / ≤ 0 → no timeout.
webhook_urlstringnoFlask L2 alert webhook (overrides ALERT_WEBHOOK_URL)
data_sourcestringnoOptional external history provider id (free paths: chart / mock only)
data_optionsobjectnoProvider configuration

Bar

FieldTypeRequiredNotes
timenumberrecommendedUnix ms preferred
opennumberyes
highnumberyes
lownumberyes
closenumberyes
volumenumbernoDefault host-dependent
bid / asknumbernoRuntime bid/ask hooks

Response (success)

FieldTypeDescription
status"success"Flask sets this; workers may omit and use HTTP 200 only — prefer including it
plots(number|null)[]Primary series (plot 0)
seriesRecord<string, (number|null)[]>Named multi-series
plot_metaRecord<string, PlotMeta>Display metadata
eventsStrategyEvent[]Strategy / trade-like events
drawingsDrawing[]line/label/box export
alertsAlertEvent[]alert() / true alertcondition() firings (interpret; [] on compile)
alert_conditionsobject[]optional condition evaluations
alert_forwardobjectoptional L2 webhook delivery summary
countnumberBars evaluated
script_idstringStable hash prefix of source
run_idstringPer-invocation id
modestringEffective mode (interpret | compile)
auto_backendstringauto only: which path ran
compile_fallback_reasonstringauto only: why compile was skipped or failed
object_modeboolCompile path: pure-Python bar loop (no njit)
timed_outboolInterpret budget exceeded (Runtime.run(timeout_seconds=…))
data_sourcestringFlask 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:

  1. Envelope status === "error" with message, or
  2. Presence of top-level error string (raw Runtime.run shape).

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 fieldFlask source
plots / series / plot_metapynescript.runtime.host.Runtime.run packing loop
eventsevaluator._strategy_state.drain_events()to_dict() (compile: __events)
drawingsDrawingRegistry.export_for_api (compile: __drawings)
alertsexport_alerts_from_evaluator (interpret; compile returns [])
alert_forwardbackend/alert_forwarder.maybe_forward_run_alerts
script_idsha256(source)[:16]
run_idRuntime._run_id
compile_fallback_reasonRuntime._run_auto
timed_outInterpret 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

SurfaceDivergence
Preview / backtestColumnar data dict, not Bar[]; not the evaluate contract
Compile modeExtra keys object_mode, compile_cached, compile_ms, nopython_fallback_reason. generated_code only if PYNESCRIPT_RETURN_GENERATED_CODE=1
timeout_secondsIn-process Runtime.run, edge workers, and Flask /run / /run/batch (optional; omit = no budget)
HealthFlask GET / and GET /health (CORS-free); not part of evaluate
AuthFlask free /run open (bar/rate/concurrency caps); workers may require HOOX mTLS / API gateways
Error codesFlask uses code enums; workers should map to stable strings when possible

Invariants and edge cases

  1. Bar order is chronological ascending — hosts do not sort for you.
  2. na / missing appear as JSON null in series arrays.
  3. Duplicate plot titles get suffixes (_2, …) on Flask; clients should key on returned map keys, not assumed titles.
  4. Idempotent script_id for identical source text across hosts.
  5. run_id is not stable across retries — use for log correlation only.
  6. 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 bugSymptom
Sending columnar preview data to /runSchema data must be a list
Assuming batch on worker404 / unknown field
Ignoring series and only reading plotsMulti-plot scripts look single-series

See also