Tenor and Curve Bootstrapping Architecture
Table of Contents
Summary
A tenor label (1M, 1F, 1Y 1RQ) travels through two code domains
before it becomes a point on a bootstrapped curve. The vocabulary
lives in ores.refdata: label catalogs, conventions, anchors, and
schedules are all reference-data entities. The runtime lives in
ores.refdata.api and ores.marketdata: tenor_resolution.cpp
resolves a tenor code to a date under a convention, and the bootstrap
chain (ir_curve_bootstrap_configs → ir_curve_bootstrap_pillars →
curve_republish_resolver → curve_bootstrap_engine) turns an ordered
list of tenor pairs into a solved discount-factor curve.
The unifying model, decided 2026-08-09 (FOMC analysis, story * Decisions
D1–D4): a tenor resolves as anchor + a calendar offset + n steps along
a named schedule axis. Calendar arithmetic (days, weeks, months,
years) is one axis; a schedule — a set of externally-given dates such
as IMM roll dates or FOMC meeting dates — is the other. The schedule's
dates come either from a closed-form rule (ROLL_QUARTER) or from
typed events on a calendar (FOMC_MEETING). FOMC tenors 1F..=nF=
are the concrete instance of the schedule axis; the FOMC-dated SOFR
short end is one configuration of the existing engine, not new engine
code.
Detail
The two code domains
| Concern | Where it lives |
|---|---|
| Tenor label catalog, conventions, anchors, schedules, calendars, event store | ores.refdata (codegen'd entities, bitemporal SQL) |
| Tenor → date resolution (the resolver) | ores.refdata/api/src/domain/tenor_resolution.cpp |
| Bootstrap config and pillar list | ores.refdata (ir_curve_bootstrap_configs, ir_curve_bootstrap_pillars) |
| Pillar dates + observed quotes + fixed-leg schedules | ores.marketdata/service/src/curve_republish_resolver.cpp |
| The numerical solve (discount factors) | curve_bootstrap_engine (ores.analytics.quant.service) |
The boundary is clean: refdata describes (codes, conventions, schedules), marketdata executes (resolves, looks up quotes, solves). The resolver is the bridge — it is the only place a tenor code becomes a date.
The tenor vocabulary (ores.refdata)
The domain concept is in Tenor; here is the persisted shape.
| Entity | Role | Key columns |
|---|---|---|
tenors |
Label catalog. Identity only — no date math. | code, display_name, kind (PERIOD/SPECIAL), unit (DAY/WEEK/MONTH/YEAR/NONE), multiplier |
tenor_kinds |
Vocabulary: PERIOD (calendar arithmetic), SPECIAL (named point) |
|
tenor_units |
Vocabulary: DAY, WEEK, MONTH, YEAR, NONE |
|
tenor_anchors |
Vocabulary: SPOT, TODAY, TOMORROW, NEAR_LEG, IMM_ROLL, NONE |
|
tenor_conventions |
Picks the measured_from anchor and the resolution_algorithm | measured_from, resolution_algorithm |
tenor_convention_resolutions |
Per-tenor overrides and offsets | convention_code, tenor_code, anchor_override, offset_unit, offset_multiplier, schedule_code (new, nullable), schedule_step_count (new, nullable) |
tenor_resolution_algorithms |
Vocabulary: ANCHOR_OFFSET, SCHEDULE_STEP (new), IMM_ROLL (superseded) |
Seed data (populate scripts): O/N (SPECIAL/NONE, no multiplier) and
SPOT (PERIOD/DAY/0) are tenors; RATES_SPOT_FORWARD and
FX_SWAP_NEAR_LEG are ANCHOR_OFFSET conventions; CREDIT_CDS_IMM is
the IMM convention.
Resolution semantics: anchor + steps along an axis
tenor_resolution.cpp implements
ores::refdata::api::domain::resolve_end_date(): given a tenor, a
convention, a resolution row, a horizon date and a spot date, it
returns the maturity date. Two branches:
- ANCHOR_OFFSET (implemented): resolved date = anchor + n × unit.
The anchor comes from the resolution row's
anchor_overrideif set, else from the convention'smeasured_from;resolve_anchor_date()handlesSPOT,TODAY,TOMORROW(NEAR_LEGandIMM_ROLLneed convention-specific handling). The offset is the resolution row'soffset_unit=/=offset_multiplierif set, else the tenor's ownunit=/=multiplier— so a bare1Mresolves as "spot + 1 month" underRATES_SPOT_FORWARD. - SCHEDULE_STEP (decided, to be implemented): resolved date =
walk(anchor + calendar offset, schedule, steps). The calendar offset
uses the same
offset_unit=/=offset_multipliercolumns; the schedule axis isschedule_code+schedule_step_count. The resolver walks the schedule's date set from (anchor + offset) forward, taking the n-th date on-or-after it.
Today the resolver throws for IMM_ROLL and for ROLL_QUARTER offsets
(std::logic_error) — the seeded rows document the gap. Under the
unified model, SCHEDULE_STEP implements both; see the schedule
section below.
The calendar and event cluster (ores.refdata)
The domain model is in Calendars, Holidays and Events and Calendar Events; here is the persisted shape.
| Entity | Role |
|---|---|
calendars |
Named, code-keyed calendar: code, calendar_type, country_code, source, is_editable, base_calendar_code |
calendar_types |
Vocabulary: public_holiday, central_bank_meeting, data_release, financial_centre, other |
calendar_rules |
Holiday recurrence grammar: kind (fixed_date, …), month, day, weekday, occurrence, day_offset, shift |
calendar_exceptions |
Ad-hoc holiday overrides: exception_date, is_business_day |
calendar_date |
The materialized business-day grid: one row per (calendar, date), is_business_day, source |
currency_calendars |
Currency ↔ calendar junction (a currency can need several calendars) |
calendar_event (new, decided) |
Typed event store: id, calendar_code, event_date, diary_entry_type, name, description (nullable), source (nullable provenance), plus the standard audit/temporal columns |
diary_entry_types (new, decided) |
Vocabulary: holiday, central_bank_meeting, data_release, other — open-ended |
The architectural split: holidays keep their recurrence machinery
(calendar_rules, calendar_exceptions) and feed the
calendar_date grid; calendar_event holds the non-holiday kinds.
The holiday entry type stays in the vocabulary so the whole
classification lives in one place. Events are diary entries with no
downstream effect modelled — the Diary/Effects split
(The Diary/Effects Model)
keeps calendar_event stable regardless of future effect types.
Template/Instance recurrence: an event row is an instance; its
template is the (calendar, diary_entry_type, name) triple. Formulaic
recurrence (e.g. Non-Farm Payrolls) is deferred; calendar_rules's
grammar can later feed a template link.
The unified schedule model
tenor_schedules (new, decided) is the schedule-axis vocabulary:
| Entity | Role | Key columns |
|---|---|---|
tenor_schedules |
Named date sets a SCHEDULE_STEP convention walks |
code, name, description, display_order, schedule_source, calendar_code, diary_entry_type |
Two seeded schedules:
- ROLL_QUARTER (CLOSED_FORM): the IMM rule — first business day after
20 Mar/Jun/Sep/Dec. Derivable by rule; the closed form stays
code-side in the resolver. This is the schedule the
CREDIT_CDS_IMMconvention walks: offset YEAR × 1 + schedule ROLL_QUARTER 1 step = "1Y 1RQ" (one year, one roll quarter). - FOMC_MEETING (EVENT_LOOKUP): the FOMC meeting dates, read from
calendar_eventrows on theUS.FOMCcalendar (diary_entry_type= central_bank_meeting). Externally announced — data, not formula. This is the axis theRATES_SPOT_FOMCconvention walks.
The two are the same mechanism: "the n-th date in a schedule, on or
after the anchor". They differ only in where the dates come from. The
old IMM_ROLL algorithm row and ROLL_QUARTER unit are superseded —
one algorithm (SCHEDULE_STEP), one schedule vocabulary, no special
cases.
The bootstrap chain
The domain concepts are in Pillar and Interest Rate Curves; here is the code path.
- Config.
ir_curve_bootstrap_configsnames one curve: output series, party, source series,curve_family_role(FUNDING / PROJECTION),discount_curve_config_id,interpolation_method,day_count_convention, andsplit_tenor_code— the tenor at which the short-end and long-end methods divide. - Pillars.
ir_curve_bootstrap_pillarsholds one row per pillar:start_tenor_code,end_tenor_code,curve_role_code(DEPOSIT/FRA/SWAP),sequence_index. Order is the fixed-leg schedule: a SWAP pillar's fixed-leg dates are the end dates of all prior pillars plus its own. - Resolve + quote.
curve_republish_resolver.cppsorts pillars bysequence_indexand, per pillar: resolves both tenor codes to dates via the tenor resolver, and looks up the observed rate. The end tenor code is the point id into the raw grid (market_observations) —bp.point_id = p.end_tenor_code— and a pillar whose end code has no quote fails loudly. - Solve.
curve_bootstrap_enginesolves discount-factor-by- discount-factor from the short end, honouringinterpolation_methodandday_count_convention. The engine already acceptsFLAT_FORWARD_THEN_LOG_LINEAR;CUBIC_SPLINEthrows today.
Terminology
| Term | Definition |
|---|---|
1F, nF |
The n-th FOMC meeting date on-or-after the anchor. FOMC tenors are SPECIAL=/=NONE with multiplier n, mirroring O/N's shape; the meeting axis is the FOMC_MEETING schedule. |
1Y 1RQ |
"One year, one roll quarter" — CDS disambiguation notation for a tenor measured from the IMM roll schedule, not from spot. |
nIMM |
n IMM quarters forward (e.g. IMM3 = three roll dates). |
| measured_from | The tenor_anchors code a convention resolves tenors from (SPOT for rates, NEAR_LEG for FX swaps, etc.). |
| Anchor | The base date the offset and schedule steps start from. |
| Point id | The key a pillar's observed rate is looked up by in the raw grid; equals the pillar's end_tenor_code. |
| Split tenor | The split_tenor_code marking where a curve changes interpolation method (short-end flat forward → long-end continuous). |
| Pillar | A tenor pair on ir_curve_bootstrap_pillars that is a live input to the bootstrap — see Pillar. |
| Broken date | A bespoke maturity between standard tenors, interpolated normally — see Broken Dates and Turn Points. |
| Turn point | A fixed-calendar-time discontinuity (e.g. year-end) interpolated around — same doc. |
| Schedule step | One position on a named schedule axis (ROLL_QUARTER, FOMC_MEETING). |
| Flat forward | Interpolation holding the forward rate constant within an interval — the correct method between FOMC meetings. |
Worked example: the FOMC-dated SOFR short end
The decided model (story * Decisions D1–D4) in one chain:
US.FOMCis a row incalendars(calendar_typecentral_bank_meeting,country_codeUS,sourcefederalreserve.gov);currency_calendarslinks USD ↔ US.FOMC.- The 2025–2027 FOMC meeting dates are
calendar_eventrows (diary_entry_typecentral_bank_meeting) onUS.FOMC, transcribed from the Fed's published calendar. tenor_schedulescarriesFOMC_MEETING(EVENT_LOOKUP on that calendar);tenorscarries1F..=nF= (SPECIAL/NONE, multiplier n);RATES_SPOT_FOMCconvention pairs measured_fromSPOTwith algorithmSCHEDULE_STEPand resolution rowsschedule_step_count= n.ir_curve_bootstrap_configsnames the curve withinterpolation_methodFLAT_FORWARD_THEN_LOG_LINEAR andsplit_tenor_codemarking the boundary;ir_curve_bootstrap_pillarslists SPOT→1F, 1F→2F, …, nF→split.curve_republish_resolverresolves each end tenor (a lookup into the FOMC event set), finds the quote at that point id, and the engine solves — flat forward between meetings, continuous beyond.
No engine change, no new table per calendar, no special case: the FOMC short end is data plus one configuration.
See also
- Knowledge — the knowledge index.
- Term Structures and Tenors — the tenor-cluster hub (concepts).
- Calendars, Holidays and Events — the calendar-cluster hub (concepts).
- Tenor — tenor labels and conventions by curve type.
- IMM Dates — the ROLL_QUARTER schedule's rule.
- FOMC Dates — the FOMC_MEETING schedule's calendar.
- Interest Rate Curves — single-curve bootstrapping mechanics.
- Market Data Architecture — how the raw grid (
market_observations) reaches consumers.