Lean  $LEAN_TAG$
UniverseExtensions.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 using System;
17 using System.IO;
18 using System.Linq;
19 using QuantConnect.Logging;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Provides extension methods for the <see cref="Universe"/> class
27  /// </summary>
28  public static class UniverseExtensions
29  {
30  /// <summary>
31  /// Creates a new universe that logically is the result of wiring the two universes together such that
32  /// the first will produce subscriptions for the second and the second will only select on data that has
33  /// passed the first.
34  ///
35  /// NOTE: The <paramref name="first"/> and <paramref name="second"/> universe instances provided
36  /// to this method should not be manually added to the algorithm.
37  /// </summary>
38  /// <param name="first">The first universe in this 'chain'</param>
39  /// <param name="second">The second universe in this 'chain'</param>
40  /// <param name="configurationPerSymbol">True if each symbol as its own configuration, false otherwise</param>
41  /// <returns>A new universe that can be added to the algorithm that represents invoking the first universe
42  /// and then the second universe using the outputs of the first. </returns>
43  public static Universe ChainedTo(this Universe first, Universe second, bool configurationPerSymbol)
44  {
45  var prefilteredSecond = second.PrefilterUsing(first);
46  return new GetSubscriptionRequestsUniverseDecorator(first, (security, currentTimeUtc, maximumEndTimeUtc) =>
47  {
48  return first.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc).Select(request => new SubscriptionRequest(
49  template: request,
50  isUniverseSubscription: true,
51  universe: prefilteredSecond,
52  security: security,
53  configuration: configurationPerSymbol ? new SubscriptionDataConfig(prefilteredSecond.Configuration, symbol: security.Symbol) : prefilteredSecond.Configuration,
54  startTimeUtc: currentTimeUtc - prefilteredSecond.Configuration.Resolution.ToTimeSpan(),
55  endTimeUtc: currentTimeUtc.AddSeconds(-1)
56  ));
57  });
58  }
59 
60  /// <summary>
61  /// Creates a new universe that restricts the universe selection data to symbols that passed the
62  /// first universe's selection critera
63  ///
64  /// NOTE: The <paramref name="second"/> universe instance provided to this method should not be manually
65  /// added to the algorithm. The <paramref name="first"/> should still be manually (assuming no other changes).
66  /// </summary>
67  /// <param name="second">The universe to be filtere</param>
68  /// <param name="first">The universe providing the set of symbols used for filtered</param>
69  /// <returns>A new universe that can be added to the algorithm that represents invoking the second
70  /// using the selections from the first as a filter.</returns>
71  public static Universe PrefilterUsing(this Universe second, Universe first)
72  {
73  return new SelectSymbolsUniverseDecorator(second, (utcTime, data) =>
74  {
75  var clone = (BaseDataCollection)data.Clone();
76  clone.Data = clone.Data.Where(d => first.ContainsMember(d.Symbol)).ToList();
77  return second.SelectSymbols(utcTime, clone);
78  });
79  }
80 
81  /// <summary>
82  /// Creates a universe symbol
83  /// </summary>
84  /// <param name="securityType">The security</param>
85  /// <param name="market">The market</param>
86  /// <param name="ticker">The Universe ticker</param>
87  /// <returns>A symbol for user defined universe of the specified security type and market</returns>
88  public static Symbol CreateSymbol(SecurityType securityType, string market, string ticker)
89  {
90  // TODO looks like we can just replace this for Symbol.Create?
91 
93  switch (securityType)
94  {
95  case SecurityType.Base:
96  sid = SecurityIdentifier.GenerateBase(null, ticker, market);
97  break;
98 
99  case SecurityType.Equity:
101  break;
102 
103  case SecurityType.Option:
104  var underlying = SecurityIdentifier.GenerateEquity(SecurityIdentifier.DefaultDate, ticker, market);
105  sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying, market, 0, 0, 0);
106  break;
107 
108  case SecurityType.FutureOption:
109  var underlyingFuture = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market);
110  sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingFuture, market, 0, 0, 0);
111  break;
112 
113  case SecurityType.IndexOption:
114  var underlyingIndex = SecurityIdentifier.GenerateIndex(ticker, market);
115  sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingIndex, market, 0, 0, OptionStyle.European);
116  break;
117 
118  case SecurityType.Forex:
119  sid = SecurityIdentifier.GenerateForex(ticker, market);
120  break;
121 
122  case SecurityType.Cfd:
123  sid = SecurityIdentifier.GenerateCfd(ticker, market);
124  break;
125 
126  case SecurityType.Index:
127  sid = SecurityIdentifier.GenerateIndex(ticker, market);
128  break;
129 
130  case SecurityType.Future:
132  break;
133 
134  case SecurityType.Crypto:
135  sid = SecurityIdentifier.GenerateCrypto(ticker, market);
136  break;
137 
138  case SecurityType.CryptoFuture:
140  break;
141 
142  case SecurityType.Commodity:
143  default:
144  throw new NotImplementedException($"The specified security type is not implemented yet: {securityType}");
145  }
146 
147  return new Symbol(sid, ticker);
148  }
149 
150  /// <summary>
151  /// Processes the universe download based on parameters.
152  /// </summary>
153  /// <param name="dataDownloader">The data downloader instance.</param>
154  /// <param name="universeDownloadParameters">The parameters for universe downloading.</param>
155  public static void RunUniverseDownloader(IDataDownloader dataDownloader, DataUniverseDownloaderGetParameters universeDownloadParameters)
156  {
157  var universeDataBySymbol = new Dictionary<Symbol, DerivativeUniverseData>();
158  foreach (var (processingDate, universeDownloaderParameters) in universeDownloadParameters.CreateDataDownloaderGetParameters())
159  {
160  universeDataBySymbol.Clear();
161 
162  foreach (var downloaderParameters in universeDownloaderParameters)
163  {
164  Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generating universe for {downloaderParameters.Symbol} on {processingDate:yyyy/MM/dd}");
165 
166  var historyData = dataDownloader.Get(downloaderParameters);
167 
168  if (historyData == null)
169  {
170  Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: No data available for the following parameters: {universeDownloadParameters}");
171  continue;
172  }
173 
174  foreach (var baseData in historyData)
175  {
176  switch (baseData)
177  {
178  case TradeBar tradeBar:
179  if (!universeDataBySymbol.TryAdd(tradeBar.Symbol, new(tradeBar)))
180  {
181  universeDataBySymbol[tradeBar.Symbol].UpdateByTradeBar(tradeBar);
182  }
183  break;
184  case OpenInterest openInterest:
185  if (!universeDataBySymbol.TryAdd(openInterest.Symbol, new(openInterest)))
186  {
187  universeDataBySymbol[openInterest.Symbol].UpdateByOpenInterest(openInterest);
188  }
189  break;
190  case QuoteBar quoteBar:
191  if (!universeDataBySymbol.TryAdd(quoteBar.Symbol, new(quoteBar)))
192  {
193  universeDataBySymbol[quoteBar.Symbol].UpdateByQuoteBar(quoteBar);
194  }
195  break;
196  default:
197  throw new InvalidOperationException($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: Unexpected data type encountered.");
198  }
199  }
200  }
201 
202  if (universeDataBySymbol.Count == 0)
203  {
204  continue;
205  }
206 
207  using var writer = new StreamWriter(universeDownloadParameters.GetUniverseFileName(processingDate));
208 
209  writer.WriteLine($"#{OptionUniverse.CsvHeader}");
210 
211  // Write option data, sorted by contract type (Call/Put), strike price, expiration date, and then by full ID
212  foreach (var universeData in universeDataBySymbol
213  .OrderBy(x => x.Key.Underlying != null)
214  .ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.OptionRight : 0)
215  .ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.StrikePrice : 0)
216  .ThenBy(d => d.Key.ID.Date)
217  .ThenBy(d => d.Key.ID))
218  {
219  writer.WriteLine(universeData.Value.ToCsv());
220  }
221 
222  Log.Trace($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generated for {universeDownloadParameters.Symbol} on {processingDate:yyyy/MM/dd} with {universeDataBySymbol.Count} entries");
223  }
224  }
225  }
226 }