# ModelPricer

URL: https://stochastic.rust-dd.com/docs/concepts/model-pricer

> The pricing trait — the struct holds model parameters and each call carries its own (s, k, r, q, tau) query, so one model prices a whole grid.

The single-underlying pricing surface. A plain `f64` trait — not generic
over `T: FloatExt`.

```rust
pub trait ModelPricer {
  fn price_call(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64;

  // default: put-call parity from price_call
  fn price_put(&self, s: f64, k: f64, r: f64, q: f64, tau: f64) -> f64 {
    let call = self.price_call(s, k, r, q, tau);
    call - s * (-q * tau).exp() + k * (-r * tau).exp()
  }

  // default: dispatches to price_call / price_put
  fn price_option(&self, s: f64, k: f64, r: f64, q: f64, tau: f64, option_type: OptionType) -> f64 {
    match option_type {
      OptionType::Call => self.price_call(s, k, r, q, tau),
      OptionType::Put => self.price_put(s, k, r, q, tau),
    }
  }
}
```

## Model, then query

The struct holds **model parameters only** — `BSMPricer` holds a
volatility and a cost-of-carry convention, `HestonPricer` holds
$(v_0,\kappa,\theta,\sigma,\rho)$. Nothing about the contract (spot,
strike, rate, dividend yield, maturity) is baked in; every price query
passes its own `(s, k, r, q, tau)`.

That is what makes vectorized pricing across a strike/maturity grid —
vol-surface construction, calibration — cheap: one model instance, many
`price_option` calls, no `&dyn`, no `Box<dyn>`.

Half of the implementors also expose an inherent `call_put(s, k, r, q,
tau) -> (f64, f64)` returning both legs from one evaluation, which is
what their trait methods project from.

## Override `price_put` unless parity really holds

The default `price_put` recomposes the put from the call by **European**
put-call parity with a carry factor of exactly $e^{-q\tau}$. Two families
must override it or the default is a silent mispricing:

* **Non-standard cost of carry** — `BSMPricer`, `AsianPricer` and
  `Merton1976Pricer` carry at $e^{(b-r)\tau}$, which equals $e^{-q\tau}$
  only when $b = r - q$.
* **American exercise** — `BjerksundStensland2002Pricer`,
  `SnellEnvelopePricer` and `FiniteDifferencePricer` price a put carrying
  an early-exercise premium the call does not, so European parity is not
  an approximation but the wrong model.

Exercise style is the **implementor's** choice, not the trait's: what
`ModelPricer` fixes is the *query* shape, not the exercise right.

## Blanket implementations

Two blanket impls do most of the work:

* `impl<T: FourierModelExt> ModelPricer for T` — any model with a
  characteristic function gets priced by Gil-Pelaez quadrature for free.
* `impl<T: VanillaEuropeanCall + ?Sized> ModelSurface for T` — any model
  that asserts it really is a European vanilla call gets
  `vol_surface(s, r, q, strikes, maturities)`. A `ModelPricer` that is *not*
  one (a digital, an American put) has no method there at all.

`ToModel` bridges a calibration result to the concrete `ModelPricer` it
produces:

```rust
pub trait ToModel {
  type Model: ModelPricer;
  fn to_model(&self, r: f64, q: f64) -> Self::Model;
}
```

## Bonds and multi-asset payoffs

`ModelPricer`'s query has one underlying and one strike, so two families
sit outside it deliberately:

* **Short-rate bonds** (`Vasicek`, `Cir`, `HullWhite`) implement
  `ShortRatePricer` instead — they price off a short rate and a tenor,
  not a spot and a strike.
* **Multi-asset payoffs** (`MargrabePricer`, `KirkSpreadPricer`,
  `McSpreadPricer`, the basket and rainbow pricers) carry **no** pricing
  trait. `MargrabePricer` is an exchange option with no strike at all;
  `KirkSpreadPricer` strikes against a spread of two forwards, so its
  query is `spread_call_put(f1, f2, x, r, tau)`; the basket and rainbow
  pricers take N legs. A shared signature would need an `Option<f64>` strike
  and a variable-length underlying list — a dishonest shape for all of them.

  All eight follow the model/query split through inherent methods: each holds
  its volatilities and correlation and takes the spots, strike, rate, yields
  and maturity per call, and every Monte Carlo member returns an
  `McEstimate` — the mean plus its standard error and `ci_95()` — rather
  than a bare number. `pricer_registry.rs` records the state of each.

  `KirkSpreadPricer` is the one member whose methods carry a `spread_`
  prefix. Its query is five `f64`s, exactly the arity of the
  `(s, k, r, q, tau)` the single-underlying pricers take, and it disagrees
  with them in four of the five positions — so under the plain names a
  misdirected call compiled and returned a finite, well-scaled, wrong price.
  The other seven are separated by their signatures already
  (`GeometricBasketPricer::price_call` takes `ArrayView1` legs,
  `MargrabePricer::price` has no strike), so they keep the plain names.

The path-dependent family (barriers, lookbacks, cliquets, autocallables,
Bermudans, chooser, compound, variance/volatility swaps) is trait-less
for the same reason: each carries its own contract schedule.

## Greeks

A pricer's Greeks follow the same model/query split as its prices:
**inherent methods** taking the query, not a trait. `BSMPricer`,
`HestonPricer`, `Merton1976Pricer`, `CashOrNothingPricer` and
`AssetOrNothingPricer` each expose
`greeks(s, k, r, q, tau, option_type) -> Greeks` — one identical signature
across all five — alongside per-Greek accessors on the same shape. `Greeks`
is a plain `f64` struct of nine named fields (`delta`, `gamma`, `vega`,
`theta`, `rho`, `vanna`, `charm`, `volga`, `veta`), NaN where the pricer
exposes nothing.

`GreeksExt` is **not** that interface. Its accessors take `&self` and
nothing else, so only an estimator that already owns its query can
implement it: the Monte Carlo Malliavin pair `GbmMalliavinGreeks` and
`HestonMalliavinGreeks`, whose `greeks()` override runs a single simulation
so the returned estimators share their paths — see the
[greeks-pattern SKILL](https://github.com/dancixx/stochastic-rs/blob/main/.claude/skills/greeks-pattern/SKILL.md)
for that single-pass protocol. It is reached via
`stochastic_rs::traits::GreeksExt`, not the prelude.

## `TimeExt` — dates to `tau`

`ModelPricer` takes `tau` in years. When the contract is given as dates
instead, `TimeExt::tau_or_from_dates()` derives $\tau$ from either an
explicit `tau` or an `(eval, expiration)` pair under a day-count
convention, and `tau_with_dcc(dcc)` overrides the convention explicitly.
Convert at the call site, then price.

**Instruments implement it; pricers do not.** `EuropeanOption` and
`DigitalOption` own a maturity and resolve it; a pricer holds model state and
receives $\tau$ as a query argument, so it has no dates to resolve. The
design that retired `PricerExt` also suggested moving this trait's role into
the calendar module — that is dropped rather than pending. The date
arithmetic is already there (`DayCountConvention::year_fraction`, which both
derivations call); what `TimeExt` adds is the instrument-side question of
*which* maturity slot is populated, and that belongs on the instrument.

## What replaced `PricerExt`

Before 3.0 a second trait, `PricerExt: TimeExt`, bundled the market data
and the strike **into** the pricer and exposed `calculate_call_put()` /
`calculate_price()` / `implied_volatility()` with no arguments. It is
removed: pricing a second strike meant constructing a second pricer,
which is exactly what made grid pricing expensive.

Every former implementor moved to `ModelPricer`, to `ShortRatePricer`, or
— for `KirkSpreadPricer` — to inherent methods carrying its own natural
query. `calculate_call_put()` becomes `call_put(...)`,
`calculate_price()` becomes `price_call(...)`, and the four pricers that
genuinely invert a price to a vol keep `implied_volatility` as an
inherent method. `KirkSpreadPricer` is the exception on naming as well:
its two-forward query took the same five `f64`s, so its methods are
`spread_call_put` / `spread_call` / `spread_put`.

For the per-pricer mapping, each type's own rustdoc names what its model
state is and what its query is; `pricer_registry.rs` in the crate's test
directory is the compile-checked inventory of which pricer carries which
trait, including the ones that deliberately carry none.

## See also

* [Calibrator and CalibrationResult](/docs/concepts/traits#calibrator-and-calibrationresult)
* [Quant catalog](/docs/quant) — pricers, calibrators, vol surface, risk

