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="minutesBeforeOpen"/>
158  /// </summary>
159  /// <param name="symbol">The symbol whose market open we want an event for</param>
160  /// <param name="minutesBeforeOpen">The minutes before 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 before the symbol's market open</returns>
163  public ITimeRule BeforeMarketOpen(Symbol symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false)
164  {
165  return AfterMarketOpen(symbol, minutesBeforeOpen * (-1), extendedMarketOpen);
166  }
167 
168  /// <summary>
169  /// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
170  /// </summary>
171  /// <param name="symbol">The symbol whose market open we want an event for</param>
172  /// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
173  /// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
174  /// <returns>A time rule that fires the specified number of minutes after the symbol's market open</returns>
175  public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false)
176  {
177  var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen";
178  var afterOrBefore = minutesAfterOpen > 0 ? "after" : "before";
179  var name = Invariant($"{symbol}: {Math.Abs(minutesAfterOpen):0.##} min {afterOrBefore} {type}");
180  var exchangeHours = GetSecurityExchangeHours(symbol);
181 
182  var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen);
183  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
184  from date in dates
185  let marketOpen = exchangeHours.GetNextMarketOpen(date, extendedMarketOpen)
186  // make sure the market open is of this date
187  where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == date.Date
188  let localEventTime = marketOpen + timeAfterOpen
189  let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
190  select utcEventTime;
191 
192  return new FuncTimeRule(name, applicator);
193  }
194 
195  /// <summary>
196  /// Specifies an event should fire at the market close +- <paramref name="minutesAfterClose"/>
197  /// </summary>
198  /// <param name="symbol">The symbol whose market close we want an event for</param>
199  /// <param name="minutesAfterClose">The time after market close that the event should fire</param>
200  /// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
201  /// <returns>A time rule that fires the specified number of minutes after the symbol's market close</returns>
202  public ITimeRule AfterMarketClose(Symbol symbol, double minutesAfterClose = 0, bool extendedMarketClose = false)
203  {
204  return BeforeMarketClose(symbol, minutesAfterClose * (-1), extendedMarketClose);
205  }
206 
207  /// <summary>
208  /// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
209  /// </summary>
210  /// <param name="symbol">The symbol whose market close we want an event for</param>
211  /// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
212  /// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
213  /// <returns>A time rule that fires the specified number of minutes before the symbol's market close</returns>
214  public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false)
215  {
216  var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose";
217  var afterOrBefore = minutesBeforeClose > 0 ? "before" : "after";
218  var name = Invariant($"{symbol}: {Math.Abs(minutesBeforeClose):0.##} min {afterOrBefore} {type}");
219  var exchangeHours = GetSecurityExchangeHours(symbol);
220 
221  var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose);
222  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
223  from date in dates
224  let marketClose = exchangeHours.GetNextMarketClose(date, extendedMarketClose)
225  // make sure the market open is of this date
226  where exchangeHours.IsDateOpen(date, extendedMarketClose) && marketClose.Date == date.Date
227  let localEventTime = marketClose - timeBeforeClose
228  let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
229  select utcEventTime;
230 
231  return new FuncTimeRule(name, applicator);
232  }
233 
234  /// <summary>
235  /// For each provided date will yield all the time intervals based on the supplied time span
236  /// </summary>
237  /// <param name="dates">The dates for which we want to create the different intervals</param>
238  /// <param name="interval">The interval value to use, can not be zero or less</param>
239  /// <param name="timeZone">The time zone the date time is expressed in</param>
240  private static IEnumerable<DateTime> EveryIntervalIterator(IEnumerable<DateTime> dates, TimeSpan interval, DateTimeZone timeZone)
241  {
242  if (interval <= TimeSpan.Zero)
243  {
244  throw new ArgumentException("TimeRules.EveryIntervalIterator(): time span interval can not be zero or less");
245  }
246  foreach (var date in dates)
247  {
248  for (var time = TimeSpan.Zero; time < Time.OneDay; time += interval)
249  {
250  yield return (date + time).ConvertToUtc(timeZone);
251  }
252  }
253  }
254  }
255 }