User Journey: Adjust a curve or surface by hand
Table of Contents
This page documents a user journey: A trader corrects a candidate curve or surface by hand where the market is wrong or thin, with a manual point or a spread overlay, and each correction carries a reason.
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 override the parts of this candidate the market got wrong or never gave me, and I want the hand value to stay put while the feeds keep ticking." The journey is done when every correction the trader intended is on the candidate, each carries a reason, and the candidate still reads as a candidate: nothing has been published.
The trader keys a point for three reasons. The market is wrong, so the hand value corrects it. The market is thin or absent, so the hand value stands in for it. The trader wants a what-if, so the hand value is a candidate only.
2. Actor and trigger
The actor is the trader, acting as maker: the person who holds the market-data management role for this party and owns the candidate. An approver owns the decision to publish, in a different journey. The trigger is a bad print, a stale or missing quote, a thin tenor, a broken pillar, or a request from pricing to mark a point by hand. The journey starts at a candidate curve or surface and ends when the trader leaves it, still a candidate.
A manual point is a candidate correction. It stays a candidate until it is approved and published; see What is missing for the override and shift operations and the precedence rule that do not exist yet.
3. Steps
- Open the candidate. The person opens Curves and surfaces and selects
the candidate curve or surface. The system reads the
market_seriesrow and lists its points withmarketdata.v1.market_observations.list_by_series_id. - Read the points. The system shows one row per
oresmd_uriwith its value, itssourcetag and itsobservation_datetime. The row carries no actor and no marker that says the value was written by hand, so the screen cannot yet tell a manual point from a feed point. See What is missing. - Key a curve point. The person chooses Set manual point, picks a tenor, types a value and states a reason, and confirms. For a curve the coordinate is the tenor. The system has no override operation, so the button names the subject it needs and changes nothing. See What is missing.
- Key a surface point. For a surface the coordinate is an expiry and a strike. The person picks both, types a value and states a reason. There is no strike-label vocabulary and no surface point coordinate, so the screen cannot address the point; it marks the strike picker as absent.
- Clear a manual point. The person selects a manual point and chooses Clear manual point. The system would drop the override so the feed value shows again. There is no way to identify which point is manual and no operation to clear one, so the button is inert. See What is missing.
- Apply a spread overlay. The person chooses Apply spread overlay, picks a parallel shift or a twist, picks a node or a segment to apply it over, types the shift and states a reason, and confirms. The system has no shift operation, so the button names the subject it needs and changes nothing. See What is missing.
- Read the corrected candidate. The system would redraw the curve or surface with the manual points and the overlay applied, and mark each correction with its reason. Because no correction was written, the screen shows the feed values unchanged.
- Confirm the hand value survives a feed update. The person waits for, or forces, a feed update. The hand value should stay until it is cleared: a feed tick writes the point again and the manual value must win. No precedence rule exists, so a feed write is indistinguishable from a hand write and either can overwrite the other. See What is missing.
- Leave unpublished. The person chooses Leave unpublished and returns to the list. The corrected candidate is still a candidate.
Writing a correction, if it existed, would be immediate: the reply would carry the corrected candidate, so the journey carries no state of its own. The only state it would leave is the row on the candidate, which stays until it is cleared.
4. Screens and wireframes
One screen. The candidate and its points sit at the top, the two correction forms sit in the middle, and the state of every correction sits at the foot. Each correction form asks for its reason, and the foot states that a manual point survives later feed updates until it is cleared.
Figure 1: Manual adjustment: the candidate's points, a manual point, a surface point, and a spread overlay, each with a reason.
5. Prototype
A static page at doc/prototypes/market_data/adjust-a-curve-or-surface-by-hand/index.html: mock data only, no backend,
no build step and no session. It argues that a hand adjustment is a first-class point with an actor and a reason, and it must read as a point the market did not send.
- Variants
- a edit one node, b a spread overlay over a segment (parallel or twist), c paste a set of nodes
- States
- none, some, overwrite (a feed tick tried to take a manual point back)
- Source
doc/prototypes/market_data/adjust-a-curve-or-surface-by-hand/index.html- Open
/OreStudio/doc/prototypes/market_data/adjust-a-curve-or-surface-by-hand/once the site is built, or the file directly
6. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
market_series |
The candidate's identity: the oresmd_uri and its =derivation_kind=/config/version |
ores.marketdata.market_series |
market_observation |
The points the person reads and corrects, one per oresmd_uri and observation_datetime, with its value and source |
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 whose id/version the candidate series carries | ores.refdata.ir_curve_bootstrap_config |
ir_curve_bootstrap_pillar |
The pillars, and their tenors and roles, a manual curve point sits against | ores.refdata.ir_curve_bootstrap_pillar |
curve_role |
The pillar's role (deposit, FRA, swap) that a curve point corrects | ores.refdata.curve_role |
tenor |
The tenor coordinate a manual curve point is keyed by | ores.refdata.tenor |
A manual point is not a separate entity today: the screen reads and would
write market_observation rows. Because those rows carry no actor and no
hand-written marker, the correction has no entity of its own; 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 points | List the observations of one series | marketdata.v1.market_observations.list_by_series_id |
exists |
| Read the recipe | Read the pillars a point sits against | refdata.v1.ir_curve_bootstrap_pillars.list |
exists |
| Key a curve point | Write a manual point | marketdata.v1.market_observations.put |
partial |
| Key a point | Offer the reasons | dq.v1.change_reasons.list |
exists |
| Key a surface point | Write a manual surface point | marketdata.v1.market_observations.put |
partial |
| Clear a manual point | Remove the manual point | marketdata.v1.market_observations.override.clear |
missing |
| Apply a spread overlay | Apply a parallel or twist shift | marketdata.v1.curve.shift |
missing |
| Apply a spread overlay | Apply a surface shift | marketdata.v1.surface.shift |
missing |
| Confirm survival | Keep the manual point against a feed write | (no precedence rule) | missing |
| Leave unpublished | Arrange the approval later | inbox.v1.ops.raise_approval |
exists |
marketdata.v1.market_observations.put exists, but it writes a row
indistinguishable from a feed write: the market_observation table has no
actor, no source kind and no reason, and its source column is a producer
channel tag, not a marker that a person wrote the value. The subject is
therefore partial as an override. The generic history read is
marketdata.v1.history.get with entity_type ores.marketdata.market_series;
market_observation registers no history provider, so an override would
leave no readable trail.
8. What is missing
- Observations carry no actor and no source kind. The
market_observationtable is append-only, bitemporal and deliberately carries no audit columns; itssourcecolumn names the producer channel, so a row cannot say that a person wrote it. The candidate is a source kind on the point, or a separate override entity that records the actor, the value and the reason. Until then the screen cannot mark a row as manual and shows every row as a feed value. - There is no override operation. The subject
marketdata.v1.market_observations.override(with a matching clear) would have to be added to write a manual point and to remove one. Until then Set manual point and Clear manual point name the step and stay inert; the screen does not fall back to.put, because that would write a feed row with no reason and no actor. - There is no precedence rule between a manual point and a later feed write. Nothing decides whether a hand value or the next tick wins, so a manual point cannot be promised to survive. The candidate is a stated precedence: a manual point wins for its coordinate until it is cleared, and a feed write to that coordinate is held or rejected. Until then the screen warns that a feed update may overwrite a hand value.
- There is no spread overlay operation. The subjects
marketdata.v1.curve.shiftandmarketdata.v1.surface.shiftwould have to be added, each naming the kind (parallel or twist), the node or segment it applies over, the shift and the reason. Until then Apply spread overlay names the step and stays inert. - There is no strike-label vocabulary and no surface point coordinate. A
surface point is an expiry and a strike, and nothing says whether a strike
is an absolute level, a delta or a moneyness, so a surface override cannot
be addressed. The candidate is a vol quoting convention, and with it a
strike coordinate in the
oresmd_uri. Until then the screen shows the expiry and marks the strike picker as absent. - A correction cannot carry a reason. The change reasons exist
(
dq.v1.change_reasons.list), but a point has no field to hold one, so the reason the person types has nowhere to go. The candidate is a reason field on the override entity from item 1, or a reason on the observation. Until then the reason field is shown but not stored. - The correction leaves no history.
market_observationregisters no history provider, so even if a manual point were written,marketdata.v1.history.getcould not show when it was made or by whom. The candidate is a history provider for the override entity. Until then the screen cannot show the trail of a by-hand correction.
9. Related journeys
- Bootstrap or refresh a curve — build the candidate this journey corrects.
- Calibrate or refresh a surface — build the surface candidate this journey corrects.
- Check a curve's fit — see how a manual point moves the candidate against its quotes.
- Ask for a publish to be approved — put the corrected candidate to a second pair of eyes.
- See who changed a curve, and when — read the trail of a correction once it exists.
10. Related knowledge
11. See also
- Market data journeys
- Watch the market and clear its exceptions — the feed side of the same quotes.
- Publish the approved set — where a corrected candidate becomes what the system uses.
- User Journeys