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
- book —
Types.TopOfBook - last trade —
Types.Quote - Greeks, IV, open interest —
Types.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
Types
@type right() :: :call | :put
@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
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.
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.