Deterministic term structures from normalized option-skew observations.
term_structure/1 accepts a list of maps with this contract:
| Field | Contract |
|---|---|
:expiry | Date.t() |
:tenor_days | Non-negative integer number of calendar days |
:measure | Non-empty atom or string identifying the caller-selected skew measure |
:units | Non-empty atom or string identifying the value units |
:value | Numeric skew observation, or nil for an explicitly missing observation |
Every observation in one call must use the same measure and units. The module
does not interpret those identifiers; examples include
:delta_25_risk_reversal measured in :decimal_volatility.
Results are sorted by expiry and then tenor. Exact duplicate expiry/tenor
coordinates are reduced to the arithmetic mean of their numeric values,
independent of input order. Missing duplicates do not enter that mean, and a
coordinate with no numeric values remains nil.
:change_from_previous is the current value minus the immediately preceding
sorted value. It is nil for the first point or when either adjacent point is
missing. No missing value is filled and no interpolation is performed.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
term_structure | 1 | Build a sorted skew term structure without interpolation. | observations: value |
Summary
Types
Validation failure returned by term_structure/1.
Normalized caller-selected skew observation.
A sorted and duplicate-reduced term point.
Skew term structure with its explicit measure, units, and policies.
Functions
Build a sorted skew term structure without interpolation.
Types
@type error_reason() :: :invalid_observations | {:invalid_observation, non_neg_integer()} | {:missing_field, non_neg_integer(), atom()} | {:invalid_field, non_neg_integer(), atom()} | {:mixed_measure, atom() | String.t(), atom() | String.t()} | {:mixed_units, atom() | String.t(), atom() | String.t()}
Validation failure returned by term_structure/1.
@type observation() :: %{ expiry: Date.t(), tenor_days: non_neg_integer(), measure: atom() | String.t(), units: atom() | String.t(), value: number() | nil }
Normalized caller-selected skew observation.
@type term_point() :: %{ expiry: Date.t(), tenor_days: non_neg_integer(), value: float() | nil, sample_count: non_neg_integer(), change_from_previous: float() | nil }
A sorted and duplicate-reduced term point.
@type term_structure() :: %{ measure: atom() | String.t() | nil, units: atom() | String.t() | nil, duplicate_policy: :mean_observed_values, missing_policy: :preserve_nil, observations: [term_point()] }
Skew term structure with its explicit measure, units, and policies.
Functions
@spec term_structure([observation()] | term()) :: {:ok, term_structure()} | {:error, error_reason()}
Build a sorted skew term structure without interpolation.
Parameters
observations- Normalized maps with Date :expiry, non-negative :tenor_days, caller-defined :measure/:units, and numeric or nil :value (value)
Returns
{:ok, %{measure, units, duplicate_policy, missing_policy, observations}} or {:error, reason} (result_tuple)
Example
{:ok,
%{
observations: [
%{
value: 0.03,
expiry: ~D[2026-08-28],
tenor_days: 30,
sample_count: 1,
change_from_previous: nil
}
],
units: :decimal_volatility,
measure: :delta_25_risk_reversal,
duplicate_policy: :mean_observed_values,
missing_policy: :preserve_nil
}}Errors
:invalid_observations- Input must be a list:invalid_observation- Each item must be a normalized observation map:missing_field- A required observation field is absent:invalid_field- An observation field has an unsupported type or domain:mixed_measure- All observations must identify the same skew measure:mixed_units- All observations must identify the same value units
# descripex:contract
%{
params: %{
observations: %{
description: "Normalized maps with Date :expiry, non-negative :tenor_days, caller-defined :measure/:units, and numeric or nil :value",
kind: :value
}
},
errors: [
invalid_observations: "Input must be a list",
invalid_observation: "Each item must be a normalized observation map",
missing_field: "A required observation field is absent",
invalid_field: "An observation field has an unsupported type or domain",
mixed_measure: "All observations must identify the same skew measure",
mixed_units: "All observations must identify the same value units"
],
returns: %{
type: :result_tuple,
description: "{:ok, %{measure, units, duplicate_policy, missing_policy, observations}} or {:error, reason}"
},
returns_example: {:ok,
%{
observations: [
%{
value: 0.03,
expiry: ~D[2026-08-28],
tenor_days: 30,
sample_count: 1,
change_from_previous: nil
}
],
units: :decimal_volatility,
measure: :delta_25_risk_reversal,
duplicate_policy: :mean_observed_values,
missing_policy: :preserve_nil
}}
}