Skip to content

Count option expirations on their last trading date - #9796

Open
jhonabreul wants to merge 11 commits into
QuantConnect:masterfrom
jhonabreul:feature-option-last-trading-date-expiries
Open

jhonabreul wants to merge 11 commits into
QuantConnect:masterfrom
jhonabreul:feature-option-last-trading-date-expiries

Conversation

@jhonabreul

@jhonabreul jhonabreul commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Description

The option expiration filters count days to the expiration date. Contracts dated on a Saturday or a holiday stop trading the previous open day, so zero_dte() missed them on that day.

# Friday 2012-02-17: the contracts dated Saturday 2012-02-18 stop trading today
chain.zero_dte()                          # now returns them, before: empty
chain.expiration(0, 0)                    # same

What changes:

  • ContractSecurityFilterUniverse.Expiration(min, max) compares a new virtual GetLastTradingDate(contract), default the expiration date, so futures keep their behavior.
  • OptionFilterUniverse overrides it: OptionSymbol.GetLastDayOfTrading for equity options, the expiration date for index and future options.
  • OptionSymbol.GetLastDayOfTrading gains an overload taking the exchange hours. The universe passes its own and keeps the last resolved expiration, since contracts come grouped by it.
  • Chains carry their symbol properties and exchange hours, from the security in the slice factory or option_chain()'s lookup, and hand them to the chain filters and days_to_expiry.
  • zero_dte(), expiration(days, days) and expiration(timedelta, timedelta) follow, on the universe and the chain.
  • The strategy filters' min_days_till_expiry uses the same date, so naked_call(0) on the last trading date picks the contract dated the next day.
  • OptionContract.days_to_expiry counts to the last trading date, matching the filters.
  • expiring_after(), expiring_before() and expiration(dates) keep comparing the expiration date itself.

Related Issue

N/A

Motivation and Context

An option dated on a Saturday or a holiday is a 0 DTE contract on its last trading date, which is when algorithms look for it. Counting to the calendar date hid it for a day and shifted every relative expiration window.

Requires Documentation Change

Yes: the relative expiration filters and days_to_expiry count to the last trading date.

How Has This Been Tested?

  • OptionChainTests.ExpirationFilterCountsSaturdayExpiriesOnTheirLastTradingDate: 2012 Saturday expiries are 0 DTE on the Friday, universe and chain, including after Good Friday.
  • OptionChainTests.StrategyFiltersCountSaturdayExpiriesOnTheirLastTradingDate: naked_call(min_days) picks the Saturday-dated contract on its last trading date.
  • OptionChainTests.ZeroDteCountsHolidayExpiriesOnThePreviousTradingDate: five holiday expiries are 0 DTE the previous trading date and 1 DTE the day before. All fail on master.
  • OptionChainTests.ZeroDteCountsIndexAndFutureOptionsOnTheirExpirationDate: SPX, SPXW and an ES future option are 0 DTE on their expiration date, on the universe, the chain and days_to_expiry.
  • OptionChainTests.ChainBuiltWithExchangeHoursCountsSaturdayExpiriesOnTheFriday: the walk-back runs on the hours the chain was built with, for filters and days_to_expiry, also after filtering.
  • OptionFilterTests: the weekend expiries 8 and 9 days out now count as 7 on a Friday, so the 7-day window keeps them.
  • Option chain, option filter, futures chain, option contract, time slice factory and option price model suites: 488 passed.
  • Every option regression algorithm (index, future option, chain filter, strategy, 0DTE and expiration ones), C# and Python: 212 passed, 0 failed.

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>

at(), closest_expiry(), select() and days_to_expiry count Saturday and holiday
expiries on the previous trading day, matching the shared filters.
Equity options listed before February 2015 carry the OCC Saturday expiration
date but stop trading on the Friday, and some weeklies are listed on holidays.
ContractSecurityFilterUniverse.GetLastTradingDate feeds Expiration(), the option
base moves non trading dates back to the previous open day, memoized per date,
and the strategy pickers use the same date, so expiration(0, 0) selects a 0 DTE
contract on its last trading day.
…t, as the exchange and the brokerage mapping do
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.

2 participants