equity-options
Skill268 starsChanged 48 days agoApache-2.0View on GitHub
Use when an algorithm trades options on a SINGLE named underlying Equity — subscribing (`add_option` vs `add_option_contract`), reading the chain (the slice's `option_chains` vs the `option_chain()` method), greeks/IV data freshness, strike/expiry filters, price seeding, and warm-up. Triggers — code calling any of those four APIs; questions like "why are my deltas/IV stale", "why can't I trade the contract I just added", "why is the chain empty", "which API gives decision-time greeks". Skip when — options on a DYNAMIC equity universe (see chained-universes-options), historical/backfilled option data for past dates (see historical-data-equity-options), or multi-leg order placement and assignment/exercise handling (see option-strategies).
In this repository39 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- docs-styleSkill
- calendarSkill
- code-qualitySkill
- cryptoSkill
- data-exportSkill
- data-issuesSkill
- debug-algorithmSkill
- debug-performanceSkill
- equity-fundamental-dataSkill
- equity-optionsSkill
- futuresSkill
- historical-data-equity-optionsSkill
- historySkill
- indicatorsSkill
- loggingSkill
- option-strategiesSkill
- ordersSkill
- pricingSkill
- scheduled-eventsSkill
- universesSkill
- calendarSkill
- code-qualitySkill
- cryptoSkill
- data-exportSkill
- data-issuesSkill
- debug-algorithmSkill
- debug-performanceSkill
- equity-fundamental-dataSkill
- futuresSkill
- historical-data-equity-optionsSkill
- historySkill
- indicatorsSkill
- loggingSkill
- option-strategiesSkill
- ordersSkill
- pricingSkill
- scheduled-eventsSkill
- universesSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 34
Prefer a strategy-shaped filter when one matches the structure — u.iron_condor(30, 5, 10), u.straddle(30), u.call_spread(30, 5), and …
- Very specific: line 50
Default filter: if you never call set_filter: standards + weeklys, within 1 strike of the underlying price, expiring within 35 days — …
- Very specific: line 99
Forgetting
include_weeklys(): when a spec's DTE window (e.g. - Very specific: line 103
Only American-style US equity options are supported.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Equity Options: subscriptions and chain data — QuantConnect / LEAN
Two decisions shape every equity-options algorithm, and each has two answers that look interchangeable but carry different data: how you subscribe (add_option vs add_option_contract) and how you read the chain (the slice's option_chains vs the option_chain() method). Picking the wrong member of either pair produces algorithms that trade on day-old numbers without any error.
Greeks/IV freshness — the core rule
There are two different option datasets behind the APIs (doc: "There is a daily, pre-calculated dataset based on the end of the previous trading day. There is also a stream of values that are calculated on-the-fly as your algorithm runs."):
| Access path | Data you get |
option.set_filter(...) filter function (incl. .delta()/.iv() filters) | "daily, pre-calculated values based on the end of the previous trading day" — not customizable |
self.option_chain(symbol) method | same prior-day EOD pre-calculated values; returns all tradable contracts, not just your filter's picks |
self.history[OptionUniverse](...) | same prior-day EOD values, one full chain per trading day |
the slice: data.option_chains (or self.current_slice.option_chains) | current values from the Option price model, computed on request against live data — customizable via option.price_model |
The operating rule that follows: any intraday decision — entry sizing, delta hedging, IV-threshold checks, strike selection at a decision time — should read the slice's chain. The option_chain() method is for daily contract discovery (find which contracts exist, filter by prior-day delta/OI, then subscribe); using it for intraday sizing or greeks means every number is one day stale, and nothing errors to tell you. Greeks on slice contracts are computed lazily — accessing greeks costs nothing; accessing greeks.delta triggers the calculation — so only read the greeks you need.
Pick ONE route — and subscribe only what the strategy can trade
Decide the route once, in initialize. Mixing them is usually a smell:
- Universe route (
add_option+ filter): a universe's purpose is its slice chain. If all selection, pricing, and sizing happen throughoption_chain()anyway, the minute-resolution subscriptions are pure cost — either read the slice for the decisions that need live data, or drop the universe and subscribe the picks directly. - Discovery route (
option_chain()+add_option_contract): the daily chain picks the contracts; subscribe each pick. Match the data to the decision: theoption_chain()rows are previous-close values — fine when daily-granularity data suits the strategy (screening, ranking, a daily-cadence rule that tolerates day-old marks), but when the strategy calls for decision-time values (intraday sizing, hedging, entry marks), read the subscribed picks' live quotes from the slice. Whichever you use, know which one you are using. - A subscription earns its cost by being read or being held: a contract you own needs its subscription (position pricing, fills, expiry processing) even if you never read a chain. What doesn't earn its cost is breadth — a wide
add_optionuniverse where the algorithm neither reads the slice chain nor holds more than its few picks. option_chain()needs no option subscription — the underlying's equity subscription is sufficient. Never addadd_option, a filter, oradd_universe_optionsjust to make a chain available; an empty chain has some other cause.
Size the subscription to the trade, not to the chain:
- Prefer a strategy-shaped filter when one matches the structure —
u.iron_condor(30, 5, 10),u.straddle(30),u.call_spread(30, 5), and friends subscribe only the legs the strategy needs. - Otherwise derive the strike span and DTE window from the structure's own numbers (target deltas, wing widths, expiry rules), adding a delta-band filter (
u.delta(0.05, 0.30)) where the structure is delta-targeted. The span should be the tightest one that always contains the tradable candidates — not a safety blanket. strikes(-100, 100), or an unfilteredexpiration(0, 500)window, subscribes thousands of minute-resolution contracts — a cost bug even when the algorithm is otherwise correct.
Subscription route 1: add_option — a filtered basket (universe)
For strategies that pick contracts from a chain at decision time (spreads, condors, straddles, rolling structures), subscribe once in initialize:
self.universe_settings.asynchronous = True
option = self.add_option("SPY") # underlying auto-subscribed RAW
option.set_filter(lambda u: u.include_weeklys().strikes(-3, 3).expiration(15, 60))
self._symbol = option.symbol # canonical Symbol — save it- The canonical
option.symbolis not tradable — it is the key intodata.option_chainsand the first argument of everyOptionStrategiesfactory. - Filter timing: selection "usually runs at the first bar of every day"; a newly selected contract's data arrives "in the next
Slice". So the chain can be absent on any given event — always guard:chain = data.option_chains.get(self._symbol); if not chain: return. - Default filter if you never call
set_filter: standards + weeklys, within 1 strike of the underlying price, expiring within 35 days — almost never what a spec wants; set the filter explicitly. - Filter building blocks:
strikes(min, max)(relative strike counts around the money, not dollars),expiration(min_days, max_days),calls_only()/puts_only(),weeklys_only()/standards_only()/include_weeklys(),front_month()/back_month(), greek/OI filtersdelta(min, max),iv(min, max),open_interest(min, max)(these compare prior-day EOD values — use them for coarse pre-selection, then pick precisely from live slice greeks), and strategy-shaped helpers (u.straddle(30),u.iron_condor(30, 5, 10)) that narrow the universe to exactly the legs a named strategy needs. - Resolution rule: the option resolution must be ≥ the underlying Equity's resolution (minute equity → minute/hour/daily options). Contract subscriptions support minute, hour, and daily; minute is the default.
Reading the chain at a decision time:
def on_data(self, data: Slice) -> None:
chain = data.option_chains.get(self._symbol)
if not chain:
return
spot = chain.underlying.price
atm_call = min((c for c in chain if c.right == OptionRight.CALL),
key=lambda c: abs(c.strike - spot))
delta = atm_call.greeks.delta # live price-model value, computed on request
mid = (atm_call.bid_price + atm_call.ask_price) / 2In a Scheduled Event there is no slice parameter — read the same object through self.current_slice.option_chains.
Subscription route 2: add_option_contract — individual known contracts
For strategies that hold a few specific contracts found by daily screening, discover with the option_chain() method, then subscribe each pick:
# In a scheduled event — option_chain() carries PRIOR-DAY EOD greeks: fine for
# discovering contracts, wrong for intraday sizing or hedging.
chain = self.option_chain(self._underlying, flatten=True).data_frame
expiry = chain.expiry.min()
symbol = chain[(chain.expiry == expiry) & (chain.right == OptionRight.CALL) &
(chain.delta > 0.3)].sort_values("openinterest").index[-1]
self.add_option_contract(symbol)The three idioms that must accompany this route:
- Seed prices — a contract subscribed this time step has no data until the next slice ("you'll need to wait until the next
Sliceto receive data and trade the contract"). Setself.settings.seed_initial_prices = Trueininitializeto trade it immediately. - RAW underlying — subscribe the underlying with
self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW)so strike-vs-price comparisons share a scale. If you skip this, LEAN auto-subscribes (or force-switches an existing subscription) to RAW anyway — see the history caveat below. - Volatility warm-up —
set_warm_upininitializecannot anticipate contracts added later; warm the underlying's volatility model in a security initializer (feed ~30 daily bars throughsecurity.volatility_model.update(...)) so slice-chain IV/greeks are sane from the first read. On theadd_optionroute the simpleself.set_warm_up(31, Resolution.DAILY)suffices (default volatility model needs 30+1 daily bars).
To read a subscribed contract from the slice, index the chain with the canonical: data.option_chains.get(self._contract_symbol.canonical). remove_option_contract(symbol) cancels its open orders and liquidates the position.
The RAW-underlying history caveat
Because any option subscription forces the underlying Equity to RAW normalization, a history() call on that underlying now returns raw closes — dividend gaps included. Any realized-volatility, daily-return, or overnight-gap calculation on an option underlying must request adjusted data explicitly: self.history(self._underlying, 22, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.ADJUSTED). Do not "fix" this by subscribing the underlying ADJUSTED — LEAN silently switches it back to RAW.
Common mistakes
- Sizing or hedging from
option_chain()intraday. Its marks and greeks are the previous close; position sizes computed from them are provably off versus decision-time quotes. Decision-time numbers come from the slice's chain. - Trading the canonical symbol, or passing a contract symbol where the canonical is required (chain lookup,
OptionStrategiesfactories). - No
if not chain:guard — the chain is legitimately absent before the first daily selection and whenever the filter turns over. - Forgetting
include_weeklys()when a spec's DTE window (e.g. 21–45 days, closest to 30) needs weekly expiries to hit its target. - Carrying a wide universe you never read — subscribing a broad chain while doing all selection, pricing, and sizing through
option_chain(): every subscription beyond the contracts actually held is pure cost. Read the universe's slice chain for the decisions that need live data, or drop the universe and useadd_option_contracton the picks. - Subscribing a huge chain as a tradability crutch (±100 strikes, unbounded expiries) so any discovered contract can be ordered directly — subscribe the picks with
add_option_contractinstead, and let a strategy-shaped or structure-derived filter keep the universe minimal. - Computing RV/returns on the force-RAW underlying without an explicit adjusted-normalization history request.
- Only American-style US equity options are supported; strikes in the filter API are relative counts, not dollar offsets —
strikes(-1, 1)means one strike either side of the money.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

