Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-3.919
Tracking Error
0.136
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion

/*
https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/equity-options/requesting-data/individual-contracts#99-Examples
Example 5: Scan and Update Option Chain Every 5 Minutes 
*/
namespace QuantConnect.Algorithm.CSharp
{
    public class OptionChainFullExample : QCAlgorithm
    {
        private Dictionary<Symbol, OptionChainManager> _chainManager = new();
        public override void Initialize()
        {
            SetStartDate(2023, 1, 2);
            SetEndDate(2023, 1, 30);
            SetCash(100000);

            UniverseSettings.Asynchronous = true;
            UniverseSettings.MinimumTimeInUniverse = TimeSpan.Zero;
            // Seed the security price to ensure the underlying price data is ready at the initial filtering
            SetSecurityInitializer(new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));
            // Set the data normalization mode as raw for option strike-price comparability
            var spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
            // Set up a OptionChainManager to filter the contracts based on latest data by request
            _chainManager[QuantConnect.Symbol.CreateCanonicalOption(spy)] = new(-2, 2, 0, 1);

            
            // Daily population of the available contracts to ensure the contracts are tradable
            PopulateOptionChain();
            Schedule.On(DateRules.EveryDay(spy), TimeRules.AfterMarketOpen(spy, 1), PopulateOptionChain);
            // Filter for contract in every 5 minutes interval through scheduled event
            Schedule.On(DateRules.EveryDay(spy), TimeRules.Every(TimeSpan.FromMinutes(5)), Filter);
        }
        
        private void PopulateOptionChain()
        {
            var index = 0;

            Log("Populating option chain...");

            // The contract list is updated daily, so we can get it and apply
            // the expiration filter as soon as the market opens.
            foreach (var (symbol, manager) in _chainManager)
            {
                Log($"{++index} Populating option chain for {symbol}...");
                manager.SetChain(OptionChain(symbol), Time);
            }
            Filter();
        }
        
        private void Filter()
        {
            var index = 0;

            Log("Filtering option chain...");
            
            foreach (var (symbol, manager) in _chainManager)
            {
                Log($"{++index} Filtering option chain for {symbol}...");
                manager.Select(this, symbol);
            }
        }
        
        public override void OnData(Slice slice)
        {
            // Iterate the saved symbol and chain manager to obtain only the contract wanted
            foreach (var (symbol, manager) in _chainManager)
            {
                if (!slice.OptionChains.TryGetValue(symbol, out var chain))
                    continue;
                /*
                // Handle option exercise and assignment on unwanted underlying position
                if (Portfolio[symbol.Underlying].Invested)
                {
                    Liquidate(symbol.Underlying);
                }

                // Buy the ATM call with nearest expiry to speculate-trade the underlying through low cost, leveraging position
                var expiry = chain.Min(x => x.Expiry);
                var atmCall = chain
                    .Where(x => x.Expiry == expiry && x.Right == OptionRight.Call && Securities[x.Symbol].IsTradable)
                    .OrderBy(x => Math.Abs(chain.Underlying.Price - x.Strike))
                    .FirstOrDefault();
                if (atmCall != null && !Portfolio[atmCall.Symbol].Invested)
                {
                    MarketOrder(atmCall.Symbol, 1);
                }
                */
            }
        }
    }

    internal class OptionChainManager
    {
        private readonly int _minStrike;
        private readonly int _maxStrike;
        private readonly int _minExpiry;
        private readonly int _maxExpiry;
        private List<OptionContract> _chain = new();
        private readonly List<Symbol> _symbols = new();

        public OptionChainManager(int minStrike, int maxStrike, int minExpiry, int maxExpiry)
        {
            _minStrike = minStrike;
            _maxStrike = maxStrike;
            _minExpiry = minExpiry;
            _maxExpiry = maxExpiry;
        }

        public void SetChain(OptionChain chain, DateTime time)
        {
            // Expiry criteria will not affect intra-day universe filtering, so it is done in a daily basis in higher level
            _chain = chain.Where(x =>
            {
                var totalDays = (x.Expiry - time).TotalDays;
                return _minExpiry <= totalDays && totalDays <= _maxExpiry;
            }).ToList();
        }
        
        public void Select(QCAlgorithm algorithm, Symbol underlyingSymbol)
        {
            if (_chain.IsNullOrEmpty())
                return;
            if (underlyingSymbol.IsCanonical())
                underlyingSymbol = underlyingSymbol.Underlying;

            var strikes = _chain.Select(x => x.Strike).OrderBy(x => x).Distinct().ToList();
            var spot = algorithm.Securities[underlyingSymbol].Price;
            var atm = strikes.OrderBy(x => Math.Abs(spot - x)).FirstOrDefault();
            var index = strikes.IndexOf(atm);
            var minStrike = strikes[Math.Max(0, index + _minStrike)];
            var maxStrike = strikes[Math.Min(strikes.Count - 1, index + _maxStrike)];
            var symbols = _chain
                .Where(x => minStrike <= x.Strike && x.Strike <= maxStrike)
                .Select(x => x.Symbol).ToList();

            // Also remove data subscription on the contracts being filtered out to release computation resources
                foreach (var symbol in _symbols.Except(symbols).ToList())
            {
                if (algorithm.RemoveOptionContract(symbol))
                    _symbols.Remove(symbol);
            }
            foreach (var symbol in symbols.Except(_symbols).ToList())
            {
                _symbols.Add(symbol);
                algorithm.AddOptionContract(symbol);
            }
        }
    }
}