Files
quantconnect--lean/Common/Securities/ContractSecurityFilterUniverse.cs
2026-07-13 13:02:50 +08:00

436 lines
16 KiB
C#

/*
* 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
{
/// <summary>
/// Base class for contract symbols filtering universes.
/// Used by OptionFilterUniverse and FutureFilterUniverse
/// </summary>
public abstract class ContractSecurityFilterUniverse<T, TData> : IDerivativeSecurityFilterUniverse<TData>
where T : ContractSecurityFilterUniverse<T, TData>
where TData : IChainUniverseData
{
private bool _alreadyAppliedTypeFilters;
private IReadOnlyList<TData> _data;
/// <summary>
/// Defines listed contract types with Flags attribute
/// </summary>
[Flags]
protected enum ContractExpirationType : int
{
/// <summary>
/// Standard contracts
/// </summary>
Standard = 1,
/// <summary>
/// Non standard weekly contracts
/// </summary>
Weekly = 2
}
/// <summary>
/// The default expiration type filter value
/// </summary>
protected static readonly ContractExpirationType DefaultExpirationType = ContractExpirationType.Standard | ContractExpirationType.Weekly;
/// <summary>
/// Expiration Types allowed through the filter
/// Standards only by default
/// </summary>
protected ContractExpirationType Type { get; set; } = DefaultExpirationType;
/// <summary>
/// The local exchange current time
/// </summary>
public DateTime LocalTime { get; private set; }
/// <summary>
/// The number of contracts in the universe
/// </summary>
public int Count => _data.Count;
/// <summary>
/// All data in this filter
/// Marked internal for use by extensions
/// </summary>
/// <remarks>
/// Setting it will also set AllSymbols
/// </remarks>
internal IReadOnlyList<TData> Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
/// <summary>
/// All Symbols in this filter
/// Marked internal for use by extensions
/// </summary>
/// <remarks>
/// Setting it will remove any data that doesn't have a symbol in AllSymbols
/// </remarks>
internal IEnumerable<Symbol> 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();
}
}
/// <summary>
/// Constructs ContractSecurityFilterUniverse
/// </summary>
protected ContractSecurityFilterUniverse()
{
Type = DefaultExpirationType;
}
/// <summary>
/// Constructs ContractSecurityFilterUniverse
/// </summary>
protected ContractSecurityFilterUniverse(IReadOnlyList<TData> allData, DateTime localTime)
{
Data = allData;
LocalTime = localTime;
Type = DefaultExpirationType;
}
/// <summary>
/// Function to determine if the given symbol is a standard contract
/// </summary>
/// <returns>True if standard type</returns>
protected abstract bool IsStandard(Symbol symbol);
/// <summary>
/// Creates a new instance of the data type for the given symbol
/// </summary>
/// <returns>A data instance for the given symbol</returns>
protected abstract TData CreateDataInstance(Symbol symbol);
/// <summary>
/// Returns universe, filtered by contract type
/// </summary>
/// <returns>Universe with filter applied</returns>
internal T ApplyTypesFilter()
{
if (_alreadyAppliedTypeFilters)
{
return (T)this;
}
// memoization map for ApplyTypesFilter()
var memoizedMap = new Dictionary<DateTime, bool>();
Func<TData, bool> 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;
}
/// <summary>
/// Refreshes this filter universe
/// </summary>
/// <param name="allData">All data for contracts in the Universe</param>
/// <param name="localTime">The local exchange current time</param>
public virtual void Refresh(IReadOnlyList<TData> allData, DateTime localTime)
{
Data = allData;
LocalTime = localTime;
Type = DefaultExpirationType;
_alreadyAppliedTypeFilters = false;
}
/// <summary>
/// Sets universe of standard contracts (if any) as selection
/// Contracts by default are standards; only needed to switch back if changed
/// </summary>
/// <returns>Universe with filter applied</returns>
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;
}
/// <summary>
/// Includes universe of non-standard weeklys contracts (if any) into selection
/// </summary>
/// <returns>Universe with filter applied</returns>
[Obsolete("IncludeWeeklys is obsolete because weekly contracts are now included by default.")]
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;
}
/// <summary>
/// Sets universe of weeklys contracts (if any) as selection
/// </summary>
/// <returns>Universe with filter applied</returns>
public T WeeklysOnly()
{
Type = ContractExpirationType.Weekly;
return (T)this;
}
/// <summary>
/// Returns front month contract
/// </summary>
/// <returns>Universe with filter applied</returns>
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;
}
/// <summary>
/// Returns a list of back month contracts
/// </summary>
/// <returns>Universe with filter applied</returns>
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;
}
/// <summary>
/// Returns first of back month contracts
/// </summary>
/// <returns>Universe with filter applied</returns>
public T BackMonth()
{
return BackMonths().FrontMonth();
}
/// <summary>
/// Adjust the reference date used for expiration filtering. By default it just returns the same date.
/// </summary>
/// <param name="referenceDate">The reference date to be adjusted</param>
/// <returns>The adjusted date</returns>
protected virtual DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
{
return referenceDate;
}
/// <summary>
/// Applies filter selecting options contracts based on a range of expiration dates relative to the current day
/// </summary>
/// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
/// would exclude contracts expiring in more than 10 days</param>
/// <returns>Universe with filter applied</returns>
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;
}
/// <summary>
/// Applies filter selecting contracts based on a range of expiration dates relative to the current day
/// </summary>
/// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in less than 10 days</param>
/// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
/// would exclude contracts expiring in more than 10 days</param>
/// <returns>Universe with filter applied</returns>
public T Expiration(int minExpiryDays, int maxExpiryDays)
{
return Expiration(TimeSpan.FromDays(minExpiryDays), TimeSpan.FromDays(maxExpiryDays));
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(PyObject contracts)
{
// Let's first check if the object is a selector:
if (contracts.TrySafeAs(out Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector))
{
return Contracts(contractSelector);
}
// Else, it should be a list of symbols:
return Contracts(contracts.ConvertToSymbolEnumerable());
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(IEnumerable<Symbol> contracts)
{
AllSymbols = contracts.ToList();
return (T)this;
}
/// <summary>
/// Explicitly sets the selected contract symbols for this universe.
/// This overrides and and all other methods of selecting symbols assuming it is called last.
/// </summary>
/// <param name="contracts">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(IEnumerable<TData> contracts)
{
Data = contracts.ToList();
return (T)this;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="contractSelector">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector)
{
// force materialization using ToList
AllSymbols = contractSelector(Data).ToList();
return (T)this;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="contractSelector">The option contract symbol objects to select</param>
/// <returns>Universe with filter applied</returns>
public T Contracts(Func<IEnumerable<TData>, IEnumerable<TData>> contractSelector)
{
// force materialization using ToList
Data = contractSelector(Data).ToList();
return (T)this;
}
/// <summary>
/// Instructs the engine to only filter contracts on the first time step of each market day.
/// </summary>
/// <returns>Universe with filter applied</returns>
/// <remarks>Deprecated since filters are always non-dynamic now</remarks>
[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;
}
/// <summary>
/// IEnumerable interface method implementation
/// </summary>
/// <returns>IEnumerator of Symbols in Universe</returns>
public IEnumerator<TData> GetEnumerator()
{
return Data.GetEnumerator();
}
/// <summary>
/// IEnumerable interface method implementation
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return Data.GetEnumerator();
}
}
}