diff --git a/03 Writing Algorithms/03 Securities/99 Asset Classes/02 Equity Options/01 Requesting Data/02 Individual Contracts/99 Examples.html b/03 Writing Algorithms/03 Securities/99 Asset Classes/02 Equity Options/01 Requesting Data/02 Individual Contracts/99 Examples.html index 0dc5e695e4..0c482f77ab 100644 --- a/03 Writing Algorithms/03 Securities/99 Asset Classes/02 Equity Options/01 Requesting Data/02 Individual Contracts/99 Examples.html +++ b/03 Writing Algorithms/03 Securities/99 Asset Classes/02 Equity Options/01 Requesting Data/02 Individual Contracts/99 Examples.html @@ -1,420 +1,435 @@

Example 1: Covered Call

A cover call consists of a short call and with a lot of the underlying equity. Although it capped the maximum profit if the underlying skyrocketted, it also provide extra credit received while speculating the underlying will rise.

-
private Symbol _aapl;
-private List<Symbol> _options = new();
-
-public override void Initialize()
-{
-    // Seed the security price to ensure the retrieval of the ATM calls at the initial filtering
-    SetSecurityInitializer((security) => new FuncSecuritySeeder(GetLastKnownPrices).SeedSecurity(security));
-    // Set the data normalization mode as raw for option strike-price comparability
-    _aapl = AddEquity("AAPL", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
-
-    // Filter an updated option list at market open everyday by a scheduled event
-    Schedule.On(
-        DateRules.EveryDay(_aapl),
-        TimeRules.AfterMarketOpen(_aapl, 0),
-        RefreshOptionList
-    );
-}
-
-private void RefreshOptionList()
+    
public class EquityOptionExampleAlgorithm : QCAlgorithm
 {
-    // Get all tradable option contracts for AAPL at the current time for filtering
-    var contractSymbols = OptionChainProvider.GetOptionContractList(_aapl, Time);
-    // Select the calls expires within 30 days and within $5 strike from ATM as leg of the covered call
-    // $5 buffer is given on selecting the ATM call due to price movement
-    _options = contractSymbols
-        .Where(symbol => 
-            symbol.ID.Date < Time.AddDays(30) && 
-            symbol.ID.OptionRight == OptionRight.Call && 
-            Math.Abs(symbol.ID.StrikePrice - Securities[_aapl].Price) <= 5
-        )
-        .Select(symbol => AddOptionContract(symbol).Symbol)
-        .ToList();
-}
-
-public override void OnData(Slice slice)
-{
-    if (!Portfolio.Invested && slice.Bars.ContainsKey(_aapl) && _options.Count > 0)
+    private Symbol _aapl;
+    private List<Symbol> _options = new();
+    
+    public override void Initialize()
     {
-        // To form a covered call, get the contract closest to ATM and expiry
-        var contract = _options.OrderBy(x => x.ID.Date)
-            .ThenBy(x => Math.Abs(x.ID.StrikePrice - slice.Bars[_aapl].Price))
-            .First();
-
-        // Covered call involves shorting 1 ATM call and ordering 1 lot of underlying
-        MarketOrder(contract, -1);
-        MarketOrder(_aapl, Securities[contract].SymbolProperties.ContractMultiplier);
+        // Seed the security price to ensure the retrieval of the ATM calls at the initial filtering.
+        SetSecurityInitializer((security) => new FuncSecuritySeeder(GetLastKnownPrices).SeedSecurity(security));
+        // Set the data normalization mode as raw for option strike-price comparability.
+        _aapl = AddEquity("AAPL", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
+    
+        // Filter an updated option list at market open everyday by a scheduled event.
+        Schedule.On(
+            DateRules.EveryDay(_aapl),
+            TimeRules.AfterMarketOpen(_aapl, 0),
+            RefreshOptionList
+        );
     }
-}
-
-public override void OnOrderEvent(OrderEvent orderEvent)
-{
-    // Close AAPL position if the short call is not exercised (OTM)
-    // If it is exercised, the underlying will be used to settle the contract automatically
-    if (orderEvent.Ticket.OrderType == OrderType.OptionExercise &&
-        !orderEvent.IsInTheMoney)
+    
+    private void RefreshOptionList()
     {
-        MarketOrder(_aapl, -Securities[orderEvent.Symbol].SymbolProperties.ContractMultiplier);
+        // Get all tradable option contracts for AAPL at the current time for filtering.
+        var contractSymbols = OptionChainProvider.GetOptionContractList(_aapl, Time);
+        // Select the calls expires within 30 days and within $5 strike from ATM as leg of the covered call.
+        // $5 buffer is given on selecting the ATM call due to price movement.
+        _options = contractSymbols
+            .Where(symbol => 
+                symbol.ID.Date < Time.AddDays(30) && 
+                symbol.ID.OptionRight == OptionRight.Call && 
+                Math.Abs(symbol.ID.StrikePrice - Securities[_aapl].Price) <= 5
+            )
+            .Select(symbol => AddOptionContract(symbol).Symbol)
+            .ToList();
+    }
+    
+    public override void OnData(Slice slice)
+    {
+        if (!Portfolio.Invested && slice.Bars.ContainsKey(_aapl) && _options.Count > 0)
+        {
+            // To form a covered call, get the contract closest to ATM and expiry.
+            var contract = _options.OrderBy(x => x.ID.Date)
+                .ThenBy(x => Math.Abs(x.ID.StrikePrice - slice.Bars[_aapl].Price))
+                .First();
+    
+            // Covered call involves shorting 1 ATM call and ordering 1 lot of underlying.
+            MarketOrder(contract, -1);
+            MarketOrder(_aapl, Securities[contract].SymbolProperties.ContractMultiplier);
+        }
+    }
+    
+    public override void OnOrderEvent(OrderEvent orderEvent)
+    {
+        // Close AAPL position if the short call is not exercised (OTM).
+        // If it is exercised, the underlying will be used to settle the contract automatically.
+        if (orderEvent.Ticket.OrderType == OrderType.OptionExercise &&
+            !orderEvent.IsInTheMoney)
+        {
+            MarketOrder(_aapl, -Securities[orderEvent.Symbol].SymbolProperties.ContractMultiplier);
+        }
     }
 }
-
def initialize(self) -> None:
-    self.options = []
-    # Seed the security price to ensure the retrieval of the ATM calls at the initial filtering
-    self.set_security_initializer(lambda security: FuncSecuritySeeder(self.get_last_known_prices).seed_security(security))
-    # Set the data normalization mode as raw for option strike-price comparability
-    self.aapl = self.add_equity("AAPL", data_normalization_mode=DataNormalizationMode.RAW).symbol
-    
-    # Filter an updated option list at market open everyday by a scheduled event
-    self.schedule.on(
-        self.date_rules.every_day(self.aapl),
-        self.time_rules.at(9, 0),
-        self.refresh_option_list
-    )
-
-def refresh_option_list(self) -> None:
-    # Get all tradable option contracts for AAPL at the current time for filtering
-    contract_symbols = self.option_chain_provider.get_option_contract_list(self.aapl, self.time)
-    # Select the calls expires within 30 days and within $5 strike from ATM as leg of the covered call
-    # $5 buffer is given on selecting the ATM call due to price movement
-    self.options = [self.add_option_contract(symbol).symbol for symbol in contract_symbols
-        if symbol.id.date < self.time + timedelta(30) \
-        and symbol.id.option_right == OptionRight.CALL \
-        and abs(symbol.id.strike_price - self.securities[self.aapl].price) <= 5]
-
-def on_data(self, slice: Slice) -> None:
-    if not self.portfolio.invested and self.aapl in slice.bars and self.options:
-        # To form a covered call, get the contract closest to ATM and expiry
-        expiry = min(x.id.date for x in self.options)
-        contract = sorted([x for x in self.options if x.id.date == expiry],
-            key=lambda x: abs(x.id.strike_price - self.securities[self.aapl].price))[0]
-            
-        # Covered call involves shorting 1 ATM call and ordering 1 lot of underlying
-        self.market_order(contract, -1)
-        self.market_order(self.aapl, self.securities[contract].symbol_properties.contract_multiplier)
-
-def on_order_event(self, order_event: OrderEvent) -> None:
-    # Close AAPL position if the short call is not exercised (OTM)
-    # If it is exercised, the underlying will be used to settle the contract automatically
-    if order_event.ticket.order_type == OrderType.OPTION_EXERCISE \
-    and not order_event.is_in_the_money:
-        self.market_order(self.aapl, -self.securities[order_event.symbol].symbol_properties.contract_multiplier)
+
class EquityOptionExampleAlgorithm(QCAlgorithm):
+    def initialize(self) -> None:
+        self.options = []
+        # Seed the security price to ensure the retrieval of the ATM calls at the initial filtering.
+        self.set_security_initializer(lambda security: FuncSecuritySeeder(self.get_last_known_prices).seed_security(security))
+        # Set the data normalization mode as raw for option strike-price comparability.
+        self.aapl = self.add_equity("AAPL", data_normalization_mode=DataNormalizationMode.RAW).symbol
+        
+        # Filter an updated option list at market open everyday by a scheduled event.
+        self.schedule.on(
+            self.date_rules.every_day(self.aapl),
+            self.time_rules.at(9, 0),
+            self.refresh_option_list
+        )
+    
+    def refresh_option_list(self) -> None:
+        # Get all tradable option contracts for AAPL at the current time for filtering.
+        contract_symbols = self.option_chain_provider.get_option_contract_list(self.aapl, self.time)
+        # Select the calls expires within 30 days and within $5 strike from ATM as leg of the covered call.
+        # $5 buffer is given on selecting the ATM call due to price movement.
+        self.options = [self.add_option_contract(symbol).symbol for symbol in contract_symbols
+            if symbol.id.date < self.time + timedelta(30) \
+            and symbol.id.option_right == OptionRight.CALL \
+            and abs(symbol.id.strike_price - self.securities[self.aapl].price) <= 5]
+    
+    def on_data(self, slice: Slice) -> None:
+        if not self.portfolio.invested and self.aapl in slice.bars and self.options:
+            # To form a covered call, get the contract closest to ATM and expiry.
+            expiry = min(x.id.date for x in self.options)
+            contract = sorted([x for x in self.options if x.id.date == expiry],
+                key=lambda x: abs(x.id.strike_price - self.securities[self.aapl].price))[0]
+                
+            # Covered call involves shorting 1 ATM call and ordering 1 lot of underlying.
+            self.market_order(contract, -1)
+            self.market_order(self.aapl, self.securities[contract].symbol_properties.contract_multiplier)
+    
+    def on_order_event(self, order_event: OrderEvent) -> None:
+        # Close AAPL position if the short call is not exercised (OTM).
+        # If it is exercised, the underlying will be used to settle the contract automatically.
+        if order_event.ticket.order_type == OrderType.OPTION_EXERCISE \
+        and not order_event.is_in_the_money:
+            self.market_order(self.aapl, -self.securities[order_event.symbol].symbol_properties.contract_multiplier)

Example 2: 0-DTE Bull Put Spread

0DTE options often trades with high volume and volatility, providing arbitration opportunities and higher profit margin on spread type trading. In this example, we make use of 0-DTE SPY options to trade bull put spread option strategy.

-
private Symbol _spy;
-private List<Symbol> _options = new();
-
-public override void Initialize()
+    
public class EquityOptionExampleAlgorithm : QCAlgorithm
 {
-    // Seed the underlying security price to ensure accurate filtering for puts of $5 above/below current market price
-    SetSecurityInitializer((security) => new FuncSecuritySeeder(GetLastKnownPrices).SeedSecurity(security));
-    // Set the data normalization mode as raw for option strike-price comparability
-    _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
-
-    // Filter an updated option list at market open everyday by a scheduled event
-    Schedule.On(
-        DateRules.EveryDay(_spy),
-        TimeRules.At(9, 0),
-        RefreshOptionList
-    );
-
-    // Use a scheduled event to close all positions before market close
-    Schedule.On(
-        DateRules.EveryDay(_spy),
-        TimeRules.BeforeMarketClose(_spy, 1),
-        ExitPositions
-    );
-}
-
-private void RefreshOptionList()
-{
-    // Get all tradable option contracts for SPY at the current time for filtering
-    var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time);
-    // Select the 0-DTE puts by setting expiry within 1 day
-    var filteredSymbols = contractSymbols
-        .Where(symbol => symbol.ID.Date < Time.AddDays(1) && symbol.ID.OptionRight == OptionRight.Put)
-        .ToList();
-    // Ensure at least 2 contracts available to form a put spread
-    if (filteredSymbols.Count < 2)
+    private Symbol _spy;
+    private List<Symbol> _options = new();
+    
+    public override void Initialize()
     {
-        _options = new();
-        return;
+        // Seed the underlying security price to ensure accurate filtering for puts of $5 above/below current market price.
+        SetSecurityInitializer((security) => new FuncSecuritySeeder(GetLastKnownPrices).SeedSecurity(security));
+        // Set the data normalization mode as raw for option strike-price comparability.
+        _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
+    
+        // Filter an updated option list at market open everyday by a scheduled event.
+        Schedule.On(
+            DateRules.EveryDay(_spy),
+            TimeRules.At(9, 0),
+            RefreshOptionList
+        );
+    
+        // Use a scheduled event to close all positions before market close.
+        Schedule.On(
+            DateRules.EveryDay(_spy),
+            TimeRules.BeforeMarketClose(_spy, 1),
+            ExitPositions
+        );
     }
-    // To form a put spread, select and subscribe to put contracts $5 above and below from the current underlying price
-    var itmPut = filteredSymbols.MinBy(symbol => Math.Abs(symbol.ID.StrikePrice - Securities[_spy].Price - 5));
-    var otmPut = filteredSymbols.MinBy(symbol => Math.Abs(symbol.ID.StrikePrice - Securities[_spy].Price + 5));
-    _options = new List<Symbol> { AddOptionContract(itmPut).Symbol, AddOptionContract(otmPut).Symbol };
-}
-
-public override void OnData(Slice slice)
-{
-    // To avoid over-trading, limit the position opening to before 3pm
-    // To ensure the put spread formed correctly, make sure at least 2 contracts selected
-    if (!Portfolio.Invested && Time.Hour < 15 &&  _options.Count == 2)
+    
+    private void RefreshOptionList()
     {
-        // A bull put spread involves buying a lower-strike put and selling a high-strike put
-        MarketOrder(_options.MaxBy(x => x.ID.StrikePrice), -1);
-        MarketOrder(_options.MinBy(x => x.ID.StrikePrice), 1);
+        // Get all tradable option contracts for SPY at the current time for filtering.
+        var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time);
+        // Select the 0-DTE puts by setting expiry within 1 day.
+        var filteredSymbols = contractSymbols
+            .Where(symbol => symbol.ID.Date < Time.AddDays(1) && symbol.ID.OptionRight == OptionRight.Put)
+            .ToList();
+        // Ensure at least 2 contracts available to form a put spread.
+        if (filteredSymbols.Count < 2)
+        {
+            _options = new();
+            return;
+        }
+        // To form a put spread, select and subscribe to put contracts $5 above and below from the current underlying price.
+        var itmPut = filteredSymbols.MinBy(symbol => Math.Abs(symbol.ID.StrikePrice - Securities[_spy].Price - 5));
+        var otmPut = filteredSymbols.MinBy(symbol => Math.Abs(symbol.ID.StrikePrice - Securities[_spy].Price + 5));
+        _options = new List<Symbol> { AddOptionContract(itmPut).Symbol, AddOptionContract(otmPut).Symbol };
+    }
+    
+    public override void OnData(Slice slice)
+    {
+        // To avoid over-trading, limit the position opening to before 3pm.
+        // To ensure the put spread formed correctly, make sure at least 2 contracts selected.
+        if (!Portfolio.Invested && Time.Hour < 15 &&  _options.Count == 2)
+        {
+            // A bull put spread involves buying a lower-strike put and selling a high-strike put
+            MarketOrder(_options.MaxBy(x => x.ID.StrikePrice), -1);
+            MarketOrder(_options.MinBy(x => x.ID.StrikePrice), 1);
+        }
+    }
+    
+    private void ExitPositions()
+    {
+        // Exit all positions before market close to avoid option assignment and overnight risk.
+        Liquidate();
     }
-}
-
-private void ExitPositions()
-{
-    // Exit all positions before market close to avoid option assignment and overnight risk
-    Liquidate();
 }
-
def initialize(self) -> None:
-    self.options = []
-    # Seed the underlying security price to ensure accurate filtering for puts of $5 above/below current market price
-    self.set_security_initializer(lambda security: FuncSecuritySeeder(self.get_last_known_prices).seed_security(security))
-    # Set the data normalization mode as raw for option strike-price comparability
-    self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
-    
-    # Filter an updated option list at market open everyday by a scheduled event
-    self.schedule.on(
-        self.date_rules.every_day(self.spy),
-        self.time_rules.at(9, 0),
-        self.refresh_option_list
-    )
-
-    # Use a scheduled event to close all positions before market close
-    self.schedule.on(
-        self.date_rules.every_day(self.spy),
-        self.time_rules.before_market_close(self.spy, 1),
-        self.exit_position
-    )
-
-def refresh_option_list(self) -> None:
-    # Get all tradable option contracts for SPY at the current time for filtering
-    contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
-    # Select the 0-DTE puts by setting expiry within 1 day
-    filtered_symbols = [symbol for symbol in contract_symbols
-        if symbol.id.date < self.time + timedelta(1) and symbol.id.option_right == OptionRight.PUT]
-    # Ensure at least 2 contracts available to form a put spread
-    if len(filtered_symbols) < 2:
+    
class EquityOptionExampleAlgorithm(QCAlgorithm):
+    def initialize(self) -> None:
         self.options = []
-        return
+        # Seed the underlying security price to ensure accurate filtering for puts of $5 above/below current market price.
+        self.set_security_initializer(lambda security: FuncSecuritySeeder(self.get_last_known_prices).seed_security(security))
+        # Set the data normalization mode as raw for option strike-price comparability.
+        self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
         
-    # To form a put spread, select and subscribe to put contracts $5 above and below from the current underlying price
-    itm_put = sorted(filtered_symbols, key=lambda symbol: abs(symbol.id.strike_price - self.securities[self.spy].price - 5))[0]
-    otm_put = sorted(filtered_symbols, key=lambda symbol: abs(symbol.id.strike_price - self.securities[self.spy].price + 5))[0]
-    self.options = [self.add_option_contract(itm_put).symbol, self.add_option_contract(otm_put).symbol]
-
-def on_data(self, slice: Slice) -> None:
-    # To avoid over-trading, limit the position opening to before 3pm
-    # To ensure the put spread formed correctly, make sure at least 2 contracts selected
-    if not self.portfolio.invested and self.time.hour < 15 and len(self.options) == 2:
-        # A bull put spread involves buying a lower-strike put and selling a high-strike put
-        sorted_by_strike = sorted(self.options, key=lambda x: x.id.strike_price)
-        self.market_order(sorted_by_strike[-1], -1)
-        self.market_order(sorted_by_strike[0], 1)
-
-def exit_position(self) -> None:
-    # Exit all positions before market close to avoid option assignment and overnight risk
-    self.liquidate()
+ # Filter an updated option list at market open everyday by a scheduled event. + self.schedule.on( + self.date_rules.every_day(self.spy), + self.time_rules.at(9, 0), + self.refresh_option_list + ) + + # Use a scheduled event to close all positions before market close. + self.schedule.on( + self.date_rules.every_day(self.spy), + self.time_rules.before_market_close(self.spy, 1), + self.exit_position + ) + + def refresh_option_list(self) -> None: + # Get all tradable option contracts for SPY at the current time for filtering. + contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time) + # Select the 0-DTE puts by setting expiry within 1 day. + filtered_symbols = [symbol for symbol in contract_symbols + if symbol.id.date < self.time + timedelta(1) and symbol.id.option_right == OptionRight.PUT] + # Ensure at least 2 contracts available to form a put spread. + if len(filtered_symbols) < 2: + self.options = [] + return + + # To form a put spread, select and subscribe to put contracts $5 above and below from the current underlying price. + itm_put = sorted(filtered_symbols, key=lambda symbol: abs(symbol.id.strike_price - self.securities[self.spy].price - 5))[0] + otm_put = sorted(filtered_symbols, key=lambda symbol: abs(symbol.id.strike_price - self.securities[self.spy].price + 5))[0] + self.options = [self.add_option_contract(itm_put).symbol, self.add_option_contract(otm_put).symbol] + + def on_data(self, slice: Slice) -> None: + # To avoid over-trading, limit the position opening to before 3pm. + # To ensure the put spread formed correctly, make sure at least 2 contracts selected. + if not self.portfolio.invested and self.time.hour < 15 and len(self.options) == 2: + # A bull put spread involves buying a lower-strike put and selling a high-strike put + sorted_by_strike = sorted(self.options, key=lambda x: x.id.strike_price) + self.market_order(sorted_by_strike[-1], -1) + self.market_order(sorted_by_strike[0], 1) + + def exit_position(self) -> None: + # Exit all positions before market close to avoid option assignment and overnight risk. + self.liquidate()

Example 3: Filter Option Contracts By Option Greeks indicator

In this example, we demonstrate filtering option contracts by Delta, an Option Greeks indicator. We filter all the call contracts with Delta greater than 0.99 that expires just more than 7 days later on Monday morning. This filter is useful when creating a hedge replicate portfolio for arbitration.

-
private Symbol _spy;
-private DividendYieldProvider _dividendYieldModel;
-private List<Symbol> _universe = new();
-
-public override void Initialize()
+    
public class EquityOptionExampleAlgorithm : QCAlgorithm
 {
-    // Set the data normalization mode as raw for option strike-price comparability
-    _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
-    // Instantiate a dividend yield provider to calculate accurate greeks later
-    _dividendYieldModel = new DividendYieldProvider(_spy);
-
-    // Scheduled week start filtering to obtain the contracts to be traded for the week
-    Schedule.On(
-        DateRules.Every(DayOfWeek.Monday),
-        TimeRules.At(7, 30),
-        Filter
-    );
-}
-
-private void Filter()
-{
-    // Get all tradable option contracts for SPY at the current time for filtering
-    var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time);
-    // Filter the contracts tradable the whole week but expire soon to ensure liquidity
-    var expiry = contractSymbols.Where(x => x.ID.Date >= Time.AddDays(7))
-        .Min(x => x.ID.Date);
-    var filtered = contractSymbols.Where(x => x.ID.Date == expiry).ToList();
-
-    // Group by strike price to create Delta that take both call and put input
-    var symbolDelta = new Dictionary<Symbol, decimal>();
-    foreach (var strike in filtered.Select(x => x.ID.StrikePrice).Distinct())
+    private Symbol _spy;
+    private DividendYieldProvider _dividendYieldModel;
+    private List<Symbol> _universe = new();
+    
+    public override void Initialize()
     {
-        // There should only be 1 call and put per strike, make sure both are present to feed into the Delta indicator
-        var call = filtered.SingleOrDefault(x => 
-            x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Call);
-        var put = filtered.SingleOrDefault(x => 
-            x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Put);
-        if (call == null || put == null) continue;
-
-        // Since positive delta must be a call, save the call symbol and delta in the dictionary for later sorting
-        var delta = GetDelta(call, put);
-        symbolDelta.Add(call, delta);
+        // Set the data normalization mode as raw for option strike-price comparability.
+        _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
+        // Instantiate a dividend yield provider to calculate accurate greeks later.
+        _dividendYieldModel = new DividendYieldProvider(_spy);
+    
+        // Scheduled week start filtering to obtain the contracts to be traded for the week.
+        Schedule.On(
+            DateRules.Every(DayOfWeek.Monday),
+            TimeRules.At(7, 30),
+            Filter
+        );
+    }
+    
+    private void Filter()
+    {
+        // Get all tradable option contracts for SPY at the current time for filtering.
+        var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time);
+        // Filter the contracts tradable the whole week but expire soon to ensure liquidity.
+        var expiry = contractSymbols.Where(x => x.ID.Date >= Time.AddDays(7))
+            .Min(x => x.ID.Date);
+        var filtered = contractSymbols.Where(x => x.ID.Date == expiry).ToList();
+    
+        // Group by strike price to create Delta that take both call and put input.
+        var symbolDelta = new Dictionary<Symbol, decimal>();
+        foreach (var strike in filtered.Select(x => x.ID.StrikePrice).Distinct())
+        {
+            // There should only be 1 call and put per strike, make sure both are present to feed into the Delta indicator.
+            var call = filtered.SingleOrDefault(x => 
+                x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Call);
+            var put = filtered.SingleOrDefault(x => 
+                x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Put);
+            if (call == null || put == null) continue;
+    
+            // Since positive delta must be a call, save the call symbol and delta in the dictionary for later sorting.
+            var delta = GetDelta(call, put);
+            symbolDelta.Add(call, delta);
+        }
+    
+        // Select the calls using the dictionary with delta filtering, request for the individual contract data for trading.
+        _universe = symbolDelta.Where(kvp => kvp.Value >= 0.99m)
+            .Select(x => AddOptionContract(x.Key).Symbol)
+            .ToList();
+    }
+    
+    private decimal GetDelta(Symbol call, Symbol put)
+    {
+        // Use both call and put with interest rate and dividend yield provider to obtain accurate OTM contract IV as IV for both call and put.
+        // For American option, Forward Tree option pricing model is more accurate.
+        var delta = new Delta(call, RiskFreeInterestRateModel, _dividendYieldModel, put, OptionPricingModelType.ForwardTree);
+        // Warm up the indicator using both mirror contract pairs and the underlying to calculate the delta.
+        IndicatorHistory(delta, new[] { call, put, _spy }, 1);
+        // Return the latest delta value.
+        return delta.Current.Value;
     }
-
-    // Select the calls using the dictionary with delta filtering, request for the individual contract data for trading
-    _universe = symbolDelta.Where(kvp => kvp.Value >= 0.99m)
-        .Select(x => AddOptionContract(x.Key).Symbol)
-        .ToList();
-}
-
-private decimal GetDelta(Symbol call, Symbol put)
-{
-    // Use both call and put with interest rate and dividend yield provider to obtain accurate OTM contract IV as IV for both call and put
-    // For American option, Forward Tree option pricing model is more accurate
-    var delta = new Delta(call, RiskFreeInterestRateModel, _dividendYieldModel, put, OptionPricingModelType.ForwardTree);
-    // Warm up the indicator using both mirror contract pairs and the underlying to calculate the delta
-    IndicatorHistory(delta, new[] { call, put, _spy }, 1);
-    // Return the latest delta value
-    return delta.Current.Value;
 }
-
def initialize(self) -> None:
-    self._universe = []
-    # Set the data normalization mode as raw for option strike-price comparability
-    self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
-    # Instantiate a dividend yield provider to calculate accurate greeks later
-    self.dividend_yield_provider = DividendYieldProvider(self.spy)
-
-    # Scheduled week start filtering to obtain the contracts to be traded for the week
-    self.schedule.on(
-        self.date_rules.every(DayOfWeek.MONDAY),
-        self.time_rules.at(7, 30),
-        self.filter
-    )
-
-def filter(self) -> None:
-    # Get all tradable option contracts for SPY at the current time for filtering
-    contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
-    # Filter the contracts tradable the whole week but expire soon to ensure liquidity
-    expiry = min(x.id.date for x in contract_symbols if x.id.date >= self.time + timedelta(7))
-    filtered = [x for x in contract_symbols if x.id.date == expiry]
-
-    # Group by strike price to create Delta that take both call and put input
-    symbol_delta = {}
-    for strike in set(x.id.strike_price for x in filtered):
-        # There should only be 1 call and put per strike, make sure both are present to feed into the Delta indicator
-        call = next(filter(lambda x: x.id.option_right == OptionRight.CALL and x.id.strike_price == strike, filtered), None)
-        put = next(filter(lambda x: x.id.option_right == OptionRight.PUT and x.id.strike_price == strike, filtered), None)
-        if not call or not put:
-            continue
+    
class EquityOptionExampleAlgorithm(QCAlgorithm):
+    def initialize(self) -> None:
+        self._universe = []
+        # Set the data normalization mode as raw for option strike-price comparability.
+        self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
+        # Instantiate a dividend yield provider to calculate accurate greeks later.
+        self.dividend_yield_provider = DividendYieldProvider(self.spy)
+    
+        # Scheduled week start filtering to obtain the contracts to be traded for the week.
+        self.schedule.on(
+            self.date_rules.every(DayOfWeek.MONDAY),
+            self.time_rules.at(7, 30),
+            self.filter
+        )
+    
+    def filter(self) -> None:
+        # Get all tradable option contracts for SPY at the current time for filtering.
+        contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
+        # Filter the contracts tradable the whole week but expire soon to ensure liquidity.
+        expiry = min(x.id.date for x in contract_symbols if x.id.date >= self.time + timedelta(7))
+        filtered = [x for x in contract_symbols if x.id.date == expiry]
+    
+        # Group by strike price to create Delta that take both call and put input.
+        symbol_delta = {}
+        for strike in set(x.id.strike_price for x in filtered):
+            # There should only be 1 call and put per strike, make sure both are present to feed into the Delta indicator.
+            call = next(filter(lambda x: x.id.option_right == OptionRight.CALL and x.id.strike_price == strike, filtered), None)
+            put = next(filter(lambda x: x.id.option_right == OptionRight.PUT and x.id.strike_price == strike, filtered), None)
+            if not call or not put:
+                continue
+            
+            # Since positive delta must be a call, save the call symbol and delta in the dictionary for later sorting.
+            delta = self.get_delta(call, put)
+            symbol_delta[call] = delta
         
-        # Since positive delta must be a call, save the call symbol and delta in the dictionary for later sorting
-        delta = self.get_delta(call, put)
-        symbol_delta[call] = delta
-    
-    # Select the calls using the dictionary with delta filtering, request for the individual contract data for trading
-    self._universe = [self.add_option_contract(call).symbol 
-        for call, delta in symbol_delta.items() if delta >= 0.99]
-    
-def get_delta(self, call: Symbol, put: Symbol) -> float:
-    # Use both call and put with interest rate and dividend yield provider to obtain accurate OTM contract IV as IV for both call and put
-    # For American option, Forward Tree option pricing model is more accurate
-    delta = Delta(call, self.risk_free_interest_rate_model, self.dividend_yield_provider, put, OptionPricingModelType.FORWARD_TREE)
-    # Warm up the indicator using both mirror contract pairs and the underlying to calculate the delta
-    self.indicator_history(delta, [call, put, self.spy], 1)
-    # Return the latest delta value
-    return delta.current.value
+ # Select the calls using the dictionary with delta filtering, request for the individual contract data for trading. + self._universe = [self.add_option_contract(call).symbol + for call, delta in symbol_delta.items() if delta >= 0.99] + + def get_delta(self, call: Symbol, put: Symbol) -> float: + # Use both call and put with interest rate and dividend yield provider to obtain accurate OTM contract IV as IV for both call and put. + # For American option, Forward Tree option pricing model is more accurate. + delta = Delta(call, self.risk_free_interest_rate_model, self.dividend_yield_provider, put, OptionPricingModelType.FORWARD_TREE) + # Warm up the indicator using both mirror contract pairs and the underlying to calculate the delta. + self.indicator_history(delta, [call, put, self.spy], 1) + # Return the latest delta value. + return delta.current.value

Example 4: Wheel Strategy

The Wheel strategy is a popular trading strategy for Options that enables traders to build a steady flow of income from Equity assets they want to hold for the long term.

-
private Symbol _spy;
-// Set OTM threshold for wheel strategy profit margin
-private decimal _otmThreshold = 0.05m;
-
-public override void Initialize()
+    
public class EquityOptionExampleAlgorithm : QCAlgorithm
 {
-    // Set the data normalization mode as raw for option strike-price comparability
-    _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
-}
-
-public override void OnData(Slice slice)
-{
-    // To use the latest underlying price to filter the option contract, ensure the SPY in the bar data
-    // Open short put contract position only when the last wheel is completed
-    if (!Portfolio.Invested && slice.Bars.ContainsKey(_spy))
+    private Symbol _spy;
+    // Set OTM threshold for wheel strategy profit margin.
+    private decimal _otmThreshold = 0.05m;
+    
+    public override void Initialize()
     {
-        // Initiate the wheel by shorting a least-OTM put contract that the strike is below the threshold
-        var symbol = GetTargetContract(OptionRight.Put, slice.Bars[_spy].Price * (1 - _otmThreshold));
-        SetHoldings(symbol, -0.2m);
+        // Set the data normalization mode as raw for option strike-price comparability.
+        _spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
     }
-    // Open short call contract position only when the put is assigned (portfolio with the underlying) to close the wheel and underlying position by the call assignment
-    else if (Portfolio[_spy].Invested && slice.Bars.ContainsKey(_spy))
+    
+    public override void OnData(Slice slice)
     {
-        // Short the corresponding number of a least-OTM call contract that the strike is above the threshold
-        var symbol = GetTargetContract(OptionRight.Call, slice.Bars[_spy].Price * (1 + _otmThreshold));
-        MarketOrder(symbol, -Portfolio[_spy].Quantity / Securities[_spy].SymbolProperties.ContractMultiplier);
+        // To use the latest underlying price to filter the option contract, ensure the SPY in the bar data.
+        // Open short put contract position only when the last wheel is completed.
+        if (!Portfolio.Invested && slice.Bars.ContainsKey(_spy))
+        {
+            // Initiate the wheel by shorting a least-OTM put contract that the strike is below the threshold.
+            var symbol = GetTargetContract(OptionRight.Put, slice.Bars[_spy].Price * (1 - _otmThreshold));
+            SetHoldings(symbol, -0.2m);
+        }
+        // Open short call contract position only when the put is assigned (portfolio with the underlying) to close the wheel and underlying position by the call assignment.
+        else if (Portfolio[_spy].Invested && slice.Bars.ContainsKey(_spy))
+        {
+            // Short the corresponding number of a least-OTM call contract that the strike is above the threshold.
+            var symbol = GetTargetContract(OptionRight.Call, slice.Bars[_spy].Price * (1 + _otmThreshold));
+            MarketOrder(symbol, -Portfolio[_spy].Quantity / Securities[_spy].SymbolProperties.ContractMultiplier);
+        }
+    }
+    
+    private Symbol GetTargetContract(OptionRight right, decimal targetPrice)
+    {
+        // Get all tradable option contracts for SPY at the current time for filtering.
+        var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time).ToList();
+        // Filter for the least-OTM contract that is off by the threshold to form the wheel.
+        // Expiry is set to be at least 30 days to earn the time decay, which is highest in the last month.
+        var expiry = contractSymbols.Where(x => x.ID.Date > Time.AddDays(30))
+            .Min(x => x.ID.Date);
+        var filtered = contractSymbols.Where(x =>
+                x.ID.Date == expiry &&
+                x.ID.OptionRight == right &&
+                (right == OptionRight.Call ? x.ID.StrikePrice >= targetPrice : x.ID.StrikePrice <= targetPrice)
+            )
+            .OrderBy(x => x.ID.StrikePrice)
+            .ToList();
+        var selected = right == OptionRight.Call ? filtered.First() : filtered.Last();
+        // Request the selected contract data for trading.
+        return AddOptionContract(selected).Symbol;
     }
-}
-
-private Symbol GetTargetContract(OptionRight right, decimal targetPrice)
-{
-    // Get all tradable option contracts for SPY at the current time for filtering
-    var contractSymbols = OptionChainProvider.GetOptionContractList(_spy, Time).ToList();
-    // Filter for the least-OTM contract that is off by the threshold to form the wheel
-    // Expiry is set to be at least 30 days to earn the time decay, which is highest in the last month
-    var expiry = contractSymbols.Where(x => x.ID.Date > Time.AddDays(30))
-        .Min(x => x.ID.Date);
-    var filtered = contractSymbols.Where(x =>
-            x.ID.Date == expiry &&
-            x.ID.OptionRight == right &&
-            (right == OptionRight.Call ? x.ID.StrikePrice >= targetPrice : x.ID.StrikePrice <= targetPrice)
-        )
-        .OrderBy(x => x.ID.StrikePrice)
-        .ToList();
-    var selected = right == OptionRight.Call ? filtered.First() : filtered.Last();
-    // Request the selected contract data for trading
-    return AddOptionContract(selected).Symbol;
 }
-
def initialize(self) -> None:
-    # Set the data normalization mode as raw for option strike-price comparability
-    self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
-    # Set OTM threshold for wheel strategy profit margin
-    self.otm_threshold = 0.05
-
-def on_data(self, slice: Slice) -> None:
-    # To use the latest underlying price to filter the option contract, ensure the SPY in the bar data
-    # Open short put contract position only when the last wheel is completed
-    if not self.portfolio.invested and self.spy in slice.bars:
-        # Get the target put contract
-        symbol = self.get_target_contract(OptionRight.PUT, slice.bars[self.spy].price * (1 - self.otm_threshold))
-        # Short the put contract
-        self.set_holdings(symbol, -0.2)
-    # Open short call contract position only when the put is assigned (portfolio with the underlying) to close the wheel and underlying position by the call assignment
-    elif self.portfolio[self.spy].invested and self.spy in slice.bars:
-        # Short the corresponding number of a least-OTM call contract that the strike is above the threshold
-        symbol = self.get_target_contract(OptionRight.CALL, slice.bars[self.spy].price * (1 + self.otm_threshold))
-        self.market_order(symbol, self.portfolio[self.spy].quantity / self.securities[self.spy].symbol_properties.contract_multipliers)
-    
-def get_target_contract(self, right: OptionRight, target_price: float) -> Symbol:
-    # Get all tradable option contracts for SPY at the current time for filtering
-    contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
-    # Filter for the least-OTM contract that is off by the threshold to form the wheel
-    # Expiry is set to be at least 30 days to earn the time decay, which is highest in the last month
-    expiry = min(x.id.date for x in contract_symbols)
-    filtered = [x for x in contract_symbols \
-        if x.id.date == expiry \
-        and x.id.option_right == right \
-        and (x.id.strike_price >= target_price if right == OptionRight.CALL else x.id.strike_price <= target_price)]
-    sorted_by_strikes = sorted(filtered, key=lambda x: x.id.strike_price)
-    selected = sorted_by_strikes[0] if right == OptionRight.CALL else sorted_by_strikes[-1]
-    # Request the selected contract data for trading
-    return self.add_option_contract(selected).symbol
+
class EquityOptionExampleAlgorithm(QCAlgorithm):
+    def initialize(self) -> None:
+        # Set the data normalization mode as raw for option strike-price comparability.
+        self.spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
+        # Set OTM threshold for wheel strategy profit margin.
+        self.otm_threshold = 0.05
+    
+    def on_data(self, slice: Slice) -> None:
+        # To use the latest underlying price to filter the option contract, ensure the SPY in the bar data.
+        # Open short put contract position only when the last wheel is completed.
+        if not self.portfolio.invested and self.spy in slice.bars:
+            # Initiate the wheel by shorting a least-OTM put contract that the strike is below the threshold.
+            symbol = self.get_target_contract(OptionRight.PUT, slice.bars[self.spy].price * (1 - self.otm_threshold))
+            self.set_holdings(symbol, -0.2)
+        # Open short call contract position only when the put is assigned (portfolio with the underlying) to close the wheel and underlying position by the call assignment.
+        elif self.portfolio[self.spy].invested and self.spy in slice.bars:
+            # Short the corresponding number of a least-OTM call contract that the strike is above the threshold.
+            symbol = self.get_target_contract(OptionRight.CALL, slice.bars[self.spy].price * (1 + self.otm_threshold))
+            self.market_order(symbol, self.portfolio[self.spy].quantity / self.securities[self.spy].symbol_properties.contract_multipliers)
+        
+    def get_target_contract(self, right: OptionRight, target_price: float) -> Symbol:
+        # Get all tradable option contracts for SPY at the current time for filtering.
+        contract_symbols = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
+        # Filter for the least-OTM contract that is off by the threshold to form the wheel.
+        # Expiry is set to be at least 30 days to earn the time decay, which is highest in the last month.
+        expiry = min(x.id.date for x in contract_symbols)
+        filtered = [x for x in contract_symbols \
+            if x.id.date == expiry \
+            and x.id.option_right == right \
+            and (x.id.strike_price >= target_price if right == OptionRight.CALL else x.id.strike_price <= target_price)]
+        sorted_by_strikes = sorted(filtered, key=lambda x: x.id.strike_price)
+        selected = sorted_by_strikes[0] if right == OptionRight.CALL else sorted_by_strikes[-1]
+        # Request the selected contract data for trading.
+        return self.add_option_contract(selected).symbol

For more details, refer to the Wheel Strategy research post.