Record and read an option chain¶
Record an underlying's listed option chain from a data plugin, read it and its open interest from a reading plugin, ask for a series' record before an order names it, and run the options measures over it. What each part means is in Listed options and futures.
You need SDK 0.36.0 or later, CLI 0.1.60, and a deployment
(chart 0.1.538) whose platform publishes the TPL-listed-option
template. meridian plugin new on CLI 0.1.60 writes a plugin on 0.35.0, which
meridian plugin migrate moves to 0.36.0.
In a data plugin: record the chain¶
Declare a dataset serving the chain's kinds in the plugin's catalogue, one
dataset per timeliness: meridian.v1.ListedSeries for the series,
meridian.v1.Statistic for open interest and volume, and
meridian.v1.Quote for the chain's quotes.
For each underlying it lists, record each series as the listed_option
template's fields, keyed by its OSI symbol. The underlying is the
deployment's INS- record, resolved as any subject is; the series itself
is never resolved, and recording it mints nothing.
import meridian
from decimal import Decimal
from meridian.helpers import chains
symbol = chains.osi_symbol("AAPL", "2026-11-20", "put", "230") # "AAPL 261120P00230000"
row = chains.option_series(
aapl, symbol,
template_id="TPL-listed-option", template_version="1",
dataset=wants.dataset("options"), currency="USD",
exercise_style="EXERCISE_STYLE_AMERICAN",
settlement_method="SETTLEMENT_METHOD_PHYSICAL",
deliverable=[(aapl, 100)], # 100 shares of the underlying
)
await wants.record("series", [row], "options")
Read the template's current version from the template catalogue
(meridian.TemplateCatalogue, or
read_template) rather than
writing it in. An adjustment the source states, a new deliverable, strike or
multiplier, is recorded as the next version of the same row: the series
keeps its key.
Record each series' open interest for the trading date it is for, and its quotes under the underlying with the series' key on the envelope:
oi = chains.statistic(aapl, chains.osi_key(symbol), "open_interest", 12834, "2026-10-23",
dataset=wants.dataset("options"))
await wants.record("statistics", [oi], "options")
quote = meridian.Quote(
meta=chains.series_meta(aapl, chains.osi_key(symbol), row_key=f"{symbol}:q:{at_ns}",
dataset=wants.dataset("options"), valid_from_ns=at_ns),
bid=meridian.Money(Decimal("4.10"), "USD"), ask=meridian.Money(Decimal("4.25"), "USD"),
)
await wants.record("quotes", [quote], "options")
A future, or an option on one, is keyed by one ticker identifier, the
exchange's symbol with its month code, its venue's MIC in source:
meridian.Identifier(scheme="ticker", value="ESZ6", source="XCME").
The data suite. records-a-chains-series, records-open-interest and
a-chain-row-is-never-resolved are about: option chains. A plugin
recording a chain maps them to its own producers; one whose source lists
no options names them not presented, with why.
In a reading plugin: read the chain¶
A portfolio, signal, oms or ems plugin reads an underlying's series
expiring in a window, every page read, each with the deployment's record
where one is held:
series = await chains.read_chain(plugin, aapl, "2026-11-01", "2026-12-31")
for s in series:
print(s.meta.series_key[0].value, s.instrument_id or "(no record yet)")
portfolio, reporting and signal read the chain's open interest for a
trading date, the latest where none is named:
The chain's quotes are read with
list_quotes, naming
series_keys or all_series=True; a read naming neither never answers a
chain's rows.
Ask for a series before an order names it¶
An order, a package's leg or a proposal names a series by its INS- ID.
Ask for the record first, with why:
answer = await chains.want_series(plugin, aapl, chains.osi_key(symbol),
note="Proposing a protective put", acting_for=caller)
Each series answers with its INS- record, pending_review while it is
reviewed, or refused_by_rule where the deployment's
Series request rule does not allow its cause, or the day's limit is
reached. A deployment admin sets the rule on the dashboard's settings home
page, Series request rule (/admin#series-rule), and may ask for
series by hand there with Request series.
Run the options measures¶
A signal, portfolio, reporting or compliance plugin asks the
analytics component for the chain's measures with
run_job, naming the chain; a job of
chain measures alone names no account:
import meridian
from meridian import analytics
from meridian.plugin.v1 import operations_pb2 as ops
job = meridian.Job(
measures=[analytics.measure("implied_volatility", by=["series", "quote_side"]),
analytics.measure("surface_fit", by=["expiry", "parameter"])],
chains=[ops.ChainRef(underlying_instrument_id=aapl,
expiration_from="2026-11-01", expiration_to="2026-12-31")],
)
answer = await analytics.Jobs().run(plugin, job) # inline, or waited for
The component prices at the US Treasury's par yield curve through the deployment's source priority for rates, so the deployment needs a rates plugin recording it (see Rates); without it every option is not evaluated, naming the curve. Each measure, its parameters and its breakdowns are in Options measures.