DpExchange.Core.Types.OptionContract (DpExchangeCore v0.3.10)

Copy Markdown View Source

One option contract — what it is, not what it is worth.

Identity only, deliberately

Venues return option chains as fat rows: strike and expiry sitting beside bid, ask, last, mark, volume, open interest and five Greeks. This type takes only the first group.

That is the same decision as splitting Types.Quote from Types.TopOfBook, and for the same reason. A row carrying lastPrice, bidPrice, askPrice, markPrice and theoreticalOptionValue gives a caller five plausible numbers to reach for and no help choosing, and this family has already shipped one defect from exactly that. So:

  • identity — this type
  • bookTypes.TopOfBook
  • last tradeTypes.Quote
  • Greeks, IV, open interestTypes.OptionGreeks

An option is not a symbol, and the contract does not pretend otherwise

SymbolNormalizer speaks BASE-QUOTE and refuses to construct option symbols, which is correct: venue option symbology is positional and venue-specific. Schwab's is fixed-width — XYZ 240315C00500000 is underlying, padded to six, then YYMMDD, then C or P, then an eight-digit strike in thousandths.

A package must not build one by string arithmetic on a canonical pair. The four identity fields here are the contract; :venue_symbol carries whatever the venue calls it, produced by the venue package and never reconstructed elsewhere.

:multiplier is load-bearing and is not always 100

Contract size varies — mini contracts, index options, adjusted contracts after a corporate action. A caller computing notional as price × quantity and omitting the multiplier is wrong by a factor of a hundred on a standard contract, and by an unpredictable factor on an adjusted one. nil means the venue did not say; it does not mean 100.

The flags are not decoration

:non_standard marks a contract whose deliverable has been altered by a corporate action — its price will not track the underlying the way its strike suggests. :index_option marks cash settlement rather than delivery. A caller that treats either as an ordinary equity option will be wrong about what it holds, not merely about its price.

Summary

Functions

Whether the contract is in the money at underlying_price.

Builds a t/0, failing closed if a required field is absent or nil.

Types

right()

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

t()

@type t() :: %DpExchange.Core.Types.OptionContract{
  expiration_type: String.t() | nil,
  expiry: Date.t(),
  index_option: boolean() | nil,
  last_trading_day: Date.t() | nil,
  mini: boolean() | nil,
  multiplier: Decimal.t() | nil,
  non_standard: boolean() | nil,
  provider: atom(),
  right: right(),
  settlement_type: String.t() | nil,
  strike: Decimal.t(),
  underlying: String.t(),
  venue_symbol: String.t() | nil
}

Functions

in_the_money?(option_contract, underlying_price)

@spec in_the_money?(t(), Decimal.t()) :: boolean()

Whether the contract is in the money at underlying_price.

Computed here rather than taken from the venue's isInTheMoney flag, because venues compute it against their own mark at their own time and a caller comparing contracts across venues would be comparing two different questions.

A caller wanting the venue's own opinion should keep it; this answers the arithmetic one.

new(attrs)

@spec new(keyword() | map()) :: t()

Builds a t/0, failing closed if a required field is absent or nil.

@enforce_keys guards presence, not nil — see DpExchange.Core.Types.Validate.