options

package
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package options provides access to the Market Data options endpoints: option chains (Chain), expiration dates (Expirations), single and bulk contract quotes (Quote and Quotes), and OCC option symbol lookup (Lookup). It is used through the Options service on a marketdata client rather than on its own.

API documentation: https://www.marketdata.app/docs/api/options/index

Zero Values and Null Data

Numeric fields use Go value types (float64, int64) rather than pointers. When the API returns null for a field, it is unmarshaled as the zero value (0 for integers, 0.0 for floats). This means a zero value may represent either an actual zero or the absence of data. For fields like IV and the Greeks (Delta, Gamma, Theta, Vega), a zero value may indicate that the data was not calculable rather than a true zero. Timestamp fields are the exception: a null or absent timestamp decodes to the zero time.Time, so IsZero reliably detects missing data.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CSVService

type CSVService struct {
	// contains filtered or unexported fields
}

CSVService is the CSV facet of options, reached through Service.AsCSV. Every endpoint here returns a response.CSVResponse carrying the API's raw CSV text — see ADR-018 for why this exists alongside the typed JSON methods on Service. The SDK does not parse the CSV into rows; callers get the text exactly as the API sent it.

Service.Lookup has no CSV facet: it resolves to a single OCC symbol string, not tabular data.

func (*CSVService) Chain

func (s *CSVService) Chain(ctx context.Context, symbol string, opts ...ChainOption) (*response.CSVResponse, error)

Chain fetches the options chain for an underlying stock symbol as CSV. See Service.Chain for parameter and validation details.

func (*CSVService) Expirations

func (s *CSVService) Expirations(ctx context.Context, symbol string, opts ...ExpirationOption) (*response.CSVResponse, error)

Expirations fetches the expiration dates for an underlying stock symbol as CSV. See Service.Expirations for parameter and validation details.

func (*CSVService) Quote

func (s *CSVService) Quote(ctx context.Context, optionSymbol string, opts ...QuoteOption) (*response.CSVResponse, error)

Quote fetches a single option contract's quote as CSV. See Service.Quote for parameter and validation details.

func (*CSVService) Quotes

func (s *CSVService) Quotes(ctx context.Context, optionSymbols []string, opts ...QuoteOption) (map[string]*response.CSVResponse, error)

Quotes fetches quotes for multiple option contracts as CSV, one request per symbol (mirroring Service.Quotes' own per-symbol fan-out), and returns a map keyed by option symbol instead of merging into one result — each contract's CSV text is independent, unlike candle chunks of the same time series. The first error cancels the remaining in-flight requests (ADR-014).

The options apply to every symbol in the batch, so a historical window is expressible for a watchlist and not only for a single contract — the same reason Service.Quotes takes a slice plus options rather than a variadic symbol list.

Unlike Service.Quotes, there is no NoData omission: every requested symbol gets an entry in the map holding whatever the API returned for it (see ADR-018 — the CSV/HTML facets have no NoData concept).

type ChainOption

type ChainOption interface {
	// contains filtered or unexported methods
}

ChainOption is a functional option that filters or otherwise refines a Service.Chain request. Chain options combine: pass several to narrow the chain by expiration, strike, side, liquidity, and more. The two mutually-exclusive parameter groups are each collapsed into a single sealed-union option — WithStrike and WithExpiry — so the API's silently-conflicting combinations cannot be written.

func WithAM

func WithAM(am bool) ChainOption

WithAM controls whether AM-settled contracts are included. Pass true to limit the chain to AM-settled contracts or false to exclude them; when the option is not used, settlement is not filtered. Settlement style is meaningful only for index options (for example SPX, NDX); on single-stock and ETF options the API tolerates the parameter but it has no effect.

func WithChainDate

func WithChainDate(t time.Time) ChainOption

WithChainDate requests the option chain as it stood on a single historical trading day (date=YYYY-MM-DD). Only the calendar date is used; a zero time returns the current chain. It is independent of the expiration selectors, so a historical chain can still be narrowed by expiration or strike. (A from/to date range filters expirations, not history — use options.ExpirationBetween via WithExpiry for that.)

func WithExpirationTypes

func WithExpirationTypes(f ExpirationTypeFilter) ChainOption

WithExpirationTypes filters the chain by expiration cadence, either including or excluding a set of types via a single ExpirationTypeFilter value. Because inclusion and exclusion are one value, the API's forbidden include/exclude mix cannot be expressed.

func WithExpiry

func WithExpiry(f ExpiryFilter) ChainOption

WithExpiry sets the chain's expiration selector from a single ExpiryFilter value, such as options.OnExpiration(t), options.InDTE(45), or options.InMonthOfYear(6, 2026). It replaces the API's mutually-exclusive expiration, dte, month, and year parameters with one option.

func WithMaxAsk

func WithMaxAsk(max float64) ChainOption

WithMaxAsk limits the chain to contracts whose ask price is less than or equal to max.

func WithMaxBid

func WithMaxBid(max float64) ChainOption

WithMaxBid limits the chain to contracts whose bid price is less than or equal to max.

func WithMaxBidAskSpread

func WithMaxBidAskSpread(max float64) ChainOption

WithMaxBidAskSpread limits the chain to contracts whose bid-ask spread is less than or equal to max, expressed in dollars. See WithMaxBidAskSpreadPct for a relative version.

func WithMaxBidAskSpreadPct

func WithMaxBidAskSpreadPct(max float64) ChainOption

WithMaxBidAskSpreadPct limits the chain to contracts whose bid-ask spread is less than or equal to max as a percentage of the UNDERLYING price — not of the contract's own midpoint, which this godoc claimed until 2026-08-20. The distinction matters: on a $316 underlying, 0.15 admits a $0.47 spread, while a caller reading "percentage of the midpoint" would expect it to admit only spreads under 15% of a mid that is often well under a dollar.

Verified live by sweeping the value against one chain: 0.09 admitted only contracts whose spread was at most 9% of the underlying, 0.11 at most 11%, 0.13 at most 13% — tracking the underlying exactly at every step, and never the midpoint. See WithMaxBidAskSpread for an absolute dollar version, which is unambiguous.

func WithMinAsk

func WithMinAsk(min float64) ChainOption

WithMinAsk limits the chain to contracts whose ask price is greater than or equal to min.

func WithMinBid

func WithMinBid(min float64) ChainOption

WithMinBid limits the chain to contracts whose bid price is greater than or equal to min. Useful for excluding illiquid or worthless contracts.

func WithMinOpenInterest

func WithMinOpenInterest(min int) ChainOption

WithMinOpenInterest limits the chain to contracts with open interest greater than or equal to min, filtering out thinly held contracts.

func WithMinVolume

func WithMinVolume(min int) ChainOption

WithMinVolume limits the chain to contracts with trading volume greater than or equal to min, filtering out thinly traded contracts.

func WithNonstandard

func WithNonstandard(nonstandard bool) ChainOption

WithNonstandard controls whether nonstandard contracts (for example, adjusted contracts created by splits or mergers) are included in the chain. Pass true to include them or false to exclude them; when the option is not used, the API default applies.

func WithPM

func WithPM(pm bool) ChainOption

WithPM controls whether PM-settled contracts are included. Pass true to limit the chain to PM-settled contracts or false to exclude them; when the option is not used, settlement is not filtered. Settlement style is meaningful only for index options (for example SPX, NDX); on single-stock and ETF options the API tolerates the parameter but it has no effect.

func WithRange

func WithRange(r Moneyness) ChainOption

WithRange filters the chain by moneyness, using one of MoneynessITM, MoneynessOTM, or MoneynessAll. MoneynessUnset leaves the filter off. Pair it with WithStrikeLimit to ask for "the N strikes around the money".

func WithSide

func WithSide(side OptionSide) ChainOption

WithSide limits the chain to one side of the market: SideCall for calls only or SidePut for puts only. Passing SideBoth leaves the parameter unset, which returns both sides (the default).

func WithStrike

func WithStrike(f StrikeFilter) ChainOption

WithStrike sets the chain's contract selector from a single StrikeFilter value, such as options.Strike(150), options.StrikeRange(150, 160), or options.ByDelta(0.30). It replaces the API's mutually-exclusive strike and delta parameters with one option, so they can never conflict.

func WithStrikeLimit

func WithStrikeLimit(limit int) ChainOption

WithStrikeLimit limits the chain to the n strikes nearest the money on EACH side of it, so a request can come back with up to 2n distinct strikes, not n (verified live 2026-08-20: strikeLimit=1 returned 2 distinct strikes, 2 returned 4, 3 returned 6). Values less than or equal to zero leave the limit unset.

type ExpirationOption

type ExpirationOption interface {
	// contains filtered or unexported methods
}

ExpirationOption is a functional option that refines a Service.Expirations request. The two filters are independent and may be combined.

func WithExpirationDate

func WithExpirationDate(d time.Time) ExpirationOption

WithExpirationDate requests the expiration dates that were available on the given historical date rather than today's list. Only the calendar date is used; a zero time leaves the parameter unset.

func WithExpirationStrike

func WithExpirationStrike(strike float64) ExpirationOption

WithExpirationStrike limits the expiration list to dates that have a contract listed at the given strike price. The value must be greater than zero to take effect.

type ExpirationType

type ExpirationType string

ExpirationType identifies an expiration cadence for the chain's expiration-type filter (WithExpirationTypes).

const (
	// Weekly is a weekly expiration cadence.
	Weekly ExpirationType = "weekly"
	// Monthly is a standard monthly expiration cadence.
	Monthly ExpirationType = "monthly"
	// Quarterly is a quarterly expiration cadence.
	Quarterly ExpirationType = "quarterly"
)

type ExpirationTypeFilter

type ExpirationTypeFilter interface {
	// contains filtered or unexported methods
}

ExpirationTypeFilter includes or excludes expiration cadences (weekly, monthly, quarterly) from the chain. It is a sealed union built with exactly one of IncludeExpirationTypes or ExcludeExpirationTypes. The API forbids mixing inclusion and exclusion of expiration types in one request ("weekly=true&monthly=false" is an error); because the choice of include vs. exclude is a single value here, that illegal mix cannot be written.

func ExcludeExpirationTypes

func ExcludeExpirationTypes(types ...ExpirationType) ExpirationTypeFilter

ExcludeExpirationTypes excludes the given expiration cadences from the chain (for example ExcludeExpirationTypes(options.Quarterly) sends quarterly=false). Passing none is a no-op.

func IncludeExpirationTypes

func IncludeExpirationTypes(types ...ExpirationType) ExpirationTypeFilter

IncludeExpirationTypes limits the chain to only the given expiration cadences (for example IncludeExpirationTypes(options.Weekly, options.Monthly) sends weekly=true&monthly=true). Passing none is a no-op.

type Expirations

type Expirations struct {
	// Dates is the list of expiration dates, in Eastern time.
	Dates []time.Time

	// Updated is when the server last refreshed this expirations list, as
	// reported by the API's response-level updated field. It is the zero
	// time if the API omits the field.
	Updated time.Time
}

Expirations is the result of a Service.Expirations request: the expiration dates with listed option contracts for an underlying symbol, plus the server's response-level update time. Both mirror the API response exactly.

func (Expirations) String

func (e Expirations) String() string

String returns a summary of the expirations list.

type ExpiryFilter

type ExpiryFilter interface {
	// contains filtered or unexported methods
}

ExpiryFilter selects which expirations a Service.Chain request returns. It is a sealed union: build it with exactly one of the mode constructors below and pass it to WithExpiry. Because an ExpiryFilter is a single value, the API's mutually-exclusive expiry selectors can never be combined by mistake — combining expiration with dte makes the API silently honor expiration, and combining expiration with month/year yields an empty (broken) response.

The constructors mirror the ways the API accepts an expiry filter:

AllExpirations()       // every listed expiration       -> expiration=all
OnExpiration(t)        // a single expiration date     -> expiration=YYYY-MM-DD
InDTE(days)            // the expiry nearest days out   -> dte=days
InMonth(month)         // every year's given month      -> month=month
InYear(year)           // every month of a given year   -> year=year
InMonthOfYear(m, y)    // one specific month and year   -> month=m&year=y

month must be 1 through 12, year a four-digit year (at least 1900), and dte zero or greater. Values are checked before any request is made.

Note that omitting the filter entirely is NOT the same as AllExpirations: with no expiry filter the chain endpoint returns only the front-month expiration.

func AllExpirations

func AllExpirations() ExpiryFilter

AllExpirations requests the whole chain across every listed expiration (expiration=all).

This is not the same as omitting the expiry filter: with no expiry filter the chain endpoint returns only the front-month (nearest) expiration, so AllExpirations is the only way to obtain the complete chain. For a liquid underlying the difference is large — an AAPL chain returns roughly 190 contracts across 1 expiration unfiltered, against roughly 3,500 contracts across 24 expirations with AllExpirations — so the request is correspondingly more expensive in API credits. The independent filters (side, strike, the liquidity filters, the expiration-type filters) still narrow the result on top of it.

func ExpirationBetween

func ExpirationBetween(from, to time.Time) ExpiryFilter

ExpirationBetween limits the chain to contracts expiring within an explicit, inclusive date range (from=YYYY-MM-DD&to=YYYY-MM-DD). Only the calendar dates are used. It selects which expirations are returned and so is mutually exclusive with the other expiry selectors (the API forbids combining a from/to expiration range with dte).

func InDTE

func InDTE(days int) ExpiryFilter

InDTE limits the chain to the expiration closest to the given number of days to expiry, counted from today (dte=days).

func InMonth

func InMonth(month int) ExpiryFilter

InMonth limits the chain to contracts expiring in the given calendar month across all years (month=month), expressed as 1 through 12.

func InMonthOfYear

func InMonthOfYear(month, year int) ExpiryFilter

InMonthOfYear limits the chain to contracts expiring in a specific month of a specific year (month=month&year=year).

func InYear

func InYear(year int) ExpiryFilter

InYear limits the chain to contracts expiring in the given four-digit year across all months (year=year).

func OnExpiration

func OnExpiration(t time.Time) ExpiryFilter

OnExpiration limits the chain to contracts expiring on the given date (expiration=YYYY-MM-DD). Only the calendar date is used.

type Moneyness

type Moneyness string

Moneyness is the chain's moneyness filter, passed to WithRange. It is a closed set of API keywords rather than a free string, so a typo cannot reach the wire as a silently-ignored filter.

const (
	// MoneynessITM limits the chain to in-the-money contracts.
	MoneynessITM Moneyness = "itm"
	// MoneynessOTM limits the chain to out-of-the-money contracts.
	MoneynessOTM Moneyness = "otm"
	// MoneynessAll requests every contract regardless of moneyness (the
	// API default).
	MoneynessAll Moneyness = "all"
	// MoneynessUnset leaves the filter off, equivalent to omitting the option.
	MoneynessUnset Moneyness = ""
)

type OptionQuote

type OptionQuote struct {
	// OptionSymbol is the OCC option symbol
	OptionSymbol string `json:"optionSymbol"`

	// Underlying is the underlying stock symbol
	Underlying string `json:"underlying"`

	// Expiration is the expiration date
	Expiration time.Time `json:"expiration"`

	// Strike is the strike price
	Strike float64 `json:"strike"`

	// Type is call or put
	Type OptionType `json:"side"`

	// Bid is the bid price
	Bid float64 `json:"bid"`

	// BidSize is the bid size
	BidSize int `json:"bidSize"`

	// Ask is the ask price
	Ask float64 `json:"ask"`

	// AskSize is the ask size
	AskSize int `json:"askSize"`

	// Last is the last trade price
	Last float64 `json:"last"`

	// Volume is the trading volume
	Volume int64 `json:"volume"`

	// OpenInterest is the open interest
	OpenInterest int64 `json:"openInterest"`

	// IV is the implied volatility
	IV float64 `json:"iv"`

	// Delta is the delta greek
	Delta float64 `json:"delta"`

	// Gamma is the gamma greek
	Gamma float64 `json:"gamma"`

	// Theta is the theta greek
	Theta float64 `json:"theta"`

	// Vega is the vega greek
	Vega float64 `json:"vega"`

	// Rho is the rho greek — the contract's sensitivity to interest rates.
	// The API models rho internally but does not currently serialize it on
	// the chain or quotes endpoints (verified live 2026-08-12), so this
	// field is zero today. It is declared so that the value surfaces without
	// an SDK change once the API emits it, matching sdk-java, which models it
	// for the same reason.
	Rho float64 `json:"rho"`

	// Mid is the midpoint price from the API
	Mid float64 `json:"mid"`

	// UnderlyingPrice is the current price of the underlying
	UnderlyingPrice float64 `json:"underlyingPrice"`

	// IntrinsicValue is the intrinsic value of the option
	IntrinsicValue float64 `json:"intrinsicValue"`

	// ExtrinsicValue is the extrinsic (time) value of the option
	ExtrinsicValue float64 `json:"extrinsicValue"`

	// FirstTraded is the date the option was first traded
	FirstTraded time.Time `json:"firstTraded"`

	// DTE is the days to expiration
	DTE int `json:"dte"`

	// InTheMoney indicates if the option is ITM
	InTheMoney bool `json:"inTheMoney"`

	// Updated is when this contract was last updated
	Updated time.Time `json:"updated"`
}

OptionQuote is a quote for a single option contract, including pricing, volume, open interest, implied volatility, and the Greeks. It is used both for the entries of an OptionsChain returned by Service.Chain and for the single-contract responses of Service.Quote and Service.Quotes. Timestamps are normalized to Eastern time (the exchange time zone). See the package documentation for how null API values map to Go zero values.

func (*OptionQuote) CalcMid

func (c *OptionQuote) CalcMid() float64

CalcMid calculates the bid-ask midpoint locally from the Bid and Ask fields, unlike the Mid field, which is the midpoint reported by the API.

func (*OptionQuote) Spread

func (c *OptionQuote) Spread() float64

Spread returns the bid-ask spread (Ask minus Bid).

func (OptionQuote) String

func (c OptionQuote) String() string

String returns a summary of the option contract.

type OptionQuoteWindow

type OptionQuoteWindow interface {
	// contains filtered or unexported methods
}

OptionQuoteWindow selects the date range for a Service.Quote request. It is a sealed union: build it with exactly one of the mode constructors below and pass it to WithOptionQuoteWindow. Because an OptionQuoteWindow is a single value, the API's mutually-exclusive date parameters can never be combined by mistake — sending both date and from/to returns HTTP 400 ("Invalid date. Use either date, or from and to.").

The constructors mirror the ways the API accepts a quote window:

QuoteOnDate(t)            // a single historical date -> date=YYYY-MM-DD
QuoteRange(from, to)      // an explicit date range    -> from=...&to=...
QuoteLastN(n)             // the n most recent quotes  -> countback=n
QuoteLastNUntil(n, to)    // n quotes ending at to     -> countback=n&to=...

Only the calendar date of each time.Time is used. A countback must be greater than zero. The countback modes mirror the stocks and funds date windows: the API pairs countback with to, never with from, and that pairing is the only one expressible here.

func QuoteLastN

func QuoteLastN(n int) OptionQuoteWindow

QuoteLastN requests n quotes for the contract (countback=n), sent with an explicit to= anchor of today's date in Eastern time. n must be greater than zero.

The anchor is required: this endpoint ignores a countback that arrives without a to=, returning a single current quote instead of n.

Known API defect

The endpoint currently returns the n OLDEST quotes of the contract's history rather than the n most recent, and ignores the to= anchor when choosing them (verified live 2026-08-11: countback=3 with to= of 2026-08-11, 2026-08-08, and 2025-08-13 all return the same three earliest rows). Until that is fixed, prefer QuoteRange when you need a specific period. The SDK sends the parameters exactly as documented and does not attempt to compensate; integration/discrepancy_test.go carries the strict assertion that will pass once the API is corrected.

func QuoteLastNUntil

func QuoteLastNUntil(n int, to time.Time) OptionQuoteWindow

QuoteLastNUntil requests n quotes ending on the given date (countback=n&to=YYYY-MM-DD). n must be greater than zero and to must not be zero. Only the calendar date of to is used.

See QuoteLastN for the known API defect that currently makes this endpoint ignore the to= anchor and return the contract's oldest quotes.

func QuoteOnDate

func QuoteOnDate(t time.Time) OptionQuoteWindow

QuoteOnDate requests the contract's quote on a single historical date (date=YYYY-MM-DD).

func QuoteRange

func QuoteRange(from, to time.Time) OptionQuoteWindow

QuoteRange requests the contract's quotes across an explicit date range (from=...&to=...).

type OptionSide

type OptionSide string

OptionSide is the side filter for options chain requests, passed to WithSide. Use SideCall for calls only, SidePut for puts only, or SideBoth (the default) for both sides of the chain.

const (
	// SideCall limits a chain request to call options.
	SideCall OptionSide = "call"
	// SidePut limits a chain request to put options.
	SidePut OptionSide = "put"
	// SideBoth requests both calls and puts (the default).
	SideBoth OptionSide = ""
)

type OptionType

type OptionType string

OptionType identifies a contract as a call or a put. It is used as the Type field of OptionQuote and as the required contract type argument to Service.Lookup. Use the Call and Put constants.

const (
	// Call is a call option contract.
	Call OptionType = "call"
	// Put is a put option contract.
	Put OptionType = "put"
)

type OptionsChain

type OptionsChain struct {
	// Underlying is the underlying stock symbol
	Underlying string

	// Options is the list of option quotes in the chain
	Options []OptionQuote
}

OptionsChain is the result of a Service.Chain request: the set of option contracts listed for a single underlying symbol, after any server-side filters have been applied. Each contract appears as an OptionQuote in the Options slice, carrying its own quote-snapshot Updated time; the API reports no chain-level timestamp, so none is exposed here.

func (OptionsChain) String

func (oc OptionsChain) String() string

String returns a summary of the options chain.

type QuoteOption

type QuoteOption interface {
	// contains filtered or unexported methods
}

QuoteOption is a functional option that refines a Service.Quote request. The historical date range is set with WithOptionQuoteWindow using a single OptionQuoteWindow value, so the API's mutually-exclusive date and from/to parameters cannot be combined.

func WithOptionQuoteWindow

func WithOptionQuoteWindow(w OptionQuoteWindow) QuoteOption

WithOptionQuoteWindow requests a historical quote for a single contract from a single OptionQuoteWindow value: options.QuoteOnDate(t) for a single day or options.QuoteRange(from, to) for a range. Omitting it returns the current quote.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service provides access to the Market Data options endpoints. It is not meant to be constructed directly; use the Options field of the marketdata client, which shares the client's HTTP transport, rate-limit tracking, and concurrency pool.

func NewService

func NewService(httpClient *http.Client) *Service

NewService creates a new options service backed by the given HTTP client. Most callers should use the Options service on a marketdata client rather than calling NewService directly.

func (*Service) AsCSV

func (s *Service) AsCSV() *CSVService

AsCSV returns the CSV facet of this service.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	csv, err := client.Options.AsCSV().Chain(context.Background(), "AAPL")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(csv.CSV())
}

func (*Service) Chain

func (s *Service) Chain(ctx context.Context, symbol string, opts ...ChainOption) (*OptionsChain, *response.Response, error)

Chain fetches the options chain for an underlying stock symbol, returning a real-time or historical OptionsChain with one OptionQuote per contract. The symbol is required; all other parameters are optional and supplied as ChainOption values.

Without an expiry filter the API returns only the front-month (nearest) expiration, not the whole chain. To fetch every listed expiration, pass options.AllExpirations() to WithExpiry — and expect a much larger response, billed accordingly.

The two groups of mutually-exclusive API parameters are each expressed as a single sealed-union option, so the API's silently-conflicting combinations cannot be written: WithStrike selects contracts by strike or delta, and WithExpiry selects expirations by date, dte, month, year, or an expiration range. WithChainDate requests a historical snapshot as of a day and is independent. Free filters such as WithSide, WithStrikeLimit, WithRange, the bid/ask and liquidity filters, and the expiration-type filters combine freely. See the ChainOption constructors in this package for the complete set.

Option values that cannot be encoded in the type system (a strike or delta out of range, a month outside 1..12, a malformed date range) are rejected with a sdkerrors.ValidationError before any request is made.

A symbol the API does not recognize is reported as a [NotFoundError]. A valid request that simply matches no contracts is not an error: Chain returns an empty chain (safe to range), a nil error, and a response whose NoData field is true. See the "Missing Data and Unknown Symbols" section of the marketdata package documentation.

API documentation: https://www.marketdata.app/docs/api/options/chain

Example:

chain, _, err := client.Options.Chain(ctx, "AAPL",
    options.WithExpiry(options.OnExpiration(time.Now().AddDate(0, 1, 0))),
    options.WithStrike(options.StrikeRange(150, 160)),
)
Example

ExampleService_Chain fetches a filtered options chain for AAPL, limiting the result to a single expiration and a strike range.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
	"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
)

func main() {
	client, err := marketdata.NewClient() // token from MARKETDATA_TOKEN
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	expiration := time.Now().AddDate(0, 1, 0)
	chain, _, err := client.Options.Chain(context.Background(), "AAPL",
		options.WithExpiry(options.OnExpiration(expiration)),
		options.WithStrike(options.StrikeRange(150, 200)),
	)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(chain)
}

func (*Service) Expirations

func (s *Service) Expirations(ctx context.Context, symbol string, opts ...ExpirationOption) (*Expirations, *response.Response, error)

Expirations fetches the expiration dates with listed option contracts for an underlying stock symbol. The returned Expirations carries the dates in Eastern time plus the server's response-level update time, mirroring the API response. The symbol is required. The optional ExpirationOption values WithExpirationStrike (limit to expirations offering a given strike) and WithExpirationDate (list the expirations that were available on a past date) refine the request.

A request with no listed expirations is not an error: Expirations returns an empty result (Dates is safe to range), a nil error, and a response whose NoData field is true. Note that options/expirations answers an unknown symbol with an unmarked 404 too, so an unrecognized symbol is reported the same way rather than as a [NotFoundError] — the API sends nothing that tells the two apart.

API documentation: https://www.marketdata.app/docs/api/options/expirations

Example:

exps, _, err := client.Options.Expirations(ctx, "AAPL")
if err != nil {
    log.Fatal(err)
}
if exps != nil {
    for _, d := range exps.Dates { ... }
}
Example

ExampleService_Expirations lists the available expiration dates for AAPL.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	expirations, _, err := client.Options.Expirations(context.Background(), "AAPL")
	if err != nil {
		log.Fatal(err)
	}
	if expirations != nil {
		for _, exp := range expirations.Dates {
			fmt.Println(exp.Format("2006-01-02"))
		}
	}
}

func (*Service) GetChain

func (s *Service) GetChain(symbol string, opts ...ChainOption) (*OptionsChain, error)

GetChain is a convenience wrapper for Service.Chain that uses context.Background() and discards the response metadata. It accepts the same required symbol and optional ChainOption filters, and shares Chain's no-data behavior: when the API returns 404, GetChain returns a nil chain and a nil error.

Example

ExampleService_GetChain uses the convenience wrapper, which needs no context and returns only the chain and an error.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
	"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	chain, err := client.Options.GetChain("AAPL",
		options.WithExpiry(options.OnExpiration(time.Now().AddDate(0, 1, 0))),
	)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(chain)
}

func (*Service) GetExpirations

func (s *Service) GetExpirations(symbol string, opts ...ExpirationOption) (*Expirations, error)

GetExpirations is a convenience wrapper for Service.Expirations that uses context.Background() and discards the response metadata. When the API returns 404, GetExpirations returns nil and a nil error.

func (*Service) GetLookup

func (s *Service) GetLookup(underlying string, expiration time.Time, strike float64, optionType OptionType) (string, error)

GetLookup is a convenience wrapper for Service.Lookup that uses context.Background() and discards the response metadata. When the API cannot resolve the contract (404), GetLookup returns an empty string and a nil error.

func (*Service) GetLookupQuery

func (s *Service) GetLookupQuery(query string) (string, error)

GetLookupQuery is a convenience wrapper for Service.LookupQuery that uses context.Background() and discards the response metadata. When the API cannot resolve the contract (404), GetLookupQuery returns an empty string and a nil error.

func (*Service) GetQuote

func (s *Service) GetQuote(optionSymbol string, opts ...QuoteOption) (*OptionQuote, error)

GetQuote is a convenience wrapper for Service.Quote that uses context.Background() and discards the response metadata. When the API returns 404, GetQuote returns a nil quote and a nil error.

func (*Service) GetQuoteHistory

func (s *Service) GetQuoteHistory(optionSymbol string, opts ...QuoteOption) ([]OptionQuote, error)

GetQuoteHistory is a convenience wrapper for Service.QuoteHistory that uses context.Background() and discards the response metadata. When the API returns 404, GetQuoteHistory returns a nil slice and a nil error.

func (*Service) GetQuotes

func (s *Service) GetQuotes(optionSymbols []string, opts ...QuoteOption) ([]OptionQuote, error)

GetQuotes is a convenience wrapper for Service.Quotes that uses context.Background() and discards the response metadata. Like Quotes, it fetches the symbols concurrently and omits symbols with no data from the result.

func (*Service) GetQuotesBySymbol

func (s *Service) GetQuotesBySymbol(optionSymbols []string, opts ...QuoteOption) (map[string]*OptionQuote, error)

GetQuotesBySymbol is a convenience wrapper for Service.QuotesBySymbol that uses context.Background() and discards the response metadata. Like QuotesBySymbol, it returns one entry per requested symbol, with a nil value where the API had no data.

func (*Service) Lookup

func (s *Service) Lookup(ctx context.Context, underlying string, expiration time.Time, strike float64, optionType OptionType) (string, *response.Response, error)

Lookup resolves an option contract to its OCC option symbol. It takes the underlying stock symbol, the expiration date, the strike price, and an OptionType (Call or Put), all of which are required, and returns the OCC symbol string (for example "AAPL250117C00150000") suitable for use with Service.Quote and Service.Quotes.

All four arguments are validated before any request is made: a missing underlying, a zero expiration, a strike at or below zero, or an option type other than Call or Put is rejected with a sdkerrors.ValidationError. Without those checks an unset argument would be interpolated into the query verbatim, and the resulting 404 would be reported as no-data — making a malformed call indistinguishable from a contract that does not exist.

If the API cannot resolve the contract (HTTP 404), Lookup returns an empty string, a response whose NoData field is true, and a nil error.

API documentation: https://www.marketdata.app/docs/api/options/lookup

Example:

symbol, _, err := client.Options.Lookup(ctx, "AAPL", expDate, 150.0, options.Call)
Example

ExampleService_Lookup resolves an underlying, expiration, strike, and option type to the corresponding OCC option symbol.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
	"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	expiration := time.Now().AddDate(0, 1, 0)
	symbol, _, err := client.Options.Lookup(context.Background(), "AAPL", expiration, 150, options.Call)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(symbol)
}

func (*Service) LookupQuery

func (s *Service) LookupQuery(ctx context.Context, query string) (string, *response.Response, error)

LookupQuery resolves an option contract to its OCC option symbol from a free-form, human-readable description, exactly as the endpoint accepts it — for example "AAPL 7/26/23 $200 Call". The query is required and is sent as a single URL path segment; the endpoint does its own parsing, so the accepted phrasings are the API's, not the SDK's.

Service.Lookup is the typed alternative for the common case where the underlying, expiration, strike, and type are already known separately; it validates those four and assembles the query. Use LookupQuery when the description arrives as text — from a user, a spreadsheet cell, or a broker export — and cannot be decomposed first.

If the API cannot resolve the contract (HTTP 404), LookupQuery returns an empty string, a response whose NoData field is true, and a nil error.

API documentation: https://www.marketdata.app/docs/api/options/lookup

Example:

symbol, _, err := client.Options.LookupQuery(ctx, "AAPL 7/26/23 $200 Call")

func (*Service) Quote

func (s *Service) Quote(ctx context.Context, optionSymbol string, opts ...QuoteOption) (*OptionQuote, *response.Response, error)

Quote fetches a real-time or historical OptionQuote for a single option contract, identified by its OCC option symbol (for example "AAPL250117C00150000"). The option symbol is required; if it is not known, Service.Lookup can resolve one from the underlying, expiration, strike, and type. The optional WithOptionQuoteWindow carries a single OptionQuoteWindow value — options.QuoteOnDate(t), options.QuoteRange(from, to), options.QuoteLastN(n), or options.QuoteLastNUntil(n, to) — to request a historical quote; because it is one value, the API's mutually-exclusive date, from/to, and countback parameters cannot be combined (the API returns HTTP 400 when both a date and a from/to are sent).

A malformed quote window (a zero date, a from after its to, or a countback at or below zero) is rejected with a sdkerrors.ValidationError before any request is made.

Quote returns exactly one quote, as its name says. A window that selects several days — a range, or a countback greater than one — makes the API return one row per day, of which Quote keeps only the first; use Service.QuoteHistory to receive all of them.

An OCC symbol matching no contract is reported as a [NotFoundError]. If the contract exists but the API has no data for the requested window, Quote returns a nil quote, a response whose NoData field is true, and a nil error — an OptionQuote has no meaningful empty value, so callers must check for nil. See the "Missing Data and Unknown Symbols" section of the marketdata package documentation.

API documentation: https://www.marketdata.app/docs/api/options/quotes

Example:

quote, _, err := client.Options.Quote(ctx, "AAPL230120C00150000")
Example

ExampleService_Quote fetches a quote for a single option contract by its OCC option symbol.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	quote, _, err := client.Options.Quote(context.Background(), "AAPL250117C00150000")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(quote)
}

func (*Service) QuoteHistory

func (s *Service) QuoteHistory(ctx context.Context, optionSymbol string, opts ...QuoteOption) ([]OptionQuote, *response.Response, error)

QuoteHistory fetches every quote the API returns for a single option contract, as a slice in the order the API sent them. It takes the same arguments and options as Service.Quote.

It exists because a historical window can select more than one quote: a QuoteRange spanning several days, or a QuoteLastN/QuoteLastNUntil countback, returns one row per day. Quote returns only the first of those rows, since its contract is a single quote; QuoteHistory returns all of them. For a current quote, or a window that selects a single day, the two are equivalent and QuoteHistory returns a one-element slice.

An OCC symbol matching no contract is reported as a [NotFoundError]. If the contract exists but the window selects nothing, QuoteHistory returns a nil slice (safe to range), a response whose NoData field is true, and a nil error.

API documentation: https://www.marketdata.app/docs/api/options/quotes

Example:

quotes, _, err := client.Options.QuoteHistory(ctx, "AAPL260821C00300000",
    options.WithOptionQuoteWindow(options.QuoteLastN(5)))

func (*Service) Quotes

func (s *Service) Quotes(ctx context.Context, optionSymbols []string, opts ...QuoteOption) ([]OptionQuote, *response.Response, error)

Quotes fetches quotes for multiple option contracts identified by their OCC option symbols. At least one symbol is required. The optional QuoteOption values are the same ones Service.Quote accepts and apply to every symbol in the batch, so WithOptionQuoteWindow requests the same historical window across the whole set. The method fans out one concurrent Service.Quote request per symbol; the goroutines draw slots from the client's shared concurrency pool (at most 50 in-flight requests per client across all services), so passing many symbols will not exceed that limit. The results are merged into a single slice in the order the symbols were given.

Symbols the API has no data for, and symbols naming a contract it does not recognize, are both omitted from the result rather than producing an error, so the returned slice may be shorter than the input and gives no indication of which symbols were dropped; use Service.QuotesBySymbol when that distinction matters. This is where the batch methods differ from Service.Quote, which reports an unknown contract as a [NotFoundError]: in a batch a bad symbol is information about that symbol, not a failure of the whole request. If any request fails with a real error, Quotes returns that error and no quotes. The returned response corresponds to one of the individual requests, not an aggregate.

API documentation: https://www.marketdata.app/docs/api/options/quotes

Example:

quotes, _, err := client.Options.Quotes(ctx, []string{"AAPL230120C00150000", "AAPL230120P00150000"})
Example

ExampleService_Quotes fetches quotes for several option contracts concurrently and prints the merged results.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/MarketDataApp/sdk-go/v2/marketdata"
)

func main() {
	client, err := marketdata.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	quotes, _, err := client.Options.Quotes(context.Background(), []string{
		"AAPL250117C00150000",
		"AAPL250117P00150000",
	})
	if err != nil {
		log.Fatal(err)
	}
	for _, q := range quotes {
		fmt.Println(q)
	}
}

func (*Service) QuotesBySymbol

func (s *Service) QuotesBySymbol(ctx context.Context, optionSymbols []string, opts ...QuoteOption) (map[string]*OptionQuote, *response.Response, error)

QuotesBySymbol fetches quotes for multiple option contracts and returns them keyed by the OCC option symbol that was requested. At least one symbol is required. It fetches exactly like Service.Quotes — one concurrent request per symbol, drawn from the client's shared 50-slot pool, with the first hard error cancelling its siblings.

It accepts the same QuoteOption values as Service.Quote, applied to every symbol.

The difference is what happens to contracts the API has no data for, or does not recognize at all. Quotes omits them, so the returned slice is simply shorter than the input and the caller cannot tell which symbols were dropped. QuotesBySymbol returns one map entry for every requested symbol, with a nil value for those, so a missing contract is distinguishable from one that was never asked for. Repeated symbols collapse into a single entry. Neither method reports an unknown contract as an error the way Service.Quote does — see Quotes for why.

If any request fails with a real error, QuotesBySymbol returns that error and no quotes. The returned response corresponds to one of the individual requests, not an aggregate.

API documentation: https://www.marketdata.app/docs/api/options/quotes

Example:

quotes, _, err := client.Options.QuotesBySymbol(ctx, []string{"AAPL230120C00150000", "AAPL230120P00150000"})
for symbol, q := range quotes {
    if q == nil {
        log.Printf("%s: no data", symbol)
        continue
    }
    ...
}

type StrikeFilter

type StrikeFilter interface {
	// contains filtered or unexported methods
}

StrikeFilter selects which contracts a Service.Chain request returns by strike price or by delta. It is a sealed union: build it with exactly one of the mode constructors below and pass it to WithStrike. Because a StrikeFilter is a single value, the API's mutually-exclusive contract selectors can never be combined by mistake — the API silently honors strike over delta when both are sent, and that footgun is not expressible here.

The constructors mirror the ways the API accepts a strike filter:

Strike(x)          // an exact strike            -> strike=x
Strikes(x, y, ...) // several exact strikes      -> strike=x,y,...
StrikeRange(lo, hi)// an inclusive strike range  -> strike=lo-hi
MinStrike(x)       // strikes at or above x      -> strike=>=x
MaxStrike(x)       // strikes at or below x      -> strike=<=x
StrikeExpr(expr)   // a raw strike expression    -> strike=expr
ByDelta(d)         // the strike nearest delta d -> delta=d
ByDeltas(c, d, ...)// the strikes nearest each   -> delta=c,d,...

Strike prices must be greater than zero and a range's low must not exceed its high; a delta must be non-zero and within [-1, 1] (puts have negative delta). The list forms require at least one value and apply the same rules to every element. Values are checked before any request is made.

func ByDelta

func ByDelta(d float64) StrikeFilter

ByDelta selects the strikes nearest the given delta (delta=d). The API filters on the ABSOLUTE value of delta and always returns both sides (calls and puts), so ByDelta does not choose a side — combine it with WithSide to keep only calls or puts.

The filter can silently do nothing. If ANY contract in the chain the API fetched carries a null delta, the whole filter is skipped and the full chain comes back with a 200 and no signal (https://github.com/MarketData-App/api/issues/352). Null greeks are not rare — illiquid strikes, freshly listed contracts, provider metadata rot — so this fires on some symbols and expirations and not others.

It reads as a side limitation because that is how it first presented: on 2026-08-20 delta=0.30&side=call returned one contract at 0.338 while side=put returned 99 and no side at all returned 198, which looked like "calls only". It is not — on 2026-08-26 the same query returned one contract per side and two with no side, correctly filtered. What differed was whether the fetched chain happened to contain a null delta. Check the result rather than assuming either behavior. Tracked in integration/discrepancy_test.go.

Delta is mutually exclusive with strike (the API silently honors strike when both are sent), so it is a mode of StrikeFilter rather than a separate option. d must be non-zero and within [-1, 1] (negative values are accepted and, per the absolute-value rule, behave the same as their positive counterpart).

func ByDeltas

func ByDeltas(deltas ...float64) StrikeFilter

ByDeltas selects the strikes nearest each of the given deltas (delta=c,d,...), for example the 0.16 and 0.30 deltas of a strangle in one request. Like ByDelta it filters on the ABSOLUTE value of delta and returns both sides — combine it with WithSide to keep only calls or puts. Like ByDelta the filter is silently dropped when the fetched chain contains a null delta; see ByDelta. At least one delta is required and every value must be non-zero and within [-1, 1].

func MaxStrike

func MaxStrike(x float64) StrikeFilter

MaxStrike limits the chain to strikes at or below x (strike=<=x).

func MinStrike

func MinStrike(x float64) StrikeFilter

MinStrike limits the chain to strikes at or above x (strike=>=x).

func Strike

func Strike(x float64) StrikeFilter

Strike limits the chain to contracts at an exact strike price (strike=x).

func StrikeExpr

func StrikeExpr(expr string) StrikeFilter

StrikeExpr sets the strike filter using the API's raw expression syntax, passed through verbatim (strike=expr): an exact strike ("150"), an inclusive range ("140-160"), or a one-sided bound (">=140", "<=160"). It is an escape hatch for expressions the typed constructors do not cover.

func StrikeRange

func StrikeRange(lo, hi float64) StrikeFilter

StrikeRange limits the chain to strikes between lo and hi inclusive (strike=lo-hi).

func Strikes

func Strikes(strikes ...float64) StrikeFilter

Strikes limits the chain to an explicit set of exact strike prices (strike=x,y,z), returning both sides at each one unless WithSide narrows it. Use it to price a spread or a specific set of legs in a single request rather than one call per strike. At least one strike is required and every value must be greater than zero.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL