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