Lean  $LEAN_TAG$
BaseRealTimeHandler.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.Threading;
19 using QuantConnect.Util;
20 using QuantConnect.Packets;
25 using System.Collections.Generic;
26 using System.Collections.Concurrent;
30 
32 {
33  /// <summary>
34  /// Base class for the real time handler <see cref="LiveTradingRealTimeHandler"/>
35  /// and <see cref="BacktestingRealTimeHandler"/> implementations
36  /// </summary>
37  public abstract class BaseRealTimeHandler : IRealTimeHandler
38  {
39  private int _scheduledEventUniqueId;
40  // For performance only add OnEndOfDay Symbol scheduled events if the method is implemented.
41  // When there are many securities it adds a significant overhead
42  private bool _implementsOnEndOfDaySymbol;
43  private bool _implementsOnEndOfDay;
44 
45  /// <summary>
46  /// Keep track of this event so we can remove it when we need to update it
47  /// </summary>
48  private ScheduledEvent _algorithmOnEndOfDay;
49 
50  /// <summary>
51  /// Keep a separate track of these scheduled events so we can remove them
52  /// if the security gets removed
53  /// </summary>
54  private readonly ConcurrentDictionary<Symbol, ScheduledEvent> _securityOnEndOfDay = new();
55 
56  /// <summary>
57  /// The result handler instance
58  /// </summary>
59  private IResultHandler ResultHandler { get; set; }
60 
61  /// <summary>
62  /// Thread status flag.
63  /// </summary>
64  public abstract bool IsActive { get; protected set; }
65 
66  /// <summary>
67  /// The scheduled events container
68  /// </summary>
69  /// <remarks>Initialize this immediately since the Initialize method gets
70  /// called after IAlgorithm.Initialize, so we want to be ready to accept
71  /// events as soon as possible</remarks>
72  protected ConcurrentDictionary<ScheduledEvent, int> ScheduledEvents { get; } = new();
73 
74  /// <summary>
75  /// The isolator limit result provider instance
76  /// </summary>
77  protected IIsolatorLimitResultProvider IsolatorLimitProvider { get; private set; }
78 
79  /// <summary>
80  /// The algorithm instance
81  /// </summary>
82  protected IAlgorithm Algorithm { get; private set; }
83 
84  /// <summary>
85  /// The time monitor instance to use
86  /// </summary>
87  protected TimeMonitor TimeMonitor { get; private set; }
88 
89  /// <summary>
90  /// Adds the specified event to the schedule
91  /// </summary>
92  /// <param name="scheduledEvent">The event to be scheduled, including the date/times
93  /// the event fires and the callback</param>
94  public abstract void Add(ScheduledEvent scheduledEvent);
95 
96  /// <summary>
97  /// Removes the specified event from the schedule
98  /// </summary>
99  /// <param name="scheduledEvent">The event to be removed</param>
100  public abstract void Remove(ScheduledEvent scheduledEvent);
101 
102  /// <summary>
103  /// Set the current time for the event scanner (so we can use same code for backtesting and live events)
104  /// </summary>
105  /// <param name="time">Current real or backtest time.</param>
106  public abstract void SetTime(DateTime time);
107 
108  /// <summary>
109  /// Scan for past events that didn't fire because there was no data at the scheduled time.
110  /// </summary>
111  /// <param name="time">Current time.</param>
112  public abstract void ScanPastEvents(DateTime time);
113 
114  /// <summary>
115  /// Initializes the real time handler for the specified algorithm and job.
116  /// Adds EndOfDayEvents
117  /// </summary>
118  public virtual void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider)
119  {
120  Algorithm = algorithm;
121  ResultHandler = resultHandler;
123  IsolatorLimitProvider = isolatorLimitProvider;
124 
125  if (job.Language == Language.CSharp)
126  {
127  var method = Algorithm.GetType().GetMethod("OnEndOfDay", new[] { typeof(Symbol) });
128  var method2 = Algorithm.GetType().GetMethod("OnEndOfDay", new[] { typeof(string) });
129  if (method != null && method.DeclaringType != typeof(QCAlgorithm)
130  || method2 != null && method2.DeclaringType != typeof(QCAlgorithm))
131  {
132  _implementsOnEndOfDaySymbol = true;
133  }
134 
135  // Also determine if we are using the soon to be deprecated EOD so we don't use it
136  // unnecessarily and post messages about its deprecation to the user
137  var eodMethod = Algorithm.GetType().GetMethod("OnEndOfDay", Type.EmptyTypes);
138  if (eodMethod != null && eodMethod.DeclaringType != typeof(QCAlgorithm))
139  {
140  _implementsOnEndOfDay = true;
141  }
142  }
143  else if (job.Language == Language.Python)
144  {
145  var wrapper = Algorithm as AlgorithmPythonWrapper;
146  if (wrapper != null)
147  {
148  _implementsOnEndOfDaySymbol = wrapper.IsOnEndOfDaySymbolImplemented;
149  _implementsOnEndOfDay = wrapper.IsOnEndOfDayImplemented;
150  }
151  }
152  else
153  {
154  throw new ArgumentException(nameof(job.Language));
155  }
156 
157  // Here to maintain functionality until deprecation in August 2021
158  AddAlgorithmEndOfDayEvent(start: algorithm.Time, end: algorithm.EndDate, currentUtcTime: algorithm.UtcTime);
159  }
160 
161  /// <summary>
162  /// Gets a new scheduled event unique id
163  /// </summary>
164  /// <remarks>This value is used to order scheduled events in a deterministic way</remarks>
166  {
167  return Interlocked.Increment(ref _scheduledEventUniqueId);
168  }
169 
170  /// <summary>
171  /// Get's the timeout the scheduled task time monitor should use
172  /// </summary>
173  protected virtual int GetTimeMonitorTimeout()
174  {
175  return 100;
176  }
177 
178  /// <summary>
179  /// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
180  /// </summary>
181  /// <param name="start">The date to start the events</param>
182  /// <param name="end">The date to end the events</param>
183  /// <param name="currentUtcTime">Specifies the current time in UTC, before which,
184  /// no events will be scheduled. Specify null to skip this filter.</param>
185  [Obsolete("This method is deprecated. It will add ScheduledEvents for the deprecated IAlgorithm.OnEndOfDay()")]
186  private void AddAlgorithmEndOfDayEvent(DateTime start, DateTime end, DateTime? currentUtcTime = null)
187  {
188  // If the algorithm didn't implement it no need to support it.
189  if (!_implementsOnEndOfDay) { return; }
190 
191  if (_algorithmOnEndOfDay != null)
192  {
193  // if we already set it once we remove the previous and
194  // add a new one, we don't want to keep both
195  Remove(_algorithmOnEndOfDay);
196  }
197 
198  // add end of day events for each tradeable day
199  _algorithmOnEndOfDay = ScheduledEventFactory.EveryAlgorithmEndOfDay(
200  Algorithm,
201  ResultHandler,
202  start,
203  end,
205  currentUtcTime);
206 
207  Add(_algorithmOnEndOfDay);
208  }
209 
210  /// <summary>
211  /// Creates a new <see cref="ScheduledEvent"/> that will fire before market
212  /// close by the specified time for each provided securities.
213  /// </summary>
214  /// <param name="securities">The securities for which we want to add the OnEndOfDay event</param>
215  /// <param name="start">The date to start the events</param>
216  /// <param name="end">The date to end the events</param>
217  /// <param name="currentUtcTime">Specifies the current time in UTC, before which,
218  /// no events will be scheduled. Specify null to skip this filter.</param>
219  private void AddSecurityDependentEndOfDayEvents(
220  IEnumerable<Security> securities,
221  DateTime start,
222  DateTime end,
223  DateTime? currentUtcTime = null)
224  {
225  if (_implementsOnEndOfDaySymbol)
226  {
227  // add end of trading day events for each security
228  foreach (var security in securities)
229  {
230  if (!security.IsInternalFeed())
231  {
232  var scheduledEvent = ScheduledEventFactory.EverySecurityEndOfDay(
233  Algorithm, ResultHandler, security, start, end, ScheduledEvent.SecurityEndOfDayDelta, currentUtcTime);
234 
235  // we keep separate track so we can remove it later
236  _securityOnEndOfDay[security.Symbol] = scheduledEvent;
237 
238  // assumes security.Exchange has been updated with today's hours via RefreshMarketHoursToday
239  Add(scheduledEvent);
240  }
241  }
242  }
243  }
244 
245  /// <summary>
246  /// Event fired each time that we add/remove securities from the data feed
247  /// </summary>
249  {
250  if (changes != SecurityChanges.None)
251  {
252  if (_implementsOnEndOfDaySymbol)
253  {
254  AddSecurityDependentEndOfDayEvents(changes.AddedSecurities,
258 
259  foreach (var security in changes.RemovedSecurities)
260  {
261  ScheduledEvent scheduledEvent;
262  if (_securityOnEndOfDay.TryRemove(security.Symbol, out scheduledEvent))
263  {
264  // we remove the schedule events of the securities that were removed
265  Remove(scheduledEvent);
266  }
267  }
268  }
269 
270  // we re add the algorithm end of day event because it depends on the securities
271  // tradable dates
272  // Here to maintain functionality until deprecation in August 2021
273  AddAlgorithmEndOfDayEvent(Algorithm.UtcTime, Algorithm.EndDate, Algorithm.UtcTime);
274  }
275  }
276 
277  /// <summary>
278  /// Stop the real time thread
279  /// </summary>
280  public virtual void Exit()
281  {
282  TimeMonitor.DisposeSafely();
283  TimeMonitor = null;
284  }
285  }
286 }