oresmd: ORE Studio Market Data URI
Table of Contents
1. Summary
oresmd is the identifier ORE Studio stores for market data. It is a real
RFC 3986 URI with the scheme
oresmd. One URI names one of three things:
- a quote: one ORE market datum, for example the 5Y CDS spread on ACME;
- a series: the quotes that differ only in their coordinates, for example every term of that CDS curve. A series is also what market data calls a curve or a surface; see Curves and surfaces are series;
- a fixing: an ORE index, for example
EUR-EURIBOR-6M, whose fixings are stored against it.
The URI is not parsed or written by hand anywhere. Three parts of
ores.marketdata own it:
- A typed model:
market_datumfor a quote or a series, andmarket_indexfor a fixing. The schema of each is aconstexprtable, not a type per instrument. - Two codecs between ORE's text and the model:
ore_key_codecreads and writes ORE market data keys, andore_index_codecreads and writes ORE index names. Each follows ORE's own parser, and the tests compare them with ORE's output. oresmd_uri_codec, which writes the model as a URI and reads it back. It walks the schema row, so it has no code per instrument type.
A key read into ORE Studio means what ORE means by it, and it goes back out as the same key. The proof is in the tests: see Tests.
Every other part of market data names its object by one of these URIs. A vendor mapping, a tick, a stored series, a composite and a screen all use the same reference, so there is no second name for a curve, a surface or a rate. The page The oresmd datum identity states the datum behind the URI.
2. The typed model
2.1. A quote: market_datum
A market_datum holds three things:
- the instrument type, an enum of all 45 that ORE's
parseMarketDatumreads, in ORE's order, followed by oresmd's own, which name series ORE has no market datum for and so have no ORE key:fx_vol_surface, the calibrated FX volatility surface; - the ORE quote type, an enum of all 16, plus
nonefor the ORE tokenNULL; - a list of
(field, value)pairs.fieldis an enum of the 61 fields that any ORE key carries, andsurface_id, which only oresmd's own types use. A value is one of five kinds: text, an ORE period or date, a decimal, a strike, or an enum code.
Equality compares every member, so a series and a quote are never equal. Free-text values, such as a ticker, a reference entity or a curve id, keep the case ORE wrote.
2.2. The schema is a table
The schema has one row per instrument type. A row lists the type's fields in key order. For each field it states two facts:
- the role:
identitywhen the field names the series,coordinatewhen it names a point in the series; - whether the field may be none: an ORE key may leave the token out, as a
CDS key may leave out its doc clause. The datum then holds an explicit
none. A missing field never means a default.
A row also names its subject, the field the URI path holds, and its asset
class. The asset classes are ir, fx, credit, equity, commodity,
inflation, security, correlation, rating, shape_profile and
generic.
The only constructor, market_datum::make, checks a datum against its row.
It refuses an unknown field, a repeated field, a missing required field and
a value of the wrong kind. So a datum that exists is valid, and code that
reads it can rely on its row. A traits map gives each field its value type,
so get<field::expiry>() is typed at compile time.
2.3. A series
A series is a datum without its coordinate fields. series_of(d) drops them,
and market_datum::make_series builds one directly. Two quotes on the same
CDS curve differ only in term, so they share one series.
The coordinate fields of a series are its axes. A series with one axis is a term structure, which is the same thing as a curve. A series with two or more axes is a surface or a cube. The next section states this in full.
2.4. A fixing: market_index
A market_index holds an index family, a subject and the family's fields.
The families are the ones ORE's parseIndex reads:
| Family | Asset class | Subject | Other fields | ORE name |
|---|---|---|---|---|
ibor |
ir |
ccy |
name, tenor (optional) |
CCY-NAME[-TENOR] |
swap |
ir |
ccy |
tag (optional), tenor |
CCY-CMS[-TAG]-TENOR |
inflation |
inflation |
name |
UKRPI |
|
fx |
fx |
unit |
source, ccy |
FX-SOURCE-CCY1-CCY2 |
equity |
equity |
name |
EQ-NAME |
|
commodity |
commodity |
name |
expiry (optional) |
COMM-NAME[-YYYY-MM[-DD]] |
power |
commodity |
name |
delivery, start, end (optional) |
POWER-NAME[-YYYY-MM-DD[-START-END]] |
bond |
security |
security |
expiry (optional) |
BOND-ID[-YYYY-MM[-DD]] |
bond_future |
security |
contract |
BOND_FUTURE-CONTRACT |
|
cmb |
security |
family |
tenor |
CMB-FAMILY-TENOR |
generic |
generic |
name |
GENERIC-NAME |
market_index::make checks the fields against the family's row in the same
way as a datum.
3. The ORE codecs
3.1. ore_key_codec
ore_key_codec::read follows ORE's marketdatumparser.cpp form by form. It
accepts each key form ORE accepts, and it puts each token in the field ORE
puts it in. It chooses a field by what a token is, such as a period, a date,
a number, C or P, or ATM, not by its position. Each form cites the ORE
form it reads. Code that differs per instrument type is a switch on the
type, with one translation unit per asset class. The build treats a missing
case as an error, so a new type cannot be forgotten.
ore_key_codec::write is the inverse. write(read(k)) is k, with one
exception: a key that spells its type or quote type with an alias ORE also
reads is written in the canonical spelling. The aliases are FX_SPOT for
FX, FX_FWD for FXFWD and RATE_GVOL for RATE_LNVOL. The import
reports each key it stores under another spelling.
The codec refuses two kinds of key that ORE accepts, because a datum could not write them back:
- a key with tokens ORE ignores, such as a trailing token after a cap/floor shift, or a sixth token on a base correlation tranche;
- a numeric slot that holds text ORE's
std::stodreads only in part.
3.2. ore_index_codec
ore_index_codec::read follows ORE's parseIndex. It tries the families in
ORE's order, and it splits the name into the fields ORE's parser reads. It
accepts a name by its structure, as ORE does when a convention file defines
the name. So CZK-CZEONIA reads with no list of index names.
ore_index_codec::write is the inverse. write(read(n)) is n, with one
exception: ORE's spaced inflation aliases, such as UK RPI, are written
without the space, as UKRPI.
The codec refuses a name that ORE completes from outside the name, because the index could not write it back:
- an empty name after
EQ-,BOND-orCOMM-; - a power index with no delivery date, which ORE takes from the evaluation date;
- a power index with a DST flag, which ORE's own catalogue refuses too.
4. The URI
4.1. Grammar
oresmd://<asset class>/<subject>?type=quote&instrument=<type>"e=<quote type>&<field>=<value>... oresmd://<asset class>/<subject>?type=series&instrument=<type>"e=<quote type>&<field>=<value>... oresmd://<asset class>/<subject>?type=fixing&index=<family>&<field>=<value>...
- The authority is the row's asset class.
- The path is the row's subject field, percent-encoded with its case kept. The query does not repeat it.
- The query starts with
type. For a quote or a series,instrumentandquotefollow, as the ORE instrument and quote types in lower case. For a fixing,indexfollows, as the family. - Each other field follows under its schema name, in schema order, with the case of its value kept.
- A field that holds none is left out.
4.2. Why a URI, and why query parameters
A URI has a real structure that a standard library parses; boost::urls
parses any RFC 3986 URI, not only HTTP URLs. The instruments differ in shape
from one asset class to the next. A positional grammar would need a variant
per asset class, or a NONE placeholder in every position a class does not
use. Query parameters name each field, so each row writes only the fields it
has.
4.3. The strict contract
The reader takes only the writer's own spelling, so one datum has one URI, and stored URIs compare as text. For a quote, this is a rule of the grammar:
- A
type=quoteURI names exactly one market datum. - Every required field of the type's schema row is present.
- A field that may be none is present only when it holds a value.
- No other key is present.
- No default applies. No partial form and no requirement form exist.
The reader refuses each of these:
- a missing key for a field that may not be none;
- an unknown key, a repeated key or an empty value;
- a coordinate field in a series URI;
- any other spelling of a valid URI, such as a reordered query or a percent-encoded letter in the path.
The reader does not ask whether ORE can spell the datum. The URI is the datum's
identity and an ORE key is a boundary format, so a quote whose type ORE has no
token for, such as hazard_rate, is a datum like any other and has a URI. What
ORE can name is the question of
ORE import and export, at the edge
where ORE XML is read and written.
A series URI follows the same rules, without the coordinate fields. A fixing URI follows them too, and the reader also requires that the index writes an ORE name.
4.4. Curves and surfaces are series
A curve, a surface and a cube are not separate kinds of identifier. Each is a
series, and the series URI is its name. This is the identity that
Add proper curve support to ores.marketdata
settled: a composite object is a market_series row, and the row is named by
its oresmd_uri. The task
Resolve a composite object from its typed identity
states it and builds the lookup by the URI's parts.
- The identity fields of the instrument type's schema row name the object. They are in the series URI, in schema order.
- The coordinate fields of the row are the object's axes. They are not in the series URI. They are in the quote URI of each point.
- A point is a quote of the series, and its coordinate values are its position on the axes. The ordered values of each axis are the object's shape, and they are stored beside the series; see Storage and the export.
- The axes are the coordinate fields the codec already reads. There is no second vocabulary of axis names that could disagree with the grammar. The decision is recorded in the story named above.
A term structure, as the page
Term Structure defines it, is a
series with one axis whose values are tenors. A bootstrapped interest rate
curve, described in Interest Rate Curves,
is one such series. The index tenor of a floating-rate curve is the tenor
identity field, and the points are the term values.
4.4.1. What the series URI is for each kind of object
| Object | Series URI | Axes |
|---|---|---|
| Interest rate swap curve | oresmd://ir/EUR?type=series&instrument=ir_swap"e=rate&fwd_start=2D&tenor=6M |
term |
| Discount curve | oresmd://ir/EUR?type=series&instrument=discount"e=rate&curve_id=EUR1D |
term |
| Credit spread curve | oresmd://credit/ACME?type=series&instrument=cds"e=credit_spread&seniority=SNRFOR&ccy=USD&doc_clause=XR14 |
term |
| FX volatility surface | oresmd://fx/EUR?type=series&instrument=fx_option"e=rate_lnvol&ccy=USD |
expiry, strike_label |
| Calibrated FX volatility surface | oresmd://fx/EUR?type=series&instrument=fx_vol_surface"e=rate_lnvol&ccy=USD&surface_id=EURUSD_BROKER |
expiry, strike |
| Swaption cube | oresmd://ir/EUR?type=series&instrument=swaption"e=rate_nvol |
expiry, term, dimension |
| Cap or floor surface | oresmd://ir/EUR?type=series&instrument=capfloor"e=rate_lnvol&index_tenor=6M&atm=0&relative=0 |
term, strike_level |
A reader that wants one component of a surface, such as the at-the-money ladder of a volatility surface, fixes the other axes and reads the rest as a term structure. The reads are described in Read one slice of a composite object over time and Read a composite object's aligned evolution.
4.4.2. A curve family has no URI of its own
A funding curve and the projection curves that depend on it form a family,
as Funding and Projection Curves
explains. A family is several term structures related by a discounting
dependency. It is not one larger term structure, so it is not one series.
Each curve of the family is a series with its own URI. The projection curve
for each floating index tenor is the series whose tenor is that index
tenor. The dependency between the curves is data of the build, for example
discount_curve_config_id on the bootstrap configuration described in
Tenor and Curve Bootstrapping Architecture.
It is not part of any identity.
4.4.3. A calibrated surface is a series of its own
A bootstrapped curve and the swap quotes it was solved from are two series. The
curve's points are a discount series named by its curve_id, whose identity
says the points are inferred, and the curve's blueprint is the only writer of
that series. A calibrated volatility surface stands in the same relation to
the quotes it was calibrated to. The fx_option series holds observed quotes and has a
writer per contributor; the calibrated surface is a separate series of the
instrument type fx_vol_surface, named by the pair and a surface_id in the
place of curve_id, with the axes expiry and strike, as in
oresmd://fx/EUR?type=series&instrument=fx_vol_surface"e=rate_lnvol&ccy=USD&surface_id=EURUSD_BROKER.
fx_vol_surface is oresmd's own type: ORE has no market datum for a calibrated
surface, so it has no ORE key, and the key grammar neither reads nor writes it. strike takes the strike grammar the equity and commodity surfaces
already use, so a grid point is a delta of a call or a put, or an absolute
strike. The series is created with the surface blueprint and written only by
its calibration, as
Constructing a volatility surface: from quotes to a published set
describes.
4.4.4. What the grammar does not hold
The grammar names what is observed. It does not hold three ideas of the first design:
- the curve role, discount or projection. A role says how a consumer uses a curve, and the same series can serve more than one use. A role is a property of the consumer, such as a bootstrap configuration or a synthetic feed, and each of them states it in its own data;
- a requirement that leaves fields open, for example "the EUR discount curve" before a source is chosen. The pages Market Data Requirement and Market Data Configuration describe requirements and their resolution;
- the resolution of a requirement to an instance. A URI is strict, so it names exactly one datum, one series or one index.
A fixing URI follows the same strict rules as a quote URI. It names an ORE index. It does not name a curve, because an index is a rate that is fixed, and a curve is a series of quotes.
5. Worked examples
Each row below is the output of the codecs for the ORE text in the first column. The quote URI and the series URI are written from the datum the key reads into.
5.1. Quotes and series
| ORE key | Quote URI | Series URI |
|---|---|---|
FX/RATE/EUR/USD |
oresmd://fx/EUR?type=quote&instrument=fx_spot"e=rate&ccy=USD |
oresmd://fx/EUR?type=series&instrument=fx_spot"e=rate&ccy=USD |
FXFWD/RATE/EUR/USD/6M |
oresmd://fx/EUR?type=quote&instrument=fx_fwd"e=rate&ccy=USD&term=6M |
oresmd://fx/EUR?type=series&instrument=fx_fwd"e=rate&ccy=USD |
FX_OPTION/RATE_LNVOL/EUR/USD/1Y/ATM |
oresmd://fx/EUR?type=quote&instrument=fx_option"e=rate_lnvol&ccy=USD&expiry=1Y&strike_label=ATM |
oresmd://fx/EUR?type=series&instrument=fx_option"e=rate_lnvol&ccy=USD |
IR_SWAP/RATE/EUR/2D/6M/10Y |
oresmd://ir/EUR?type=quote&instrument=ir_swap"e=rate&fwd_start=2D&tenor=6M&term=10Y |
oresmd://ir/EUR?type=series&instrument=ir_swap"e=rate&fwd_start=2D&tenor=6M |
DISCOUNT/RATE/EUR/EUR1D/1Y |
oresmd://ir/EUR?type=quote&instrument=discount"e=rate&curve_id=EUR1D&term=1Y |
oresmd://ir/EUR?type=series&instrument=discount"e=rate&curve_id=EUR1D |
MM_FUTURE/PRICE/EUR/2024-04/XICE:FEI/3M |
oresmd://ir/EUR?type=quote&instrument=mm_future"e=price&contract_month=2024-04&contract=XICE:FEI&tenor=3M |
oresmd://ir/EUR?type=series&instrument=mm_future"e=price&contract=XICE:FEI&tenor=3M |
SWAPTION/RATE_NVOL/EUR/5Y/2Y/ATM |
oresmd://ir/EUR?type=quote&instrument=swaption"e=rate_nvol&expiry=5Y&term=2Y&dimension=ATM |
oresmd://ir/EUR?type=series&instrument=swaption"e=rate_nvol |
CAPFLOOR/RATE_LNVOL/EUR/5Y/6M/0/0/0.03 |
oresmd://ir/EUR?type=quote&instrument=capfloor"e=rate_lnvol&term=5Y&index_tenor=6M&atm=0&relative=0&strike_level=0.03 |
oresmd://ir/EUR?type=series&instrument=capfloor"e=rate_lnvol&index_tenor=6M&atm=0&relative=0 |
CDS/CREDIT_SPREAD/ACME/SNRFOR/USD/XR14/5Y |
oresmd://credit/ACME?type=quote&instrument=cds"e=credit_spread&seniority=SNRFOR&ccy=USD&doc_clause=XR14&term=5Y |
oresmd://credit/ACME?type=series&instrument=cds"e=credit_spread&seniority=SNRFOR&ccy=USD&doc_clause=XR14 |
CDS_INDEX/BASE_CORRELATION/CDXIG/5Y/0.03 |
oresmd://credit/CDXIG?type=quote&instrument=cds_index"e=base_correlation&term=5Y&detachment_point=0.03 |
oresmd://credit/CDXIG?type=series&instrument=cds_index"e=base_correlation&term=5Y |
EQUITY_OPTION/RATE_LNVOL/SP5/USD/1Y/3000 |
oresmd://equity/SP5?type=quote&instrument=equity_option"e=rate_lnvol&ccy=USD&expiry=1Y&strike=3000 |
oresmd://equity/SP5?type=series&instrument=equity_option"e=rate_lnvol&ccy=USD |
COMMODITY_FWD/PRICE/GOLD/USD/1Y |
oresmd://commodity/GOLD?type=quote&instrument=commodity_fwd"e=price&ccy=USD&expiry=1Y |
oresmd://commodity/GOLD?type=series&instrument=commodity_fwd"e=price&ccy=USD |
ZC_INFLATIONSWAP/RATE/EUHICPXT/5Y |
oresmd://inflation/EUHICPXT?type=quote&instrument=zc_inflationswap"e=rate&term=5Y |
oresmd://inflation/EUHICPXT?type=series&instrument=zc_inflationswap"e=rate |
SEASONALITY/RATE/MULT/EUHICPXT/JAN |
oresmd://inflation/EUHICPXT?type=quote&instrument=seasonality"e=rate&seasonality_type=MULT&month=JAN |
oresmd://inflation/EUHICPXT?type=series&instrument=seasonality"e=rate&seasonality_type=MULT |
BOND/PRICE/ISIN0001 |
oresmd://security/ISIN0001?type=quote&instrument=bond"e=price |
oresmd://security/ISIN0001?type=series&instrument=bond"e=price |
CORRELATION/RATE/INDEX1/INDEX2/1Y/ATM |
oresmd://correlation/INDEX1?type=quote&instrument=correlation"e=rate&index2=INDEX2&expiry=1Y&strike_label=ATM |
oresmd://correlation/INDEX1?type=series&instrument=correlation"e=rate&index2=INDEX2 |
RATING/TRANSITION_PROBABILITY/MASTER/AA/A |
oresmd://rating/MASTER?type=quote&instrument=rating"e=transition_probability&from_rating=AA&to_rating=A |
oresmd://rating/MASTER?type=series&instrument=rating"e=transition_probability |
SHAPE_PROFILE/SHAPE_FACTOR/PJM_WH_RT/2027-02-02/0/HOUR |
oresmd://shape_profile/PJM_WH_RT?type=quote&instrument=shape_profile"e=shape_factor&delivery_date=2027-02-02&start_time_in_sec=0&time_unit=HOUR |
oresmd://shape_profile/PJM_WH_RT?type=series&instrument=shape_profile"e=shape_factor&time_unit=HOUR |
FX_SPOT/RATE/EUR/USD reads as the first row and writes back as
FX/RATE/EUR/USD, its canonical spelling.
5.2. Curves and surfaces
The series URI of a curve or surface is the series URI in the third column of the table above. The table in What the series URI is for each kind of object gives the axes of each.
5.3. Fixings
| ORE index name | Fixing URI |
|---|---|
EUR-EURIBOR-6M |
oresmd://ir/EUR?type=fixing&index=ibor&name=EURIBOR&tenor=6M |
USD-SOFR |
oresmd://ir/USD?type=fixing&index=ibor&name=SOFR |
EUR-CMS-10Y |
oresmd://ir/EUR?type=fixing&index=swap&tenor=10Y |
UKRPI |
oresmd://inflation/UKRPI?type=fixing&index=inflation |
FX-ECB-EUR-USD |
oresmd://fx/EUR?type=fixing&index=fx&source=ECB&ccy=USD |
EQ-RIC:.SPX |
oresmd://equity/RIC:.SPX?type=fixing&index=equity |
COMM-NYMEX:CL-2025-01 |
oresmd://commodity/NYMEX:CL?type=fixing&index=commodity&expiry=2025-01 |
POWER-ICE:PDQ-2021-01-01-3600-7200 |
oresmd://commodity/ICE:PDQ?type=fixing&index=power&delivery=2021-01-01&start=3600&end=7200 |
BOND_FUTURE-TYM26 |
oresmd://security/TYM26?type=fixing&index=bond_future |
CMB-US-TREASURY-10Y |
oresmd://security/US-TREASURY?type=fixing&index=cmb&tenor=10Y |
GENERIC-JuniorNote |
oresmd://generic/JuniorNote?type=fixing&index=generic |
UK RPI reads as UKRPI and gets the same URI.
6. Storage and the export
market_series.oresmd_uriholds the series URI or the fixing URI. It is the series' natural key withparty_id, and the import finds a series by it. A curve or a surface is one row of this table; see Series and observation.ores_marketdata_series_axes_tblandores_marketdata_series_axis_values_tblhold the shape of a series: one row for each coordinate field of its instrument type, and the ordered values of each. They join the series byseries_id. A build writes the shape from the points it published. An import writes it from the terms its file states. The task Give a composite object its shape tables records the design.market_observations.oresmd_uriholds the quote URI. No row stores the raw ORE key.- The export writes each observation's key as
ore_key_codec::write(oresmd_uri_codec::read(uri)), and each fixing's index name asore_index_codec::write(oresmd_uri_codec::read_index(uri)). - The import reads each key or index name with its ORE codec. A key or a name the codec refuses is reported and skipped, one row at a time.
ores.orekeeps only the file reader, which yields the date, the key text and the value of each line. It does not split a key.
7. Tests
In order of authority:
- ORE's own catalogue. An offline harness under
external/ore/tools/datum_catalogue/links against ORE and runsparseMarketDatumandparseIndexover every documented form and every distinct key and index name in the ORE examples. Its output is checked in underexternal/ore/catalogue/. The codec tests match each form and each corpus key with ORE's reading, field by field. Each deliberate difference is listed in the test with its reason. - Round trips. Over the whole catalogue: key to datum to key, datum to URI to datum, and index name to index to URI to index. Each series URI parses.
- Refusals. Keys and names that ORE refuses are refused, and so are the URIs the strict contract forbids.
- The database. The corpus import stores each value under its own key, and the export writes back the file it imported.
8. History
The first design of this record, in sprint 24, proposed a URI with defaults,
partial requirement forms and one identifier struct per asset class. Sprint 26
generated that grammar from twelve quote-type models and a SQL key shape
table. The generated parser was 2,585 lines of C++ inside a template with 187
tags, and the models only chose which hand-written function ran.
Rewrite the oresmd grammar as a hand-written codec checked against ORE
replaced it with the model and codecs above, and deleted the templates, the
models, the key shape table and series_key_registry.
9. See also
- Market Data Architecture — the structure note that orders this cluster, and where to read this page in it.
- Market Data Requirements and Resolution — the structure note that orders this cluster, and where to read this page in it.
- Rewrite the oresmd grammar as a hand-written codec checked against ORE — the story whose decisions this record states.
- ORE market data catalogue — the per-type quote-key shapes ORE documents.
- Gap analysis: ORE Studio identifiers vs ORE's real addressing scheme — the gaps the first design set out to close.
- External market data identifiers — the ORE, Bloomberg and Reuters notations the first design surveyed.
- Market Data Identifier — the
market_serieskey thatoresmdreplaced. - Market Data Requirement — the logical requirement, which the quote grammar does not express.
- Dual-curve discount+projection model — the curve-role work, which a consumer states and the URI does not.
- The oresmd datum identity — the datum, the series and the schema row behind the URI.
- Series and observation — the tables that hold a series and its points.
- Term Structures and Tenors — the hub for curves, surfaces and tenors.
- Term Structure — what a curve is, and how it differs from a scalar.
- Funding and Projection Curves — the family of curves that share no single URI.
- Add proper curve support to ores.marketdata — the story that made a curve a series.
- The IR curve producer — the synthetic feed that publishes a curve.