/* * 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 Python.Runtime; using System.Collections; using System.Collections.Generic; namespace QuantConnect.Securities { /// /// Base class for contract symbols filtering universes. /// Used by OptionFilterUniverse and FutureFilterUniverse /// public abstract class ContractSecurityFilterUniverse : IDerivativeSecurityFilterUniverse where T: ContractSecurityFilterUniverse where TData: IChainUniverseData { private bool _alreadyAppliedTypeFilters; private IEnumerable _data; /// /// Defines listed contract types with Flags attribute /// [Flags] protected enum ContractExpirationType : int { /// /// Standard contracts /// Standard = 1, /// /// Non standard weekly contracts /// Weekly = 2 } /// /// Expiration Types allowed through the filter /// Standards only by default /// protected ContractExpirationType Type { get; set; } = ContractExpirationType.Standard; /// /// The local exchange current time /// public DateTime LocalTime { get; private set; } /// /// All data in this filter /// Marked internal for use by extensions /// /// /// Setting it will also set AllSymbols /// internal IEnumerable Data { get { return _data; } set { _data = value; } } /// /// All Symbols in this filter /// Marked internal for use by extensions /// /// /// Setting it will remove any data that doesn't have a symbol in AllSymbols /// internal IEnumerable AllSymbols { get { return _data.Select(x => x.Symbol); } set { // We create a "fake" data instance for each symbol that is not in the data, // so we are polite to the user and keep backwards compatibility _data = value.Select(symbol => _data.FirstOrDefault(x => x.Symbol == symbol) ?? CreateDataInstance(symbol)).ToList(); } } /// /// Constructs ContractSecurityFilterUniverse /// protected ContractSecurityFilterUniverse() { } /// /// Constructs ContractSecurityFilterUniverse /// protected ContractSecurityFilterUniverse(IEnumerable allData, DateTime localTime) { Data = allData; LocalTime = localTime; Type = ContractExpirationType.Standard; } /// /// Function to determine if the given symbol is a standard contract /// /// True if standard type protected abstract bool IsStandard(Symbol symbol); /// /// Creates a new instance of the data type for the given symbol /// /// A data instance for the given symbol protected abstract TData CreateDataInstance(Symbol symbol); /// /// Returns universe, filtered by contract type /// /// Universe with filter applied internal T ApplyTypesFilter() { if (_alreadyAppliedTypeFilters) { return (T) this; } // memoization map for ApplyTypesFilter() var memoizedMap = new Dictionary(); Func memoizedIsStandardType = data => { var dt = data.ID.Date; bool result; if (memoizedMap.TryGetValue(dt, out result)) return result; var res = IsStandard(data.Symbol); memoizedMap[dt] = res; return res; }; Data = Data.Where(x => { switch (Type) { case ContractExpirationType.Weekly: return !memoizedIsStandardType(x); case ContractExpirationType.Standard: return memoizedIsStandardType(x); case ContractExpirationType.Standard | ContractExpirationType.Weekly: return true; default: return false; } }).ToList(); _alreadyAppliedTypeFilters = true; return (T) this; } /// /// Refreshes this filter universe /// /// All data for contracts in the Universe /// The local exchange current time public virtual void Refresh(IEnumerable allData, DateTime localTime) { Data = allData; LocalTime = localTime; Type = ContractExpirationType.Standard; _alreadyAppliedTypeFilters = false; } /// /// Sets universe of standard contracts (if any) as selection /// Contracts by default are standards; only needed to switch back if changed /// /// Universe with filter applied public T StandardsOnly() { if (_alreadyAppliedTypeFilters) { throw new InvalidOperationException("Type filters have already been applied, " + "please call StandardsOnly() before applying other filters such as FrontMonth() or BackMonths()"); } Type = ContractExpirationType.Standard; return (T)this; } /// /// Includes universe of non-standard weeklys contracts (if any) into selection /// /// Universe with filter applied public T IncludeWeeklys() { if (_alreadyAppliedTypeFilters) { throw new InvalidOperationException("Type filters have already been applied, " + "please call IncludeWeeklys() before applying other filters such as FrontMonth() or BackMonths()"); } Type |= ContractExpirationType.Weekly; return (T)this; } /// /// Sets universe of weeklys contracts (if any) as selection /// /// Universe with filter applied public T WeeklysOnly() { Type = ContractExpirationType.Weekly; return (T)this; } /// /// Returns front month contract /// /// Universe with filter applied public virtual T FrontMonth() { ApplyTypesFilter(); var ordered = Data.OrderBy(x => x.ID.Date).ToList(); if (ordered.Count == 0) return (T) this; var frontMonth = ordered.TakeWhile(x => ordered[0].ID.Date == x.ID.Date); Data = frontMonth.ToList(); return (T) this; } /// /// Returns a list of back month contracts /// /// Universe with filter applied public virtual T BackMonths() { ApplyTypesFilter(); var ordered = Data.OrderBy(x => x.ID.Date).ToList(); if (ordered.Count == 0) return (T) this; var backMonths = ordered.SkipWhile(x => ordered[0].ID.Date == x.ID.Date); Data = backMonths.ToList(); return (T) this; } /// /// Returns first of back month contracts /// /// Universe with filter applied public T BackMonth() { return BackMonths().FrontMonth(); } /// /// Adjust the reference date used for expiration filtering. By default it just returns the same date. /// /// The reference date to be adjusted /// The adjusted date protected virtual DateTime AdjustExpirationReferenceDate(DateTime referenceDate) { return referenceDate; } /// /// Applies filter selecting options contracts based on a range of expiration dates relative to the current day /// /// The minimum time until expiry to include, for example, TimeSpan.FromDays(10) /// would exclude contracts expiring in less than 10 days /// The maximum time until expiry to include, for example, TimeSpan.FromDays(10) /// would exclude contracts expiring in more than 10 days /// Universe with filter applied public virtual T Expiration(TimeSpan minExpiry, TimeSpan maxExpiry) { if (LocalTime == default) { return (T)this; } if (maxExpiry > Time.MaxTimeSpan) maxExpiry = Time.MaxTimeSpan; var referenceDate = AdjustExpirationReferenceDate(LocalTime.Date); var minExpiryToDate = referenceDate + minExpiry; var maxExpiryToDate = referenceDate + maxExpiry; Data = Data .Where(symbol => symbol.ID.Date.Date >= minExpiryToDate && symbol.ID.Date.Date <= maxExpiryToDate) .ToList(); return (T)this; } /// /// Applies filter selecting contracts based on a range of expiration dates relative to the current day /// /// The minimum time, expressed in days, until expiry to include, for example, 10 /// would exclude contracts expiring in less than 10 days /// The maximum time, expressed in days, until expiry to include, for example, 10 /// would exclude contracts expiring in more than 10 days /// Universe with filter applied public T Expiration(int minExpiryDays, int maxExpiryDays) { return Expiration(TimeSpan.FromDays(minExpiryDays), TimeSpan.FromDays(maxExpiryDays)); } /// /// Explicitly sets the selected contract symbols for this universe. /// This overrides and and all other methods of selecting symbols assuming it is called last. /// /// The option contract symbol objects to select /// Universe with filter applied public T Contracts(PyObject contracts) { // Let's first check if the object is a selector: if (contracts.TryConvertToDelegate(out Func, IEnumerable> contractSelector)) { return Contracts(contractSelector); } // Else, it should be a list of symbols: return Contracts(contracts.ConvertToSymbolEnumerable()); } /// /// Explicitly sets the selected contract symbols for this universe. /// This overrides and and all other methods of selecting symbols assuming it is called last. /// /// The option contract symbol objects to select /// Universe with filter applied public T Contracts(IEnumerable contracts) { AllSymbols = contracts.ToList(); return (T) this; } /// /// Explicitly sets the selected contract symbols for this universe. /// This overrides and and all other methods of selecting symbols assuming it is called last. /// /// The option contract symbol objects to select /// Universe with filter applied public T Contracts(IEnumerable contracts) { Data = contracts.ToList(); return (T)this; } /// /// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector' /// function will be the already filtered list if any other filters have already been applied. /// /// The option contract symbol objects to select /// Universe with filter applied public T Contracts(Func, IEnumerable> contractSelector) { // force materialization using ToList AllSymbols = contractSelector(Data).ToList(); return (T) this; } /// /// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector' /// function will be the already filtered list if any other filters have already been applied. /// /// The option contract symbol objects to select /// Universe with filter applied public T Contracts(Func, IEnumerable> contractSelector) { // force materialization using ToList Data = contractSelector(Data).ToList(); return (T)this; } /// /// Instructs the engine to only filter contracts on the first time step of each market day. /// /// Universe with filter applied /// Deprecated since filters are always non-dynamic now [Obsolete("Deprecated as of 2023-12-13. Filters are always non-dynamic as of now, which means they will only bee applied daily.")] public T OnlyApplyFilterAtMarketOpen() { return (T) this; } /// /// IEnumerable interface method implementation /// /// IEnumerator of Symbols in Universe public IEnumerator GetEnumerator() { return Data.GetEnumerator(); } /// /// IEnumerable interface method implementation /// IEnumerator IEnumerable.GetEnumerator() { return Data.GetEnumerator(); } } }