Investigation: the market tick data model
Table of Contents
A point-in-time record of an investigation, written on 2026-10-04 at origin/main 2a000020ea, while Rewrite the oresmd grammar as a hand-written codec checked against ORE was open.
It is a snapshot, not a description of the system. Whatever it concludes that the code must keep doing belongs on the page that owns the subject; cite that page from code, recipes and models, not this one.
1. Context
Checklist item P02 asks that every subject marketdata owns is declared once, in
its models. The one subject marketdata owns and does not declare is the root of
the republished live stream, marketdata.v1.tick. Declaring it means modelling
the message that travels on it, and two message types travel on it today:
fx_spot_tick and ir_curve_tick, both hand-written. A model declares one
subject per message, so the question became what a tick should be.
The driver is the market data model, not the checklist item. The model is meant to extend to every asset class, so the question is which tick shape still holds when it does.
2. Methodology
The code was read at 2a000020ea:
- the two tick types,
ores.marketdata.api/domain/fx_spot_tick.hppandir_curve_tick.hpp, and the subject constants intick_subjects.hpp; - the ingest path in
ores.marketdata/service/src/app/feed_ingest_loop.cpp, from decode to stored observation and republish; - every reader of the republished stream, by searching for the subject, the decode of each tick type and the client's subject helper;
feed_binding.hpp, the producer-to-owner mapping.
How other market data systems model a tick comes from general knowledge of their published protocols. It was not checked against their documentation in this investigation.
3. Paths taken
3.1. What the code does today
| Tick | Fields | Owner from | Key on the wire |
|---|---|---|---|
fx_spot_tick |
ore_key, datetime, mid |
the feed binding of its source | the whole ORE key |
ir_curve_tick |
tenant_id, party_id, series_type, metric, qualifier, asset_class, subclass, point_id, source_name, datetime, value |
the tick itself | the retired registry's split of the key |
- Both are one quote per message. The IR "curve" tick is one curve point; a
curve is the set of points published with one
datetime. - Both ingest paths end in the same step.
persist_tick_observationrebuilds an ORE key (infeed_ingest_loop.cpp,ingest_ir_curveglues the IR parts back together andpersist_tick_observationreads them withore_key_codec), and stores one observation with a value, a time, a source and an owner. - The IR tick's
asset_classandsubclassrepeat what the classifier derives from the datum. The file import already usesclassification_key_ofand the classifier instead. - The ingest loop republishes the payload unchanged, with the two
js_publishcalls infeed_ingest_loop.cpp, onmarketdata.v1.tick.<tenant>.<workspace>.<party>.<key>, where<key>is the ORE key lower-cased with its slashes turned to dots (ore_key_to_publish_subject). Its only reader is the client'sfx_spot_subscription, which decodesfx_spot_tickand has no production caller. - The FX path finds its owners by source name: one source fans out to every binding of that source, each with its tenant, workspace and party, and a tick from a source with no enabled binding is dropped with one warning per source.
- Bindings are created during tenant provisioning, by
create_theme_feed_bindings, from the FX spot generation configs only. - The two shapes differ only in how they spell the key and where the owner comes from. Neither difference is a fact about FX or about rates.
3.2. How other systems model a tick
- FIX. One market data message for every asset, as a snapshot or an incremental refresh. The instrument is a structured identifier, and the data is a list of typed entries such as bid, offer, trade and settlement.
- Bloomberg BLPAPI. A subscription names a security and a field list, and each update is field-value pairs on that security, whatever the asset.
- LSEG (Refinitiv) OMM. Message domains split by the shape of the data: a level-1 quote (MarketPrice), an order book (MarketByOrder, MarketByPrice) and lists. An FX rate and a swap rate are both MarketPrice.
- OpenGamma Strata. A quote is a number under an observable id, a standard id and a field name. A curve is built from quotes; it is not a tick.
- ORE. A market data file is date, key and value rows; the typed quote classes appear only after parsing.
None of them has an FX tick and a rates tick. The asset class lives in the identifier, and a new message type appears only when the data has a new shape.
3.3. Approaches weighed
- Keep one tick type per asset class, and model each. Rejected. Every new asset class would add a wire type that repeats what the datum schema already says, and two types would share one subject root.
- A subjects table in codegen, declaring the root with no message. Rejected. It would meet P02 and leave the two shapes in place.
- Record P02 as an exception. Rejected. The standard allows an exception only where layering forbids the reference, which is not the case.
- One general tick keyed by the datum. Chosen.
4. Conclusions
A tick is one observation of one market datum. ORE Studio already has the typed identity of a datum: the oresmd quote URI, which maps one to one onto the canonical ORE key and holds the asset class, the instrument and every field. So the tick needs no asset-specific type. The two types today are two spellings of the same fact, and one of them spells the key in the retired registry's terms.
Decided with the owner on 2026-10-04:
- One approach for every feed and every asset class. No FX path and IR path.
- One source of ownership for every feed: the feed binding. A binding maps a producer's source to the tenant, workspace and party that consume it, which is the routing the republished subject already carries. A tick is a market fact and carries no owner. Feeds that publish without a binding today, the IR curve feeds, get bindings.
- The datum is named by its oresmd quote URI.
- No backwards compatibility.
ores.syntheticchanges freely; the market data model is the driver, and it extends to every asset class.
The tick is:
| Field | Meaning |
|---|---|
oresmd_uri |
the quote URI of the datum observed, under the strict contract |
value |
the observed value, as decimal text |
observation_time |
when it was observed |
source |
the producer, which the feed binding resolves to an owner |
The value is text, as stored observations and the file import are: a decimal written as text keeps the exact digits, and the wire is generated from the model, so the type is settled before the model is written.
The republished subject keeps its shape. Its suffix is the datum's canonical
ORE key, written by ore_key_codec from the datum the URI names, lower-cased
with its slashes turned to dots, so a subscriber still addresses a stream by
the ORE key it knows.
A tick finds its owners as FX ticks do today, for every feed: by its source,
which names a producer uniquely within a tenant. Each enabled binding of that
source is one consumer. A tick from a source with no enabled binding is dropped
with one warning per source.
A curve stays a set of ticks with one observation time. A batch message, a list of ticks published as one NATS message, is a later choice to be measured, not part of the model.
5. Recommendations
- Replace the asset-specific ticks with one market tick builds it: one
modelled
market_tickdeclaringmarketdata.v1.tick, the ingest loop reading the URI withoresmd_uri_codecand classifying withclassification_key_of, bindings for every feed, and the synthetic FX and IR curve feeds publishing the one type. It closes P02 for marketdata. - The feed binding stores an
asset_classthat itsoresmd_urialready implies. The same task should derive it from the URI or drop it. - Tenant provisioning creates bindings for the IR curve generation configs as it does for the FX spot ones.
synthetic.v1.tickbelongs toores.syntheticand is declared there, under that component's own P02.
6. See also
- oresmd: ORE Studio Market Data URI — the quote URI the tick is keyed by.
- Clean the marketdata code — the task whose P02 work led here.
- Component Clean Standard — P02.