Screener engine¶
ONIX's live screener is a standalone Rust process (onix-screener, crates/onix-screener-live)
that computes a registry of typed features over incoming bars and answers bounded, filterable
screens against them. It communicates over a newline-delimited canonical-JSON socket protocol —
one ingest connection per relay identity, one query connection per client — and is fronted for
ONIX's Python side by screener.bridge/screener.client, and for the gateway/MCP surface by
GET /v2/screener/* and the corresponding MCP tools.
Feature registry¶
Every screenable value is declared once in a feature registry entry (onix.feature_spec): a
stable feature_id and semver feature_version, where it computes (implementation), what it
consumes (input_kind), its canonical parameters (exact decimal/integer strings — never floats,
because these bytes are hashed into a config_hash and a float has no single spelling), its
warmup requirement, and its declared outputs. GET /v2/screener/features (and the
spine_screener_features MCP tool) enumerate the full registry — enumeration only, no values.
Resolutions and rows¶
onix.feature_row is the per-instrument, per-resolution snapshot the registry actually produced.
At this pin, the row-producing projection (onix_engine.screener.projection) emits frames for
all seven resolutions in the frozen chart-resolution contract — 1m (native, folded from the
spine's own 1-minute fanout) and 5m/15m/30m/1h/4h/1d (folded from derived closed bars the Rust
runtime emits back over the same ingest socket as DERIVED_BAR egress frames, via
onix_engine.screener.bridge). projection.RESOLUTIONS_PRODUCED is built directly from
spine.resolutions.RESOLUTIONS rather than a second, hand-typed list. This closes a defect that
used to leave RESOLUTIONS_PRODUCED == ("1m",), pinned shut by
test_screener_bridge.py's own executable assertion: assert RESOLUTIONS_PRODUCED == ("1m", "5m",
"15m", "30m", "1h", "4h", "1d"). A derived timeframe's values still report ABSENT until its own
series has accumulated enough closed bars to warm up its indicators (e.g. ema_200 needs 200
closed bars of that timeframe) — that is ordinary warmup behavior, identical to how 1m itself
starts ABSENT for a freshly-subscribed instrument, not the structural defect this section used to
describe.
The absent-value honesty rule¶
This is the screener's central design guarantee, and it is enforced at the schema level, not left
to convention. A feature value on the wire is one of a closed set of self-describing typed
variants (DECIMAL, an integer, a UnixNanos string, or ABSENT). The schema states the rule
directly: a feature that is not yet warm, or whose input is unavailable, reports ABSENT with a
reason — it never reports a zero, a null, or a silently-omitted key. A missing key means "not
in the registry"; ABSENT means "registered, but currently untrustworthy" — and the schema is
explicit that conflating the two is exactly how a screen ends up silently matching on garbage. The
same distinction holds for row deltas: a key can leave a row entirely (removed — the feature left
the registry) or become ABSENT while staying on screen (still registered, currently
untrustworthy) — a consumer that conflated them would either display a stale value forever or blank
a column that never should have been touched.
Confluence: honest arithmetic over honest inputs, and a fixed defect¶
GET /v2/screener/instrument/{id} additionally returns a cross-timeframe confluence vote, derived
client-side from the rows the screener already returned (no second query). The rule: per
timeframe, trend is +1/-1/0 from price versus a long moving average, momentum is
+1/-1/0 from a bounded RSI-style oscillator, and a timeframe only votes when its inputs are
trustworthy — the row must exist, be warm, and the value must not be ABSENT.
A stale statement in the Python confluence module's own docstring says every derived timeframe
is permanently ABSENT because "the row-producing projection only emits 1m." That was true of
an earlier state of this codebase and is contradicted, at this pin, by projection.py (see
"Resolutions and rows" above) and by test_screener_bridge.py's own executable test, whose comment
says so directly: "Pins the CONFLUENCE DEFECT closed. RESOLUTIONS_PRODUCED == ("1m",) WAS the
defect." A stale docstring is prose, not behavior — this documentation follows the test, which
would fail if the claim it pins were false, not the comment, which would not. At this pin every
derived timeframe can vote once its own series is warm; a freshly-subscribed instrument's higher
timeframes still report ABSENT (and therefore vote neutral) only until enough closed bars of that
timeframe have accumulated to initialize their indicators — the same warmup behavior 1m itself
exhibits for a brand-new subscription, not a structural inability to ever vote. This documentation
does not have a live measurement of a fully-warmed multi-timeframe confluence score at this pin
(the only measurement on record predates the fix and shows the old, defective behavior), so the
end-to-end quality of a fully warm vote is unverified; the mechanism that makes it possible —
all seven resolutions being projected and eligible to vote — is verified by the cited test.
Health¶
onix.screener_health reports process state, input lag, processed/dropped counters, checkpoint
age, and connection/egress accounting — every field a real, verifiable counter, not a synthesized
conclusion. A screener that is not running reports process_state: STOPPED with a real
last_error, produced by the gateway itself when it cannot reach the process at all — this is how
"the screener is not running" is distinguished from "it is running and nothing currently matches,"
which is the failure mode a health surface exists to prevent. Even a synthesized STOPPED
document reports its connection counters honestly as all-zero (a true fact about a process that
never ran) rather than omitting the block entirely, and an egress-accounting block that has no
seam to describe is reported as an explicit null, not three zeroes that would falsely claim a
seam ran and lost nothing.
Evidence and source pins for this page
Verified. Current behaviour, confirmed in source at the pinned commit.
Verified on against the following immutable sources:
backend@efa38e04:schemas/feature_row.schema.jsonbackend@efa38e04:schemas/feature_spec.schema.jsonbackend@efa38e04:python/src/onix_engine/screener/projection.pybackend@efa38e04:python/src/onix_engine/screener/bridge.pybackend@efa38e04:python/tests/test_screener_bridge.pybackend@efa38e04:schemas/screener_health.schema.json
Status tokens are defined on the documentation and status model page. Every pin on this site is listed under versions and source pins.