/*
* 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();
}
}
}