Lean  $LEAN_TAG$
BacktestingRealTimeHandler.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.Linq;
19 using QuantConnect.Util;
20 using QuantConnect.Logging;
21 using QuantConnect.Packets;
24 using System.Collections.Generic;
26 
28 {
29  /// <summary>
30  /// Pseudo realtime event processing for backtesting to simulate realtime events in fast forward.
31  /// </summary>
33  {
34  private bool _sortingScheduledEventsRequired;
35  private List<ScheduledEvent> _scheduledEventsSortedByTime = new List<ScheduledEvent>();
36 
37  /// <summary>
38  /// Flag indicating the hander thread is completely finished and ready to dispose.
39  /// this doesn't run as its own thread
40  /// </summary>
41  public override bool IsActive { get; protected set; }
42 
43  /// <summary>
44  /// Initializes the real time handler for the specified algorithm and job
45  /// </summary>
46  public override void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider)
47  {
48  // create events for algorithm's end of tradeable dates
49  // set up the events for each security to fire every tradeable date before market close
50  base.Setup(algorithm, job, resultHandler, api, isolatorLimitProvider);
51 
52  foreach (var scheduledEvent in GetScheduledEventsSortedByTime())
53  {
54  // zoom past old events
55  scheduledEvent.SkipEventsUntil(algorithm.UtcTime);
56  // set logging accordingly
57  scheduledEvent.IsLoggingEnabled = Log.DebuggingEnabled;
58  }
59  // after skipping events we should re order
60  _sortingScheduledEventsRequired = true;
61  }
62 
63  /// <summary>
64  /// Adds the specified event to the schedule
65  /// </summary>
66  /// <param name="scheduledEvent">The event to be scheduled, including the date/times the event fires and the callback</param>
67  public override void Add(ScheduledEvent scheduledEvent)
68  {
69  if (Algorithm != null)
70  {
71  scheduledEvent.SkipEventsUntil(Algorithm.UtcTime);
72  }
73 
74  ScheduledEvents.AddOrUpdate(scheduledEvent, GetScheduledEventUniqueId());
75 
77  {
78  scheduledEvent.IsLoggingEnabled = true;
79  }
80 
81  _sortingScheduledEventsRequired = true;
82  }
83 
84  /// <summary>
85  /// Removes the specified event from the schedule
86  /// </summary>
87  /// <param name="scheduledEvent">The event to be removed</param>
88  public override void Remove(ScheduledEvent scheduledEvent)
89  {
90  int id;
91  ScheduledEvents.TryRemove(scheduledEvent, out id);
92 
93  _sortingScheduledEventsRequired = true;
94  }
95 
96  /// <summary>
97  /// Set the time for the realtime event handler.
98  /// </summary>
99  /// <param name="time">Current time.</param>
100  public override void SetTime(DateTime time)
101  {
102  var scheduledEvents = GetScheduledEventsSortedByTime();
103 
104  // the first element is always the next
105  while (scheduledEvents.Count > 0 && scheduledEvents[0].NextEventUtcTime <= time)
106  {
107  try
108  {
109  IsolatorLimitProvider.Consume(scheduledEvents[0], time, TimeMonitor);
110  }
111  catch (Exception exception)
112  {
113  Algorithm.SetRuntimeError(exception, $"Scheduled event: '{scheduledEvents[0].Name}' at {time}");
114  break;
115  }
116 
117  SortFirstElement(scheduledEvents);
118  }
119  }
120 
121  /// <summary>
122  /// Scan for past events that didn't fire because there was no data at the scheduled time.
123  /// </summary>
124  /// <param name="time">Current time.</param>
125  public override void ScanPastEvents(DateTime time)
126  {
127  var scheduledEvents = GetScheduledEventsSortedByTime();
128 
129  // the first element is always the next
130  while (scheduledEvents.Count > 0 && scheduledEvents[0].NextEventUtcTime < time)
131  {
132  var scheduledEvent = scheduledEvents[0];
133  var nextEventUtcTime = scheduledEvent.NextEventUtcTime;
134 
135  Algorithm.SetDateTime(nextEventUtcTime);
136 
137  try
138  {
139  IsolatorLimitProvider.Consume(scheduledEvent, nextEventUtcTime, TimeMonitor);
140  }
141  catch (Exception exception)
142  {
143  Algorithm.SetRuntimeError(exception, $"Scheduled event: '{scheduledEvent.Name}' at {nextEventUtcTime}");
144  break;
145  }
146 
147  SortFirstElement(scheduledEvents);
148  }
149  }
150 
151  private List<ScheduledEvent> GetScheduledEventsSortedByTime()
152  {
153  if (_sortingScheduledEventsRequired)
154  {
155  _sortingScheduledEventsRequired = false;
156  _scheduledEventsSortedByTime = ScheduledEvents
157  // we order by next event time
158  .OrderBy(x => x.Key.NextEventUtcTime)
159  // then by unique id so that for scheduled events in the same time
160  // respect their creation order, so its deterministic
161  .ThenBy(x => x.Value)
162  .Select(x => x.Key).ToList();
163  }
164 
165  return _scheduledEventsSortedByTime;
166  }
167 
168  /// <summary>
169  /// Sorts the first element of the provided list and supposes the rest of the collection is sorted.
170  /// Supposes the collection has at least 1 element
171  /// </summary>
172  public static void SortFirstElement(IList<ScheduledEvent> scheduledEvents)
173  {
174  var scheduledEvent = scheduledEvents[0];
175  var nextEventUtcTime = scheduledEvent.NextEventUtcTime;
176 
177  if (scheduledEvents.Count > 1
178  // if our NextEventUtcTime is after the next event we sort our selves
179  && nextEventUtcTime > scheduledEvents[1].NextEventUtcTime)
180  {
181  // remove ourselves and re insert at the correct position, the rest of the items are sorted!
182  scheduledEvents.RemoveAt(0);
183 
184  var position = scheduledEvents.BinarySearch(nextEventUtcTime,
185  (time, orderEvent) => time.CompareTo(orderEvent.NextEventUtcTime));
186  if (position >= 0)
187  {
188  // we have to insert after existing position to respect existing order, see ScheduledEventsOrderRegressionAlgorithm
189  var finalPosition = position + 1;
190  if (finalPosition == scheduledEvents.Count)
191  {
192  // bigger than all of them add at the end
193  scheduledEvents.Add(scheduledEvent);
194  }
195  else
196  {
197  // Calling insert isn't that performant but note that we are doing it once
198  // and has better performance than sorting the entire collection
199  scheduledEvents.Insert(finalPosition, scheduledEvent);
200  }
201  }
202  else
203  {
204  var index = ~position;
205  if (index == scheduledEvents.Count)
206  {
207  // bigger than all of them insert in the end
208  scheduledEvents.Add(scheduledEvent);
209  }
210  else
211  {
212  // index + 1 is bigger than us so insert before
213  scheduledEvents.Insert(index, scheduledEvent);
214  }
215  }
216  }
217  }
218  }
219 }