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

Cox-Ross-Rubinstein lattice pricing for vanilla European and American options.

The named consumer is the zen_quant operator, for comparing early-exercise
values with the European Black-Scholes-Merton prices in
`ZenQuant.Options.Pricing`.

## Supported contract

`price/4` supports `:call` and `:put` payoffs with either `:european` exercise
at expiry or `:american` exercise at every lattice step. Rates are annualized
and continuously compounded. Dividends or carry are represented by a
continuous annual `:dividend_yield`; discrete cash dividends are not modeled.
Time is a caller-supplied year fraction, with no calendar convention imposed.

The caller controls convergence through the step count, which defaults to
500. The implementation uses O(steps) memory and O(steps²)
time. At expiry it returns intrinsic value. Zero volatility is evaluated as a
deterministic path, avoiding a degenerate up/down tree.

The input map uses the same fields and units as `ZenQuant.Options.Pricing`:
positive `:spot` and `:strike`, non-negative `:time_to_expiry_years` and
`:volatility`, and numeric `:risk_free_rate` and `:dividend_yield`.

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `price` | 4 | Price a vanilla European or American option with a CRR lattice. | `option_type: value`, `exercise: value`, `inputs: value`, `steps: value` |

# `error_reason`

```elixir
@type error_reason() ::
  {:invalid_option_type, term()}
  | {:invalid_exercise, term()}
  | {:missing_input, atom()}
  | {:invalid_input, atom()}
  | {:invalid_lattice, map()}
```

Lattice pricing failure reason.

# `exercise`

```elixir
@type exercise() :: :european | :american
```

Exercise convention supported by the lattice.

# `inputs`

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

CRR input map using continuous annual rates and a caller-supplied year fraction.

# `option_type`

```elixir
@type option_type() :: :call | :put
```

Vanilla option side.

# `price`

```elixir
@spec price(
  option_type() | term(),
  exercise() | term(),
  inputs() | term(),
  pos_integer() | term()
) ::
  {:ok, float()} | {:error, error_reason()}
```

Price a vanilla European or American option with a CRR lattice.

## Parameters

  * `option_type` - `:call` or `:put` (value)
  * `exercise` - `:european` or `:american` (value)
  * `inputs` - Map with positive :spot/:strike, time in years, continuous annual rates/yield, and annual decimal volatility (value)
  * `steps` - Positive CRR step count controlling convergence and runtime (default: `500`, value)

## Returns

`{:ok, price}` in spot currency units or `{:error, reason}` (`result_tuple`)

### Example

```elixir
{:ok, 9.223118455216966}
```

## Errors

  * `:invalid_option_type` - Option type is not :call or :put
  * `:invalid_exercise` - Exercise convention is not :european or :american
  * `:missing_input` - A required map field is absent
  * `:invalid_input` - A field or step count has an unsupported type or domain
  * `:invalid_lattice` - Inputs imply a risk-neutral probability outside zero to one

```elixir
# descripex:contract
%{
  params: %{
    steps: %{
      default: 500,
      description: "Positive CRR step count controlling convergence and runtime",
      kind: :value
    },
    option_type: %{description: "`:call` or `:put`", kind: :value},
    inputs: %{
      description: "Map with positive :spot/:strike, time in years, continuous annual rates/yield, and annual decimal volatility",
      kind: :value
    },
    exercise: %{description: "`:european` or `:american`", kind: :value}
  },
  errors: [
    invalid_option_type: "Option type is not :call or :put",
    invalid_exercise: "Exercise convention is not :european or :american",
    missing_input: "A required map field is absent",
    invalid_input: "A field or step count has an unsupported type or domain",
    invalid_lattice: "Inputs imply a risk-neutral probability outside zero to one"
  ],
  returns: %{
    type: :result_tuple,
    description: "`{:ok, price}` in spot currency units or `{:error, reason}`"
  },
  returns_example: {:ok, 9.223118455216966}
}
```

---

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