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 range and return each date as a datetime object in the date range
485  /// </summary>
486  /// <param name="from">DateTime start date</param>
487  /// <param name="thru">DateTime end date</param>
488  /// <returns>Enumerable date range</returns>
489  public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
490  {
491  for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
492  yield return day;
493  }
494 
495 
496  /// <summary>
497  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
498  /// </summary>
499  /// <param name="securities">Securities we have in portfolio</param>
500  /// <param name="from">Start date</param>
501  /// <param name="thru">End date</param>
502  /// <returns>Enumerable date range</returns>
503  public static IEnumerable<DateTime> EachTradeableDay(ICollection<Security> securities, DateTime from, DateTime thru)
504  {
505  for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
506  {
507  if (TradableDate(securities, day))
508  {
509  yield return day;
510  }
511  }
512  }
513 
514 
515  /// <summary>
516  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
517  /// </summary>
518  /// <param name="security">The security to get tradeable dates for</param>
519  /// <param name="from">Start date</param>
520  /// <param name="thru">End date</param>
521  /// <returns>Enumerable date range</returns>
522  public static IEnumerable<DateTime> EachTradeableDay(Security security, DateTime from, DateTime thru)
523  {
524  return EachTradeableDay(security.Exchange.Hours, from, thru);
525  }
526 
527 
528  /// <summary>
529  /// Define an enumerable date range of tradeable dates - skip the holidays and weekends when securities in this algorithm don't trade.
530  /// </summary>
531  /// <param name="exchange">The security to get tradeable dates for</param>
532  /// <param name="from">Start date</param>
533  /// <param name="thru">End date</param>
534  /// <returns>Enumerable date range</returns>
535  public static IEnumerable<DateTime> EachTradeableDay(SecurityExchangeHours exchange, DateTime from, DateTime thru)
536  {
537  for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
538  {
539  if (exchange.IsDateOpen(day))
540  {
541  yield return day;
542  }
543  }
544  }
545 
546  /// <summary>
547  /// Define an enumerable date range of tradeable dates but expressed in a different time zone.
548  /// </summary>
549  /// <remarks>
550  /// This is mainly used to bridge the gap between exchange time zone and data time zone for file written to disk. The returned
551  /// enumerable of dates is guaranteed to be the same size or longer than those generated via <see cref="EachTradeableDay(ICollection{Security},DateTime,DateTime)"/>
552  /// </remarks>
553  /// <param name="exchange">The exchange hours</param>
554  /// <param name="from">The start time in the exchange time zone</param>
555  /// <param name="thru">The end time in the exchange time zone (inclusive of the final day)</param>
556  /// <param name="timeZone">The timezone to project the dates into (inclusive of the final day)</param>
557  /// <param name="includeExtendedMarketHours">True to include extended market hours trading in the search, false otherwise</param>
558  /// <returns></returns>
559  public static IEnumerable<DateTime> EachTradeableDayInTimeZone(SecurityExchangeHours exchange, DateTime from, DateTime thru, DateTimeZone timeZone, bool includeExtendedMarketHours = true)
560  {
561  var currentExchangeTime = from;
562  thru = thru.Date.AddDays(1); // we want to include the full thru date
563  while (currentExchangeTime < thru)
564  {
565  // take steps of max size of one day in the data time zone
566  var currentInTimeZone = currentExchangeTime.ConvertTo(exchange.TimeZone, timeZone);
567  var currentInTimeZoneEod = currentInTimeZone.Date.AddDays(1);
568 
569  var currentExchangeTimeEod = currentInTimeZoneEod.ConvertTo(timeZone, exchange.TimeZone);
570 
571  // don't pass the end
572  if (currentExchangeTimeEod > thru)
573  {
574  currentExchangeTimeEod = thru;
575  }
576 
577  // perform market open checks in the exchange time zone
578  if (exchange.IsOpen(currentExchangeTime, currentExchangeTimeEod, includeExtendedMarketHours))
579  {
580  yield return currentInTimeZone.Date;
581  }
582 
583  currentExchangeTime = currentExchangeTimeEod;
584  }
585  }
586 
587  /// <summary>
588  /// Make sure this date is not a holiday, or weekend for the securities in this algorithm.
589  /// </summary>
590  /// <param name="securities">Security manager from the algorithm</param>
591  /// <param name="day">DateTime to check if trade-able.</param>
592  /// <returns>True if tradeable date</returns>
593  public static bool TradableDate(IEnumerable<Security> securities, DateTime day)
594  {
595  try
596  {
597  foreach (var security in securities)
598  {
599  if (security.Exchange.DateIsOpen(day.Date)) return true;
600  }
601  }
602  catch (Exception err)
603  {
604  Log.Error(err);
605  }
606  return false;
607  }
608 
609 
610  /// <summary>
611  /// Could of the number of tradeable dates within this period.
612  /// </summary>
613  /// <param name="securities">Securities we're trading</param>
614  /// <param name="start">Start of Date Loop</param>
615  /// <param name="finish">End of Date Loop</param>
616  /// <returns>Number of dates</returns>
617  public static int TradeableDates(ICollection<Security> securities, DateTime start, DateTime finish)
618  {
619  var count = 0;
620  Log.Trace(Invariant($"Time.TradeableDates(): {Messages.Time.SecurityCount(securities.Count)}"));
621  try
622  {
623  foreach (var day in EachDay(start, finish))
624  {
625  if (TradableDate(securities, day))
626  {
627  count++;
628  }
629  }
630  }
631  catch (Exception err)
632  {
633  Log.Error(err);
634  }
635  return count;
636  }
637 
638  /// <summary>
639  /// Determines the start time required to produce the requested number of bars and the given size
640  /// </summary>
641  /// <param name="exchangeHours">The exchange hours used to test for market open hours</param>
642  /// <param name="end">The end time of the last bar over the requested period</param>
643  /// <param name="barSize">The length of each bar</param>
644  /// <param name="barCount">The number of bars requested</param>
645  /// <param name="extendedMarketHours">True to allow extended market hours bars, otherwise false for only normal market hours</param>
646  /// <param name="dataTimeZone">Timezone for this data</param>
647  /// <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>
648  public static DateTime GetStartTimeForTradeBars(SecurityExchangeHours exchangeHours, DateTime end, TimeSpan barSize, int barCount, bool extendedMarketHours, DateTimeZone dataTimeZone)
649  {
650  if (barSize <= TimeSpan.Zero)
651  {
652  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
653  }
654 
655  // need to round down in data timezone because data is stored in this time zone
656  var current = end.RoundDownInTimeZone(barSize, exchangeHours.TimeZone, dataTimeZone);
657  for (int i = 0; i < barCount;)
658  {
659  var previous = current;
660  current = current - barSize;
661  if (exchangeHours.IsOpen(current, previous, extendedMarketHours))
662  {
663  i++;
664  }
665  }
666  return current;
667  }
668 
669  /// <summary>
670  /// Determines the end time at which the requested number of bars of the given will have elapsed.
671  /// NOTE: The start time is not discretized by barSize units like is done in <see cref="GetStartTimeForTradeBars"/>
672  /// </summary>
673  /// <param name="exchangeHours">The exchange hours used to test for market open hours</param>
674  /// <param name="start">The end time of the last bar over the requested period</param>
675  /// <param name="barSize">The length of each bar</param>
676  /// <param name="barCount">The number of bars requested</param>
677  /// <param name="extendedMarketHours">True to allow extended market hours bars, otherwise false for only normal market hours</param>
678  /// <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>
679  public static DateTime GetEndTimeForTradeBars(SecurityExchangeHours exchangeHours, DateTime start, TimeSpan barSize, int barCount, bool extendedMarketHours)
680  {
681  if (barSize <= TimeSpan.Zero)
682  {
683  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
684  }
685 
686  var current = start;
687  if (barSize == OneDay)
688  {
689  for (int i = 0; i < barCount;)
690  {
691  current = current + OneDay;
692  if (exchangeHours.IsDateOpen(current))
693  {
694  i++;
695  }
696  }
697 
698  return current;
699  }
700 
701  for (int i = 0; i < barCount;)
702  {
703  var previous = current;
704  current = current + barSize;
705  if (exchangeHours.IsOpen(previous, current, extendedMarketHours))
706  {
707  i++;
708  }
709  }
710  return current;
711  }
712 
713  /// <summary>
714  /// Gets the number of trade bars of the specified <paramref name="barSize"/> that fit between the <paramref name="start"/> and <paramref name="end"/>
715  /// </summary>
716  /// <param name="exchangeHours">The exchange used to test for market open hours</param>
717  /// <param name="start">The start time of the interval in the exchange time zone</param>
718  /// <param name="end">The end time of the interval in the exchange time zone</param>
719  /// <param name="barSize">The step size used to count number of bars between start and end</param>
720  /// <returns>The number of bars of the specified size between start and end times</returns>
721  public static int GetNumberOfTradeBarsInInterval(SecurityExchangeHours exchangeHours, DateTime start, DateTime end, TimeSpan barSize)
722  {
723  if (barSize <= TimeSpan.Zero)
724  {
725  throw new ArgumentException(Messages.Time.InvalidBarSize, nameof(barSize));
726  }
727 
728  var count = 0;
729  var current = start;
730  if (barSize == OneDay)
731  {
732  while (current < end)
733  {
734  if (exchangeHours.IsDateOpen(current))
735  {
736  count++;
737  }
738 
739  current = current + OneDay;
740  }
741 
742  return count;
743  }
744 
745  while (current < end)
746  {
747  var previous = current;
748  current = current + barSize;
749  if (exchangeHours.IsOpen(previous, current, false))
750  {
751  count++;
752  }
753  }
754 
755  return count;
756  }
757 
758  /// <summary>
759  /// Normalizes the current time within the specified period
760  /// time = start => 0
761  /// time = start + period => 1
762  /// </summary>
763  /// <param name="start">The start time of the range</param>
764  /// <param name="current">The current time we seek to normalize</param>
765  /// <param name="period">The time span of the range</param>
766  /// <returns>The normalized time</returns>
767  public static double NormalizeInstantWithinRange(DateTime start, DateTime current, TimeSpan period)
768  {
769  // normalization of a point time only has a value at that specific point
770  if (period == TimeSpan.Zero)
771  {
772  return start == current ? 1 : 0;
773  }
774 
775  var delta = (current - start).TotalSeconds;
776  return delta / period.TotalSeconds;
777  }
778 
779  /// <summary>
780  /// Normalizes the step size as a percentage of the period.
781  /// </summary>
782  /// <param name="period">The period to normalize against</param>
783  /// <param name="stepSize">The step size to be normaized</param>
784  /// <returns>The normalized step size as a percentage of the period</returns>
785  public static double NormalizeTimeStep(TimeSpan period, TimeSpan stepSize)
786  {
787  // normalization of a time step for an instantaneous period will always be zero
788  if (period == TimeSpan.Zero)
789  {
790  return 0;
791  }
792 
793  return stepSize.TotalSeconds / period.TotalSeconds;
794  }
795 
796  /// <summary>
797  /// Gets the absolute value of the specified time span
798  /// </summary>
799  /// <param name="timeSpan">Time span whose absolute value we seek</param>
800  /// <returns>The absolute value of the specified time span</returns>
801  public static TimeSpan Abs(this TimeSpan timeSpan)
802  {
803  return TimeSpan.FromTicks(Math.Abs(timeSpan.Ticks));
804  }
805 
806  /// <summary>
807  /// Helper method to deserialize month/year
808  /// </summary>
809  public class MonthYearJsonConverter : IsoDateTimeConverter
810  {
811  /// <summary>
812  /// Creates a new instance
813  /// </summary>
815  {
816  DateTimeFormat = @"MM/yy";
817  }
818  }
819  }
820 }