User Journey: Check a curve's fit

Table of Contents

This page documents a user journey: A trader or quant checks a candidate curve against the quotes it came from: the quotes overlaid on the candidate, the pillar residuals, the forward-rate smoothness, and the calendar and roll logic.

A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.

1. Goal

"I want to know whether this candidate curve still stands on the quotes it was built from, and whether its forwards and its dates make sense." The journey is done when the person has seen the raw quotes against the candidate, the pillar residuals, the forward-rate smoothness, and the calendar and roll logic, and can say whether the candidate is sound. The person does not publish here.

2. Actor and trigger

The actor is the trader or the quant. The trader owns the candidate; the quant owns the method and reads the same screen. Neither decides to publish here: an approver does that in a different journey. The trigger is a bootstrap or refresh just run, a residual that looks too large, a forward curve that steps, or a pillar that settles on an unexpected date. The journey starts at a candidate curve and ends when the person leaves it, still a candidate.

This journey reads and diagnoses; it changes nothing. It is the check the maker runs before the candidate goes to a second pair of eyes.

3. Steps

  1. Open the candidate. The person opens Curves and surfaces and selects the candidate curve. The system reads the market_series row and its =derivation_kind=/config/version, which says the curve is derived and by which recipe.
  2. Read the candidate points. The person chooses Read candidate. The system reconstructs the latest snapshot of the curve with marketdata.v1.curve_snapshot.get, one point per point_id, at now. The read has no as-of parameter, so the person cannot look at the candidate as of an earlier instant.
  3. Read the raw quotes. The system lists the quotes the curve was built from with marketdata.v1.market_observations.list_by_series_id, one row per oresmd_uri with its value, source and time. The quotes and the candidate are two separate reads.
  4. Overlay the quotes on the candidate. The person reads each raw quote against its candidate point. No operation joins the two lists: they come from different subjects and are keyed differently (the quote by an oresmd_uri, the candidate by a point_id), so the screen places them side by side by reading both and matching on the coordinate. See What is missing.
  5. Read the pillar residuals. The person reads the residual at each pillar, meaning the distance between the quote the curve was built from and the rate the curve returns at that pillar. No operation serves a residual; the compute reply carries bootstrapped points and no per-pillar error. See What is missing.
  6. Read the forward-rate smoothness. The person reads the forward curve between pillars and looks for a step. A server-side health checker exists that flags a negative forward rate as INFO and a leave-one-out outlier as WARNING, but no operation calls it, so nothing reaches the screen. See What is missing.
  7. Read the calendar and roll logic. The person checks how each pillar tenor resolves to a date and which calendar and roll convention applied. The system reads the recipe with refdata.v1.ir_curve_bootstrap_configs.get and its pillars with refdata.v1.ir_curve_bootstrap_pillars.list, and resolves a tenor through refdata.v1.tenor_convention_resolutions.list_by_convention_code. The resolved dates are not stored on a point, so the screen derives them.
  8. Recompute if needed. If the person suspects a stale candidate, they choose Recompute candidate, which calls marketdata.v1.ops.compute_curve with the config and an as-of and returns the bootstrapped points. The operation exists but no caller sends it today.
  9. Leave. The person returns to the list. The candidate is unchanged; the person did not write or publish anything.

The compute and the snapshot reads are server-side and synchronous, so the journey carries no state of its own. Recomputing a candidate returns points; it does not publish them.

4. Screens and wireframes

One screen. The candidate and its as-of sit at the top; the quotes overlaid on the candidate, the residuals and the forwards sit in the middle; the calendar and roll logic sits at the foot, so the person reads the market, then the fit, then the dates it settles on.

wireframe_check-a-curves-fit.png

Figure 1: Curve fit check: the candidate against its raw quotes, the pillar residuals, the forward-rate smoothness, and the calendar and roll logic.

5. Prototype

A static page at doc/prototypes/market_data/check-a-curves-fit/index.html: mock data only, no backend, no build step and no session. It argues that a curve is checked against the quotes it came from: residuals, forward smoothness and the roll logic, on one screen.

Variants
a the residuals table, b the overlay chart, c both
States
fit, large (one pillar's residual is large), gap (a quote the candidate does not join)
Source
doc/prototypes/market_data/check-a-curves-fit/index.html
Open
/OreStudio/doc/prototypes/market_data/check-a-curves-fit/ once the site is built, or the file directly

6. Entities composed

Entity What it contributes Model
market_series The candidate's identity and its =derivation_kind=/config/version ores.marketdata.market_series
market_observation The candidate points and the raw quotes, one per oresmd_uri and observation_datetime ores.marketdata.market_observation
observation_lineage The provenance of a derived point: which config/version and which source series it read ores.marketdata.observation_lineage
ir_curve_bootstrap_config The recipe: interpolation, day count, tenor convention and split tenor ores.refdata.ir_curve_bootstrap_config
ir_curve_bootstrap_pillar The pillars the residuals are read at: sequence, start and end tenor, role ores.refdata.ir_curve_bootstrap_pillar
curve_role The pillar's instrument role (deposit, FRA, swap) that decides the pricing derivation ores.refdata.curve_role
tenor The tenor labels a pillar is written in ores.refdata.tenor
tenor_convention The scheme that resolves a tenor to a date (spot/forward, FOMC, IMM) ores.refdata.tenor_convention
day_count_fraction_type The day-count convention applied per pillar ores.refdata.day_count_fraction_type
currency The currency the pillars are quoted in ores.refdata.currency

A residual, a forward-rate finding and a resolved pillar date have no entity today. The screen computes the residual and the forward on the client from the two reads, and resolves the pillar dates from the recipe; see What is missing.

7. Operations and messages

Step Operation Subject Status
Open the candidate Read one series marketdata.v1.market_series.get exists
Read the candidate points Read the curve snapshot at now marketdata.v1.curve_snapshot.get partial
Read the raw quotes List the observations of one series marketdata.v1.market_observations.list_by_series_id exists
Overlay the quotes Join the quotes to the candidate points (no joint read) missing
Read the pillar residuals Read the fit residual per pillar marketdata.v1.curve_bootstrap.diagnostics missing
Read the forward rates Read the forward-rate findings marketdata.v1.curve_bootstrap.health missing
Read the calendar and roll Read the recipe refdata.v1.ir_curve_bootstrap_configs.get exists
Read the calendar and roll Read the pillars refdata.v1.ir_curve_bootstrap_pillars.list exists
Read the calendar and roll Resolve a tenor to a date refdata.v1.tenor_convention_resolutions.list_by_convention_code exists
Recompute the candidate Bootstrap the candidate without publishing marketdata.v1.ops.compute_curve exists
Recompute and publish Bootstrap and republish marketdata.v1.ops.republish_curve exists
Read the recipe lineage Read the lineage of a derived point marketdata.v1.observation_lineages.list exists

marketdata.v1.ops.compute_curve and .republish both exist and no caller sends them today; the preview half is the one this journey would use. The snapshot read is partial because it always reads now and has no as-of parameter. The generic history read is marketdata.v1.history.get with entity_type ores.marketdata.market_series; market_series and observation_lineage register history providers.

8. What is missing

  1. There is no residuals or diagnostics operation. The subject marketdata.v1.curve_bootstrap.diagnostics would have to be added to return, per pillar, the quote the curve was built from, the rate the curve returns at that pillar, and the residual. Until then the screen reads the candidate and the quotes separately and matches them by coordinate, and it cannot say what the bootstrap engine itself measured.
  2. The health checker has no caller. curve_health_checker exists in ores.analytics.quant and flags a non-positive discount factor, a negative forward rate as INFO and a leave-one-out forward-rate outlier as WARNING. Nothing calls it outside its tests, and the compute reply carries points only, so no finding reaches the wire. The candidate is a subject such as marketdata.v1.curve_bootstrap.health, or a findings field on the compute response. Until then the screen plots the forwards and marks that no check ran.
  3. A read cannot be taken as of a time in the past. The snapshot read always reads now, so the person cannot check the candidate as of the instant it was built. The candidate is an as_of parameter on the snapshot request. Until then the screen reads the current snapshot only.
  4. The quotes and the candidate are not joined. They are keyed differently, the quote by an oresmd_uri and the candidate by a point_id, and no operation returns both. The candidate is an overlay read that returns a quote and its candidate rate together. Until then the screen joins the two lists itself and shows an unmatched quote as a gap.
  5. A pillar's resolved date is not stored. The recipe names a tenor convention, and a resolution read returns the scheme's rows, but the date a given pillar resolved to is not on the point, so the screen must resolve it again and the two can disagree. The candidate is the resolved date on the lineage of a derived point. Until then the screen derives the dates and labels them as derived.
  6. The curve-fit screen exists for curves only. There is no surface or slice read and no surface residual, so a surface fit cannot be checked on this screen. The candidate is a surface read and a surface residual subject. Until then a surface fit is checked on the concurrent journey Calibrate or refresh a surface, where the same gap is recorded.

9. Related journeys

10. Related knowledge

11. See also

Emacs 29.3 (Org mode 9.6.15)