# `ZenQuant.Options.Chain`
[🔗](https://github.com/ZenHive/zen_quant/blob/v0.8.1/lib/zen_quant/options/chain.ex#L1)

Builds and enriches canonical option chains from already-fetched payloads.

`from_payload/2` accepts a list of entry maps, a JSON-RPC-style
`%{"result" => entries}` envelope, or a map keyed by option symbol. Source
field names are configurable through `:fields`, so the input contract is not
tied to one venue's response keys.

`enrich/3` derives each priceable leg's Black-Scholes greeks from its implied
volatility. It performs no I/O and reads no system clock: callers supply the
reference date or datetime. Unpriceable legs are retained and marked by
default; callers can instead request a distinct error. Canonical raw greek
units match the chain analytics surface: delta and gamma are per
underlying-price unit, vega and rho are per percentage-point change, and
theta is per calendar day.

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `enrich` | 3 | Populate every chain leg with analytic greeks from its implied volatility. | `chain: exchange_data`, `reference: value` |
| `from_payload` | 2 | Build a canonical option chain from an already-fetched payload. | `payload: exchange_data` |

# `enrichment_status`

```elixir
@type enrichment_status() :: :enriched | {:unpriceable, unpriceable_reason()}
```

Per-leg result added by `enrich/3`.

# `error_reason`

```elixir
@type error_reason() ::
  {:invalid_option_payload, term()}
  | {:invalid_option_fields, term()}
  | {:invalid_option_entry, term()}
  | {:unpriceable_option_entry, term(), unpriceable_reason()}
  | {:invalid_reference, term()}
  | {:invalid_enrichment_options, term()}
```

Chain construction or enrichment failure.

# `field_selector`

```elixir
@type field_selector() :: atom() | String.t() | [atom() | String.t()]
```

Source key or ordered source-key alternatives for a canonical field.

# `fields`

```elixir
@type fields() :: %{optional(atom()) =&gt; field_selector()}
```

Source selectors keyed by canonical option field.

# `option`

```elixir
@type option() :: %{
  optional(:enrichment_status) =&gt; enrichment_status(),
  optional(:enrichment_reference) =&gt; DateTime.t(),
  optional(:pricing_inputs) =&gt; pricing_inputs(),
  symbol: String.t(),
  open_interest: number(),
  bid_price: number() | nil,
  ask_price: number() | nil,
  mark_price: number() | nil,
  underlying_price: number() | nil,
  mark_iv: number() | nil,
  implied_volatility: number() | nil,
  raw: map()
}
```

Canonical chain entry consumed by `ZenQuant.Options` analytics.

# `pricing_inputs`

```elixir
@type pricing_inputs() :: %{
  spot: number(),
  strike: number(),
  time_to_expiry_years: number(),
  risk_free_rate: number(),
  dividend_yield: number(),
  volatility: number()
}
```

Black-Scholes-Merton inputs retained on a priceable enriched leg.

# `t`

```elixir
@type t() :: %{optional(String.t()) =&gt; option()}
```

Canonical option chain keyed by Deribit-format option symbol.

# `unpriceable_reason`

```elixir
@type unpriceable_reason() ::
  :expired
  | :at_expiry
  | :underlying_price_unavailable
  | :implied_volatility_unavailable
  | {:pricing_error, term()}
```

Why a readable option leg could not receive Black-Scholes greeks.

# `enrich`

```elixir
@spec enrich(t() | term(), Date.t() | DateTime.t() | term(), keyword()) ::
  {:ok, t()} | {:error, error_reason()}
```

Enriches each priceable leg and explicitly marks or rejects unpriceable legs.

Each enriched leg retains the exact map passed to `Pricing.greeks/2` as
`:pricing_inputs` plus the normalized `:enrichment_reference`. Replacing only
`:spot` in that map provides sticky-strike re-evaluation with the same time,
rates, and volatility used for the stored greeks.

# `from_payload`

```elixir
@spec from_payload(
  term(),
  keyword()
) :: {:ok, t()} | {:error, error_reason()}
```

Builds the canonical chain without fetching or silently dropping entries.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
