ZenQuant.Options.Probability (zen_quant v0.2.0)

Copy Markdown View Source

Terminal risk-neutral probability estimates from vertical option spreads.

A call vertical uses the lower-strike call price minus the upper-strike call price. A put vertical uses the upper-strike put price minus the lower-strike put price. Dividing that spread price by discounted strike spacing gives the risk-neutral expectation of the vertical's normalized terminal payoff.

This is a finite-strike terminal-state estimate. It is not a touch probability, a physical/real-world probability, or a forecast of where the underlying will trade before expiry.

Input contract

estimate/1 accepts a map with:

  • :side:call or :put
  • :lower_strike and :upper_strike — strictly increasing strikes
  • :lower_price and :upper_price — non-negative option prices
  • :time_to_expiry_years — non-negative caller-computed year fraction
  • :annual_discount_rate — annualized rate as a decimal
  • :discount_convention:continuous or :simple
  • :quote_side:mid or :natural

With :mid, both prices are midpoint quotes. With :natural, the long leg is priced at its ask and the short leg at its bid: lower ask/upper bid for calls, and upper ask/lower bid for puts.

Continuous discounting uses exp(-rate * years). Simple discounting uses 1 / (1 + rate * years) and requires a positive denominator.

Results outside [0, 1] return {:error, {:probability_out_of_bounds, details}}; they are not silently clamped.

API Functions

FunctionArityDescriptionParam Kinds
estimate1Estimate a terminal risk-neutral probability from a vertical option spread.inputs: value

Summary

Types

Calculation assumptions returned with a probability estimate.

Annual discounting convention.

Validation or no-arbitrage failure.

Explicit vertical-spread inputs.

Option side used to construct the vertical.

Quote selection assumption for the two spread legs.

Successful risk-neutral terminal probability estimate.

Functions

Estimate a terminal risk-neutral probability from a vertical option spread.

Types

assumptions()

@type assumptions() :: %{
  side: option_side(),
  terminal_state: :above_strike_interval | :below_strike_interval,
  lower_strike: float(),
  upper_strike: float(),
  strike_spacing: float(),
  lower_price: float(),
  upper_price: float(),
  spread_price: float(),
  time_to_expiry_years: float(),
  annual_discount_rate: float(),
  discount_convention: discount_convention(),
  discount_factor: float(),
  quote_side: quote_side(),
  quote_application: :both_mid | :lower_ask_upper_bid | :lower_bid_upper_ask
}

Calculation assumptions returned with a probability estimate.

discount_convention()

@type discount_convention() :: :continuous | :simple

Annual discounting convention.

error_reason()

@type error_reason() ::
  {:invalid_input, :spread | atom()}
  | {:missing_input, atom()}
  | {:invalid_strike_order, %{lower_strike: number(), upper_strike: number()}}
  | {:invalid_discount_factor, float()}
  | {:probability_out_of_bounds,
     %{probability: float(), minimum: float(), maximum: float()}}

Validation or no-arbitrage failure.

inputs()

@type inputs() :: %{
  side: option_side(),
  lower_strike: number(),
  upper_strike: number(),
  lower_price: number(),
  upper_price: number(),
  time_to_expiry_years: number(),
  annual_discount_rate: number(),
  discount_convention: discount_convention(),
  quote_side: quote_side()
}

Explicit vertical-spread inputs.

option_side()

@type option_side() :: :call | :put

Option side used to construct the vertical.

quote_side()

@type quote_side() :: :mid | :natural

Quote selection assumption for the two spread legs.

result()

@type result() :: %{
  type: :risk_neutral_terminal_probability,
  probability: float(),
  assumptions: assumptions()
}

Successful risk-neutral terminal probability estimate.

Functions

estimate(inputs)

@spec estimate(inputs() | term()) :: {:ok, result()} | {:error, error_reason()}

Estimate a terminal risk-neutral probability from a vertical option spread.

Parameters

  • inputs - Map with side, ordered strikes, leg prices, expiry years, annual discount rate/convention, and :mid or :natural quote-side assumption (value)

Returns

{:ok, %{type: :risk_neutral_terminal_probability, probability, assumptions}} or {:error, reason} (result_tuple)

Example

{:ok,
 %{
   type: :risk_neutral_terminal_probability,
   assumptions: %{
     side: :call,
     time_to_expiry_years: 0.5,
     lower_price: 6.0,
     upper_price: 1.0,
     lower_strike: 100.0,
     upper_strike: 110.0,
     annual_discount_rate: 0.0,
     discount_convention: :continuous,
     quote_side: :mid,
     terminal_state: :above_strike_interval,
     strike_spacing: 10.0,
     spread_price: 5.0,
     discount_factor: 1.0,
     quote_application: :both_mid
   },
   probability: 0.5
 }}

Errors

  • :missing_input - A required input field is absent
  • :invalid_input - An input has an unsupported type, value, or convention
  • :invalid_strike_order - Lower strike must be strictly below upper strike
  • :invalid_discount_factor - Discounting did not produce a strictly positive factor
  • :probability_out_of_bounds - Spread prices violate the zero-to-one no-arbitrage bounds
# descripex:contract
%{
  params: %{
    inputs: %{
      description: "Map with side, ordered strikes, leg prices, expiry years, annual discount rate/convention, and :mid or :natural quote-side assumption",
      kind: :value
    }
  },
  errors: [
    missing_input: "A required input field is absent",
    invalid_input: "An input has an unsupported type, value, or convention",
    invalid_strike_order: "Lower strike must be strictly below upper strike",
    invalid_discount_factor: "Discounting did not produce a strictly positive factor",
    probability_out_of_bounds: "Spread prices violate the zero-to-one no-arbitrage bounds"
  ],
  returns: %{
    type: :result_tuple,
    description: "`{:ok, %{type: :risk_neutral_terminal_probability, probability, assumptions}}` or `{:error, reason}`"
  },
  returns_example: {:ok,
   %{
     type: :risk_neutral_terminal_probability,
     assumptions: %{
       side: :call,
       time_to_expiry_years: 0.5,
       lower_price: 6.0,
       upper_price: 1.0,
       lower_strike: 100.0,
       upper_strike: 110.0,
       annual_discount_rate: 0.0,
       discount_convention: :continuous,
       quote_side: :mid,
       terminal_state: :above_strike_interval,
       strike_spacing: 10.0,
       spread_price: 5.0,
       discount_factor: 1.0,
       quote_application: :both_mid
     },
     probability: 0.5
   }}
}