Selecting contracts
Contract selection turns a target into a held option. For each leg, you give option.contract(...) a DTE plus one of delta, log-moneyness, or strike, and the engine resolves that to a single real contract per minute on the equity-option spine. Selection is one of the backtest-context accessors: a leg's contract selection returns an option.contract(...) expression, and you configure it per leg while designing the position.
What contract selection does
For each minute on the equity-option spine, the engine picks the single real contract per leg whose DTE and your chosen dimension (delta, log-moneyness, or strike) sit closest to your targets, within thresholds. If nothing clears every threshold at a minute, the leg is null for that minute, and an entry attempt at that minute fails.
Selection materializes to a per-minute table before entry logic evaluates. option.contract(...) produces one row per minute across the run's date range, each row carrying the contract selected at that minute (or nulls where none qualifies). The engine does not stream the pick minute by minute; it reads the row at the entry minute to decide which contract to fetch and hold.
// A leg's contract selection: a 30-DTE put nearest 30 delta
option.contract(dte=30, type="put", delta=-0.30)The selectors: DTE plus one of delta / moneyness / strike
Every selection sets dte plus exactly one of delta, moneyness, or strike. There is no default selection dimension: leave all three unset and the selection is rejected; set more than one and it is rejected. This differs from the surface-read accessors (option.iv, option.delta), which fall back to a default delta.
dtetype"call" | "put""call"deltamoneyness and strike.moneynessln(K/forward), centered on the implied forward. Mutually exclusive with delta and strike.strikedelta and moneyness.size+1scaled_* columns are each greek times size.Set exactly one of delta, moneyness, or strike. The four thresholds are documented below.
A leg's size, the signed per-lot contract count that sets direction and quantity, is the size argument on the selection. It defaults to +1 (one long contract per lot); a negative size is short and 0 is rejected. Size shapes one lot; the whole-position lots multiplier is separate, set under position sizing and capital.
Log-moneyness
moneyness is ln(K/forward), log-moneyness centered on the implied forward, not the raw ratio K/forward. At-the-money-forward is 0.0. A strike 3% above the forward is ln(1.03) ≈ 0.03; 3% below is ln(0.97) ≈ -0.03. Passing 1.0 targets a strike near e times the forward, not an at-the-money contract.
Thresholds
Each dimension carries a threshold that caps how far the selected contract may sit from its target. A contract is eligible only if every active criterion (DTE plus your one chosen dimension) is within its threshold. Among eligible contracts, selection minimizes the threshold-normalized weighted sum of absolute distances to the targets. Ties break ascending on (expiration, strike, right).
dte_threshold5|dte_actual - dte| in calendar daysdelta_threshold0.05|delta_actual - delta|moneyness_threshold0.05|moneyness_actual - moneyness|strike_threshold1.0|strike_actual - strike|Every threshold must be greater than 0. Normalizing by the threshold is what lets DTE distance and delta distance trade off against each other on one scale: a contract one day off target counts as 1/5 of its DTE budget, a contract 0.025 off a 30-delta target counts as 1/2 of its delta budget.
When nothing matches
If no contract clears all thresholds at a minute, every numeric column of that leg is null for that minute. An entry attempt at that minute is rejected as a no-action (reason contract_selection_failed); it is not a close and it does not force any exit. The engine records the rejection and moves on. The next minute that a qualifying contract exists, entry can proceed.
Selection never extrapolates beyond the available surface. It picks a real contract that exists in the quote data or it picks nothing. A too-narrow threshold on a sparse chain produces null minutes rather than a stretched-to-fit contract.
The resolved contract surface
option.contract(...) returns a per-minute table. Project a single column with field access to get a Signal:
// The short put's per-minute mid premium as a Signal
option.contract(dte=30, type="put", delta=-0.30).midThe table exposes the shared 20-column option surface, all read from the selected real contract:
strikedtedte_threshold.delta, gamma, theta, vega, rhoivmid(bid + ask) / 2.bidaskunderlying_pricemoneynessln(K/forward) (centered on the implied forward) of the selected contract at this minute.multipliersizescaled_delta … scaled_rhosize.These are the selected contract's own quoted values, not interpolated surface reads. option.contract(...) resolves to one concrete listed contract per minute and reports that contract's actual strike, DTE, greeks, IV, moneyness, and quotes. For a continuous read across the surface (a 30-DTE 16-delta IV or greek that does not have to land on a listed strike), use the surface accessors option.iv, option.delta, and their siblings, which interpolate. The difference is what the number is attached to: the contract table reports the one listed contract the leg holds, the surface accessors read a point off the fitted surface.
mid is (bid + ask) / 2 of the selected contract. Whether a backtest actually fills at that midpoint or somewhere across the bid/ask spread is a separate choice set by the run's fill mode, not by the resolved contract table. The table reports the quotes; the run's fill mode decides the traded price.
Cross-leg selection
One leg's selection can place it relative to another leg's resolved contract. A delta, moneyness, strike, or size argument can carry a leg("slug").<field> expression, where slug names another leg in the same strategy. dte, type, and the four thresholds stay constant, because a per-minute threshold would change which contracts are eligible, not only how they rank.
// The long put's strike, 5 below the short put's resolved strike
option.contract(dte=30, type="put", strike=leg("short_put").strike - 5)These references resolve once before the run, in dependency order across the entry legs, so a referenced leg resolves before the leg that depends on it. A leg cannot reference its own contract, a reference to a slug that no entry leg declares is rejected before the run, and a cycle (leg A depends on leg B, leg B depends on leg A) is rejected as a cyclic dependency. The referenced leg exposes its run-start candidate surface only, never the entry-snapshot columns, because a candidate has no entry yet.
The slug in leg("slug") must be a string literal; a computed or non-literal slug is rejected. See signals inside a backtest for the full accessor rules.
What is not modeled in selection
Selection does not extrapolate. A minute with no in-threshold contract, a sparse chain, or a failed underlying read produces null for that leg, never a fabricated contract.
The data-quality and provenance signals that sit behind each quote (the IV computation status, whether theta was floored, spread-quality flags) are computed upstream when the surface is built. They are not columns on the resolved contract table and are not readable from a selection. If you need to gate on data quality, do it at the surface level before the contract is selected.
The contract table carries the selected contract's strike, DTE, greeks, IV, moneyness, spot, quotes, multiplier, size, and the size-scaled greeks. Quality and provenance flags exist upstream but are not exposed here, so a selection cannot itself filter on them.