Lean  $LEAN_TAG$
LeanData.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.Collections.Generic;
18 using System.Globalization;
19 using System.IO;
20 using System.Linq;
21 using NodaTime;
22 using QuantConnect.Data;
27 using QuantConnect.Logging;
31 using static QuantConnect.StringExtensions;
32 
33 namespace QuantConnect.Util
34 {
35  /// <summary>
36  /// Provides methods for generating lean data file content
37  /// </summary>
38  public static class LeanData
39  {
40  /// <summary>
41  /// The different <see cref="SecurityType"/> used for data paths
42  /// </summary>
43  /// <remarks>This includes 'alternative'</remarks>
44  public static HashSet<string> SecurityTypeAsDataPath => Enum.GetNames(typeof(SecurityType))
45  .Select(x => x.ToLowerInvariant()).Union(new[] { "alternative" }).ToHashSet();
46 
47  /// <summary>
48  /// Converts the specified base data instance into a lean data file csv line.
49  /// This method takes into account the fake that base data instances typically
50  /// are time stamped in the exchange time zone, but need to be written to disk
51  /// in the data time zone.
52  /// </summary>
53  public static string GenerateLine(IBaseData data, Resolution resolution, DateTimeZone exchangeTimeZone, DateTimeZone dataTimeZone)
54  {
55  var clone = data.Clone();
56  clone.Time = data.Time.ConvertTo(exchangeTimeZone, dataTimeZone);
57  return GenerateLine(clone, clone.Symbol.ID.SecurityType, resolution);
58  }
59 
60  /// <summary>
61  /// Helper method that will parse a given data line in search of an associated date time
62  /// </summary>
63  public static DateTime ParseTime(string line, DateTime date, Resolution resolution)
64  {
65  switch (resolution)
66  {
67  case Resolution.Tick:
68  case Resolution.Second:
69  case Resolution.Minute:
70  var index = line.IndexOf(',', StringComparison.InvariantCulture);
71  return date.AddTicks(Convert.ToInt64(10000 * decimal.Parse(line.AsSpan(0, index))));
72  case Resolution.Hour:
73  case Resolution.Daily:
74  return DateTime.ParseExact(line.AsSpan(0, DateFormat.TwelveCharacter.Length), DateFormat.TwelveCharacter, CultureInfo.InvariantCulture);
75  default:
76  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
77  }
78  }
79 
80  /// <summary>
81  /// Converts the specified base data instance into a lean data file csv line
82  /// </summary>
83  public static string GenerateLine(IBaseData data, SecurityType securityType, Resolution resolution)
84  {
85  var milliseconds = data.Time.TimeOfDay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
86  var longTime = data.Time.ToStringInvariant(DateFormat.TwelveCharacter);
87 
88  switch (securityType)
89  {
90  case SecurityType.Equity:
91  switch (resolution)
92  {
93  case Resolution.Tick:
94  var tick = (Tick) data;
95  if (tick.TickType == TickType.Trade)
96  {
97  return ToCsv(milliseconds, Scale(tick.LastPrice), tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0");
98  }
99  if (tick.TickType == TickType.Quote)
100  {
101  return ToCsv(milliseconds, Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0");
102  }
103  break;
104  case Resolution.Minute:
105  case Resolution.Second:
106  var tradeBar = data as TradeBar;
107  if (tradeBar != null)
108  {
109  return ToCsv(milliseconds, Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume);
110  }
111  var quoteBar = data as QuoteBar;
112  if (quoteBar != null)
113  {
114  return ToCsv(milliseconds,
115  ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
116  ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
117  }
118  break;
119 
120  case Resolution.Hour:
121  case Resolution.Daily:
122  var bigTradeBar = data as TradeBar;
123  if (bigTradeBar != null)
124  {
125  return ToCsv(longTime, Scale(bigTradeBar.Open), Scale(bigTradeBar.High), Scale(bigTradeBar.Low), Scale(bigTradeBar.Close), bigTradeBar.Volume);
126  }
127  var bigQuoteBar = data as QuoteBar;
128  if (bigQuoteBar != null)
129  {
130  return ToCsv(longTime,
131  ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
132  ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
133  }
134  break;
135  }
136  break;
137 
138  case SecurityType.Crypto:
139  case SecurityType.CryptoFuture:
140  switch (resolution)
141  {
142  case Resolution.Tick:
143  var tick = data as Tick;
144  if (tick == null)
145  {
146  throw new ArgumentException($"{securityType} tick could not be created", nameof(data));
147  }
148  if (tick.TickType == TickType.Trade)
149  {
150  return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, tick.Suspicious ? "1" : "0");
151  }
152  if (tick.TickType == TickType.Quote)
153  {
154  return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Suspicious ? "1" : "0");
155  }
156  throw new ArgumentException($"{securityType} tick could not be created");
157  case Resolution.Second:
158  case Resolution.Minute:
159  var quoteBar = data as QuoteBar;
160  if (quoteBar != null)
161  {
162  return ToCsv(milliseconds,
163  ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
164  ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
165  }
166  var tradeBar = data as TradeBar;
167  if (tradeBar != null)
168  {
169  return ToCsv(milliseconds, tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
170  }
171  throw new ArgumentException($"{securityType} minute/second bar could not be created", nameof(data));
172 
173  case Resolution.Hour:
174  case Resolution.Daily:
175  var bigQuoteBar = data as QuoteBar;
176  if (bigQuoteBar != null)
177  {
178  return ToCsv(longTime,
179  ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
180  ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
181  }
182  var bigTradeBar = data as TradeBar;
183  if (bigTradeBar != null)
184  {
185  return ToCsv(longTime,
186  bigTradeBar.Open,
187  bigTradeBar.High,
188  bigTradeBar.Low,
189  bigTradeBar.Close,
190  bigTradeBar.Volume);
191  }
192  throw new ArgumentException($"{securityType} hour/daily bar could not be created", nameof(data));
193  }
194  break;
195  case SecurityType.Forex:
196  case SecurityType.Cfd:
197  switch (resolution)
198  {
199  case Resolution.Tick:
200  var tick = data as Tick;
201  if (tick == null)
202  {
203  throw new ArgumentException("Expected data of type 'Tick'", nameof(data));
204  }
205  return ToCsv(milliseconds, tick.BidPrice, tick.AskPrice);
206 
207  case Resolution.Second:
208  case Resolution.Minute:
209  var bar = data as QuoteBar;
210  if (bar == null)
211  {
212  throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data));
213  }
214  return ToCsv(milliseconds,
215  ToNonScaledCsv(bar.Bid), bar.LastBidSize,
216  ToNonScaledCsv(bar.Ask), bar.LastAskSize);
217 
218  case Resolution.Hour:
219  case Resolution.Daily:
220  var bigBar = data as QuoteBar;
221  if (bigBar == null)
222  {
223  throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data));
224  }
225  return ToCsv(longTime,
226  ToNonScaledCsv(bigBar.Bid), bigBar.LastBidSize,
227  ToNonScaledCsv(bigBar.Ask), bigBar.LastAskSize);
228  }
229  break;
230 
231  case SecurityType.Index:
232  switch (resolution)
233  {
234  case Resolution.Tick:
235  var tick = (Tick) data;
236  return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, string.Empty, string.Empty, "0");
237  case Resolution.Second:
238  case Resolution.Minute:
239  var bar = data as TradeBar;
240  if (bar == null)
241  {
242  throw new ArgumentException("Expected data of type 'TradeBar'", nameof(data));
243  }
244  return ToCsv(milliseconds, bar.Open, bar.High, bar.Low, bar.Close, bar.Volume);
245  case Resolution.Hour:
246  case Resolution.Daily:
247  var bigTradeBar = data as TradeBar;
248  return ToCsv(longTime, bigTradeBar.Open, bigTradeBar.High, bigTradeBar.Low, bigTradeBar.Close, bigTradeBar.Volume);
249  }
250  break;
251 
252  case SecurityType.Option:
253  case SecurityType.IndexOption:
254  switch (resolution)
255  {
256  case Resolution.Tick:
257  var tick = (Tick)data;
258  if (tick.TickType == TickType.Trade)
259  {
260  return ToCsv(milliseconds,
261  Scale(tick.LastPrice), tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0");
262  }
263  if (tick.TickType == TickType.Quote)
264  {
265  return ToCsv(milliseconds,
266  Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0");
267  }
268  if (tick.TickType == TickType.OpenInterest)
269  {
270  return ToCsv(milliseconds, tick.Value);
271  }
272  break;
273 
274  case Resolution.Second:
275  case Resolution.Minute:
276  // option and future data can be quote or trade bars
277  var quoteBar = data as QuoteBar;
278  if (quoteBar != null)
279  {
280  return ToCsv(milliseconds,
281  ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
282  ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
283  }
284  var tradeBar = data as TradeBar;
285  if (tradeBar != null)
286  {
287  return ToCsv(milliseconds,
288  Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume);
289  }
290  var openInterest = data as OpenInterest;
291  if (openInterest != null)
292  {
293  return ToCsv(milliseconds, openInterest.Value);
294  }
295  break;
296 
297  case Resolution.Hour:
298  case Resolution.Daily:
299  // option and future data can be quote or trade bars
300  var bigQuoteBar = data as QuoteBar;
301  if (bigQuoteBar != null)
302  {
303  return ToCsv(longTime,
304  ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
305  ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
306  }
307  var bigTradeBar = data as TradeBar;
308  if (bigTradeBar != null)
309  {
310  return ToCsv(longTime, ToScaledCsv(bigTradeBar), bigTradeBar.Volume);
311  }
312  var bigOpenInterest = data as OpenInterest;
313  if (bigOpenInterest != null)
314  {
315  return ToCsv(longTime, bigOpenInterest.Value);
316  }
317  break;
318 
319  default:
320  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
321  }
322  break;
323 
324  case SecurityType.FutureOption:
325  switch (resolution)
326  {
327  case Resolution.Tick:
328  var tick = (Tick)data;
329  if (tick.TickType == TickType.Trade)
330  {
331  return ToCsv(milliseconds,
332  tick.LastPrice, tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0");
333  }
334  if (tick.TickType == TickType.Quote)
335  {
336  return ToCsv(milliseconds,
337  tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0");
338  }
339  if (tick.TickType == TickType.OpenInterest)
340  {
341  return ToCsv(milliseconds, tick.Value);
342  }
343  break;
344 
345  case Resolution.Second:
346  case Resolution.Minute:
347  // option and future data can be quote or trade bars
348  var quoteBar = data as QuoteBar;
349  if (quoteBar != null)
350  {
351  return ToCsv(milliseconds,
352  ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
353  ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
354  }
355  var tradeBar = data as TradeBar;
356  if (tradeBar != null)
357  {
358  return ToCsv(milliseconds,
359  tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
360  }
361  var openInterest = data as OpenInterest;
362  if (openInterest != null)
363  {
364  return ToCsv(milliseconds, openInterest.Value);
365  }
366  break;
367 
368  case Resolution.Hour:
369  case Resolution.Daily:
370  // option and future data can be quote or trade bars
371  var bigQuoteBar = data as QuoteBar;
372  if (bigQuoteBar != null)
373  {
374  return ToCsv(longTime,
375  ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
376  ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
377  }
378  var bigTradeBar = data as TradeBar;
379  if (bigTradeBar != null)
380  {
381  return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume);
382  }
383  var bigOpenInterest = data as OpenInterest;
384  if (bigOpenInterest != null)
385  {
386  return ToCsv(longTime, bigOpenInterest.Value);
387  }
388  break;
389 
390  default:
391  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
392  }
393  break;
394 
395  case SecurityType.Future:
396  switch (resolution)
397  {
398  case Resolution.Tick:
399  var tick = (Tick)data;
400  if (tick.TickType == TickType.Trade)
401  {
402  return ToCsv(milliseconds,
403  tick.LastPrice, tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1": "0");
404  }
405  if (tick.TickType == TickType.Quote)
406  {
407  return ToCsv(milliseconds,
408  tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0");
409  }
410  if (tick.TickType == TickType.OpenInterest)
411  {
412  return ToCsv(milliseconds, tick.Value);
413  }
414  break;
415 
416  case Resolution.Second:
417  case Resolution.Minute:
418  // option and future data can be quote or trade bars
419  var quoteBar = data as QuoteBar;
420  if (quoteBar != null)
421  {
422  return ToCsv(milliseconds,
423  ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize,
424  ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize);
425  }
426  var tradeBar = data as TradeBar;
427  if (tradeBar != null)
428  {
429  return ToCsv(milliseconds,
430  tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume);
431  }
432  var openInterest = data as OpenInterest;
433  if (openInterest != null)
434  {
435  return ToCsv(milliseconds, openInterest.Value);
436  }
437  break;
438 
439  case Resolution.Hour:
440  case Resolution.Daily:
441  // option and future data can be quote or trade bars
442  var bigQuoteBar = data as QuoteBar;
443  if (bigQuoteBar != null)
444  {
445  return ToCsv(longTime,
446  ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize,
447  ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize);
448  }
449  var bigTradeBar = data as TradeBar;
450  if (bigTradeBar != null)
451  {
452  return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume);
453  }
454  var bigOpenInterest = data as OpenInterest;
455  if (bigOpenInterest != null)
456  {
457  return ToCsv(longTime, bigOpenInterest.Value);
458  }
459  break;
460 
461  default:
462  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
463  }
464  break;
465 
466  default:
467  throw new ArgumentOutOfRangeException(nameof(securityType), securityType, null);
468  }
469 
470  throw new NotImplementedException(Invariant(
471  $"LeanData.GenerateLine has not yet been implemented for security type: {securityType} at resolution: {resolution}"
472  ));
473  }
474 
475  /// <summary>
476  /// Gets the data type required for the specified combination of resolution and tick type
477  /// </summary>
478  /// <param name="resolution">The resolution, if Tick, the Type returned is always Tick</param>
479  /// <param name="tickType">The <see cref="TickType"/> that primarily dictates the type returned</param>
480  /// <returns>The Type used to create a subscription</returns>
481  public static Type GetDataType(Resolution resolution, TickType tickType)
482  {
483  if (resolution == Resolution.Tick) return typeof(Tick);
484  if (tickType == TickType.OpenInterest) return typeof(OpenInterest);
485  if (tickType == TickType.Quote) return typeof(QuoteBar);
486  return typeof(TradeBar);
487  }
488 
489 
490  /// <summary>
491  /// Determines if the Type is a 'common' type used throughout lean
492  /// This method is helpful in creating <see cref="SubscriptionDataConfig"/>
493  /// </summary>
494  /// <param name="baseDataType">The Type to check</param>
495  /// <returns>A bool indicating whether the type is of type <see cref="TradeBar"/>
496  /// <see cref="QuoteBar"/> or <see cref="OpenInterest"/></returns>
497  public static bool IsCommonLeanDataType(Type baseDataType)
498  {
499  if (baseDataType == typeof(Tick) ||
500  baseDataType == typeof(TradeBar) ||
501  baseDataType == typeof(QuoteBar) ||
502  baseDataType == typeof(OpenInterest))
503  {
504  return true;
505  }
506 
507  return false;
508  }
509 
510  /// <summary>
511  /// Helper method to determine if a configuration set is valid
512  /// </summary>
513  public static bool IsValidConfiguration(SecurityType securityType, Resolution resolution, TickType tickType)
514  {
515  if (securityType == SecurityType.Equity && (resolution == Resolution.Daily || resolution == Resolution.Hour))
516  {
517  return tickType != TickType.Quote;
518  }
519  return true;
520  }
521 
522  /// <summary>
523  /// Generates the full zip file path rooted in the <paramref name="dataDirectory"/>
524  /// </summary>
525  public static string GenerateZipFilePath(string dataDirectory, Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
526  {
527  // we could call 'GenerateRelativeZipFilePath' but we don't to avoid an extra string & path combine we are doing to drop right away
528  return Path.Combine(dataDirectory, GenerateRelativeZipFileDirectory(symbol, resolution), GenerateZipFileName(symbol, date, resolution, tickType));
529  }
530 
531  /// <summary>
532  /// Generates the full zip file path rooted in the <paramref name="dataDirectory"/>
533  /// </summary>
534  public static string GenerateZipFilePath(string dataDirectory, string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution)
535  {
536  return Path.Combine(dataDirectory, GenerateRelativeZipFilePath(symbol, securityType, market, date, resolution));
537  }
538 
539  /// <summary>
540  /// Generates the relative zip directory for the specified symbol/resolution
541  /// </summary>
542  public static string GenerateRelativeZipFileDirectory(Symbol symbol, Resolution resolution)
543  {
544  var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
545  var securityType = symbol.SecurityType.SecurityTypeToLower();
546 
547  var market = symbol.ID.Market.ToLowerInvariant();
548  var res = resolution.ResolutionToLower();
549  var directory = Path.Combine(securityType, market, res);
550  switch (symbol.ID.SecurityType)
551  {
552  case SecurityType.Base:
553  case SecurityType.Equity:
554  case SecurityType.Index:
555  case SecurityType.Forex:
556  case SecurityType.Cfd:
557  case SecurityType.Crypto:
558  return !isHourOrDaily ? Path.Combine(directory, symbol.Value.ToLowerInvariant()) : directory;
559 
560  case SecurityType.IndexOption:
561  // For index options, we use the canonical option ticker since it can differ from the underlying's ticker.
562  return !isHourOrDaily ? Path.Combine(directory, symbol.ID.Symbol.ToLowerInvariant()) : directory;
563 
564  case SecurityType.Option:
565  // options uses the underlying symbol for pathing.
566  return !isHourOrDaily ? Path.Combine(directory, symbol.Underlying.Value.ToLowerInvariant()) : directory;
567 
568  case SecurityType.FutureOption:
569  // For futures options, we use the canonical option ticker plus the underlying's expiry
570  // since it can differ from the underlying's ticker. We differ from normal futures
571  // because the option chain can be extraordinarily large compared to equity option chains.
572  var futureOptionPath = Path.Combine(symbol.ID.Symbol, symbol.Underlying.ID.Date.ToStringInvariant(DateFormat.EightCharacter))
573  .ToLowerInvariant();
574 
575  return Path.Combine(directory, futureOptionPath);
576 
577  case SecurityType.Future:
578  case SecurityType.CryptoFuture:
579  return !isHourOrDaily ? Path.Combine(directory, symbol.ID.Symbol.ToLowerInvariant()) : directory;
580 
581  case SecurityType.Commodity:
582  default:
583  throw new ArgumentOutOfRangeException();
584  }
585  }
586 
587  /// <summary>
588  /// Generates relative factor file paths for equities
589  /// </summary>
590  public static string GenerateRelativeFactorFilePath(Symbol symbol)
591  {
592  return Path.Combine(Globals.DataFolder,
593  "equity",
594  symbol.ID.Market,
595  "factor_files",
596  symbol.Value.ToLowerInvariant() + ".csv");
597  }
598 
599  /// <summary>
600  /// Generates the relative zip file path rooted in the /Data directory
601  /// </summary>
602  public static string GenerateRelativeZipFilePath(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
603  {
604  return Path.Combine(GenerateRelativeZipFileDirectory(symbol, resolution), GenerateZipFileName(symbol, date, resolution, tickType));
605  }
606 
607  /// <summary>
608  /// Generates the relative zip file path rooted in the /Data directory
609  /// </summary>
610  public static string GenerateRelativeZipFilePath(string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution)
611  {
612  var directory = Path.Combine(securityType.SecurityTypeToLower(), market.ToLowerInvariant(), resolution.ResolutionToLower());
613  if (resolution != Resolution.Daily && resolution != Resolution.Hour)
614  {
615  directory = Path.Combine(directory, symbol.ToLowerInvariant());
616  }
617 
618  return Path.Combine(directory, GenerateZipFileName(symbol, securityType, date, resolution));
619  }
620 
621  /// <summary>
622  /// Generate's the zip entry name to hold the specified data.
623  /// </summary>
624  public static string GenerateZipEntryName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
625  {
626  var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter);
627  var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
628 
629  switch (symbol.ID.SecurityType)
630  {
631  case SecurityType.Base:
632  case SecurityType.Equity:
633  case SecurityType.Index:
634  case SecurityType.Forex:
635  case SecurityType.Cfd:
636  case SecurityType.Crypto:
637  if (resolution == Resolution.Tick && symbol.SecurityType == SecurityType.Equity)
638  {
639  return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{tickType}_{resolution}.csv");
640  }
641 
642  if (isHourOrDaily)
643  {
644  return $"{symbol.Value.ToLowerInvariant()}.csv";
645  }
646 
647  return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{resolution.ResolutionToLower()}_{tickType.TickTypeToLower()}.csv");
648 
649  case SecurityType.Option:
650  var optionPath = symbol.Underlying.Value.ToLowerInvariant();
651 
652  if (isHourOrDaily)
653  {
654  return string.Join("_",
655  optionPath,
656  tickType.TickTypeToLower(),
657  symbol.ID.OptionStyle.OptionStyleToLower(),
658  symbol.ID.OptionRight.OptionRightToLower(),
659  Scale(symbol.ID.StrikePrice),
660  symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
661  ) + ".csv";
662  }
663 
664  return string.Join("_",
665  formattedDate,
666  optionPath,
667  resolution.ResolutionToLower(),
668  tickType.TickTypeToLower(),
669  symbol.ID.OptionStyle.OptionStyleToLower(),
670  symbol.ID.OptionRight.OptionRightToLower(),
671  Scale(symbol.ID.StrikePrice),
672  symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
673  ) + ".csv";
674 
675  case SecurityType.IndexOption:
676  case SecurityType.FutureOption:
677  // We want the future/index option ticker as the lookup name inside the ZIP file
678  var optionTickerBasedPath = symbol.ID.Symbol.ToLowerInvariant();
679 
680  if (isHourOrDaily)
681  {
682  return string.Join("_",
683  optionTickerBasedPath,
684  tickType.TickTypeToLower(),
685  symbol.ID.OptionStyle.OptionStyleToLower(),
686  symbol.ID.OptionRight.OptionRightToLower(),
687  Scale(symbol.ID.StrikePrice),
688  symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
689  ) + ".csv";
690  }
691 
692  return string.Join("_",
693  formattedDate,
694  optionTickerBasedPath,
695  resolution.ResolutionToLower(),
696  tickType.TickTypeToLower(),
697  symbol.ID.OptionStyle.OptionStyleToLower(),
698  symbol.ID.OptionRight.OptionRightToLower(),
699  Scale(symbol.ID.StrikePrice),
700  symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter)
701  ) + ".csv";
702 
703  case SecurityType.Future:
704  case SecurityType.CryptoFuture:
705  if (symbol.HasUnderlying)
706  {
707  symbol = symbol.Underlying;
708  }
709 
710  string expirationTag;
711  var expiryDate = symbol.ID.Date;
712  if(expiryDate != SecurityIdentifier.DefaultDate)
713  {
715  var contractYearMonth = expiryDate.AddMonths(monthsToAdd).ToStringInvariant(DateFormat.YearMonth);
716 
717  expirationTag = $"{contractYearMonth}_{expiryDate.ToStringInvariant(DateFormat.EightCharacter)}";
718  }
719  else
720  {
721  expirationTag = "perp";
722  }
723 
724  if (isHourOrDaily)
725  {
726  return string.Join("_",
727  symbol.ID.Symbol.ToLowerInvariant(),
728  tickType.TickTypeToLower(),
729  expirationTag
730  ) + ".csv";
731  }
732 
733  return string.Join("_",
734  formattedDate,
735  symbol.ID.Symbol.ToLowerInvariant(),
736  resolution.ResolutionToLower(),
737  tickType.TickTypeToLower(),
738  expirationTag
739  ) + ".csv";
740 
741  case SecurityType.Commodity:
742  default:
743  throw new ArgumentOutOfRangeException();
744  }
745  }
746 
747  /// <summary>
748  /// Generates the zip file name for the specified date of data.
749  /// </summary>
750  public static string GenerateZipFileName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType)
751  {
752  var tickTypeString = tickType.TickTypeToLower();
753  var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter);
754  var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
755 
756  switch (symbol.ID.SecurityType)
757  {
758  case SecurityType.Base:
759  case SecurityType.Index:
760  case SecurityType.Equity:
761  case SecurityType.Forex:
762  case SecurityType.Cfd:
763  if (isHourOrDaily)
764  {
765  return $"{symbol.Value.ToLowerInvariant()}.zip";
766  }
767 
768  return $"{formattedDate}_{tickTypeString}.zip";
769  case SecurityType.Crypto:
770  if (isHourOrDaily)
771  {
772  return $"{symbol.Value.ToLowerInvariant()}_{tickTypeString}.zip";
773  }
774 
775  return $"{formattedDate}_{tickTypeString}.zip";
776  case SecurityType.Option:
777  if (isHourOrDaily)
778  {
779  // see TryParsePath: he knows tick type position is 3
780  var optionPath = symbol.Underlying.Value.ToLowerInvariant();
781  return $"{optionPath}_{date.Year}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip";
782  }
783 
784  return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip";
785 
786  case SecurityType.IndexOption:
787  case SecurityType.FutureOption:
788  if (isHourOrDaily)
789  {
790  // see TryParsePath: he knows tick type position is 3
791  var optionTickerBasedPath = symbol.ID.Symbol.ToLowerInvariant();
792  return $"{optionTickerBasedPath}_{date.Year}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip";
793  }
794 
795  return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip";
796 
797  case SecurityType.Future:
798  case SecurityType.CryptoFuture:
799  if (isHourOrDaily)
800  {
801  return $"{symbol.ID.Symbol.ToLowerInvariant()}_{tickTypeString}.zip";
802  }
803 
804  return $"{formattedDate}_{tickTypeString}.zip";
805 
806  case SecurityType.Commodity:
807  default:
808  throw new ArgumentOutOfRangeException();
809  }
810  }
811 
812  /// <summary>
813  /// Creates the zip file name for a QC zip data file
814  /// </summary>
815  public static string GenerateZipFileName(string symbol, SecurityType securityType, DateTime date, Resolution resolution, TickType? tickType = null)
816  {
817  if (resolution == Resolution.Hour || resolution == Resolution.Daily)
818  {
819  return $"{symbol.ToLowerInvariant()}.zip";
820  }
821 
822  var zipFileName = date.ToStringInvariant(DateFormat.EightCharacter);
823 
824  if (tickType == null)
825  {
826  if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd) {
827  tickType = TickType.Quote;
828  }
829  else
830  {
831  tickType = TickType.Trade;
832  }
833  }
834 
835  var suffix = Invariant($"_{tickType.Value.TickTypeToLower()}.zip");
836  return zipFileName + suffix;
837  }
838 
839  /// <summary>
840  /// Gets the tick type most commonly associated with the specified security type
841  /// </summary>
842  /// <param name="securityType">The security type</param>
843  /// <returns>The most common tick type for the specified security type</returns>
844  public static TickType GetCommonTickType(SecurityType securityType)
845  {
846  if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd || securityType == SecurityType.Crypto)
847  {
848  return TickType.Quote;
849  }
850  return TickType.Trade;
851  }
852 
853  /// <summary>
854  /// Creates a symbol from the specified zip entry name
855  /// </summary>
856  /// <param name="symbol">The root symbol of the output symbol</param>
857  /// <param name="resolution">The resolution of the data source producing the zip entry name</param>
858  /// <param name="zipEntryName">The zip entry name to be parsed</param>
859  /// <returns>A new symbol representing the zip entry name</returns>
860  public static Symbol ReadSymbolFromZipEntry(Symbol symbol, Resolution resolution, string zipEntryName)
861  {
862  var isHourlyOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily;
863  var parts = zipEntryName.Replace(".csv", string.Empty).Split('_');
864  switch (symbol.ID.SecurityType)
865  {
866  case SecurityType.Option:
867  case SecurityType.FutureOption:
868  case SecurityType.IndexOption:
869  if (isHourlyOrDaily)
870  {
871  var style = parts[2].ParseOptionStyle();
872  var right = parts[3].ParseOptionRight();
873  var strike = Parse.Decimal(parts[4]) / 10000m;
874  var expiry = Parse.DateTimeExact(parts[5], DateFormat.EightCharacter);
875  return Symbol.CreateOption(symbol.Underlying, symbol.ID.Symbol, symbol.ID.Market, style, right, strike, expiry);
876  }
877  else
878  {
879  var style = parts[4].ParseOptionStyle();
880  var right = parts[5].ParseOptionRight();
881  var strike = Parse.Decimal(parts[6]) / 10000m;
882  var expiry = DateTime.ParseExact(parts[7], DateFormat.EightCharacter, CultureInfo.InvariantCulture);
883  return Symbol.CreateOption(symbol.Underlying, symbol.ID.Symbol, symbol.ID.Market, style, right, strike, expiry);
884  }
885 
886  case SecurityType.Future:
887  if (isHourlyOrDaily)
888  {
889  var expiryYearMonth = Parse.DateTimeExact(parts[2], DateFormat.YearMonth);
890  var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
891  var futureExpiry = futureExpiryFunc(expiryYearMonth);
892  return Symbol.CreateFuture(parts[0], symbol.ID.Market, futureExpiry);
893  }
894  else
895  {
896  var expiryYearMonth = Parse.DateTimeExact(parts[4], DateFormat.YearMonth);
897  var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol);
898  var futureExpiry = futureExpiryFunc(expiryYearMonth);
899  return Symbol.CreateFuture(parts[1], symbol.ID.Market, futureExpiry);
900  }
901 
902  default:
903  throw new NotImplementedException(Invariant(
904  $"ReadSymbolFromZipEntry is not implemented for {symbol.ID.SecurityType} {symbol.ID.Market} {resolution}"
905  ));
906  }
907  }
908 
909  /// <summary>
910  /// Scale and convert the resulting number to deci-cents int.
911  /// </summary>
912  private static long Scale(decimal value)
913  {
914  return (long)(value*10000);
915  }
916 
917  /// <summary>
918  /// Create a csv line from the specified arguments
919  /// </summary>
920  private static string ToCsv(params object[] args)
921  {
922  // use culture neutral formatting for decimals
923  for (var i = 0; i < args.Length; i++)
924  {
925  var value = args[i];
926  if (value is decimal)
927  {
928  args[i] = ((decimal) value).Normalize();
929  }
930  }
931 
932  var argsFormatted = args.Select(x => Convert.ToString(x, CultureInfo.InvariantCulture));
933  return string.Join(",", argsFormatted);
934  }
935 
936  /// <summary>
937  /// Creates a scaled csv line for the bar, if null fills in empty strings
938  /// </summary>
939  private static string ToScaledCsv(IBar bar)
940  {
941  if (bar == null)
942  {
943  return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty);
944  }
945 
946  return ToCsv(Scale(bar.Open), Scale(bar.High), Scale(bar.Low), Scale(bar.Close));
947  }
948 
949 
950  /// <summary>
951  /// Creates a non scaled csv line for the bar, if null fills in empty strings
952  /// </summary>
953  private static string ToNonScaledCsv(IBar bar)
954  {
955  if (bar == null)
956  {
957  return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty);
958  }
959 
960  return ToCsv(bar.Open, bar.High, bar.Low, bar.Close);
961  }
962 
963  /// <summary>
964  /// Get the <see cref="TickType"/> for common Lean data types.
965  /// If not a Lean common data type, return a TickType of Trade.
966  /// </summary>
967  /// <param name="type">A Type used to determine the TickType</param>
968  /// <param name="securityType">The SecurityType used to determine the TickType</param>
969  /// <returns>A TickType corresponding to the type</returns>
970  public static TickType GetCommonTickTypeForCommonDataTypes(Type type, SecurityType securityType)
971  {
972  if (type == typeof(TradeBar))
973  {
974  return TickType.Trade;
975  }
976  if (type == typeof(QuoteBar))
977  {
978  return TickType.Quote;
979  }
980  if (type == typeof(OpenInterest))
981  {
982  return TickType.OpenInterest;
983  }
984  if (type == typeof(ZipEntryName))
985  {
986  return TickType.Quote;
987  }
988  if (type == typeof(Tick))
989  {
990  return GetCommonTickType(securityType);
991  }
992 
993  return TickType.Trade;
994  }
995 
996  /// <summary>
997  /// Matches a data path security type with the <see cref="SecurityType"/>
998  /// </summary>
999  /// <remarks>This includes 'alternative'</remarks>
1000  /// <param name="securityType">The data path security type</param>
1001  /// <returns>The matching security type for the given data path</returns>
1002  public static SecurityType ParseDataSecurityType(string securityType)
1003  {
1004  if (securityType.Equals("alternative", StringComparison.InvariantCultureIgnoreCase))
1005  {
1006  return SecurityType.Base;
1007  }
1008  return (SecurityType) Enum.Parse(typeof(SecurityType), securityType, true);
1009  }
1010 
1011  /// <summary>
1012  /// Parses file name into a <see cref="Security"/> and DateTime
1013  /// </summary>
1014  /// <param name="fileName">File name to be parsed</param>
1015  /// <param name="securityType">The securityType as parsed from the fileName</param>
1016  /// <param name="market">The market as parsed from the fileName</param>
1017  public static bool TryParseSecurityType(string fileName, out SecurityType securityType, out string market)
1018  {
1019  securityType = SecurityType.Base;
1020  market = string.Empty;
1021 
1022  try
1023  {
1024  var info = SplitDataPath(fileName);
1025 
1026  // find the securityType and parse it
1027  var typeString = info.Find(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant()));
1028  securityType = ParseDataSecurityType(typeString);
1029 
1030  var existingMarkets = Market.SupportedMarkets();
1031  var foundMarket = info.Find(x => existingMarkets.Contains(x.ToLowerInvariant()));
1032  if (foundMarket != null)
1033  {
1034  market = foundMarket;
1035  }
1036  }
1037  catch (Exception e)
1038  {
1039  Log.Error($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {e.GetBaseException()}");
1040  return false;
1041  }
1042 
1043  return true;
1044  }
1045 
1046  /// <summary>
1047  /// Parses file name into a <see cref="Security"/> and DateTime
1048  /// </summary>
1049  /// <param name="filePath">File path to be parsed</param>
1050  /// <param name="symbol">The symbol as parsed from the fileName</param>
1051  /// <param name="date">Date of data in the file path. Only returned if the resolution is lower than Hourly</param>
1052  /// <param name="resolution">The resolution of the symbol as parsed from the filePath</param>
1053  /// <param name="tickType">The tick type</param>
1054  /// <param name="dataType">The data type</param>
1055  public static bool TryParsePath(string filePath, out Symbol symbol, out DateTime date,
1056  out Resolution resolution, out TickType tickType, out Type dataType)
1057  {
1058  symbol = default;
1059  tickType = default;
1060  dataType = default;
1061  date = default;
1062  resolution = default;
1063 
1064  try
1065  {
1066  if (!TryParsePath(filePath, out symbol, out date, out resolution))
1067  {
1068  return false;
1069  }
1070 
1071  tickType = GetCommonTickType(symbol.SecurityType);
1072  var fileName = Path.GetFileNameWithoutExtension(filePath);
1073  if (fileName.Contains("_"))
1074  {
1075  // example: 20140606_openinterest_american.zip
1076  var tickTypePosition = 1;
1077  if (resolution >= Resolution.Hour && symbol.SecurityType.IsOption())
1078  {
1079  // daily and hourly have the year too, example: aapl_2014_openinterest_american.zip
1080  // see GenerateZipFileName he's creating these paths
1081  tickTypePosition = 2;
1082  }
1083  tickType = (TickType)Enum.Parse(typeof(TickType), fileName.Split('_')[tickTypePosition], true);
1084  }
1085 
1086  dataType = GetDataType(resolution, tickType);
1087  return true;
1088  }
1089  catch (Exception ex)
1090  {
1091  Log.Debug($"LeanData.TryParsePath(): Error encountered while parsing the path {filePath}. Error: {ex.GetBaseException()}");
1092  }
1093  return false;
1094  }
1095 
1096  /// <summary>
1097  /// Parses file name into a <see cref="Security"/> and DateTime
1098  /// </summary>
1099  /// <param name="fileName">File name to be parsed</param>
1100  /// <param name="symbol">The symbol as parsed from the fileName</param>
1101  /// <param name="date">Date of data in the file path. Only returned if the resolution is lower than Hourly</param>
1102  /// <param name="resolution">The resolution of the symbol as parsed from the filePath</param>
1103  public static bool TryParsePath(string fileName, out Symbol symbol, out DateTime date, out Resolution resolution)
1104  {
1105  symbol = null;
1106  resolution = Resolution.Daily;
1107  date = default(DateTime);
1108 
1109  try
1110  {
1111  var info = SplitDataPath(fileName);
1112 
1113  // find where the useful part of the path starts - i.e. the securityType
1114  var startIndex = info.FindIndex(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant()));
1115 
1116  if(startIndex == -1)
1117  {
1118  if (Log.DebuggingEnabled)
1119  {
1120  Log.Debug($"LeanData.TryParsePath(): Failed to parse '{fileName}' unexpected SecurityType");
1121  }
1122  // SPDB & MHDB folders
1123  return false;
1124  }
1125  var securityType = ParseDataSecurityType(info[startIndex]);
1126 
1127  var market = Market.USA;
1128  string ticker;
1129  var isUniverses = false;
1130  if (!Enum.TryParse(info[startIndex + 2], true, out resolution))
1131  {
1132  resolution = Resolution.Daily;
1133  isUniverses = info[startIndex + 2].Equals("universes", StringComparison.InvariantCultureIgnoreCase);
1134  if (securityType != SecurityType.Base)
1135  {
1136  if (!isUniverses)
1137  {
1138  if (Log.DebuggingEnabled)
1139  {
1140  Log.Debug($"LeanData.TryParsePath(): Failed to parse '{fileName}' unexpected Resolution");
1141  }
1142  // only acept a failure to parse resolution if we are facing a universes path
1143  return false;
1144  }
1145  securityType = SecurityType.Base;
1146  }
1147  }
1148 
1149  if (securityType == SecurityType.Base)
1150  {
1151  // the last part of the path is the file name
1152  var fileNameNoPath = info[info.Count - 1].Split('_').First();
1153 
1154  if (!DateTime.TryParseExact(fileNameNoPath,
1156  DateTimeFormatInfo.InvariantInfo,
1157  DateTimeStyles.None,
1158  out date))
1159  {
1160  // if parsing the date failed we assume filename is ticker
1161  ticker = fileNameNoPath;
1162  }
1163  else
1164  {
1165  // ticker must be the previous part of the path
1166  ticker = info[info.Count - 2];
1167  }
1168  }
1169  else
1170  {
1171  // Gather components used to create the security
1172  market = info[startIndex + 1];
1173  var components = info[startIndex + 3].Split('_');
1174 
1175  // Remove the ticktype from the ticker (Only exists in Crypto and Future data but causes no issues)
1176  ticker = components[0];
1177 
1178  if (resolution < Resolution.Hour)
1179  {
1180  // Future options are special and have the following format Market/Resolution/Ticker/FutureExpiry/Date
1181  var dateIndex = securityType == SecurityType.FutureOption ? startIndex + 5 : startIndex + 4;
1182  date = Parse.DateTimeExact(info[dateIndex].Substring(0, 8), DateFormat.EightCharacter);
1183  }
1184  // If resolution is Daily or Hour for options and index options, we can only get the year from the path
1185  else if (securityType == SecurityType.Option || securityType == SecurityType.IndexOption)
1186  {
1187  var year = int.Parse(components[1], CultureInfo.InvariantCulture);
1188  date = new DateTime(year, 01, 01);
1189  }
1190  }
1191 
1192  // Future Options cannot use Symbol.Create
1193  if (securityType == SecurityType.FutureOption)
1194  {
1195  // Future options have underlying FutureExpiry date as the parent dir for the zips, we need this for our underlying
1196  var underlyingFutureExpiryDate = Parse.DateTimeExact(info[startIndex + 4].Substring(0, 8), DateFormat.EightCharacter);
1197 
1198  var underlyingTicker = OptionSymbol.MapToUnderlying(ticker, securityType);
1199  // Create our underlying future and then the Canonical option for this future
1200  var underlyingFuture = Symbol.CreateFuture(underlyingTicker, market, underlyingFutureExpiryDate);
1201  symbol = Symbol.CreateCanonicalOption(underlyingFuture);
1202  }
1203  else if(securityType == SecurityType.IndexOption)
1204  {
1205  var underlyingTicker = OptionSymbol.MapToUnderlying(ticker, securityType);
1206  // Create our underlying index and then the Canonical option
1207  var underlyingIndex = Symbol.Create(underlyingTicker, SecurityType.Index, market);
1208  symbol = Symbol.CreateCanonicalOption(underlyingIndex, ticker, market, null);
1209  }
1210  else
1211  {
1212  Type dataType = null;
1213  if (isUniverses && info[startIndex + 3].Equals("etf", StringComparison.InvariantCultureIgnoreCase))
1214  {
1215  dataType = typeof(ETFConstituentUniverse);
1216  }
1217  symbol = CreateSymbol(ticker, securityType, market, dataType, date);
1218  }
1219 
1220  }
1221  catch (Exception ex)
1222  {
1223  Log.Debug($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {ex.GetBaseException()}");
1224  return false;
1225  }
1226 
1227  return true;
1228  }
1229 
1230  /// <summary>
1231  /// Creates a new Symbol based on parsed data path information.
1232  /// </summary>
1233  /// <param name="ticker">The parsed ticker symbol.</param>
1234  /// <param name="securityType">The parsed type of security.</param>
1235  /// <param name="market">The parsed market or exchange.</param>
1236  /// <param name="dataType">Optional type used for generating the base data SID (applicable only for SecurityType.Base).</param>
1237  /// <param name="mappingResolveDate">The date used in path parsing to create the correct symbol.</param>
1238  /// <returns>A unique security identifier.</returns>
1239  /// <example>
1240  /// <code>
1241  /// path: equity/usa/minute/spwr/20071223_trade.zip
1242  /// ticker: spwr
1243  /// securityType: equity
1244  /// market: usa
1245  /// mappingResolveDate: 2007/12/23
1246  /// </code>
1247  /// </example>
1248  private static Symbol CreateSymbol(string ticker, SecurityType securityType, string market, Type dataType, DateTime mappingResolveDate = default)
1249  {
1250  if (mappingResolveDate != default && (securityType == SecurityType.Equity || securityType == SecurityType.Option))
1251  {
1252  var symbol = new Symbol(SecurityIdentifier.GenerateEquity(ticker, market, mappingResolveDate: mappingResolveDate), ticker);
1253  return securityType == SecurityType.Option ? Symbol.CreateCanonicalOption(symbol) : symbol;
1254  }
1255  else
1256  {
1257  return Symbol.Create(ticker, securityType, market, baseDataType: dataType);
1258  }
1259  }
1260 
1261  private static List<string> SplitDataPath(string fileName)
1262  {
1263  var pathSeparators = new[] { '/', '\\' };
1264 
1265  // Removes file extension
1266  fileName = fileName.Replace(fileName.GetExtension(), string.Empty);
1267 
1268  // remove any relative file path
1269  while (fileName.First() == '.' || pathSeparators.Any(x => x == fileName.First()))
1270  {
1271  fileName = fileName.Remove(0, 1);
1272  }
1273 
1274  // split path into components
1275  return fileName.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList();
1276  }
1277 
1278  /// <summary>
1279  /// Aggregates a list of second/minute bars at the requested resolution
1280  /// </summary>
1281  /// <param name="bars">List of <see cref="TradeBar"/>s</param>
1282  /// <param name="symbol">Symbol of all tradeBars</param>
1283  /// <param name="resolution">Desired resolution for new <see cref="TradeBar"/>s</param>
1284  /// <returns>List of aggregated <see cref="TradeBar"/>s</returns>
1285  public static IEnumerable<TradeBar> AggregateTradeBars(IEnumerable<TradeBar> bars, Symbol symbol, TimeSpan resolution)
1286  {
1287  return Aggregate(new TradeBarConsolidator(resolution), bars, symbol);
1288  }
1289 
1290  /// <summary>
1291  /// Aggregates a list of second/minute bars at the requested resolution
1292  /// </summary>
1293  /// <param name="bars">List of <see cref="QuoteBar"/>s</param>
1294  /// <param name="symbol">Symbol of all QuoteBars</param>
1295  /// <param name="resolution">Desired resolution for new <see cref="QuoteBar"/>s</param>
1296  /// <returns>List of aggregated <see cref="QuoteBar"/>s</returns>
1297  public static IEnumerable<QuoteBar> AggregateQuoteBars(IEnumerable<QuoteBar> bars, Symbol symbol, TimeSpan resolution)
1298  {
1299  return Aggregate(new QuoteBarConsolidator(resolution), bars, symbol);
1300  }
1301 
1302  /// <summary>
1303  /// Aggregates a list of ticks at the requested resolution
1304  /// </summary>
1305  /// <param name="ticks">List of quote ticks</param>
1306  /// <param name="symbol">Symbol of all ticks</param>
1307  /// <param name="resolution">Desired resolution for new <see cref="QuoteBar"/>s</param>
1308  /// <returns>List of aggregated <see cref="QuoteBar"/>s</returns>
1309  public static IEnumerable<QuoteBar> AggregateTicks(IEnumerable<Tick> ticks, Symbol symbol, TimeSpan resolution)
1310  {
1311  return Aggregate(new TickQuoteBarConsolidator(resolution), ticks, symbol);
1312  }
1313 
1314  /// <summary>
1315  /// Aggregates a list of ticks at the requested resolution
1316  /// </summary>
1317  /// <param name="ticks">List of trade ticks</param>
1318  /// <param name="symbol">Symbol of all ticks</param>
1319  /// <param name="resolution">Desired resolution for new <see cref="TradeBar"/>s</param>
1320  /// <returns>List of aggregated <see cref="TradeBar"/>s</returns>
1321  public static IEnumerable<TradeBar> AggregateTicksToTradeBars(IEnumerable<Tick> ticks, Symbol symbol, TimeSpan resolution)
1322  {
1323  return Aggregate(new TickConsolidator(resolution), ticks, symbol);
1324  }
1325 
1326  /// <summary>
1327  /// Helper to separate filename and entry from a given key for DataProviders
1328  /// </summary>
1329  /// <param name="key">The key to parse</param>
1330  /// <param name="fileName">File name extracted</param>
1331  /// <param name="entryName">Entry name extracted</param>
1332  public static void ParseKey(string key, out string fileName, out string entryName)
1333  {
1334  // Default scenario, no entryName included in key
1335  entryName = null; // default to all entries
1336  fileName = key;
1337 
1338  if (key == null)
1339  {
1340  return;
1341  }
1342 
1343  // Try extracting an entry name; Anything after a # sign
1344  var hashIndex = key.LastIndexOf("#", StringComparison.Ordinal);
1345  if (hashIndex != -1)
1346  {
1347  entryName = key.Substring(hashIndex + 1);
1348  fileName = key.Substring(0, hashIndex);
1349  }
1350  }
1351 
1352  /// <summary>
1353  /// Helper method to aggregate ticks or bars into lower frequency resolutions
1354  /// </summary>
1355  /// <typeparam name="T">Output type</typeparam>
1356  /// <typeparam name="K">Input type</typeparam>
1357  /// <param name="consolidator">The consolidator to use</param>
1358  /// <param name="dataPoints">The data point source</param>
1359  /// <param name="symbol">The symbol to output</param>
1360  private static IEnumerable<T> Aggregate<T, K>(PeriodCountConsolidatorBase<K, T> consolidator, IEnumerable<K> dataPoints, Symbol symbol)
1361  where T : BaseData
1362  where K : BaseData
1363  {
1364  IBaseData lastAggregated = null;
1365  var getConsolidatedBar = () =>
1366  {
1367  if (lastAggregated != consolidator.Consolidated && consolidator.Consolidated != null)
1368  {
1369  // if there's a new aggregated bar we set the symbol & return it
1370  lastAggregated = consolidator.Consolidated;
1371  lastAggregated.Symbol = symbol;
1372  return lastAggregated;
1373  }
1374  return null;
1375  };
1376 
1377  foreach (var dataPoint in dataPoints)
1378  {
1379  consolidator.Update(dataPoint);
1380  var consolidated = getConsolidatedBar();
1381  if (consolidated != null)
1382  {
1383  yield return (T)consolidated;
1384  }
1385  }
1386 
1387  // flush any partial bar
1388  consolidator.Scan(Time.EndOfTime);
1389  var lastConsolidated = getConsolidatedBar();
1390  if (lastConsolidated != null)
1391  {
1392  yield return (T)lastConsolidated;
1393  }
1394 
1395  // cleanup
1396  consolidator.DisposeSafely();
1397  }
1398  }
1399 }