DocsThe Signal Language (DSL)Signals inside a backtest (book, position, leg, contract)
The Signal Language (DSL)Reference

Signals inside a backtest (book, position, leg, contract)

Inside a backtest, a signal reads live engine state through four accessors: book(), position(), leg("slug"), and option.contract(...). Each returns a Table, not a Signal. You get a usable value by projecting one numeric column with field access, so book().excess_liquidity is a Signal and book() on its own is not. Which accessors are legal depends on the evaluation slot: an entry signal sees only book(), an adjustment trigger sees all three state accessors. Whatever you build, an entry, exit, or adjustment signal still has to resolve to a truthy Signal, and a contract-selection signal still has to resolve to one contract.

Updated Jul 20265 min read

Tables, not Signals

book(), position(), leg("slug"), and option.contract(...) each return a Table: a row per trading minute with many numeric columns. Every column is Float64 and nullable.

Project one column with field access to get a Signal:

  • book().excess_liquidity
  • position().net_delta
  • leg("short_put").delta
  • option.contract(dte=30, delta=0.30).mid

Field access is the only edge between the Table type and the Signal type. The projection is what feeds an entry, exit, or adjustment signal, because those slots need a Signal, never a Table.

Two field-access mistakes are rejected at compile time:

  • Projecting a field off a Signal (not a Table). Only Table values support field access.
  • Projecting a column a Table does not declare. The error is Field '<name>' not declared on <Table> and lists the known fields.

An accessor is available only in the slots where its backing state exists. There is no position when an entry signal runs, so position() and leg() have nothing to read there.

Slot
book()
position()
leg("slug")
Notes
Entry signal
yes
no
no
No position exists yet; only book().
Exit signal
yes
no
no
Exit closes all open positions, so there is no single position under evaluation; only book().
Adjustment trigger
yes
yes
yes (live LegTable)
Evaluated per minute per open position against live engine state.
Contract selection
no
no
yes (run-start ContractTable, no entry_*)
leg("slug") resolves another leg's run-start candidate contract.
Standalone signal
no
no
no
Runs outside a backtest; no engine state.

Using a state accessor outside its slot raises an error that names the offending accessors and the slot, for example ... uses backtest-state accessors (position()) that are not available in entry signals: no position exists when an entry signal is evaluated; only book() state is available.

leg("slug") requires a non-empty string-literal slug. A bare or computed argument raises leg() requires a non-empty string-literal slug, e.g. leg("short_put") at compile time. The runner then checks the slug against the position's declared strategy slugs at startup: a slug that no declared leg owns is a startup error, while a declared slug that is not held at a given minute reads as an all-null row (covered below).

The entry, exit, and adjustment slots themselves, and when each one is evaluated, belong to the Backtests section. This page owns the accessors.

option.contract(...), the ContractTable

option.contract(...) selects the single real option contract per minute that best matches your targets, and returns a ContractTable on the equity-option spine. This is contract selection: the program for a leg's contract ends on option.contract(...).

// Contract selection: the 30-DTE 30-delta call
option.contract(dte=30, delta=0.30)

Project a column to read one attribute as a Signal, for example option.contract(dte=30, delta=0.30).mid.

Columns. The ContractTable exposes the shared option surface, 20 columns:

Column
Meaning
strike
Strike price of the selected contract.
dte
Days to expiration of the selected contract (may differ from the 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 best bid and best ask, (bid + ask) / 2.
bid
Best bid price.
ask
Best ask price.
underlying_price
Underlying spot price at this minute.
moneyness
Log-moneyness ln(K/forward); 0 is at-the-money-forward.
multiplier
Contract multiplier (100 for US equity options).
size
Signed per-lot contract quantity; negative is short.
scaled_deltascaled_rho
Each greek times size.

The greeks are the platform's computed greeks. Premiums here are quotes, not modeled fills; the fill a backtest books is set separately by the run's fill mode.

Parameters. Keyword-only after the required dte:

Name
Type
Required
Description
dte
int
yes
Target days to expiration. Non-negative.
type
"call" / "put"
no
Default "call".
delta
number
no
Target delta. Exactly one of delta / moneyness / strike.
moneyness
number
no
Target log-moneyness ln(K/forward) (centered on the implied forward).
strike
number
no
Target strike.
dte_threshold
int
no
Max | dte_actual - dte | in days. Default5. Positive.
delta_threshold
number
no
Max | delta_actual - delta | . Default 0.05. Positive.
moneyness_threshold
number
no
Max | moneyness_actual - moneyness | . Default 0.05. Positive.
strike_threshold
number
no
Max | strike_actual - strike | . Default 1.0. Positive.
size
int
no
Signed per-lot quantity, non-zero. Default 1 (one long contract per lot).

Selection. Each minute, the accessor picks the one real contract that minimizes the threshold-normalized weighted sum of absolute distances to your targets, tie-breaking on ascending (expiration, strike, right). A minute with no contract inside every threshold yields an all-null row.

leg("slug"), the LegTable

leg("slug") reads the live per-minute state of one held leg, addressed by its strategy slug. Inside an adjustment trigger it returns a LegTable.

// Adjustment trigger: roll when the short put's delta breaches 0.40
leg("short_put").delta > 0.40

The LegTable carries the same 20 option-surface columns as the ContractTable above, plus 13 entry-snapshot columns captured when the position opened and preserved across rolls:

entry_strike, entry_dte, entry_delta, entry_gamma, entry_theta, entry_vega, entry_rho, entry_iv, entry_mid, entry_bid, entry_ask, entry_underlying_price, entry_moneyness.

There is no entry_multiplier, entry_size, or entry_scaled_*.

The slug tracks the role, not the contract. After a roll (replace_leg) the same slug serves the replacement contract, so a rolling window over leg("short_put").delta continues across the roll rather than resetting. A slug that is declared but not currently held reads as an all-null row, so a trigger built on it evaluates to null and fires no action.

The entry_* columns exist only on a live held leg. Referencing one when selecting contracts is a compile-time error: a run-start candidate has no entry snapshot, so entry_* columns require a live leg.

position(), the PositionTable

position() returns a PositionTable: the structure under evaluation, aggregated across its legs. It is available only in adjustment triggers, where it evaluates once per open position per minute.

Column
Meaning
unrealized_pnl
Mark-to-market P&L since entry: cash booked plus current market value of the legs.
realized_pnl
Net cash booked on this position so far (entry premium, roll credits, partial-close proceeds).
initial_margin
Initial-margin requirement attributed to this position.
maintenance_margin
Maintenance-margin requirement attributed to this position.
market_value
Current market value of the position's legs.
net_delta, net_gamma, net_theta, net_vega, net_rho
Sum over legs of the scaled greek times lots; null if any contributing leg value is null.
lots
Position size in lots.
leg_count
Number of legs currently held.
dte
Days to the nearest leg expiration (minimum over held legs).
entry_net_deltaentry_net_rho
The net greeks built from each leg's entry-snapshot greek (times size times current lots); null if any leg's entry value is null.

Net greeks use null-propagating aggregation. If any one contributing leg value is null, the sum is null, not a partial total. Window a net greek to smooth it:

// Adjustment trigger: 15-minute average net delta of the open position above 0.50
rolling_mean(position().net_delta, 15) > 0.50

rolling_mean(x, 15) on position() rows spans 15 minutes, because position() produces one row per trading minute. Sample to daily first if you mean days. See the execution model for observation-count windows.

book(), the BookTable

book() returns a BookTable: whole-account state, one row per trading minute for the whole run. It is the only state accessor an entry or exit signal can read, and it is also available in adjustment triggers.

// Adjustment trigger: fire when account excess liquidity drops below $5,000
book().excess_liquidity < 5000

Values are dollars except where noted:

Column
Meaning
cash
Cash balance.
nlv
Net liquidation value: cash plus the market value of all open positions.
initial_margin
Total initial-margin requirement across open positions.
maintenance_margin
Total maintenance-margin requirement across open positions.
excess_liquidity
NLV minus the total maintenance-margin requirement.
margin_utilization_pct
Maintenance margin as a percent of NLV; can exceed 100 when the account is in margin distress.
unrealized_pnl
Mark-to-market P&L across all open positions.
realized_pnl
Cumulative realized P&L of closed trades.
position_count
Number of currently open positions (a count).

book() evaluates once per minute for the whole run, regardless of how many positions are open.

Cross-leg references

One leg's contract selection can reference another leg's resolved contract with leg("slug"). Here the reference resolves to the named leg's run-start candidate (a ContractTable), so you can place one leg relative to another:

// Contract selection: long put 5 strikes below the short put
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. Only delta, moneyness, strike, and size may carry a cross-leg expression. dte, type, and the thresholds must be constants, because a per-minute threshold would change which contracts are eligible, not only how they are ranked. A leg cannot reference its own contract, and a reference to a slug that no entry leg declares is rejected before the run.

A referenced leg's candidate exposes the shared 20-column surface only, never the entry_* columns, because a candidate has no entry snapshot.

Note

The slug in leg("slug") must be a string literal. Computed or non-literal slugs are not supported.

Use the canonical leg slugs short_put, long_put, short_call, long_call when writing multi-leg selections.

The per-slot return contract

Each slot has one return shape:

  • Entry, exit, and adjustment signals resolve to a truthy Signal. The engine acts at a minute where the value is 1.0, and treats 0.0, null, and NaN as no action. Evaluation is level-based per minute, not edge-triggered: the engine reads the value each minute rather than firing on a transition. This is why a comparison like leg("short_put").delta > 0.40 works directly. Comparisons return Float64 1.0/0.0 (see operators and functions).
  • Contract selection resolves to one contract per minute via option.contract(...).

The strategy semantics behind these slots, when an entry or exit signal is checked, how adjustment triggers fire actions, and how contract selection drives which contract a leg holds, live in the Backtests section.

Was this page helpful?