When to Use
Use this skill when an options chain model, backtester, roll scheduler or expiry-day risk process needs the contractual terms of a listed option rather than a rule of thumb. It answers four questions that are routinely conflated:
| Question | Why the obvious answer is wrong |
|---|---|
| When does it expire? | "Third Friday" holds on Cboe, CME and Eurex. Deribit monthlies expire the last Friday, and Cboe VIX options expire on a Wednesday — 30 days before the third Friday of the following month. |
| When does it stop trading? | For AM-settled monthlies (SPX, NDX, RUT, VIX) trading ceases on the business day preceding the expiration date. Treating expiry day as tradeable overstates the position's life by a day. |
| How does it settle? | AM vs PM is a US-centric pair. Eurex settles from a Xetra intraday auction; Deribit from a fixed 08:00 UTC delivery price. |
| Is it European or American, cash or physical? | Not derivable from the ticker. XSP and NDXP are index options that are PM-settled; CME's quarterly ES options are American and deliver a futures position. |
The engine resolves these from a registry of sourced contract specifications,
returning a OptionsChainConventionReport with signed DTE, the last trading
date, provenance (source, source_as_of) and any unverified-input warnings.
When NOT to Use
- Not a listed-expiry calendar. It derives anchored monthly-cycle expiries
arithmetically. Weekly and end-of-month series (
SPXW,NDXP,RUTW) are not determined by a(year, month)pair, soresolve_conventions()refuses them. Useget_contract_convention()for their terms and take the date from the exchange's published expiry calendar. - Not a holiday calendar. It will not invent one. Supply
holiday_calendar=to get the "preceding business day" roll-back; without it the date is returned unadjusted and flagged inreport.warnings. Seeglobal-exchange-holiday-calendar-handling. - Not a reference-data service. The bundled registry is a worked example of
eleven contracts across four venues, each carrying
sourceandsource_as_of. Exchanges change contract specifications — re-verify before relying on an entry, or inject your own viaregistry=. - Not a pricing, margin or exercise engine. It returns conventions, not Greeks, settlement prices or assignment decisions.
- Not an intraday clock. Everything is date-granular. Cash settles on the business day following expiration, and the AM/PM distinction is a settlement basis, not a timestamp this module computes.
Prerequisites
- Exchange code (
CBOE,CME,EUREX,DERIBIT) and underlying symbol. - Reference date and target
(year, month)for the expiry being resolved. - For symbols outside the registry, an explicit
asset_class(EQUITY/ETF) — the module never infers conventions from a ticker string. - Optional but strongly recommended: the exchange's non-trading days, ideally as
a
{exchange: [dates]}mapping so one venue's calendar cannot be applied to another. - Python 3.10+. Standard library only (
dataclasses,datetime,logging).
Workflow
- Resolve the contract before resolving anything else. Look up
(exchange, symbol)in the registry. If it is absent and noasset_classwas declared, stop — do not fall back to a default. An unrecognised index symbol defaulted to American/physical is the failure mode this step exists to prevent. - Reject a cycle the contract cannot express.
WEEKLYis not derivable from(year, month)for any contract.QUARTERLYis valid only in the venue's quarterly months. CMEESacceptsQUARTERLYonly, because its European-style Third-Friday Monthly series is a different product — resolving one under the other's symbol reports the wrong exercise style. - Apply the venue's expiry rule, not the third-Friday default.
THIRD_FRIDAYfor Cboe/CME/Eurex,LAST_FRIDAYfor Deribit,VIX_30_DAY_WEDNESDAYfor VIX. Compute it arithmetically — never viacalendar.monthcalendar(), whose column layout depends on the process-globalsetfirstweekday(). - Roll back off a non-trading day, using that exchange's own calendar. Cboe and Eurex both specify the third Friday "or the immediately preceding business day if the Exchange is not open on that Friday". If no calendar covers the exchange, return the unadjusted date with a warning rather than guessing. Skip this entirely for continuously-traded venues: Deribit has no closures to roll off, so adjusting would introduce the error.
- Derive the last trading day from the settlement basis. AM-settled ⟹ the preceding business day, because the settlement value is struck at the open of the expiration date and the contract is no longer tradeable. PM-settled, auction-settled and fixed-time ⟹ the expiration date itself.
- Report signed DTE. Negative means already expired. Clamping at zero makes an expired contract indistinguishable from one expiring today.
- Carry provenance into the report —
source,source_as_ofand every warning — so a downstream audit can tell a verified date from an unverified one.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Assuming every monthly option expires on the third Friday. Deribit monthlies are the last Friday — in April 2022 that was the 29th, two weeks after the Cboe third Friday on the 15th. Cboe VIX options expire on a Wednesday and never on a Friday at all.
- Trading an AM-settled monthly on its expiration date. SPX, NDX and RUT standard monthlies stop trading the business day before. The Friday-morning SOQ is struck from component opening prices and can gap far from Thursday's close, so a position held past Thursday cannot be exited at all.
- Assuming the third Friday is always a trading day. Good Friday fell on the third Friday in April 2022 and April 2025; expiration moved to Thursday the 14th and Thursday the 17th respectively. For an AM-settled contract the last trading day then moves to the Wednesday.
- Inferring exercise style or settlement from the ticker. XSP is an index option that is PM-settled; NDXP and RUTW likewise. CME quarterly ES options are American and exercise into a futures position, not cash and not shares.
- Applying one venue's holiday calendar to another. A US calendar is not a Eurex calendar. Key holidays by exchange, and treat an uncovered exchange as unverified rather than borrowing.
- Deriving the third Friday from
calendar.monthcalendar(). Its week layout follows the process-globalcalendar.setfirstweekday(); any library that changes it silently shifts the result to a different weekday. - Clamping DTE at zero. An expired contract then looks like a 0-DTE contract, which in a backtest reads as a live position to be managed.
Verification
- Instantiate
OptionsChainExpiryConventionsEngine. QueryCBOE/SPX, January 2024,MONTHLY⟹ expiry2024-01-19, last trading day2024-01-18,AM_SETTLED,EUROPEAN,CASH. QueryDERIBIT/BTC, March 2026 ⟹2026-03-27(last Friday), not2026-03-20. QueryCBOE/XSP⟹PM_SETTLEDdespite being an index option. Query an unregistered symbol with noasset_class⟹UnknownContractError, never a guessed default. - With
holiday_calendar={"CBOE": ["2025-04-18"]}, queryCBOE/SPXApril 2025 ⟹ expiry2025-04-17, last trading day2025-04-16,holiday_adjusted=True. - Run
python -m unittest discover -s skills/options-chain-expiry-cycle-conventions-by-exchange/scripts.