Lean  $LEAN_TAG$
Time.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 Newtonsoft.Json.Converters;
20 using NodaTime;
21 using QuantConnect.Logging;
23 using static QuantConnect.StringExtensions;
24 
25 namespace QuantConnect
26 {
27  /// <summary>
28  /// Time helper class collection for working with trading dates
29  /// </summary>
30  public static class Time
31  {
32  /// <summary>
33  /// Allows specifying an offset to trigger the tradable date event
34  /// </summary>
35  /// <remarks>Useful for delaying the tradable date event until new auxiliary data is available to refresh map and factor files</remarks>
36  public static TimeSpan LiveAuxiliaryDataOffset { get; set; } = TimeSpan.FromHours(8);
37 
38  /// <summary>
39  /// Provides a value far enough in the future the current computer hardware will have decayed :)
40  /// </summary>
41  /// <value>
42  /// new DateTime(2050, 12, 31)
43  /// </value>
44  public static readonly DateTime EndOfTime = new DateTime(2050, 12, 31);
45 
46  /// <summary>
47  /// Provides a time span based on <see cref="EndOfTime"/>
48  /// </summary>
49  public static TimeSpan EndOfTimeTimeSpan = new TimeSpan(EndOfTime.Ticks);
50 
51  /// <summary>
52  /// Provides a common and normalized start time for Lean data
53  /// </summary>
54  public static readonly DateTime Start = new DateTime(1998, 1, 2);
55 
56  /// <summary>
57  /// Provides a value far enough in the past that can be used as a lower bound on dates, 12/30/1899
58  /// </summary>
59  /// <value>
60  /// DateTime.FromOADate(0)
61  /// </value>
62  public static readonly DateTime BeginningOfTime = DateTime.FromOADate(0);
63 
64  /// <summary>
65  /// Provides a value large enough that we won't hit the limit, while small enough
66  /// we can still do math against it without checking everywhere for <see cref="TimeSpan.MaxValue"/>
67  /// </summary>
68  public static readonly TimeSpan MaxTimeSpan = TimeSpan.FromDays(1000*365);
69 
70  /// <summary>
71  /// One Year TimeSpan Period Constant
72  /// </summary>
73  /// <remarks>365 days</remarks>
74  public static readonly TimeSpan OneYear = TimeSpan.FromDays(365);
75 
76  /// <summary>
77  /// One Day TimeSpan Period Constant
78  /// </summary>
79  public static readonly TimeSpan OneDay = TimeSpan.FromDays(1);
80 
81  /// <summary>
82  /// One Hour TimeSpan Period Constant
83  /// </summary>
84  public static readonly TimeSpan OneHour = TimeSpan.FromHours(1);
85 
86  /// <summary>
87  /// One Minute TimeSpan Period Constant
88  /// </summary>
89  public static readonly TimeSpan OneMinute = TimeSpan.FromMinutes(1);
90 
91  /// <summary>
92  /// One Second TimeSpan Period Constant
93  /// </summary>
94  public static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
95 
96  /// <summary>
97  /// One Millisecond TimeSpan Period Constant
98  /// </summary>
99  public static readonly TimeSpan OneMillisecond = TimeSpan.FromMilliseconds(1);
100 
101  /// <summary>
102  /// Live charting is sensitive to timezone so need to convert the local system time to a UTC and display in browser as UTC.
103  /// </summary>
104  public struct DateTimeWithZone
105  {
106  private readonly DateTime utcDateTime;
107  private readonly TimeZoneInfo timeZone;
108 
109  /// <summary>
110  /// Initializes a new instance of the <see cref="QuantConnect.Time.DateTimeWithZone"/> struct.
111  /// </summary>
112  /// <param name="dateTime">Date time.</param>
113  /// <param name="timeZone">Time zone.</param>
114  public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
115  {
116  utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTime, timeZone);
117  this.timeZone = timeZone;
118  }
119 
120  /// <summary>
121  /// Gets the universal time.
122  /// </summary>
123  /// <value>The universal time.</value>
124  public DateTime UniversalTime { get { return utcDateTime; } }
125 
126  /// <summary>
127  /// Gets the time zone.
128  /// </summary>
129  /// <value>The time zone.</value>
130  public TimeZoneInfo TimeZone { get { return timeZone; } }
131 
132  /// <summary>
133  /// Gets the local time.
134  /// </summary>
135  /// <value>The local time.</value>
136  public DateTime LocalTime
137  {
138  get
139  {
140  return TimeZoneInfo.ConvertTime(utcDateTime, timeZone);
141  }
142  }
143  }
144 
145  private static readonly DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
146  private const long SecondToMillisecond = 1000;
147 
148  /// <summary>
149  /// Helper method to get the new live auxiliary data due time
150  /// </summary>
151  /// <returns>The due time for the new auxiliary data emission</returns>
152  public static TimeSpan GetNextLiveAuxiliaryDataDueTime()
153  {
154  return GetNextLiveAuxiliaryDataDueTime(DateTime.UtcNow);
155  }
156 
157  /// <summary>
158  /// Helper method to get the new live auxiliary data due time
159  /// </summary>
160  /// <param name="utcNow">The current utc time</param>
161  /// <returns>The due time for the new auxiliary data emission</returns>
162  public static TimeSpan GetNextLiveAuxiliaryDataDueTime(DateTime utcNow)
163  {
164  var nowNewYork = utcNow.ConvertFromUtc(TimeZones.NewYork);
165  if (nowNewYork.TimeOfDay < LiveAuxiliaryDataOffset)
166  {
167  return LiveAuxiliaryDataOffset - nowNewYork.TimeOfDay;
168  }
169  return nowNewYork.Date.AddDays(1).Add(+LiveAuxiliaryDataOffset) - nowNewYork;
170  }
171 
172  /// <summary>
173  /// Helper method to adjust a waiting time, in milliseconds, so it's uneven with the second turn around
174  /// </summary>
175  /// <param name="waitTimeMillis">The desired wait time</param>
176  /// <remarks>This is useful for real time performance in live trading. We want to avoid adding unnecessary cpu usage,
177  /// during periods where we know there will be cpu time demand, like a second turn around where data is emitted.</remarks>
178  /// <returns>The adjusted wait time</returns>
179  public static int GetSecondUnevenWait(int waitTimeMillis)
180  {
181  return DateTime.UtcNow.GetSecondUnevenWait(waitTimeMillis);
182  }
183 
184  /// <summary>
185  /// Helper method to adjust a waiting time, in milliseconds, so it's uneven with the second turn around
186  /// </summary>
187  /// <param name="now">The current time</param>
188  /// <param name="waitTimeMillis">The desired wait time</param>
189  /// <remarks>This is useful for real time performance in live trading. We want to avoid adding unnecessary cpu usage,
190  /// during periods where we know there will be cpu time demand, like a second turn around where data is emitted.</remarks>
191  /// <returns>The adjusted wait time</returns>
192  public static int GetSecondUnevenWait(this DateTime now, int waitTimeMillis)
193  {
194  var wakeUpTime = now.AddMilliseconds(waitTimeMillis);
195  if (wakeUpTime.Millisecond < 100 || wakeUpTime.Millisecond > 900)
196  {
197  // if we are going to wake before/after the next second we add an offset to avoid it
198  var offsetMillis = waitTimeMillis >= 1000 ? 500 : 100;
199  return waitTimeMillis + offsetMillis;
200  }
201  return waitTimeMillis;
202  }
203 
204  /// <summary>
205  /// Create a C# DateTime from a UnixTimestamp
206  /// </summary>
207  /// <param name="unixTimeStamp">Double unix timestamp (Time since Midnight Jan 1 1970)</param>
208  /// <returns>C# date timeobject</returns>
209  public static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
210  {
211  DateTime time;
212  try
213  {
214  var ticks = unixTimeStamp * TimeSpan.TicksPerSecond;
215  time = EpochTime.AddTicks((long)ticks);
216  }
217  catch (Exception err)
218  {
219  Log.Error(err, Invariant($"UnixTimeStamp: {unixTimeStamp}"));
220  time = DateTime.Now;
221  }
222  return time;
223  }
224 
225  /// <summary>
226  /// Create a C# DateTime from a UnixTimestamp
227  /// </summary>
228  /// <param name="unixTimeStamp">Decimal unix timestamp (Time since Midnight Jan 1 1970)</param>
229  /// <returns>C# date time object</returns>
230  public static DateTime UnixTimeStampToDateTime(decimal unixTimeStamp)
231  {
232  return UnixMillisecondTimeStampToDateTime(unixTimeStamp * SecondToMillisecond);
233  }
234 
235  /// <summary>
236  /// Create a C# DateTime from a UnixTimestamp
237  /// </summary>
238  /// <param name="unixTimeStamp">Long unix timestamp (Time since Midnight Jan 1 1970)</param>
239  /// <returns>C# date time object</returns>
240  public static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
241  {
242  return UnixTimeStampToDateTime(Convert.ToDecimal(unixTimeStamp));
243  }
244 
245  /// <summary>
246  /// Create a C# DateTime from a UnixTimestamp
247  /// </summary>
248  /// <param name="unixTimeStamp">Decimal unix timestamp (Time since Midnight Jan 1 1970) in milliseconds</param>
249  /// <returns>C# date time object</returns>
250  public static DateTime UnixMillisecondTimeStampToDateTime(decimal unixTimeStamp)
251  {
252  DateTime time;
253  try
254  {
255  // Any residual decimal numbers that remain are nanoseconds from [0, 100) nanoseconds.
256  // If we cast to (long), only the integer component of the decimal is taken, and can
257  // potentially result in look-ahead bias in increments of 100 nanoseconds, i.e. 1 DateTime tick.
258  var ticks = Math.Ceiling(unixTimeStamp * TimeSpan.TicksPerMillisecond);
259  time = EpochTime.AddTicks((long)ticks);
260  }
261  catch (Exception err)
262  {
263  Log.Error(err, Invariant($"UnixTimeStamp: {unixTimeStamp}"));
264  time = DateTime.Now;
265  }
266  return time;
267  }
268 
269  /// <summary>
270  /// Create a C# DateTime from a UnixTimestamp
271  /// </summary>
272  /// <param name="unixTimeStamp">Int64 unix timestamp (Time since Midnight Jan 1 1970) in nanoseconds</param>
273  /// <returns>C# date time object</returns>
274  public static DateTime UnixNanosecondTimeStampToDateTime(long unixTimeStamp)
275  {
276  DateTime time;
277  try
278  {
279  var ticks = unixTimeStamp / 100;
280  time = EpochTime.AddTicks(ticks);
281  }
282  catch (Exception err)
283  {
284  Log.Error(err, Invariant($"UnixTimeStamp: {unixTimeStamp}"));
285  time = DateTime.Now;
286  }
287  return time;
288  }
289 
290  /// <summary>
291  /// Convert a Datetime to Unix Timestamp
292  /// </summary>
293  /// <param name="time">C# datetime object</param>
294  /// <returns>Double unix timestamp</returns>
295  public static double DateTimeToUnixTimeStamp(DateTime time)
296  {
297  double timestamp = 0;
298  try
299  {
300  timestamp = (time - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds;
301  }
302  catch (Exception err)
303  {
304  Log.Error(err, Invariant($"{time:o}"));
305  }
306  return timestamp;
307  }
308 
309  /// <summary>
310  /// Convert a Datetime to Unix Timestamp
311  /// </summary>
312  /// <param name="time">C# datetime object</param>
313  /// <returns>Double unix timestamp</returns>
314  public static double DateTimeToUnixTimeStampMilliseconds(DateTime time)
315  {
316  double timestamp = 0;
317  try
318  {
319  timestamp = (time - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
320  }
321  catch (Exception err)
322  {
323  Log.Error(err, Invariant($"{time:o}"));
324  }
325  return timestamp;
326  }
327 
328  /// <summary>
329  /// Convert a Datetime to Unix Timestamp
330  /// </summary>
331  /// <param name="time">C# datetime object</param>
332  /// <returns>Int64 unix timestamp</returns>
333  public static long DateTimeToUnixTimeStampNanoseconds(DateTime time)
334  {
335  long timestamp = 0;
336  try
337  {
338  timestamp = (time - new DateTime(1970, 1, 1, 0, 0, 0, 0)).Ticks * 100;
339  }
340  catch (Exception err)
341  {
342  Log.Error(err, Invariant($"{time:o}"));
343  }
344  return timestamp;
345  }
346 
347  /// <summary>
348  /// Get the current time as a unix timestamp
349  /// </summary>
350  /// <returns>Double value of the unix as UTC timestamp</returns>
351  public static double TimeStamp()
352  {
353  return DateTimeToUnixTimeStamp(DateTime.UtcNow);
354  }
355 
356  /// <summary>
357  /// Returns the timespan with the larger value
358  /// </summary>
359  public static TimeSpan Max(TimeSpan one, TimeSpan two)
360  {
361  return TimeSpan.FromTicks(Math.Max(one.Ticks, two.Ticks));
362  }
363 
364  /// <summary>
365  /// Returns the timespan with the smaller value
366  /// </summary>
367  public static TimeSpan Min(TimeSpan one, TimeSpan two)
368  {
369  return TimeSpan.FromTicks(Math.Min(one.Ticks, two.Ticks));
370  }
371 
372  /// <summary>
373  /// Returns the larger of two date times
374  /// </summary>
375  public static DateTime Max(DateTime one, DateTime two)
376  {
377  return one > two ? one : two;
378  }
379 
380  /// <summary>
381  /// Returns the smaller of two date times
382  /// </summary>
383  public static DateTime Min(DateTime one, DateTime two)
384  {
385  return one < two ? one : two;
386  }
387 
388  /// <summary>
389  /// Multiplies the specified interval by the multiplier
390  /// </summary>
391  /// <param name="interval">The interval to be multiplied, such as TimeSpan.FromSeconds(1)</param>
392  /// <param name="multiplier">The number of times to multiply the interval</param>
393  /// <returns>The multiplied interval, such as 1s*5 = 5s</returns>
394  public static TimeSpan Multiply(this TimeSpan interval, double multiplier)
395  {
396  return TimeSpan.FromTicks((long) (interval.Ticks * multiplier));
397  }
398 
399  /// <summary>
400  /// Parse a standard YY MM DD date into a DateTime. Attempt common date formats
401  /// </summary>
402  /// <param name="dateToParse">String date time to parse</param>
403  /// <returns>Date time</returns>
404  public static DateTime ParseDate(string dateToParse)
405  {
406  try
407  {
408  //First try the exact options:
409  DateTime date;
410  if (DateTime.TryParseExact(dateToParse, DateFormat.SixCharacter, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
411  {
412  return date;
413  }
414  if (DateTime.TryParseExact(dateToParse, DateFormat.EightCharacter, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
415  {
416  return date;
417  }
418  if (DateTime.TryParseExact(dateToParse, DateFormat.TwelveCharacter, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
419  {
420  return date;
421  }
422  if (DateTime.TryParseExact(dateToParse.SafeSubstring(0, 19), DateFormat.JsonFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
423  {
424  return date;
425  }
426  if (DateTime.TryParseExact(dateToParse, DateFormat.USShort, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
427  {
428  return date;
429  }
430  if (DateTime.TryParseExact(dateToParse, DateFormat.USShortDateOnly, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
431  {
432  return date;
433  }
434  if (DateTime.TryParseExact(dateToParse, DateFormat.US, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
435  {
436  return date;
437  }
438  if (DateTime.TryParseExact(dateToParse, DateFormat.USDateOnly, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
439  {
440  return date;
441  }
442  if (DateTime.TryParse(dateToParse, out date))
443  {
444  return date;
445  }
446  }
447  catch (Exception err)
448  {
449  Log.Error(err);
450  }
451 
452  return DateTime.Now;
453  }
454 
455  /// <summary>
456  /// Parse a standard YY MM DD date into a DateTime. Attempt common date formats
457  /// </summary>
458  /// <param name="dateToParse">String date time to parse</param>
459  /// <returns>Date time</returns>
460  public static DateTime ParseFIXUtcTimestamp(string dateToParse)
461  {
462  try
463  {
464  //First try the exact options:
465  DateTime date;
466  if (DateTime.TryParseExact(dateToParse, DateFormat.FIX, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
467  {
468  return date;
469  }
470  if (DateTime.TryParseExact(dateToParse, DateFormat.FIXWithMillisecond, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
471  {
472  return date;
473  }
474  }
475  catch (Exception err)
476  {
477  Log.Error(err);
478  }
479 
480  return DateTime.UtcNow;
481  }
482 
483  /// <summary>
484  /// Define an enumerable date time range using the given time step
485  /// </summary>
486  /// <param name="from">DateTime start date time</param>
487  /// <param name="thru">DateTime end date time</param>
488  /// <returns>Enumerable date time range</returns>
489  public static IEnumerable<DateTime> DateTimeRange(DateTime from, DateTime thru, TimeSpan step)
490  {
491  for (var dateTime = from; dateTime <= thru; dateTime = dateTime.Add(step))
492  yield return dateTime;
493  }
494 
495  /// <summary>
496  /// Define an enumerable date range and return each date as a datetime object in the date range
497  /// </summary>
498  /// <param name="from">DateTime start date</param>
499  /// <param name="thru">DateTime end date</param>
500  /// <returns>Enumerable date range</returns>
501  public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
502  {
503  return DateTimeRange(from.Date, thru.Date, TimeSpan.FromDays(1));
504  }
505 
506 
507  /// <summary>
508  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
509  /// </summary>
510  /// <param name="securities">Securities we have in portfolio</param>
511  /// <param name="from">Start date</param>
512  /// <param name="thru">End date</param>
513  /// <returns>Enumerable date range</returns>
514  public static IEnumerable<DateTime> EachTradeableDay(ICollection<Security> securities, DateTime from, DateTime thru)
515  {
516  for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
517  {
518  if (TradableDate(securities, day))
519  {
520  yield return day;
521  }
522  }
523  }
524 
525 
526  /// <summary>
527  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
528  /// </summary>
529  /// <param name="security">The security to get tradeable dates for</param>
530  /// <param name="from">Start date</param>
531  /// <param name="thru">End date</param>
532  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
533  /// <returns>Enumerable date range</returns>
534  public static IEnumerable<DateTime> EachTradeableDay(Security security, DateTime from, DateTime thru, bool extendedMarketHours = false)
535  {
536  return EachTradeableDay(security.Exchange.Hours, from, thru, extendedMarketHours);
537  }
538 
539  /// <summary>
540  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
541  /// </summary>
542  /// <param name="exchange">The security to get tradeable dates for</param>
543  /// <param name="from">Start date</param>
544  /// <param name="thru">End date</param>
545  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
546  /// <returns>Enumerable date range</returns>
547  public static IEnumerable<DateTime> EachTradeableDay(SecurityExchangeHours exchange, DateTime from, DateTime thru, bool extendedMarketHours = false)
548  {
549  for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
550  {
551  if (exchange.IsDateOpen(day, extendedMarketHours))
552  {
553  yield return day;
554  }
555  }
556  }
557 
558  /// <summary>
559  /// Define an enumerable date range of tradeable dates but expressed in a different time zone.
560  /// </summary>
561  /// <remarks>
562  /// This is mainly used to bridge the gap between exchange time zone and data time zone for file written to disk. The returned
563  /// enumerable of dates is guaranteed to be the same size or longer than those generated via <see cref="EachTradeableDay(ICollection{Security},DateTime,DateTime)"/>
564  /// </remarks>
565  /// <param name="exchange">The exchange hours</param>
566  /// <param name="from">The start time in the exchange time zone</param>
567  /// <param name="thru">The end time in the exchange time zone (inclusive of the final day)</param>
568  /// <param name="timeZone">The timezone to project the dates into (inclusive of the final day)</param>
569  /// <param name="includeExtendedMarketHours">True to include extended market hours trading in the search, false otherwise</param>
570  /// <returns></returns>
571  public static IEnumerable<DateTime> EachTradeableDayInTimeZone(SecurityExchangeHours exchange, DateTime from, DateTime thru, DateTimeZone timeZone, bool includeExtendedMarketHours = true)
572  {
573  var currentExchangeTime = from;
574  thru = thru.Date.AddDays(1); // we want to include the full thru date
575  while (currentExchangeTime < thru)
576  {
577  // take steps of max size of one day in the data time zone
578  var currentInTimeZone = currentExchangeTime.ConvertTo(exchange.TimeZone, timeZone);
579  var currentInTimeZoneEod = currentInTimeZone.Date.AddDays(1);
580 
581  var currentExchangeTimeEod = currentInTimeZoneEod.ConvertTo(timeZone, exchange.TimeZone);
582 
583  // don't pass the end
584  if (currentExchangeTimeEod > thru)
585  {
586  currentExchangeTimeEod = thru;
587  }
588 
589  // perform market open checks in the exchange time zone
590  if (exchange.IsOpen(currentExchangeTime, currentExchangeTimeEod, includeExtendedMarketHours))
591  {
592  yield return currentInTimeZone.Date;
593  }
594 
595  currentExchangeTime = currentExchangeTimeEod;
596  }
597  }
598 
599  /// <summary>
600  /// Make sure this date is not a holiday, or weekend for the securities in this algorithm.
601  /// </summary>
602  /// <param name="securities">Security manager from the algorithm</param>
603  /// <param name="day">DateTime to check if trade-able.</param>
604  /// <returns>True if tradeable date</returns>
605  public static bool TradableDate(IEnumerable<Security> securities, DateTime day)
606  {
607  try
608  {
609  foreach (var security in securities)
610  {
611  if (security.Exchange.DateIsOpen(day.Date)) return true;
612  }
613  }
614  catch (Exception err)
615  {
616  Log.Error(err);
617  }
618  return false;
619  }
620 
621 
622  /// <summary>
623  /// Could of the number of tradeable dates within this period.
624  /// </summary>
625  /// <param name="securities">Securities we're trading</param>
626  /// <param name="start">Start of Date Loop</param>
627  /// <param name="finish">End of Date Loop</param>
628  /// <returns>Number of dates</returns>
629  public static int TradeableDates(ICollection<Security> securities, DateTime start, DateTime finish)
630  {
631  var count = 0;
632  Log.Trace(Invariant($"Time.TradeableDates(): {Messages.Time.SecurityCount(securities.Count)}"));
633  try
634  {
635  foreach (var day in EachDay(start, finish))
636  {
637  if (TradableDate(securities, day))
638  {
639  count++;
640  }
641  }
642  }
643  catch (Exception err)
644  {
645  Log.Error(err);
646  }
647  return count;
648  }
649 
650  /// <summary>
651  /// Determines the start time required to produce the requested number of bars and the given size
652  /// </summary>
653  /// <param name="exchangeHours">The exchange hours used to test for market open hours</param>
654  /// <param name="end">The end time of the last bar over the requested period</param>
655  /// <param name="barSize">The length of each bar</param>
656  /// <param name="barCount">The number of bars requested</param>
657  /// <param name="extendedMarketHours">True to allow extended market hours bars, otherwise false for only normal market hours</param>
658  /// <param name="dataTimeZone">Timezone for this data</param>
659  /// <param name="dailyPreciseEndTime">True if daily strict end times are enabled</param>
660  /// <returns>The start time that would provide the specified number of bars ending at the specified end time, rounded down by the requested bar size</returns>
661  public static DateTime GetStartTimeForTradeBars(SecurityExchangeHours exchangeHours, DateTime end, TimeSpan barSize, int barCount,
662  bool extendedMarketHours, DateTimeZone dataTimeZone, bool dailyPreciseEndTime = false)
663  {
664  if (barSize <= TimeSpan.Zero)
665  {
666  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
667  }
668 
669  var current = end;
670  if (dailyPreciseEndTime && barSize == OneDay)
671  {
672  if (exchangeHours.IsDateOpen(current) && exchangeHours.GetNextMarketClose(current.Date, extendedMarketHours) > current)
673  {
674  // we round down, because data for today isn't ready/wont pass through current time.
675  // for example, for equities, current time is 3pm, 1 bar in daily should be yesterdays, today does not count
676  current = end.RoundDownInTimeZone(barSize, exchangeHours.TimeZone, dataTimeZone);
677  }
678  }
679  else
680  {
681  // need to round down in data timezone because data is stored in this time zone but only if not doing daily resolution or
682  // dailyPreciseEndTime is disabled because if we round down we might include 2 bars when we want 1, for example: say
683  // current is monday 8pm NY, if we round down we get minight monday which will return false as open, so we will return
684  // friday and monday data for daily equity, when we want only monday.
685  current = end.RoundDownInTimeZone(barSize, exchangeHours.TimeZone, dataTimeZone);
686  }
687 
688  for (int i = 0; i < barCount;)
689  {
690  var previous = current;
691  current = current - barSize;
692  if (exchangeHours.IsOpen(current, previous, extendedMarketHours))
693  {
694  i++;
695  }
696  }
697  return current;
698  }
699 
700  /// <summary>
701  /// Determines the end time at which the requested number of bars of the given will have elapsed.
702  /// NOTE: The start time is not discretized by barSize units like is done in <see cref="GetStartTimeForTradeBars"/>
703  /// </summary>
704  /// <param name="exchangeHours">The exchange hours used to test for market open hours</param>
705  /// <param name="start">The end time of the last bar over the requested period</param>
706  /// <param name="barSize">The length of each bar</param>
707  /// <param name="barCount">The number of bars requested</param>
708  /// <param name="extendedMarketHours">True to allow extended market hours bars, otherwise false for only normal market hours</param>
709  /// <returns>The start time that would provide the specified number of bars ending at the specified end time, rounded down by the requested bar size</returns>
710  public static DateTime GetEndTimeForTradeBars(SecurityExchangeHours exchangeHours, DateTime start, TimeSpan barSize, int barCount, bool extendedMarketHours)
711  {
712  if (barSize <= TimeSpan.Zero)
713  {
714  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
715  }
716 
717  var current = start;
718  if (barSize == OneDay)
719  {
720  for (int i = 0; i < barCount;)
721  {
722  current = current + OneDay;
723  if (exchangeHours.IsDateOpen(current))
724  {
725  i++;
726  }
727  }
728 
729  return current;
730  }
731 
732  for (int i = 0; i < barCount;)
733  {
734  var previous = current;
735  current = current + barSize;
736  if (exchangeHours.IsOpen(previous, current, extendedMarketHours))
737  {
738  i++;
739  }
740  }
741  return current;
742  }
743 
744  /// <summary>
745  /// Gets the number of trade bars of the specified <paramref name="barSize"/> that fit between the <paramref name="start"/> and <paramref name="end"/>
746  /// </summary>
747  /// <param name="exchangeHours">The exchange used to test for market open hours</param>
748  /// <param name="start">The start time of the interval in the exchange time zone</param>
749  /// <param name="end">The end time of the interval in the exchange time zone</param>
750  /// <param name="barSize">The step size used to count number of bars between start and end</param>
751  /// <returns>The number of bars of the specified size between start and end times</returns>
752  public static int GetNumberOfTradeBarsInInterval(SecurityExchangeHours exchangeHours, DateTime start, DateTime end, TimeSpan barSize)
753  {
754  if (barSize <= TimeSpan.Zero)
755  {
756  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
757  }
758 
759  var count = 0;
760  var current = start;
761  if (barSize == OneDay)
762  {
763  while (current < end)
764  {
765  if (exchangeHours.IsDateOpen(current))
766  {
767  count++;
768  }
769 
770  current = current + OneDay;
771  }
772 
773  return count;
774  }
775 
776  while (current < end)
777  {
778  var previous = current;
779  current = current + barSize;
780  if (exchangeHours.IsOpen(previous, current, false))
781  {
782  count++;
783  }
784  }
785 
786  return count;
787  }
788 
789  /// <summary>
790  /// Normalizes the current time within the specified period
791  /// time = start => 0
792  /// time = start + period => 1
793  /// </summary>
794  /// <param name="start">The start time of the range</param>
795  /// <param name="current">The current time we seek to normalize</param>
796  /// <param name="period">The time span of the range</param>
797  /// <returns>The normalized time</returns>
798  public static double NormalizeInstantWithinRange(DateTime start, DateTime current, TimeSpan period)
799  {
800  // normalization of a point time only has a value at that specific point
801  if (period == TimeSpan.Zero)
802  {
803  return start == current ? 1 : 0;
804  }
805 
806  var delta = (current - start).TotalSeconds;
807  return delta / period.TotalSeconds;
808  }
809 
810  /// <summary>
811  /// Normalizes the step size as a percentage of the period.
812  /// </summary>
813  /// <param name="period">The period to normalize against</param>
814  /// <param name="stepSize">The step size to be normaized</param>
815  /// <returns>The normalized step size as a percentage of the period</returns>
816  public static double NormalizeTimeStep(TimeSpan period, TimeSpan stepSize)
817  {
818  // normalization of a time step for an instantaneous period will always be zero
819  if (period == TimeSpan.Zero)
820  {
821  return 0;
822  }
823 
824  return stepSize.TotalSeconds / period.TotalSeconds;
825  }
826 
827  /// <summary>
828  /// Gets the absolute value of the specified time span
829  /// </summary>
830  /// <param name="timeSpan">Time span whose absolute value we seek</param>
831  /// <returns>The absolute value of the specified time span</returns>
832  public static TimeSpan Abs(this TimeSpan timeSpan)
833  {
834  return TimeSpan.FromTicks(Math.Abs(timeSpan.Ticks));
835  }
836 
837  /// <summary>
838  /// Helper method to deserialize month/year
839  /// </summary>
840  public class MonthYearJsonConverter : IsoDateTimeConverter
841  {
842  /// <summary>
843  /// Creates a new instance
844  /// </summary>
846  {
847  DateTimeFormat = @"MM/yy";
848  }
849  }
850  }
851 }