Lean  $LEAN_TAG$
ScheduledEventFactory.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 System.Collections.Generic;
19 using System.Linq;
22 using QuantConnect.Logging;
25 
27 {
28  /// <summary>
29  /// Provides methods for creating common scheduled events
30  /// </summary>
31  public static class ScheduledEventFactory
32  {
33  /// <summary>
34  /// Creates a new <see cref="ScheduledEvent"/> that will fire at the specified <paramref name="timeOfDay"/> for every day in
35  /// <paramref name="dates"/>
36  /// </summary>
37  /// <param name="name">An identifier for this event</param>
38  /// <param name="dates">The dates to set events for at the specified time. These act as a base time to which
39  /// the <paramref name="timeOfDay"/> is added to, that is, the implementation does not use .Date before
40  /// the addition</param>
41  /// <param name="timeOfDay">The time each tradeable date to fire the event</param>
42  /// <param name="callback">The delegate to call when an event fires</param>
43  /// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
44  /// <returns>A new <see cref="ScheduledEvent"/> instance that fires events each tradeable day from the start to the finish at the specified time</returns>
45  public static ScheduledEvent EveryDayAt(string name, IEnumerable<DateTime> dates, TimeSpan timeOfDay, Action<string, DateTime> callback, DateTime? currentUtcTime = null)
46  {
47  var eventTimes = dates.Select(x => x.Date + timeOfDay);
48  if (currentUtcTime.HasValue)
49  {
50  eventTimes = eventTimes.Where(x => x < currentUtcTime.Value);
51  }
52  return new ScheduledEvent(name, eventTimes, callback);
53  }
54 
55  /// <summary>
56  /// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
57  /// </summary>
58  /// <param name="algorithm">The algorithm instance the event is fo</param>
59  /// <param name="resultHandler">The result handler, used to communicate run time errors</param>
60  /// <param name="start">The date to start the events</param>
61  /// <param name="end">The date to end the events</param>
62  /// <param name="endOfDayDelta">The time difference between the market close and the event, positive time will fire before market close</param>
63  /// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
64  /// <returns>The new <see cref="ScheduledEvent"/> that will fire near market close each tradeable dat</returns>
65  [Obsolete("This method is deprecated. It will generate ScheduledEvents for the deprecated IAlgorithm.OnEndOfDay()")]
66  public static ScheduledEvent EveryAlgorithmEndOfDay(IAlgorithm algorithm, IResultHandler resultHandler, DateTime start, DateTime end, TimeSpan endOfDayDelta, DateTime? currentUtcTime = null)
67  {
68  if (endOfDayDelta >= Time.OneDay)
69  {
70  throw new ArgumentException("Delta must be less than a day", nameof(endOfDayDelta));
71  }
72 
73  // set up an event to fire every tradeable date for the algorithm as a whole
74  var eodEventTime = Time.OneDay.Subtract(endOfDayDelta);
75 
76  // create enumerable of end of day in algorithm's time zone
77  var times =
78  // for every date any exchange is open in the algorithm
79  from date in Time.EachTradeableDay(algorithm.Securities.Values, start, end)
80  // define the time of day we want the event to fire, a little before midnight
81  let eventTime = date + eodEventTime
82  // convert the event time into UTC
83  let eventUtcTime = eventTime.ConvertToUtc(algorithm.TimeZone)
84  // perform filter to verify it's not before the current time
85  where !currentUtcTime.HasValue || eventUtcTime > currentUtcTime.Value
86  select eventUtcTime;
87 
88  // Log a message warning the user this EOD will be deprecated soon
89  algorithm.Debug("Usage of QCAlgorithm.OnEndOfDay() without a symbol will be deprecated August 2021. Always use a symbol when overriding this method: OnEndOfDay(symbol)");
90 
91  return new ScheduledEvent(CreateEventName("Algorithm", "EndOfDay"), times, (name, triggerTime) =>
92  {
93  try
94  {
95  algorithm.OnEndOfDay();
96  }
97  catch (Exception err)
98  {
99  resultHandler.RuntimeError($"Runtime error in {name} event: {err.Message}", err.StackTrace);
100  Log.Error(err, $"ScheduledEvent.{name}:");
101  }
102  });
103  }
104 
105  /// <summary>
106  /// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
107  /// </summary>
108  /// <param name="algorithm">The algorithm instance the event is fo</param>
109  /// <param name="resultHandler">The result handler, used to communicate run time errors</param>
110  /// <param name="security">The security used for defining tradeable dates</param>
111  /// <param name="start">The first date for the events</param>
112  /// <param name="end">The date to end the events</param>
113  /// <param name="endOfDayDelta">The time difference between the market close and the event, positive time will fire before market close</param>
114  /// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
115  /// <returns>The new <see cref="ScheduledEvent"/> that will fire near market close each tradeable dat</returns>
116  public static ScheduledEvent EverySecurityEndOfDay(IAlgorithm algorithm, IResultHandler resultHandler, Security security, DateTime start, DateTime end, TimeSpan endOfDayDelta, DateTime? currentUtcTime = null)
117  {
118  if (endOfDayDelta >= Time.OneDay)
119  {
120  throw new ArgumentException("Delta must be less than a day", nameof(endOfDayDelta));
121  }
122 
123  var isMarketAlwaysOpen = security.Exchange.Hours.IsMarketAlwaysOpen;
124 
125  // define all the times we want this event to be fired, every tradeable day for the securtiy
126  // at the delta time before market close expressed in UTC
127  var times =
128  // for every date the exchange is open for this security
129  from date in Time.EachTradeableDay(security, start, end)
130  // get the next market close for the specified date if the market closes at some point.
131  // Otherwise, use the given date at midnight
132  let marketClose = isMarketAlwaysOpen ?
133  date.Date.AddDays(1) : security.Exchange.Hours.GetNextMarketClose(date, security.IsExtendedMarketHours)
134  // define the time of day we want the event to fire before marketclose
135  let eventTime = isMarketAlwaysOpen ? marketClose : marketClose.Subtract(endOfDayDelta)
136  // convert the event time into UTC
137  let eventUtcTime = eventTime.ConvertToUtc(security.Exchange.TimeZone)
138  // perform filter to verify it's not before the current time
139  where !currentUtcTime.HasValue || eventUtcTime > currentUtcTime
140  select eventUtcTime;
141 
142  return new ScheduledEvent(CreateEventName(security.Symbol.ToString(), "EndOfDay"), times, (name, triggerTime) =>
143  {
144  try
145  {
146  algorithm.OnEndOfDay(security.Symbol);
147  }
148  catch (Exception err)
149  {
150  resultHandler.RuntimeError($"Runtime error in {name} event: {err.Message}", err.StackTrace);
151  Log.Error(err, $"ScheduledEvent.{name}:");
152  }
153  });
154  }
155 
156  /// <summary>
157  /// Defines the format of event names generated by this system.
158  /// </summary>
159  /// <param name="scope">The scope of the event, example, 'Algorithm' or 'Security'</param>
160  /// <param name="name">A name for this specified event in this scope, example, 'EndOfDay'</param>
161  /// <returns>A string representing a fully scoped event name</returns>
162  public static string CreateEventName(string scope, string name)
163  {
164  return $"{scope}.{name}";
165  }
166  }
167 }