The IR curve producer

Table of Contents

1. Summary

ores::marketdata::synthetic::ir_curve_generator is the generator that turns an ir_curve_generation_config into vendor ticks, one for each pillar of the curve. It is declared in ores.marketdata.synthetic.api/generators/ir_curve_generator.hpp. It implements i_generator, and its kind() returns the string constant ir_curve_generator_kind, spelled "ir_curve".

2. Detail

2.1. How the generator is built

make_ir_curve_feed is the builder the start paths share, so the auto-start pass and an on-demand start cannot drift.

  1. It resolves the config's template entries into ir_curve_resolved_entry records through resolve().
  2. It builds the process through map_parameters_to_yield_curve_process, passing the joined definitions and value rows. The starting short rate is the stored initial_rate value row.
  3. It names each pillar's vendor instrument from the template entry: the provider, the id scheme, the external id and the field. See the mapping.

2.2. One process step, many ticks

start() runs the same fixed-mode clock the FX feed uses. The period is one hour divided by ticks_per_hour, slept in slices of one hundred milliseconds so stop() is observed promptly, and stop_flag_ is left alone at the top of the loop for the same reason recorded in the FX feed.

Each pass does this.

  1. It advances the process once with next(). The new state is read from the process by the pricing step and its return value is discarded.
  2. It takes one timestamp for the whole batch.
  3. For every resolved entry it builds one adapter tick, each with that same vendor time, and the adapter publishes it on its ingress subject.

2.3. How a pillar tick is named and priced

A curve is a series, as oresmd: ORE Studio Market Data URI explains. Each entry names its own pillar. The loop builds a quote key through ores::marketdata::core::make_pillar_quote_key from the config's currency, the config's index tenor and the entry's own start tenor code, start date and end date, then turns that key into a URI through pillar_datum_uri. The key is an IR_SWAP/RATE quote. Its fwd_start is the start of the pillar, its tenor is the tenor of the index the curve stands for, and its term is the end of the pillar. An overnight index such as SOFR has the tenor 1D. A term index such as EURIBOR 6M has the tenor 6M. The series a point lands in therefore carries the index of the curve, and a EURIBOR 6M curve and a EURIBOR 3M curve are two series, as Funding and Projection Curves requires of a projection curve per index tenor.

The value comes from price_ir_curve_entry in ir_curve_template_resolver.cpp. That function reads the process's discount_factor() values and dispatches on the entry's curve_role to the matching curve_instrument_pricer formula, which is a simple rate for a deposit, an implied forward for a forward rate agreement, and a par-rate solve for a swap. The same function serves the curve shape preview handler, so a preview and a run cannot disagree about what a state implies.

2.4. Identity and conflict

The generator reports the vendor instruments its pillars serve through vendor_instruments(): one for each resolved entry. Two generators conflict when their lists share a vendor instrument, because both would publish values for one id. The role comes from the config, but it plays no part in what the generator owns: two curves of one currency and index tenor serve the same instruments, whatever their index family or role, and so cannot run together. See The synthetic runner for the owner that enforces it.

2.5. The facts the code shape does not show

The process's own time axis is fixed and does not follow the publish cadence. The constant ir_curve_feed_dt is one over three hundred and sixty five, so one model step is one calendar day whatever ticks_per_hour says. The cadence decides how often a human sees a curve and the daily step decides how far the curve's short rate has moved. The two are independent, and the same short-rate parameters therefore describe a much slower-looking curve at a low cadence than at a high one.

The seed is the literal 42, passed directly to the process builder. The curve path has no entropy draw and no seed column, unlike the FX path, which draws from std::random_device and logs the result. Two curve feeds started with the same config and the same parameter rows therefore produce the same sequence of states.

publish_count() counts batches, not ticks. One unit of the counter is one pass of the loop and therefore one tick per resolved entry. The info-level log line says so, spelling the counter as "batch" and printing the number of points alongside it.

3. See also

Emacs 29.3 (Org mode 9.6.15)