Skip to navigation

Aggregated book

Client-configured aggregated multi-exchange books, computed merge-on-read over the gateway’s book folds and conflated at the topic’s mandatory tier (composites have no raw lane).

URL wss://marketdata.immix.xyz/

Topic agg_book.{name}.{tier}

Topic parameterDescription
namethe client-chosen handle, 1-64 of [A-Za-z0-9._-] (dots legal — the tier is the single trailing dot-separated token, parsed from the right)
tierthe mandatory conflation tier, from the configured set

Request parameters

ParameterTypeRequiredDescription
opstringYessubscribe
reqIdstringNoclient correlation id, echoed verbatim on the ack
paramsobjectYes
> topicsarray of objectsYesone composite subscription — channel is the schema-required const discriminator; the composed topic agg_book.{name}.{tier} is echoed in the ack and on every frame, and is the handle unsubscribe takes
> > channelstringYesagg_book
> > namestringYesthe client-chosen handle, scoped per session
> > tierstringYesThe conflation tier vocabulary — derived from the gateway’s configured tier set (xyz.immix.md.gateway.tiers.ms); CI validates the two agree. One of 100ms, 250ms, 500ms.
> > maxDepthintegerNomerged (post-coalesce) levels served per side — not venue contributions; absent serves the deployment’s configured maximum (50 by default)
> > replacebooleanNomarks an intentional reconfigure of this session’s live (name, tier) — without it a differing config answers name_in_use
> > sourcesarray of objectsYesone configured source of a composite — shared verbatim by agg_book and vol_book entries (the ladder prices the same adjusted merge)
> > > instrumentstringYesrefdata’s canonical symbol; all sources must share one base asset, and every source’s quantities must already speak that base asset — a source whose contractMultiplier states a ratio other than 1 changes the unit and is rejected (unit_mismatch). An unstated multiplier and a multiplier of exactly 1 (one contract is one base unit) are both unit-preserving and serve normally.
> > > bidMarginBpsintegerNosubtracts from bid prices, rounding down — never flattering
> > > askMarginBpsintegerNoadds to ask prices, rounding up
> > > normalisationModestringNoMID normalises the quote leg through the internal index: the source’s prices multiply by the current (source-quote -> normalisedAsset) rate before margins, rounding directionally (bids down, asks up). A source whose pair has no current rate within the index’s 30 s consumer window is excluded from the merge and announced (reason fx_expired) — never mixed in raw. The touch modes (FAR_TOUCH/NEAR_TOUCH) are reserved vocabulary and answer bad_normalisation. One of NONE, MID.
> > > normalisedAssetstringNothe normalisation target’s asset symbol — required with MID (enforced server-side: bad_normalisation when missing, unknown, equal to the composite’s base asset, or equal to the source’s own quote asset); meaningless with NONE (also bad_normalisation). Targets are validated per source and are NOT required to agree across sources — the unit consistency of the composite (every source priced in one quote unit, normalised or native) is the subscriber’s design, not checked by the gateway
> > > disabledbooleanNoexcluded from the merge by configuration (never announced as degradation); still counts toward the composite’s output scales, so toggling it through a replace never changes the topic’s scales

Response parameters

ParameterTypeRequiredDescription
opstringYesOne of auth, subscribe, unsubscribe, error.
reqIdstringNo
successbooleanYes
errorstringNoOne of auth_required, invalid_token, unknown_op, malformed, too_many_topics.
topicsarray of objectsNoanswers ride in request order — the correlator for entries whose topic could not compose (error rows echo the composed topic best-effort, absent parts empty)
> topicstringYes
> statestringYeslive: data flows now (latest-image lanes — tob, mark, funding, oi, ticker — answer live whether or not an image is held yet; the first image flows on arrival). syncing (book topics): the gateway holds no synced book yet — the snapshot is pushed unprompted the moment it syncs, at latest one connector restate interval. error on a plain (string-topic) row, no code: the topic composed nothing, and it never arms retroactively. Either the channel is outside the vocabulary or the tier suffix is outside the configured set (an unrecognized suffix reads as part of the symbol) — correct the topic — or the symbol is unknown to the gateway’s refdata fold (derivative symbols are refdata’s ccxt spelling BASE/QUOTE:SETTLE, e.g. OKX@BTC/USDT:USDT — there is no :SWAP form) — resubscribe once refdata holds the instrument. Composite rows: live iff every enabled source book is synced, stated within the topic’s output scales, AND every required FX pair is current (the full image pushes immediately); syncing otherwise, kept by the first tick at which the composite becomes computable — a source out for precision is named by a degraded frame at that tick, since syncing alone does not say it; a composite error row carries code. One of live, syncing, error, unsubscribed, not_subscribed.
> priceScaleintegerNocomposite live/syncing rows only — the resolved output price scale (max over all configured sources’ reference records, disabled included), bounding the fractional digits any price on the topic can carry; never padding. It holds until a status rescaled frame on the topic restates it
> qtyScaleintegerNocomposite rows only — the resolved output qty scale; as priceScale
> codestringNocomposite error rows only — the closed vocabulary clients parse; every validation failure maps to exactly one code. One of unknown_channel, bad_name, bad_tier, tier_required, name_in_use, unknown_instrument, duplicate_source, mixed_base_asset, unit_mismatch, bad_normalisation, bad_depth, bad_ladder, too_many_sources, budget_exceeded, malformed.
> messagestringNosupplementary human text naming the offending source or field — never something a client parses

Push data parameters

ParameterTypeRequiredDescription
eventstringYesagg_book
topicstringYes
seqstringYesthe stream position at render — the global sequence of the last event dispatched before this frame rendered: provenance, not a per-topic chain (gaps between a topic’s frames are expected and meaningless)
seqTsstringYessequencer epoch-ns of that event, as a decimal string
tsstringNothe newest included source’s exchangeTsNs, written by one rule on every form: present when an included source carries one, absent otherwise — never “0”. Absent therefore on the stale marker, on a degraded frame that leaves no source included, and on the empty image of a composite whose sources are all config-disabled; and always absent on rescaled
statusstringNostatus frames only; data is absent. degraded, recovered and stale are source transitions; rescaled says the topic’s output scales changed (a source’s reference record now states another scale pair, so the gateway rebound the composite) — the pair the subscribe ack echoed no longer bounds the numbers on the topic, and the frame carries the pair that does. Every frame after it is rendered at the new pair. One of degraded, recovered, rescaled, stale.
priceScaleintegerNorescaled frames only — the topic’s new output price scale: the same number the subscribe ack’s priceScale carried, restated
qtyScaleintegerNorescaled frames only — the topic’s new output quantity scale
excludedarray of objectsNodegraded frames — the sources that just left the merge, and for precision also a source that cannot enter it (announced once, even if the topic has never shown it in src) or whose announced reason became precision
> sstringYesthe source’s refdata canonical symbol
> reasonstringYesthe closed vocabulary, complete so consumers bind once; fx_expired = the source’s MID pair has no current index rate, precision = the source’s book is stated at more decimal places than the topic’s output scales hold, so its levels cannot enter the merge without dropping digits (it rejoins, as recovered, once its book is within the topic’s scales again — the topic was rescaled wide enough, or the venue’s next snapshot states a coarser scale; a rescale that widens only part of the way leaves it out), stale = any other unservable book; delisted and disabled are reserved. One of stale, fx_expired, precision, delisted, disabled.
includedarray of objectsNorecovered frames — the sources that just rejoined
> sstringYes
dataobjectNo
> namestringYesthe client-chosen composite name
> bidsarray of objectsYesmerged levels, best (highest effective bid) first
> > pxstringYesthe effective price, at the topic’s output priceScale — the ack’s, or the latest rescaled frame’s
> > qtystringYestotal resting quantity across contributions, at the topic’s output qtyScale
> > srcarray of objectsYescontributions in source-config order, a source’s own entries best-first; usually one per venue — several when a margin collapses adjacent raw levels onto one effective price
> > > sstringYesthe source’s refdata canonical symbol
> > > pxstringYesthe venue-native raw price, as the source’s book states it — shortest form, and never more fractional digits than the topic’s priceScale (a source stated finer is out of the merge, reason precision)
> > > qtystringYesthe resting quantity, as the source’s book states it; bounded by the topic’s qtyScale the same way
> asksarray of objectsYesmerged levels, best (lowest effective ask) first
> > pxstringYesthe effective price, at the topic’s output priceScale — the ack’s, or the latest rescaled frame’s
> > qtystringYestotal resting quantity across contributions, at the topic’s output qtyScale
> > srcarray of objectsYescontributions in source-config order, a source’s own entries best-first; usually one per venue — several when a margin collapses adjacent raw levels onto one effective price
> > > sstringYesthe source’s refdata canonical symbol
> > > pxstringYesthe venue-native raw price, as the source’s book states it — shortest form, and never more fractional digits than the topic’s priceScale (a source stated finer is out of the merge, reason precision)
> > > qtystringYesthe resting quantity, as the source’s book states it; bounded by the topic’s qtyScale the same way

Behaviour

Subscribed with an OBJECT entry in params.topics carrying the composition (sources with optional per-side bps margins, optional maxDepth); the per-topic ack echoes the resolved output priceScale/qtyScale, which bound every number the topic can carry. The name is the client’s handle, scoped per session; config binds to (name, tier) at first subscribe — an identical re-subscribe is an idempotent re-ack, a different config without “replace”: true answers name_in_use, and “replace”: true swaps the config atomically (full validation first: a failing replace answers its error code and leaves the old config live; the subscription survives, the ack re-echoes the possibly-changed scales, the next frame renders the new config; the tier is topic identity and cannot be replaced). Frames are full images at each tick — idempotent, no chain semantics, sides always present ([] for a one-sided market) — with merged levels as objects: the effective price that ordered the level, total quantity at the output scales, and per-venue src attribution (venue-native raw prices at each venue’s own scales). Sources that cannot be served (a stale book) leave the merge and status frames announce the transitions: degraded/recovered name the sources, and when every enabled source is out the topic behaves as the book channel’s stale — marker, then silence, then the healing image; between transitions the current exclusion set is always the configured sources minus those visible in src. Reasons served: fx_expired for a MID source whose index pair lapsed the 30 s consumer window, precision for a source whose book is stated finer than the topic’s output scales, stale for any other unservable-book transition; delisted is reserved vocabulary. precision is the one exclusion announced for a source that never entered the merge: a source merely not synced yet is covered by the ack’s syncing promise and joins in silence, but a precision source holds a servable book and stays out until the catalog or the venue moves, so its degraded frame is sent once whether it left the merge or was never in it — and when that leaves no source included, ahead of the stale posture’s silence (the frame then carries no ts, names every source that left with it, and a topic that has not served yet gets no stale marker after it: syncing still stands). precision is also the one reason said over another: a source already announced stale or fx_expired whose exclusion turns into precision is named again, in a degraded frame carrying the new reason, because the first word promised a heal within a restate interval or a rate tick and this one waits on the catalog; no other change of reason is re-announced. The output scales are the pair the ack echoed; they can change under a live subscription when a source’s reference record restates its scales, and a status rescaled frame carrying the new priceScale and qtyScale says so before the first frame rendered at them. Unsubscribe is by the topic string alone, byte-identical to the echo — the config is never re-sent. Nothing survives the socket: on reconnect, resubscribe with the full config and the ack + immediate image behave as a first subscribe.