DocsBacktestsSelecting contracts
BacktestsReference

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.

Updated Jul 20265 min read

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.

Parameter
Type
Required
Default
Description
dte
int
yes
none
Target calendar days to expiration. Non-negative integer. In the UI this is the Days to expiration field.
type
"call" | "put"
no
"call"
Option right.
delta
number
one of three
none
Target delta. Mutually exclusive with moneyness and strike.
moneyness
number
one of three
none
Target log-moneyness ln(K/forward), centered on the implied forward. Mutually exclusive with delta and strike.
strike
number
one of three
none
Target strike. Mutually exclusive with delta and moneyness.
size
int
no
+1
Signed per-lot contract count. Non-zero; negative is short. Not a selector: it sets the leg's direction and per-lot quantity, and the scaled_* 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).

Threshold
Default
Caps
dte_threshold
5
|dte_actual - dte| in calendar days
delta_threshold
0.05
|delta_actual - delta|
moneyness_threshold
0.05
|moneyness_actual - moneyness|
strike_threshold
1.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).mid

The table exposes the shared 20-column option surface, all read from the selected real contract:

Column
Meaning
strike
Strike of the selected contract.
dte
Actual days to expiration of the selected contract. May differ from your target by up to dte_threshold.
delta, gamma, theta, vega, rho
Black-Scholes greeks of the selected contract at this minute.
iv
Mid-price implied volatility of the selected contract.
mid
Midpoint of the selected contract's best bid and best ask, (bid + ask) / 2.
bid
Best bid of the selected contract.
ask
Best ask of the selected contract.
underlying_price
Underlying spot price at this minute.
moneyness
Log-moneyness ln(K/forward) (centered on the implied forward) of the selected contract at this minute.
multiplier
Contract multiplier (100 for US equity options).
size
Signed per-lot contract quantity; negative is short.
scaled_deltascaled_rho
Each greek times size.

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.

Note

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.

Was this page helpful?