Lean  $LEAN_TAG$
TimeRules.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15 */
16 
17 using System;
18 using NodaTime;
19 using System.Linq;
21 using System.Collections.Generic;
22 using static QuantConnect.StringExtensions;
23 
25 {
26  /// <summary>
27  /// Helper class used to provide better syntax when defining time rules
28  /// </summary>
30  {
31  /// <summary>
32  /// Initializes a new instance of the <see cref="TimeRules"/> helper class
33  /// </summary>
34  /// <param name="securities">The security manager</param>
35  /// <param name="timeZone">The algorithm's default time zone</param>
36  /// <param name="marketHoursDatabase">The market hours database instance to use</param>
37  public TimeRules(SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
38  : base(securities, timeZone, marketHoursDatabase)
39  {
40  }
41 
42  /// <summary>
43  /// Sets the default time zone
44  /// </summary>
45  /// <param name="timeZone">The time zone to use for helper methods that can't resolve a time zone</param>
46  public void SetDefaultTimeZone(DateTimeZone timeZone)
47  {
48  TimeZone = timeZone;
49  }
50 
51  /// <summary>
52  /// Specifies an event should fire at the current time
53  /// </summary>
54  public ITimeRule Now => new FuncTimeRule("Now", dates => {
55  return dates.Select(date =>
56  {
57  // we ignore the given date and just use the current time, why? if the algorithm used 'DateRules.Today'
58  // we get the algorithms first 'Date', which during warmup might not be a complete date, depending on the warmup period
59  // and since Today returns dates we might get a time in the past which get's ignored. See 'WarmupTrainRegressionAlgorithm'
60  // which reproduces GH issue #6410
61  return Securities.UtcTime;
62  });
63  });
64 
65  /// <summary>
66  /// Convenience property for running a scheduled event at midnight in the algorithm time zone
67  /// </summary>
68  public ITimeRule Midnight => new FuncTimeRule("Midnight", dates => dates.Select(date => date.ConvertToUtc(TimeZone)));
69 
70  /// <summary>
71  /// Convenience property for running a scheduled event at noon in the algorithm time zone
72  /// </summary>
73  public ITimeRule Noon => new FuncTimeRule("Noon", dates => dates.Select(date => date.ConvertToUtc(TimeZone).AddHours(12)));
74 
75  /// <summary>
76  /// Specifies an event should fire at the specified time of day in the algorithm's time zone
77  /// </summary>
78  /// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
79  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
80  public ITimeRule At(TimeSpan timeOfDay)
81  {
82  return At(timeOfDay, TimeZone);
83  }
84 
85  /// <summary>
86  /// Specifies an event should fire at the specified time of day in the algorithm's time zone
87  /// </summary>
88  /// <param name="hour">The hour</param>
89  /// <param name="minute">The minute</param>
90  /// <param name="second">The second</param>
91  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
92  public ITimeRule At(int hour, int minute, int second = 0)
93  {
94  return At(new TimeSpan(hour, minute, second), TimeZone);
95  }
96 
97  /// <summary>
98  /// Specifies an event should fire at the specified time of day in the specified time zone
99  /// </summary>
100  /// <param name="hour">The hour</param>
101  /// <param name="minute">The minute</param>
102  /// <param name="timeZone">The time zone the event time is represented in</param>
103  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
104  public ITimeRule At(int hour, int minute, DateTimeZone timeZone)
105  {
106  return At(new TimeSpan(hour, minute, 0), timeZone);
107  }
108 
109  /// <summary>
110  /// Specifies an event should fire at the specified time of day in the specified time zone
111  /// </summary>
112  /// <param name="hour">The hour</param>
113  /// <param name="minute">The minute</param>
114  /// <param name="second">The second</param>
115  /// <param name="timeZone">The time zone the event time is represented in</param>
116  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
117  public ITimeRule At(int hour, int minute, int second, DateTimeZone timeZone)
118  {
119  return At(new TimeSpan(hour, minute, second), timeZone);
120  }
121 
122  /// <summary>
123  /// Specifies an event should fire at the specified time of day in the specified time zone
124  /// </summary>
125  /// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
126  /// <param name="timeZone">The time zone the date time is expressed in</param>
127  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
128  public ITimeRule At(TimeSpan timeOfDay, DateTimeZone timeZone)
129  {
130  var name = string.Join(",", timeOfDay.TotalHours.ToStringInvariant("0.##"));
131  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
132  from date in dates
133  let localEventTime = date + timeOfDay
134  let utcEventTime = localEventTime.ConvertToUtc(timeZone)
135  select utcEventTime;
136 
137  return new FuncTimeRule(name, applicator);
138  }
139 
140  /// <summary>
141  /// Specifies an event should fire periodically on the requested interval
142  /// </summary>
143  /// <param name="interval">The frequency with which the event should fire, can not be zero or less</param>
144  /// <returns>A time rule that fires after each interval passes</returns>
145  public ITimeRule Every(TimeSpan interval)
146  {
147  if (interval <= TimeSpan.Zero)
148  {
149  throw new ArgumentException("TimeRules.Every(): time span interval can not be zero or less");
150  }
151  var name = Invariant($"Every {interval.TotalMinutes:0.##} min");
152  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates => EveryIntervalIterator(dates, interval, TimeZone);
153  return new FuncTimeRule(name, applicator);
154  }
155 
156  /// <summary>
157  /// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
158  /// </summary>
159  /// <param name="symbol">The symbol whose market open we want an event for</param>
160  /// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
161  /// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
162  /// <returns>A time rule that fires the specified number of minutes after the symbol's market open</returns>
163  public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false)
164  {
165  var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen";
166  var name = Invariant($"{symbol}: {minutesAfterOpen:0.##} min after {type}");
167  var exchangeHours = GetSecurityExchangeHours(symbol);
168 
169  var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen);
170  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
171  from date in dates
172  let marketOpen = exchangeHours.GetNextMarketOpen(date, extendedMarketOpen)
173  // make sure the market open is of this date
174  where exchangeHours.IsDateOpen(date) && marketOpen.Date == date.Date
175  let localEventTime = marketOpen + timeAfterOpen
176  let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
177  select utcEventTime;
178 
179  return new FuncTimeRule(name, applicator);
180  }
181 
182  /// <summary>
183  /// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
184  /// </summary>
185  /// <param name="symbol">The symbol whose market close we want an event for</param>
186  /// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
187  /// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
188  /// <returns>A time rule that fires the specified number of minutes before the symbol's market close</returns>
189  public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false)
190  {
191  var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose";
192  var name = Invariant($"{symbol}: {minutesBeforeClose:0.##} min before {type}");
193  var exchangeHours = GetSecurityExchangeHours(symbol);
194 
195  var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose);
196  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
197  from date in dates
198  let marketClose = exchangeHours.GetNextMarketClose(date, extendedMarketClose)
199  // make sure the market open is of this date
200  where exchangeHours.IsDateOpen(date) && marketClose.Date == date.Date
201  let localEventTime = marketClose - timeBeforeClose
202  let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
203  select utcEventTime;
204 
205  return new FuncTimeRule(name, applicator);
206  }
207 
208  /// <summary>
209  /// For each provided date will yield all the time intervals based on the supplied time span
210  /// </summary>
211  /// <param name="dates">The dates for which we want to create the different intervals</param>
212  /// <param name="interval">The interval value to use, can not be zero or less</param>
213  /// <param name="timeZone">The time zone the date time is expressed in</param>
214  private static IEnumerable<DateTime> EveryIntervalIterator(IEnumerable<DateTime> dates, TimeSpan interval, DateTimeZone timeZone)
215  {
216  if (interval <= TimeSpan.Zero)
217  {
218  throw new ArgumentException("TimeRules.EveryIntervalIterator(): time span interval can not be zero or less");
219  }
220  foreach (var date in dates)
221  {
222  for (var time = TimeSpan.Zero; time < Time.OneDay; time += interval)
223  {
224  yield return (date + time).ConvertToUtc(timeZone);
225  }
226  }
227  }
228  }
229 }