Skip to content

Add option chain selection helpers - #9667

Draft
jhonabreul wants to merge 2 commits into
QuantConnect:masterfrom
jhonabreul:feature-option-chain-selection-helpers
Draft

Add option chain selection helpers#9667
jhonabreul wants to merge 2 commits into
QuantConnect:masterfrom
jhonabreul:feature-option-chain-selection-helpers

Conversation

@jhonabreul

@jhonabreul jhonabreul commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Description

Options algorithms keep re-deriving the same contract selection: nearest expiry to a target DTE, same-expiry filter, nearest-strike/ATM scan. The hand-rolled version crashes on empty min(), misses Saturday-convention expiries, and picks 0-DTE contracts.

New helpers on OptionChain (both slice chains and algorithm.option_chain() chains):

  • Select(right, targetDte, minDte, maxDte, moneyness, targetDelta) — best-match single contract in one call.
  • ClosestExpiry(targetDte, minDte, maxDte) — nearest expiration; ties prefer the earlier one.
  • At(expiry) — single-expiration chain that composes with the other helpers.
  • Calls / Puts — sorted by expiration then strike.
  • AtTheMoney(right) — strike closest to the underlying price.
  • Strikes (new StrikeList type) — distinct sorted strikes with ClosestTo, FirstAbove, FirstBelow.

Design points:

  • No match returns null/None instead of throwing, so callers guard with one null check.
  • Saturday expirations (pre-2015 OCC convention) are counted at the preceding Friday, and At() matches by that date.
  • Fixes a latent bug: universe-built OptionChains reported a zero underlying price (Underlying ??= never assigned).

Deferred: contract.days_to_expiry/dte ships separately; pruning out-of-window subscribed contracts from slice chains is out of scope.

Related Issue

N/A

Motivation and Context

This exact selection is hand-built over and over (~10–15 lines per entry routine) and recurrently hits empty-sequence crashes, Saturday-expiry zero matches, and 0-DTE picks.

Requires Documentation Change

Yes: the new OptionChain members and StrikeList (XML docs included).

How Has This Been Tested?

  • New OptionChainTests fixture: 38 tests covering ceremony equivalence, moneyness/delta targeting, DTE windows, tie-breaking, Saturday expiries, composition through At(), the underlying-price fix, and null-safety on empty chains.
  • New OptionChainSelectionHelpersRegressionAlgorithm (C# + Python): asserts each helper against the hand-rolled equivalent and trades the selected contract; both languages pass with matching statistics.
  • Related suites: 2581 passed, 0 failed. New chain properties are [PandasIgnore]d, so data frames are unchanged.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

Adds composable contract selection helpers to OptionChain so the usual
hand-rolled nearest-expiry/nearest-strike/ATM scans become a single call:
Select(), ClosestExpiry(), At(expiry), AtTheMoney(right), Calls/Puts and
Strikes (with ClosestTo/FirstAbove/FirstBelow). All helpers are null-safe,
count Saturday-convention expirations at their Friday last trading date,
and support DTE windows to guard against selecting already-subscribed
contracts outside the requested expiration range.

Also fixes the OptionChain universe-contracts constructor never assigning
the chain-level Underlying (the base constructor initializes it to an
empty QuoteBar, so the null-coalescing assignment never fired).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant