/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Linq; using QuantConnect.Data; using System.Collections.Generic; using System.Collections.Specialized; using QuantConnect.Data.UniverseSelection; namespace QuantConnect.Algorithm.Selection { /// /// This universe will hold single option contracts and their underlying, managing removals and additions /// public class OptionContractUniverse : UserDefinedUniverse { private readonly HashSet _symbols; /// /// Creates a new empty instance /// /// The universe configuration to use /// The universe settings to use public OptionContractUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings) : base(AdjustUniverseConfiguration(configuration), universeSettings, Time.EndOfTimeTimeSpan, // Argument isn't used since we override 'SelectSymbols' Enumerable.Empty()) { _symbols = new HashSet(); } /// /// Returns the symbols defined by the user for this universe /// /// The current utc time /// The symbols to remain in the universe /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { return _symbols; } /// /// Event invocator for the event /// /// The notify collection changed event arguments protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Remove) { var removedSymbol = (Symbol)e.OldItems[0]; _symbols.Remove(removedSymbol); // the option has been removed! This can happen when the user manually removed the option contract we remove the underlying // but only if there isn't any other option selected using the same underlying! if (removedSymbol.SecurityType.IsOption() && !_symbols.Any(symbol => symbol.SecurityType.IsOption() && symbol.Underlying == removedSymbol.Underlying)) { Remove(removedSymbol.Underlying); } } else if (e.Action == NotifyCollectionChangedAction.Add) { // QCAlgorithm.AddOptionContract will add both underlying and option contract _symbols.Add((Symbol)e.NewItems[0]); } base.OnCollectionChanged(e); } /// /// Creates a user defined universe symbol /// /// The market /// The underlying option security type /// A symbol for user defined universe of the specified security type and market public static Symbol CreateSymbol(string market, SecurityType securityType) { var ticker = $"qc-universe-optioncontract-{securityType.SecurityTypeToLower()}-{market.ToLowerInvariant()}"; var underlying = Symbol.Create(ticker, securityType, market); var sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying.ID, market, 0, 0, 0); return new Symbol(sid, ticker); } /// /// Make sure the configuration of the universe is what we want /// private static SubscriptionDataConfig AdjustUniverseConfiguration(SubscriptionDataConfig input) { return new SubscriptionDataConfig(input, fillForward: false); } } }