Skip to navigation

Volume ladder

Client-configured volume ladders — cumulative-quantity buckets priced from the same merge the agg_book channel renders, over the same source vocabulary (margins and MID normalisation included), conflated at the topic’s mandatory tier.

URL wss://marketdata.immix.xyz/

Topic vol_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 volume-ladder subscription — channel is the schema-required const discriminator; the composed topic vol_book.{name}.{tier} is echoed in the ack and on every frame. There is no maxDepth: a ladder walks the merge as deep as its buckets need
> > channelstringYesvol_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.
> > pricingModelstringYeshow each bucket prices its swept quantity — VWAP (128-bit notional over the achieved quantity, one division per bucket, nearest at the output price scale, ties away from zero), BEST (the side’s first touch, identical for every bucket), WORST (the marginal price of the level that crossed the threshold; for an unfilled bucket, the deepest level seen). A value outside the enum answers bad_ladder. One of VWAP, BEST, WORST.
> > bucketSizesarray of stringsYescumulative quantity thresholds as decimal strings at the RESOLVED output qty scale (digits with one optional point; a finer fraction than the scale answers bad_ladder) — positive, distinct by value (“2” and “2.0” collide), submitted in ANY order: ordering is presentation, not semantics — the server sorts, the response ascends, and the adj arrays pair with the sizes as submitted
> > bidAdjBpsarray of integersNooptional per-bucket bid margins, positional with bucketSizes as submitted (length must match; absent means all zero) — subtracts from the bucket’s model price, rounding down
> > askAdjBpsarray of integersNooptional per-bucket ask margins, positional with bucketSizes as submitted — adds to the bucket’s model price, rounding up
> > replacebooleanNomarks an intentional reconfigure of this session’s live (name, tier) — without it a differing config answers name_in_use. Ladder equality is by sorted sizes with their paired margins and the model, so re-submitting the same set in a different order is the idempotent re-ack, not a replace
> > 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
eventstringYesvol_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”; 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
> pricingModelstringYesthe configured model, echoed on every image. One of VWAP, BEST, WORST.
> bucketsarray of objectsYesascending by size
> > sizestringYesthe cumulative threshold at qtyScale
> > bidobjectNoone side of one bucket — absent entirely when the book has no levels on that side (one-sided markets publish what they have)
> > > cumstringYesthe achieved cumulative quantity at qtyScale — the bucket’s size when filled, the side’s whole depth when exhausted
> > > pxstringYesthe model price before the bucket margin, at priceScale
> > > adjPxstringYesthe model price with the bucket’s directional margin applied — bid floors, ask ceils
> > > notionalstringYesthe EXACT swept cost (sum of effective price x quantity taken) at scale priceScale + qtyScale — the VWAP numerator, equally meaningful under BEST/WORST. Can exceed a 64-bit integer: parse as an arbitrary-precision decimal
> > > filledbooleanYeswhether the side reached this bucket’s threshold
> > askobjectNoone side of one bucket — absent entirely when the book has no levels on that side (one-sided markets publish what they have)
> > > cumstringYesthe achieved cumulative quantity at qtyScale — the bucket’s size when filled, the side’s whole depth when exhausted
> > > pxstringYesthe model price before the bucket margin, at priceScale
> > > adjPxstringYesthe model price with the bucket’s directional margin applied — bid floors, ask ceils
> > > notionalstringYesthe EXACT swept cost (sum of effective price x quantity taken) at scale priceScale + qtyScale — the VWAP numerator, equally meaningful under BEST/WORST. Can exceed a 64-bit integer: parse as an arbitrary-precision decimal
> > > filledbooleanYeswhether the side reached this bucket’s threshold
> > spreadBpsstringNo(ask adjPx - bid adjPx) over the adjusted mid, in bps at a fixed two-decimal scale, nearest with ties away from zero — present whenever both sides price the bucket (partial fills included: the spread of the achieved prices is what the quoted numbers say) and the adjusted mid is nonzero (a maximal bid margin can floor a sub-tick-scale price to an adjusted zero; the field is omitted rather than divided by zero), and negative when independent venues cross

Behaviour

Subscribed with an OBJECT entry carrying the composition plus the ladder: pricingModel (VWAP = 128-bit volume-weighted average, one division per bucket, rounded to nearest at the output price scale with ties away from zero — an estimate, not a quote, so directional rounding does not apply; BEST = the side’s first touch; WORST = the marginal price of the level that crossed the threshold), bucketSizes as decimal strings at the resolved output qty scale (positive, distinct, ANY order — the server sorts, the response ascends, and the optional positional bidAdjBps/askAdjBps arrays pair with the sizes AS SUBMITTED), all validated to the closed codes (a present-but-wrong ladder value answers bad_ladder). The ack, identity, replace, status-frame, and reconnect semantics are agg_book’s exactly; the two words are distinct topics and a vol_book implies no agg_book subscription. Frames are full images: buckets ascend by size; each side present carries its achieved cumulative quantity (the threshold when filled, the whole side when exhausted — filled: false, every deeper bucket sharing that tail), the model price raw (px) and with the bucket’s directional margin (adjPx — bid down, ask up), and the EXACT swept notional at priceScale + qtyScale (the VWAP numerator, equally meaningful under BEST/WORST as the swept cost — it can exceed a 64-bit integer, so parse it as the arbitrary-precision decimal string it is). A side with no levels is absent (one-sided markets publish); spreadBps rides every bucket both sides price — partial fills included, over the achieved prices — computed from the adjusted prices over the adjusted mid at a fixed two-decimal scale, nearest with ties away from zero, and negative when independent venues cross. There is no maxDepth: a ladder walks as deep as its buckets need.