Lean  $LEAN_TAG$
ContractSecurityFilterUniverse.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15 */
16 
17 using System;
18 using System.Linq;
19 using Python.Runtime;
20 using System.Collections;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Base class for contract symbols filtering universes.
27  /// Used by OptionFilterUniverse and FutureFilterUniverse
28  /// </summary>
29  public abstract class ContractSecurityFilterUniverse<T, TData> : IDerivativeSecurityFilterUniverse<TData>
30  where T: ContractSecurityFilterUniverse<T, TData>
31  // TODO: The universe data type abstraction could end up being IBaseData once Futures and FOPs universe are file-based like
32  // equity and index options.
33  where TData: ISymbol
34  {
35  private bool _alreadyAppliedTypeFilters;
36 
37  private IEnumerable<TData> _data;
38 
39  /// <summary>
40  /// Defines listed contract types with Flags attribute
41  /// </summary>
42  [Flags]
43  protected enum ContractExpirationType : int
44  {
45  /// <summary>
46  /// Standard contracts
47  /// </summary>
48  Standard = 1,
49 
50  /// <summary>
51  /// Non standard weekly contracts
52  /// </summary>
53  Weekly = 2
54  }
55 
56  /// <summary>
57  /// Expiration Types allowed through the filter
58  /// Standards only by default
59  /// </summary>
60  protected ContractExpirationType Type { get; set; } = ContractExpirationType.Standard;
61 
62  /// <summary>
63  /// The local exchange current time
64  /// </summary>
65  public DateTime LocalTime { get; private set; }
66 
67  /// <summary>
68  /// All data in this filter
69  /// Marked internal for use by extensions
70  /// </summary>
71  /// <remarks>
72  /// Setting it will also set AllSymbols
73  /// </remarks>
74  internal IEnumerable<TData> Data
75  {
76  get
77  {
78  return _data;
79  }
80  set
81  {
82  _data = value;
83  }
84  }
85 
86  /// <summary>
87  /// All Symbols in this filter
88  /// Marked internal for use by extensions
89  /// </summary>
90  /// <remarks>
91  /// Setting it will remove any data that doesn't have a symbol in AllSymbols
92  /// </remarks>
93  internal IEnumerable<Symbol> AllSymbols
94  {
95  get
96  {
97  return _data.Select(GetSymbol);
98  }
99  set
100  {
101  // We create a "fake" data instance for each symbol that is not in the data,
102  // so we are polite to the user and keep backwards compatibility
103  _data = value.Select(symbol => _data.FirstOrDefault(x => GetSymbol(x) == symbol) ?? CreateDataInstance(symbol)).ToList();
104  }
105  }
106 
107  /// <summary>
108  /// Constructs ContractSecurityFilterUniverse
109  /// </summary>
111  {
112  }
113 
114  /// <summary>
115  /// Constructs ContractSecurityFilterUniverse
116  /// </summary>
117  protected ContractSecurityFilterUniverse(IEnumerable<TData> allData, DateTime localTime)
118  {
119  Data = allData;
120  LocalTime = localTime;
121  Type = ContractExpirationType.Standard;
122  }
123 
124  /// <summary>
125  /// Function to determine if the given symbol is a standard contract
126  /// </summary>
127  /// <returns>True if standard type</returns>
128  protected abstract bool IsStandard(Symbol symbol);
129 
130  /// <summary>
131  /// Gets the symbol from the data
132  /// </summary>
133  /// <returns>The symbol that represents the datum</returns>
134  /// TODO: This method should be removed once we have a file-based universe for futures and FOPs
135  /// and the universe data type is commonly abstracted to something like IBaseData which has a Symbol property.
136  protected abstract Symbol GetSymbol(TData data);
137 
138  /// <summary>
139  /// Creates a new instance of the data type for the given symbol
140  /// </summary>
141  /// <returns>A data instance for the given symbol</returns>
142  protected abstract TData CreateDataInstance(Symbol symbol);
143 
144  /// <summary>
145  /// Returns universe, filtered by contract type
146  /// </summary>
147  /// <returns>Universe with filter applied</returns>
148  internal T ApplyTypesFilter()
149  {
150  if (_alreadyAppliedTypeFilters)
151  {
152  return (T) this;
153  }
154 
155  // memoization map for ApplyTypesFilter()
156  var memoizedMap = new Dictionary<DateTime, bool>();
157 
158  Func<TData, bool> memoizedIsStandardType = data =>
159  {
160  var dt = data.ID.Date;
161 
162  bool result;
163  if (memoizedMap.TryGetValue(dt, out result))
164  return result;
165  var res = IsStandard(GetSymbol(data));
166  memoizedMap[dt] = res;
167 
168  return res;
169  };
170 
171  Data = Data.Where(x =>
172  {
173  switch (Type)
174  {
175  case ContractExpirationType.Weekly:
176  return !memoizedIsStandardType(x);
177  case ContractExpirationType.Standard:
178  return memoizedIsStandardType(x);
179  case ContractExpirationType.Standard | ContractExpirationType.Weekly:
180  return true;
181  default:
182  return false;
183  }
184  }).ToList();
185 
186  _alreadyAppliedTypeFilters = true;
187  return (T) this;
188  }
189 
190  /// <summary>
191  /// Refreshes this filter universe
192  /// </summary>
193  /// <param name="allData">All data for contracts in the Universe</param>
194  /// <param name="localTime">The local exchange current time</param>
195  public virtual void Refresh(IEnumerable<TData> allData, DateTime localTime)
196  {
197  Data = allData;
198  LocalTime = localTime;
199  Type = ContractExpirationType.Standard;
200  _alreadyAppliedTypeFilters = false;
201  }
202 
203  /// <summary>
204  /// Sets universe of standard contracts (if any) as selection
205  /// Contracts by default are standards; only needed to switch back if changed
206  /// </summary>
207  /// <returns>Universe with filter applied</returns>
208  public T StandardsOnly()
209  {
210  if (_alreadyAppliedTypeFilters)
211  {
212  throw new InvalidOperationException("Type filters have already been applied, " +
213  "please call StandardsOnly() before applying other filters such as FrontMonth() or BackMonths()");
214  }
215 
216  Type = ContractExpirationType.Standard;
217  return (T)this;
218  }
219 
220  /// <summary>
221  /// Includes universe of non-standard weeklys contracts (if any) into selection
222  /// </summary>
223  /// <returns>Universe with filter applied</returns>
224  public T IncludeWeeklys()
225  {
226  if (_alreadyAppliedTypeFilters)
227  {
228  throw new InvalidOperationException("Type filters have already been applied, " +
229  "please call IncludeWeeklys() before applying other filters such as FrontMonth() or BackMonths()");
230  }
231 
232  Type |= ContractExpirationType.Weekly;
233  return (T)this;
234  }
235 
236  /// <summary>
237  /// Sets universe of weeklys contracts (if any) as selection
238  /// </summary>
239  /// <returns>Universe with filter applied</returns>
240  public T WeeklysOnly()
241  {
242  Type = ContractExpirationType.Weekly;
243  return (T)this;
244  }
245 
246  /// <summary>
247  /// Returns front month contract
248  /// </summary>
249  /// <returns>Universe with filter applied</returns>
250  public virtual T FrontMonth()
251  {
252  ApplyTypesFilter();
253  var ordered = Data.OrderBy(x => x.ID.Date).ToList();
254  if (ordered.Count == 0) return (T) this;
255  var frontMonth = ordered.TakeWhile(x => ordered[0].ID.Date == x.ID.Date);
256 
257  Data = frontMonth.ToList();
258  return (T) this;
259  }
260 
261  /// <summary>
262  /// Returns a list of back month contracts
263  /// </summary>
264  /// <returns>Universe with filter applied</returns>
265  public virtual T BackMonths()
266  {
267  ApplyTypesFilter();
268  var ordered = Data.OrderBy(x => x.ID.Date).ToList();
269  if (ordered.Count == 0) return (T) this;
270  var backMonths = ordered.SkipWhile(x => ordered[0].ID.Date == x.ID.Date);
271 
272  Data = backMonths.ToList();
273  return (T) this;
274  }
275 
276  /// <summary>
277  /// Returns first of back month contracts
278  /// </summary>
279  /// <returns>Universe with filter applied</returns>
280  public T BackMonth()
281  {
282  return BackMonths().FrontMonth();
283  }
284 
285  /// <summary>
286  /// Adjust the reference date used for expiration filtering. By default it just returns the same date.
287  /// </summary>
288  /// <param name="referenceDate">The reference date to be adjusted</param>
289  /// <returns>The adjusted date</returns>
290  protected virtual DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
291  {
292  return referenceDate;
293  }
294 
295  /// <summary>
296  /// Applies filter selecting options contracts based on a range of expiration dates relative to the current day
297  /// </summary>
298  /// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
299  /// would exclude contracts expiring in less than 10 days</param>
300  /// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
301  /// would exclude contracts expiring in more than 10 days</param>
302  /// <returns>Universe with filter applied</returns>
303  public virtual T Expiration(TimeSpan minExpiry, TimeSpan maxExpiry)
304  {
305  if (LocalTime == default)
306  {
307  return (T)this;
308  }
309 
310  if (maxExpiry > Time.MaxTimeSpan) maxExpiry = Time.MaxTimeSpan;
311 
312  var referenceDate = AdjustExpirationReferenceDate(LocalTime.Date);
313 
314  var minExpiryToDate = referenceDate + minExpiry;
315  var maxExpiryToDate = referenceDate + maxExpiry;
316 
317  Data = Data
318  .Where(symbol => symbol.ID.Date.Date >= minExpiryToDate && symbol.ID.Date.Date <= maxExpiryToDate)
319  .ToList();
320 
321  return (T)this;
322  }
323 
324  /// <summary>
325  /// Applies filter selecting contracts based on a range of expiration dates relative to the current day
326  /// </summary>
327  /// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
328  /// would exclude contracts expiring in less than 10 days</param>
329  /// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
330  /// would exclude contracts expiring in more than 10 days</param>
331  /// <returns>Universe with filter applied</returns>
332  public T Expiration(int minExpiryDays, int maxExpiryDays)
333  {
334  return Expiration(TimeSpan.FromDays(minExpiryDays), TimeSpan.FromDays(maxExpiryDays));
335  }
336 
337  /// <summary>
338  /// Explicitly sets the selected contract symbols for this universe.
339  /// This overrides and and all other methods of selecting symbols assuming it is called last.
340  /// </summary>
341  /// <param name="contracts">The option contract symbol objects to select</param>
342  /// <returns>Universe with filter applied</returns>
343  public T Contracts(PyObject contracts)
344  {
345  // Let's first check if the object is a selector:
346  if (contracts.TryConvertToDelegate(out Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector))
347  {
348  return Contracts(contractSelector);
349  }
350 
351  // Else, it should be a list of symbols:
352  return Contracts(contracts.ConvertToSymbolEnumerable());
353  }
354 
355  /// <summary>
356  /// Explicitly sets the selected contract symbols for this universe.
357  /// This overrides and and all other methods of selecting symbols assuming it is called last.
358  /// </summary>
359  /// <param name="contracts">The option contract symbol objects to select</param>
360  /// <returns>Universe with filter applied</returns>
361  public T Contracts(IEnumerable<Symbol> contracts)
362  {
363  AllSymbols = contracts.ToList();
364  return (T) this;
365  }
366 
367  /// <summary>
368  /// Explicitly sets the selected contract symbols for this universe.
369  /// This overrides and and all other methods of selecting symbols assuming it is called last.
370  /// </summary>
371  /// <param name="contracts">The option contract symbol objects to select</param>
372  /// <returns>Universe with filter applied</returns>
373  public T Contracts(IEnumerable<TData> contracts)
374  {
375  Data = contracts.ToList();
376  return (T)this;
377  }
378 
379  /// <summary>
380  /// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector'
381  /// function will be the already filtered list if any other filters have already been applied.
382  /// </summary>
383  /// <param name="contractSelector">The option contract symbol objects to select</param>
384  /// <returns>Universe with filter applied</returns>
385  public T Contracts(Func<IEnumerable<TData>, IEnumerable<Symbol>> contractSelector)
386  {
387  // force materialization using ToList
388  AllSymbols = contractSelector(Data).ToList();
389  return (T) this;
390  }
391 
392  /// <summary>
393  /// Sets a function used to filter the set of available contract filters. The input to the 'contractSelector'
394  /// function will be the already filtered list if any other filters have already been applied.
395  /// </summary>
396  /// <param name="contractSelector">The option contract symbol objects to select</param>
397  /// <returns>Universe with filter applied</returns>
398  public T Contracts(Func<IEnumerable<TData>, IEnumerable<TData>> contractSelector)
399  {
400  // force materialization using ToList
401  Data = contractSelector(Data).ToList();
402  return (T)this;
403  }
404 
405  /// <summary>
406  /// Instructs the engine to only filter contracts on the first time step of each market day.
407  /// </summary>
408  /// <returns>Universe with filter applied</returns>
409  /// <remarks>Deprecated since filters are always non-dynamic now</remarks>
410  [Obsolete("Deprecated as of 2023-12-13. Filters are always non-dynamic as of now, which means they will only bee applied daily.")]
412  {
413  return (T) this;
414  }
415 
416  /// <summary>
417  /// IEnumerable interface method implementation
418  /// </summary>
419  /// <returns>IEnumerator of Symbols in Universe</returns>
420  public IEnumerator<TData> GetEnumerator()
421  {
422  return Data.GetEnumerator();
423  }
424 
425  /// <summary>
426  /// IEnumerable interface method implementation
427  /// </summary>
428  IEnumerator IEnumerable.GetEnumerator()
429  {
430  return Data.GetEnumerator();
431  }
432  }
433 }