| 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);
}
}
}
}