/* * 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.Util; using QuantConnect.Interfaces; using QuantConnect.Securities; using System.Collections.Generic; using QuantConnect.Data.Auxiliary; namespace QuantConnect.Data.UniverseSelection { /// /// Continuous contract universe selection that based on the requested mapping mode will select each symbol /// public class ContinuousContractUniverse : Universe, ITimeTriggeredUniverse { private readonly IMapFileProvider _mapFileProvider; private readonly SubscriptionDataConfig _config; private readonly Security _security; private readonly bool _liveMode; private Symbol _currentSymbol; private string _mappedSymbol; /// /// True if this universe filter can run async in the data stack /// TODO: see IContinuousSecurity.Mapped /// public override bool Asynchronous => false; /// /// Creates a new instance /// public ContinuousContractUniverse(Security security, UniverseSettings universeSettings, bool liveMode, SubscriptionDataConfig universeConfig) : base(universeConfig) { _security = security; _liveMode = liveMode; UniverseSettings = universeSettings; _mapFileProvider = Composer.Instance.GetPart(); _config = new SubscriptionDataConfig(Configuration, dataMappingMode: UniverseSettings.DataMappingMode, symbol: _security.Symbol.Canonical); } /// /// Performs universe selection based on the symbol mapping /// /// The current utc time /// Empty data /// The symbols to use public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { yield return _security.Symbol.Canonical; var mapFile = _mapFileProvider.ResolveMapFile(_config); var mappedSymbol = mapFile.GetMappedSymbol(utcTime.ConvertFromUtc(_security.Exchange.TimeZone), dataMappingMode: _config.DataMappingMode); if (!string.IsNullOrEmpty(mappedSymbol) && mappedSymbol != _mappedSymbol) { if (_currentSymbol != null) { // let's emit the old and new for the mapping date yield return _currentSymbol; } _mappedSymbol = mappedSymbol; _currentSymbol = _security.Symbol.Canonical .UpdateMappedSymbol(mappedSymbol, Configuration.ContractDepthOffset) .Underlying; } if (_currentSymbol != null) { // TODO: this won't work with async universe selection ((IContinuousSecurity)_security).Mapped = _currentSymbol; yield return _currentSymbol; } } /// /// Gets the subscription requests to be added for the specified security /// /// The security to get subscriptions for /// The current time in utc. This is the frontier time of the algorithm /// The max end time /// Instance which implements interface /// All subscriptions required by this security public override IEnumerable GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc, ISubscriptionDataConfigService subscriptionService) { var configs = AddConfigurations(subscriptionService, UniverseSettings, security.Symbol); return configs.Select(config => new SubscriptionRequest(isUniverseSubscription: false, universe: this, security: security, configuration: new SubscriptionDataConfig(config, isInternalFeed: config.IsInternalFeed || config.TickType == TickType.OpenInterest), startTimeUtc: currentTimeUtc, endTimeUtc: maximumEndTimeUtc)); } /// /// Each tradeable day of the future we trigger a new selection. /// Allows use to select the current contract /// public IEnumerable GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase) { var startTimeLocal = startTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone); var endTimeLocal = endTimeUtc.ConvertFromUtc(_security.Exchange.TimeZone); return Time.EachTradeableDay(_security, startTimeLocal, endTimeLocal, Configuration.ExtendedMarketHours) // in live trading selection happens on start see 'DataQueueFuturesChainUniverseDataCollectionEnumerator' .Where(tradeableDay => _liveMode || tradeableDay >= startTimeLocal) // in live trading we delay selection so that we make sure auxiliary data is ready .Select(time => _liveMode ? time.Add(Time.LiveAuxiliaryDataOffset) : time); } /// /// Helper method to add and get the required configurations associated with a continuous universe /// public static List AddConfigurations(ISubscriptionDataConfigService subscriptionService, UniverseSettings universeSettings, Symbol symbol) { List configs = new(universeSettings.SubscriptionDataTypes.Count); foreach (var pair in universeSettings.SubscriptionDataTypes) { configs.AddRange(subscriptionService.Add(symbol, universeSettings.Resolution, universeSettings.FillForward, universeSettings.ExtendedMarketHours, dataNormalizationMode: universeSettings.DataNormalizationMode, // we need to provider the data types we want, else since it's canonical it would assume the default ZipEntry type used in universe chain subscriptionDataTypes: new List> { pair }, dataMappingMode: universeSettings.DataMappingMode, contractDepthOffset: (uint)Math.Abs(universeSettings.ContractDepthOffset), // open interest is internal and the underlying mapped contracts of the continuous canonical isInternalFeed: !symbol.IsCanonical() || pair.Item2 == TickType.OpenInterest)); } return configs; } /// /// Creates a continuous universe symbol /// /// The associated symbol /// A symbol for a continuous universe of the specified symbol public static Symbol CreateSymbol(Symbol symbol) { var ticker = $"qc-universe-continuous-{symbol.ID.Market.ToLowerInvariant()}-{symbol.SecurityType}-{symbol.ID.Symbol}"; return UniverseExtensions.CreateSymbol(symbol.SecurityType, symbol.ID.Market, ticker); } } }