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>
29  public class TimeRules
30  {
31  private DateTimeZone _timeZone;
32 
33  private readonly SecurityManager _securities;
34 
35  /// <summary>
36  /// Initializes a new instance of the <see cref="TimeRules"/> helper class
37  /// </summary>
38  /// <param name="securities">The security manager</param>
39  /// <param name="timeZone">The algorithm's default time zone</param>
40  public TimeRules(SecurityManager securities, DateTimeZone timeZone)
41  {
42  _securities = securities;
43  _timeZone = timeZone;
44  }
45 
46  /// <summary>
47  /// Sets the default time zone
48  /// </summary>
49  /// <param name="timeZone">The time zone to use for helper methods that can't resolve a time zone</param>
50  public void SetDefaultTimeZone(DateTimeZone timeZone)
51  {
52  _timeZone = timeZone;
53  }
54 
55  /// <summary>
56  /// Specifies an event should fire at the current time
57  /// </summary>
58  public ITimeRule Now => new FuncTimeRule("Now", dates => {
59  return dates.Select(date =>
60  {
61  // we ignore the given date and just use the current time, why? if the algorithm used 'DateRules.Today'
62  // we get the algorithms first 'Date', which during warmup might not be a complete date, depending on the warmup period
63  // and since Today returns dates we might get a time in the past which get's ignored. See 'WarmupTrainRegressionAlgorithm'
64  // which reproduces GH issue #6410
65  return _securities.UtcTime;
66  });
67  });
68 
69  /// <summary>
70  /// Convenience property for running a scheduled event at midnight in the algorithm time zone
71  /// </summary>
72  public ITimeRule Midnight => new FuncTimeRule("Midnight", dates => dates.Select(date => date.ConvertToUtc(_timeZone)));
73 
74  /// <summary>
75  /// Convenience property for running a scheduled event at noon in the algorithm time zone
76  /// </summary>
77  public ITimeRule Noon => new FuncTimeRule("Noon", dates => dates.Select(date => date.ConvertToUtc(_timeZone).AddHours(12)));
78 
79  /// <summary>
80  /// Specifies an event should fire at the specified time of day in the algorithm's time zone
81  /// </summary>
82  /// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
83  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
84  public ITimeRule At(TimeSpan timeOfDay)
85  {
86  return At(timeOfDay, _timeZone);
87  }
88 
89  /// <summary>
90  /// Specifies an event should fire at the specified time of day in the algorithm's time zone
91  /// </summary>
92  /// <param name="hour">The hour</param>
93  /// <param name="minute">The minute</param>
94  /// <param name="second">The second</param>
95  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
96  public ITimeRule At(int hour, int minute, int second = 0)
97  {
98  return At(new TimeSpan(hour, minute, second), _timeZone);
99  }
100 
101  /// <summary>
102  /// Specifies an event should fire at the specified time of day in the specified time zone
103  /// </summary>
104  /// <param name="hour">The hour</param>
105  /// <param name="minute">The minute</param>
106  /// <param name="timeZone">The time zone the event time is represented in</param>
107  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
108  public ITimeRule At(int hour, int minute, DateTimeZone timeZone)
109  {
110  return At(new TimeSpan(hour, minute, 0), timeZone);
111  }
112 
113  /// <summary>
114  /// Specifies an event should fire at the specified time of day in the specified time zone
115  /// </summary>
116  /// <param name="hour">The hour</param>
117  /// <param name="minute">The minute</param>
118  /// <param name="second">The second</param>
119  /// <param name="timeZone">The time zone the event time is represented in</param>
120  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
121  public ITimeRule At(int hour, int minute, int second, DateTimeZone timeZone)
122  {
123  return At(new TimeSpan(hour, minute, second), timeZone);
124  }
125 
126  /// <summary>
127  /// Specifies an event should fire at the specified time of day in the specified time zone
128  /// </summary>
129  /// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
130  /// <param name="timeZone">The time zone the date time is expressed in</param>
131  /// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
132  public ITimeRule At(TimeSpan timeOfDay, DateTimeZone timeZone)
133  {
134  var name = string.Join(",", timeOfDay.TotalHours.ToStringInvariant("0.##"));
135  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
136  from date in dates
137  let localEventTime = date + timeOfDay
138  let utcEventTime = localEventTime.ConvertToUtc(timeZone)
139  select utcEventTime;
140 
141  return new FuncTimeRule(name, applicator);
142  }
143 
144  /// <summary>
145  /// Specifies an event should fire periodically on the requested interval
146  /// </summary>
147  /// <param name="interval">The frequency with which the event should fire, can not be zero or less</param>
148  /// <returns>A time rule that fires after each interval passes</returns>
149  public ITimeRule Every(TimeSpan interval)
150  {
151  if (interval <= TimeSpan.Zero)
152  {
153  throw new ArgumentException("TimeRules.Every(): time span interval can not be zero or less");
154  }
155  var name = Invariant($"Every {interval.TotalMinutes:0.##} min");
156  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates => EveryIntervalIterator(dates, interval, _timeZone);
157  return new FuncTimeRule(name, applicator);
158  }
159 
160  /// <summary>
161  /// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
162  /// </summary>
163  /// <param name="symbol">The symbol whose market open we want an event for</param>
164  /// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
165  /// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
166  /// <returns>A time rule that fires the specified number of minutes after the symbol's market open</returns>
167  public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false)
168  {
169  var security = GetSecurity(symbol);
170 
171  var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen";
172  var name = Invariant($"{symbol}: {minutesAfterOpen:0.##} min after {type}");
173 
174  var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen);
175  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
176  from date in dates
177  let marketOpen = security.Exchange.Hours.GetNextMarketOpen(date, extendedMarketOpen)
178  // make sure the market open is of this date
179  where security.Exchange.DateIsOpen(date) && marketOpen.Date == date.Date
180  let localEventTime = marketOpen + timeAfterOpen
181  let utcEventTime = localEventTime.ConvertToUtc(security.Exchange.TimeZone)
182  select utcEventTime;
183 
184  return new FuncTimeRule(name, applicator);
185  }
186 
187  /// <summary>
188  /// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
189  /// </summary>
190  /// <param name="symbol">The symbol whose market close we want an event for</param>
191  /// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
192  /// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
193  /// <returns>A time rule that fires the specified number of minutes before the symbol's market close</returns>
194  public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false)
195  {
196  var security = GetSecurity(symbol);
197 
198  var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose";
199  var name = Invariant($"{security.Symbol}: {minutesBeforeClose:0.##} min before {type}");
200 
201  var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose);
202  Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
203  from date in dates
204  let marketClose = security.Exchange.Hours.GetNextMarketClose(date, extendedMarketClose)
205  // make sure the market open is of this date
206  where security.Exchange.DateIsOpen(date) && marketClose.Date == date.Date
207  let localEventTime = marketClose - timeBeforeClose
208  let utcEventTime = localEventTime.ConvertToUtc(security.Exchange.TimeZone)
209  select utcEventTime;
210 
211  return new FuncTimeRule(name, applicator);
212  }
213 
214  private Security GetSecurity(Symbol symbol)
215  {
216  Security security;
217  if (!_securities.TryGetValue(symbol, out security))
218  {
219  throw new KeyNotFoundException($"{symbol} not found in portfolio. Request this data when initializing the algorithm.");
220  }
221  return security;
222  }
223 
224  /// <summary>
225  /// For each provided date will yield all the time intervals based on the supplied time span
226  /// </summary>
227  /// <param name="dates">The dates for which we want to create the different intervals</param>
228  /// <param name="interval">The interval value to use, can not be zero or less</param>
229  /// <param name="timeZone">The time zone the date time is expressed in</param>
230  private static IEnumerable<DateTime> EveryIntervalIterator(IEnumerable<DateTime> dates, TimeSpan interval, DateTimeZone timeZone)
231  {
232  if (interval <= TimeSpan.Zero)
233  {
234  throw new ArgumentException("TimeRules.EveryIntervalIterator(): time span interval can not be zero or less");
235  }
236  foreach (var date in dates)
237  {
238  for (var time = TimeSpan.Zero; time < Time.OneDay; time += interval)
239  {
240  yield return (date + time).ConvertToUtc(timeZone);
241  }
242  }
243  }
244  }
245 }