Share the option universe filters with OptionChain - #9779
Open
jhonabreul wants to merge 12 commits into
Open
Conversation
Turn OptionFilterUniverse into the generic BaseOptionFilterUniverse, renamed in place, so the same filters run over universe rows and chain contracts. OptionChain gets the universe filter vocabulary (strikes, expiration, calls_only, standards/weeklys, front/back month, greeks, IV, OI and where) through an internal OptionChainFilterUniverse, each call returning a new chain. - IChainContractData and IOptionContractData let OptionContract be filtered without being BaseData; IOptionContractFilters declares the shared surface and OptionChainTests asserts the chain mirrors every universe filter - Expirations count on the contract's last trading date, so Saturday and holiday expiries match expiration() and the strategy pickers on their last trading day - Chains built from universe data now carry the underlying price - OptionChainFiltersRegressionAlgorithm exercises the filters on option_chain() and slice chains in C# and Python
Write the test universe file with OptionUniverse.ToCsv and CsvHeader and read it back with OptionUniverse.Reader, asserting the round trip, so the tests follow the file format instead of hard coding it. Drop the reflection parity test, the shared IOptionContractFilters interface keeps the chain and the universe in sync.
The universe strategy pickers, naked_call through put_ladder, select their legs straight from an option chain with the same arguments and validation, returning an empty chain when nothing matches or the underlying price is unknown. IOptionContractFilters declares them so the chain and the universe stay in sync.
OptionChain is partial now: the class core keeps the constructors and Clone, the universe filters and strategy pickers live in their own file.
Slice chains are stamped with the algorithm time, so their filters could count days from the wrong date when the algorithm and the exchange time zones differ. BaseChain.ExchangeTime, set by the slice factory from the contract subscription time zone, is now the filters' reference date, as in the universe selection.
The strategy filters now return an empty selection when there is no underlying price, in the base shared by the universe and the chain, so the chain wrapper no longer needs a per-filter flag and argument validation runs on every chain. ProtectiveCollar returns empty instead of throwing when a leg is missing. Reuse the Contracts selector for the greeks, IV, OI and strategy filters and route the Linq extensions through it, replacing three copies of the same filter primitive. ApplyTypesFilter skips the pass when every contract type is accepted, and the chain filter universe shares the chain's cached contract list instead of copying it. Filtered chains share the auxiliary data with their source, the chain type filters document that they apply in any order, and a reflection test checks every universe filter is declared on the chain interface.
This reverts commit 5309830.
The subset constructor delegates to the copy constructor, which the revert had undone, so filtered chains share the source's auxiliary data again.
This reverts commit baf6007.
The expiration filters and the strategy pickers compare the contract's listed date again, as before the shared engine. Counting Saturday and holiday expiries on their last trading day moves to its own change.
The contract filter base only needs ISymbolProvider, and the option base asks its subclasses for greeks, implied volatility and open interest through three abstract accessors, so the universe data and the chain contracts implement nothing new. ApplyTypesFilter skips its pass when every contract type is accepted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The option universe filters (
option.set_filter(u => u.strikes(-5, 5).expiration(0, 30).calls_only())) now also run on anOptionChain, with the same names and semantics. Each call returns a new chain:OptionFilterUniverseis available: strikes, expiration, calls/puts, standards/weeklys, front/back month, greeks, IV and OI ranges (with thed/g/t/v/r/iv/oialiases) and the 18 strategy pickers (naked_call…put_ladder).OptionFilterUniversekeeps its public API; the filter bodies did not move.Class model
One filter engine, two front-ends:
ContractSecurityFilterUniverse(existing) now constrains its data toISymbolProviderinstead ofIChainUniverseData, which extendsIBaseDataand so excluded chain contracts, and readsSymbol.ID.ApplyTypesFilterskips its pass when every contract type is accepted.BaseOptionFilterUniverse<TUniverse, TData>: the formerOptionFilterUniverse, renamed in place. TheOptionsecurity field became two abstract members,ExchangeHoursandSecurityType, and the greeks, IV and OI filters read their values through three abstract accessors,GetGreeks,GetImpliedVolatility,GetOpenInterest.OptionFilterUniverse: thin subclass overOptionUniverse, supplies all of the above from the security and the universe data. Same constructors,CreateDataInstanceand implicit list conversion as before.OptionChainFilterUniverse(new, internal): thin subclass overOptionContract, exchange hours from the market hours database, strike multiplier from the contract's symbol properties.IOptionContractFilters<TSelf>(new): declares every shared filter. Implemented by the base asIOptionContractFilters<TUniverse>and byOptionChainasIOptionContractFilters<OptionChain>, so a filter added to one side must be added to the other; a reflection test checks the interface covers every universe filter.OptionChain: now partial, filters inOptionChain.Filters.cs. Each filter builds anOptionChainFilterUniverseover the contracts, applies the base filter and returns a subset chain sharing the source's symbol, time, underlying and auxiliary data.BaseContract,OptionContract,OptionUniverseandIChainUniverseDataare not modified.classDiagram direction TB namespace Legend { class New["New or renamed in place"] class FrontEnd["Front-end users call"] class Touched["Existing, touched where marked [new] or [changed]"] class Untouched["Existing, untouched"] } class ISymbolProvider { <<interface, untouched>> +Symbol Symbol } class IOptionContractFilters~TSelf~ { <<interface, new>> +Strikes(min, max) TSelf +Expiration(min, max) TSelf +CallsOnly() PutsOnly() TSelf +StandardsOnly() WeeklysOnly() TSelf +FrontMonth() BackMonth() BackMonths() TSelf +Delta() Gamma() Theta() Vega() Rho() IV() OI() TSelf +NakedCall() ... PutLadder() TSelf } class OptionUniverse { <<untouched>> universe file row } class OptionContract { <<untouched>> chain entry } class ContractSecurityFilterUniverse { <<abstract, touched>> TData is ISymbolProvider [changed, was IChainUniverseData] reads Symbol.ID instead of ID [changed] +Expiration() FrontMonth() StandardsOnly() T #ApplyTypesFilter() skips the pass for the default types [changed] } class BaseOptionFilterUniverse { <<abstract, renamed in place>> was OptionFilterUniverse +Strikes() CallsOnly() greeks ranges TUniverse +NakedCall() ... PutLadder() TUniverse #ExchangeHours abstract [new] #SecurityType abstract [new] #GetGreeks() GetImpliedVolatility() GetOpenInterest() abstract [new] } class OptionFilterUniverse { <<front-end>> TData = OptionUniverse +OptionFilterUniverse(Option security) [unchanged API] accessors read the universe data [new] } class OptionChainFilterUniverse { <<internal, new>> TData = OptionContract +OptionChainFilterUniverse(OptionChain chain) accessors read the contract } class FutureFilterUniverse { <<untouched>> } class OptionChain { <<front-end>> BaseChain of OptionContract +Strikes() CallsOnly() ... OptionChain [new] -Filter(f) new chain of the survivors [new] underlying price fix in the universe data constructor [changed] } ISymbolProvider <|.. OptionUniverse ISymbolProvider <|.. OptionContract ContractSecurityFilterUniverse <|-- BaseOptionFilterUniverse ContractSecurityFilterUniverse <|-- FutureFilterUniverse BaseOptionFilterUniverse <|-- OptionFilterUniverse BaseOptionFilterUniverse <|-- OptionChainFilterUniverse IOptionContractFilters <|.. BaseOptionFilterUniverse : TSelf = TUniverse IOptionContractFilters <|.. OptionChain : TSelf = OptionChain OptionFilterUniverse ..> OptionUniverse : filters OptionChainFilterUniverse ..> OptionContract : filters OptionChain ..> OptionChainFilterUniverse : creates per call style IOptionContractFilters fill:#F6E4D8,stroke:#B4551F,color:#1B2622 style BaseOptionFilterUniverse fill:#F6E4D8,stroke:#B4551F,color:#1B2622 style OptionChainFilterUniverse fill:#F6E4D8,stroke:#B4551F,color:#1B2622 style OptionFilterUniverse fill:#DCEFEA,stroke:#0F7A68,color:#1B2622 style OptionChain fill:#DCEFEA,stroke:#0F7A68,color:#1B2622 style ContractSecurityFilterUniverse fill:#FFF3C4,stroke:#B08900,color:#1B2622 style ISymbolProvider fill:#EDEDED,stroke:#8A8A8A,color:#1B2622 style OptionUniverse fill:#EDEDED,stroke:#8A8A8A,color:#1B2622 style OptionContract fill:#EDEDED,stroke:#8A8A8A,color:#1B2622 style FutureFilterUniverse fill:#EDEDED,stroke:#8A8A8A,color:#1B2622 style New fill:#F6E4D8,stroke:#B4551F,color:#1B2622 style FrontEnd fill:#DCEFEA,stroke:#0F7A68,color:#1B2622 style Touched fill:#FFF3C4,stroke:#B08900,color:#1B2622 style Untouched fill:#EDEDED,stroke:#8A8A8A,color:#1B2622Bugs found and fixed along the way
algorithm.option_chain(symbol)) reported a zero underlying price:BaseChainpre-sets an emptyQuoteBar, so theUnderlying ??=in theOptionChainconstructor never assigned. Fixed by assigning the first contract's underlying.ProtectiveCollarreturns empty instead of throwing when a leg is missing.Notes for review
OptionFilterUniverse.cslooks large in the diff but is mostly return types (OptionFilterUniverse→TUniverse); the class was renamed in place instead of moved.OptionChain.Whereonly takes a Python predicate. A C#Funcoverload changed the type ofchain.Where(...)in existing algorithms, so C# keeps Linq.BaseChain.ExchangeTime(new, defaults toTime). The slice factory sets it from the contract's exchange time zone, since slice chains are stamped with the algorithm time and the two dates differ for algorithms outside the exchange's time zone.StandardsOnly/WeeklysOnlyapply to the contracts already selected, so unlike the universe they compose with the expiry filters in any order.Related Issue
N/A
Motivation and Context
Option algorithms keep re-deriving the same contract selections from a chain, in a vocabulary different from the one they already used in
set_filter. Sharing the filters gives one grammar for both, with the same semantics, and removes the hand-rolled expiry and strike scans that crash on empty sequences.Requires Documentation Change
Yes: the
OptionChainfilters (same names as the option universe filters) andIOptionContractFilters.How Has This Been Tested?
OptionChainTests: every chain filter, alone and chained, returns the same contracts as the universe filter over identical data (68 cases); the exchange-time reference date; type filters in any order; filters on an empty chain; the underlying price of universe-built chains; Python access to the filters andwhere. Test data is written withOptionUniverse.ToCsvand read back withOptionUniverse.Reader, asserting the round trip.OptionChainFiltersRegressionAlgorithmandOptionChainStrategyFiltersRegressionAlgorithm(C# and Python): filters onoption_chain()and slice chains against hand-rolled expectations, trading the selection.TimeSliceTests.ChainExchangeTimeIsInTheExchangeTimeZone: a Tokyo-zone slice stamps option and futures chains with New York exchange time.OptionFilterUniverseTests,OptionFilterTests,OptionStrategyFilterTests,PythonOptionTests,FutureFilterUniverseTests: pass unchanged, one test added for the universe's type filter ordering rule.Name~Option|Name~Future): 404 passed, no statistics moved.LiveTradingDataFeedTests.HandlesAllTypescases that pass when run alone.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>