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:

  1. A typed model: market_datum for a quote or a series, and market_index for a fixing. The schema of each is a constexpr table, not a type per instrument.
  2. Two codecs between ORE's text and the model: ore_key_codec reads and writes ORE market data keys, and ore_index_codec reads and writes ORE index names. Each follows ORE's own parser, and the tests compare them with ORE's output.
  3. 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 parseMarketDatum reads, 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 none for the ORE token NULL;
  • a list of (field, value) pairs. field is an enum of the 61 fields that any ORE key carries, and surface_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: identity when the field names the series, coordinate when 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::stod reads 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- or COMM-;
  • 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>&quote=<quote type>&<field>=<value>...
oresmd://<asset class>/<subject>?type=series&instrument=<type>&quote=<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, instrument and quote follow, as the ORE instrument and quote types in lower case. For a fixing, index follows, 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:

  1. A type=quote URI names exactly one market datum.
  2. Every required field of the type's schema row is present.
  3. A field that may be none is present only when it holds a value.
  4. No other key is present.
  5. 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&quote=rate&fwd_start=2D&tenor=6M term
Discount curve oresmd://ir/EUR?type=series&instrument=discount&quote=rate&curve_id=EUR1D term
Credit spread curve oresmd://credit/ACME?type=series&instrument=cds&quote=credit_spread&seniority=SNRFOR&ccy=USD&doc_clause=XR14 term
FX volatility surface oresmd://fx/EUR?type=series&instrument=fx_option&quote=rate_lnvol&ccy=USD expiry, strike_label
Calibrated FX volatility surface oresmd://fx/EUR?type=series&instrument=fx_vol_surface&quote=rate_lnvol&ccy=USD&surface_id=EURUSD_BROKER expiry, strike
Swaption cube oresmd://ir/EUR?type=series&instrument=swaption&quote=rate_nvol expiry, term, dimension
Cap or floor surface oresmd://ir/EUR?type=series&instrument=capfloor&quote=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&quote=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&quote=rate&ccy=USD oresmd://fx/EUR?type=series&instrument=fx_spot&quote=rate&ccy=USD
FXFWD/RATE/EUR/USD/6M oresmd://fx/EUR?type=quote&instrument=fx_fwd&quote=rate&ccy=USD&term=6M oresmd://fx/EUR?type=series&instrument=fx_fwd&quote=rate&ccy=USD
FX_OPTION/RATE_LNVOL/EUR/USD/1Y/ATM oresmd://fx/EUR?type=quote&instrument=fx_option&quote=rate_lnvol&ccy=USD&expiry=1Y&strike_label=ATM oresmd://fx/EUR?type=series&instrument=fx_option&quote=rate_lnvol&ccy=USD
IR_SWAP/RATE/EUR/2D/6M/10Y oresmd://ir/EUR?type=quote&instrument=ir_swap&quote=rate&fwd_start=2D&tenor=6M&term=10Y oresmd://ir/EUR?type=series&instrument=ir_swap&quote=rate&fwd_start=2D&tenor=6M
DISCOUNT/RATE/EUR/EUR1D/1Y oresmd://ir/EUR?type=quote&instrument=discount&quote=rate&curve_id=EUR1D&term=1Y oresmd://ir/EUR?type=series&instrument=discount&quote=rate&curve_id=EUR1D
MM_FUTURE/PRICE/EUR/2024-04/XICE:FEI/3M oresmd://ir/EUR?type=quote&instrument=mm_future&quote=price&contract_month=2024-04&contract=XICE:FEI&tenor=3M oresmd://ir/EUR?type=series&instrument=mm_future&quote=price&contract=XICE:FEI&tenor=3M
SWAPTION/RATE_NVOL/EUR/5Y/2Y/ATM oresmd://ir/EUR?type=quote&instrument=swaption&quote=rate_nvol&expiry=5Y&term=2Y&dimension=ATM oresmd://ir/EUR?type=series&instrument=swaption&quote=rate_nvol
CAPFLOOR/RATE_LNVOL/EUR/5Y/6M/0/0/0.03 oresmd://ir/EUR?type=quote&instrument=capfloor&quote=rate_lnvol&term=5Y&index_tenor=6M&atm=0&relative=0&strike_level=0.03 oresmd://ir/EUR?type=series&instrument=capfloor&quote=rate_lnvol&index_tenor=6M&atm=0&relative=0
CDS/CREDIT_SPREAD/ACME/SNRFOR/USD/XR14/5Y oresmd://credit/ACME?type=quote&instrument=cds&quote=credit_spread&seniority=SNRFOR&ccy=USD&doc_clause=XR14&term=5Y oresmd://credit/ACME?type=series&instrument=cds&quote=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&quote=base_correlation&term=5Y&detachment_point=0.03 oresmd://credit/CDXIG?type=series&instrument=cds_index&quote=base_correlation&term=5Y
EQUITY_OPTION/RATE_LNVOL/SP5/USD/1Y/3000 oresmd://equity/SP5?type=quote&instrument=equity_option&quote=rate_lnvol&ccy=USD&expiry=1Y&strike=3000 oresmd://equity/SP5?type=series&instrument=equity_option&quote=rate_lnvol&ccy=USD
COMMODITY_FWD/PRICE/GOLD/USD/1Y oresmd://commodity/GOLD?type=quote&instrument=commodity_fwd&quote=price&ccy=USD&expiry=1Y oresmd://commodity/GOLD?type=series&instrument=commodity_fwd&quote=price&ccy=USD
ZC_INFLATIONSWAP/RATE/EUHICPXT/5Y oresmd://inflation/EUHICPXT?type=quote&instrument=zc_inflationswap&quote=rate&term=5Y oresmd://inflation/EUHICPXT?type=series&instrument=zc_inflationswap&quote=rate
SEASONALITY/RATE/MULT/EUHICPXT/JAN oresmd://inflation/EUHICPXT?type=quote&instrument=seasonality&quote=rate&seasonality_type=MULT&month=JAN oresmd://inflation/EUHICPXT?type=series&instrument=seasonality&quote=rate&seasonality_type=MULT
BOND/PRICE/ISIN0001 oresmd://security/ISIN0001?type=quote&instrument=bond&quote=price oresmd://security/ISIN0001?type=series&instrument=bond&quote=price
CORRELATION/RATE/INDEX1/INDEX2/1Y/ATM oresmd://correlation/INDEX1?type=quote&instrument=correlation&quote=rate&index2=INDEX2&expiry=1Y&strike_label=ATM oresmd://correlation/INDEX1?type=series&instrument=correlation&quote=rate&index2=INDEX2
RATING/TRANSITION_PROBABILITY/MASTER/AA/A oresmd://rating/MASTER?type=quote&instrument=rating&quote=transition_probability&from_rating=AA&to_rating=A oresmd://rating/MASTER?type=series&instrument=rating&quote=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&quote=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&quote=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_uri holds the series URI or the fixing URI. It is the series' natural key with party_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_tbl and ores_marketdata_series_axis_values_tbl hold 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 by series_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_uri holds 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 as ore_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.ore keeps 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:

  1. ORE's own catalogue. An offline harness under external/ore/tools/datum_catalogue/ links against ORE and runs parseMarketDatum and parseIndex over every documented form and every distinct key and index name in the ORE examples. Its output is checked in under external/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.
  2. 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.
  3. Refusals. Keys and names that ORE refuses are refused, and so are the URIs the strict contract forbids.
  4. 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

Emacs 29.3 (Org mode 9.6.15)