chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.RealTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Pseudo realtime event processing for backtesting to simulate realtime events in fast forward.
|
||||
/// </summary>
|
||||
public class BacktestingRealTimeHandler : BaseRealTimeHandler
|
||||
{
|
||||
private bool _sortingScheduledEventsRequired;
|
||||
private List<ScheduledEvent> _scheduledEventsSortedByTime = new List<ScheduledEvent>();
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating the hander thread is completely finished and ready to dispose.
|
||||
/// this doesn't run as its own thread
|
||||
/// </summary>
|
||||
public override bool IsActive { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the real time handler for the specified algorithm and job
|
||||
/// </summary>
|
||||
public override void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider)
|
||||
{
|
||||
// create events for algorithm's end of tradeable dates
|
||||
// set up the events for each security to fire every tradeable date before market close
|
||||
base.Setup(algorithm, job, resultHandler, api, isolatorLimitProvider);
|
||||
|
||||
foreach (var scheduledEvent in GetScheduledEventsSortedByTime())
|
||||
{
|
||||
// zoom past old events
|
||||
scheduledEvent.SkipEventsUntil(algorithm.UtcTime);
|
||||
// set logging accordingly
|
||||
scheduledEvent.IsLoggingEnabled = Log.DebuggingEnabled;
|
||||
}
|
||||
// after skipping events we should re order
|
||||
_sortingScheduledEventsRequired = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified event to the schedule
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The event to be scheduled, including the date/times the event fires and the callback</param>
|
||||
public override void Add(ScheduledEvent scheduledEvent)
|
||||
{
|
||||
if (Algorithm != null)
|
||||
{
|
||||
scheduledEvent.SkipEventsUntil(Algorithm.UtcTime);
|
||||
}
|
||||
|
||||
ScheduledEvents.AddOrUpdate(scheduledEvent, GetScheduledEventUniqueId());
|
||||
|
||||
if (Log.DebuggingEnabled)
|
||||
{
|
||||
scheduledEvent.IsLoggingEnabled = true;
|
||||
}
|
||||
|
||||
_sortingScheduledEventsRequired = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified event from the schedule
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The event to be removed</param>
|
||||
public override void Remove(ScheduledEvent scheduledEvent)
|
||||
{
|
||||
int id;
|
||||
ScheduledEvents.TryRemove(scheduledEvent, out id);
|
||||
|
||||
_sortingScheduledEventsRequired = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the time for the realtime event handler.
|
||||
/// </summary>
|
||||
/// <param name="time">Current time.</param>
|
||||
public override void SetTime(DateTime time)
|
||||
{
|
||||
var scheduledEvents = GetScheduledEventsSortedByTime();
|
||||
|
||||
// the first element is always the next
|
||||
while (scheduledEvents.Count > 0 && scheduledEvents[0].NextEventUtcTime <= time)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsolatorLimitProvider.Consume(scheduledEvents[0], time, TimeMonitor);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Algorithm.SetRuntimeError(exception, $"Scheduled event: '{scheduledEvents[0].Name}' at {time}");
|
||||
break;
|
||||
}
|
||||
|
||||
SortFirstElement(scheduledEvents);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan for past events that didn't fire because there was no data at the scheduled time.
|
||||
/// </summary>
|
||||
/// <param name="time">Current time.</param>
|
||||
public override void ScanPastEvents(DateTime time)
|
||||
{
|
||||
var scheduledEvents = GetScheduledEventsSortedByTime();
|
||||
|
||||
// the first element is always the next
|
||||
while (scheduledEvents.Count > 0 && scheduledEvents[0].NextEventUtcTime < time)
|
||||
{
|
||||
var scheduledEvent = scheduledEvents[0];
|
||||
var nextEventUtcTime = scheduledEvent.NextEventUtcTime;
|
||||
|
||||
Algorithm.SetDateTime(nextEventUtcTime);
|
||||
|
||||
try
|
||||
{
|
||||
IsolatorLimitProvider.Consume(scheduledEvent, nextEventUtcTime, TimeMonitor);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Algorithm.SetRuntimeError(exception, $"Scheduled event: '{scheduledEvent.Name}' at {nextEventUtcTime}");
|
||||
break;
|
||||
}
|
||||
|
||||
SortFirstElement(scheduledEvents);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ScheduledEvent> GetScheduledEventsSortedByTime()
|
||||
{
|
||||
if (_sortingScheduledEventsRequired)
|
||||
{
|
||||
_sortingScheduledEventsRequired = false;
|
||||
_scheduledEventsSortedByTime = ScheduledEvents
|
||||
// we order by next event time
|
||||
.OrderBy(x => x.Key.NextEventUtcTime)
|
||||
// then by unique id so that for scheduled events in the same time
|
||||
// respect their creation order, so its deterministic
|
||||
.ThenBy(x => x.Value)
|
||||
.Select(x => x.Key).ToList();
|
||||
}
|
||||
|
||||
return _scheduledEventsSortedByTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the first element of the provided list and supposes the rest of the collection is sorted.
|
||||
/// Supposes the collection has at least 1 element
|
||||
/// </summary>
|
||||
public static void SortFirstElement(IList<ScheduledEvent> scheduledEvents)
|
||||
{
|
||||
var scheduledEvent = scheduledEvents[0];
|
||||
var nextEventUtcTime = scheduledEvent.NextEventUtcTime;
|
||||
|
||||
if (scheduledEvents.Count > 1
|
||||
// if our NextEventUtcTime is after the next event we sort our selves
|
||||
&& nextEventUtcTime > scheduledEvents[1].NextEventUtcTime)
|
||||
{
|
||||
// remove ourselves and re insert at the correct position, the rest of the items are sorted!
|
||||
scheduledEvents.RemoveAt(0);
|
||||
|
||||
var position = scheduledEvents.BinarySearch(nextEventUtcTime,
|
||||
(time, orderEvent) => time.CompareTo(orderEvent.NextEventUtcTime));
|
||||
if (position >= 0)
|
||||
{
|
||||
// we have to insert after existing position to respect existing order, see ScheduledEventsOrderRegressionAlgorithm
|
||||
var finalPosition = position + 1;
|
||||
if (finalPosition == scheduledEvents.Count)
|
||||
{
|
||||
// bigger than all of them add at the end
|
||||
scheduledEvents.Add(scheduledEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calling insert isn't that performant but note that we are doing it once
|
||||
// and has better performance than sorting the entire collection
|
||||
scheduledEvents.Insert(finalPosition, scheduledEvent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = ~position;
|
||||
if (index == scheduledEvents.Count)
|
||||
{
|
||||
// bigger than all of them insert in the end
|
||||
scheduledEvents.Add(scheduledEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// index + 1 is bigger than us so insert before
|
||||
scheduledEvents.Insert(index, scheduledEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Securities;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.AlgorithmFactory.Python.Wrappers;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.RealTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for the real time handler <see cref="LiveTradingRealTimeHandler"/>
|
||||
/// and <see cref="BacktestingRealTimeHandler"/> implementations
|
||||
/// </summary>
|
||||
public abstract class BaseRealTimeHandler : IRealTimeHandler
|
||||
{
|
||||
private int _scheduledEventUniqueId;
|
||||
// For performance only add OnEndOfDay Symbol scheduled events if the method is implemented.
|
||||
// When there are many securities it adds a significant overhead
|
||||
private bool _implementsOnEndOfDaySymbol;
|
||||
private bool _implementsOnEndOfDay;
|
||||
|
||||
/// <summary>
|
||||
/// Keep track of this event so we can remove it when we need to update it
|
||||
/// </summary>
|
||||
private ScheduledEvent _algorithmOnEndOfDay;
|
||||
|
||||
/// <summary>
|
||||
/// Keep a separate track of these scheduled events so we can remove them
|
||||
/// if the security gets removed
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<Symbol, ScheduledEvent> _securityOnEndOfDay = new();
|
||||
|
||||
/// <summary>
|
||||
/// The result handler instance
|
||||
/// </summary>
|
||||
private IResultHandler ResultHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Thread status flag.
|
||||
/// </summary>
|
||||
public abstract bool IsActive { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scheduled events container
|
||||
/// </summary>
|
||||
/// <remarks>Initialize this immediately since the Initialize method gets
|
||||
/// called after IAlgorithm.Initialize, so we want to be ready to accept
|
||||
/// events as soon as possible</remarks>
|
||||
protected ConcurrentDictionary<ScheduledEvent, int> ScheduledEvents { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The isolator limit result provider instance
|
||||
/// </summary>
|
||||
protected IIsolatorLimitResultProvider IsolatorLimitProvider { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The algorithm instance
|
||||
/// </summary>
|
||||
protected IAlgorithm Algorithm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time monitor instance to use
|
||||
/// </summary>
|
||||
protected TimeMonitor TimeMonitor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified event to the schedule
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The event to be scheduled, including the date/times
|
||||
/// the event fires and the callback</param>
|
||||
public abstract void Add(ScheduledEvent scheduledEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified event from the schedule
|
||||
/// </summary>
|
||||
/// <param name="scheduledEvent">The event to be removed</param>
|
||||
public abstract void Remove(ScheduledEvent scheduledEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Set the current time for the event scanner (so we can use same code for backtesting and live events)
|
||||
/// </summary>
|
||||
/// <param name="time">Current real or backtest time.</param>
|
||||
public abstract void SetTime(DateTime time);
|
||||
|
||||
/// <summary>
|
||||
/// Scan for past events that didn't fire because there was no data at the scheduled time.
|
||||
/// </summary>
|
||||
/// <param name="time">Current time.</param>
|
||||
public abstract void ScanPastEvents(DateTime time);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the real time handler for the specified algorithm and job.
|
||||
/// Adds EndOfDayEvents
|
||||
/// </summary>
|
||||
public virtual void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider)
|
||||
{
|
||||
Algorithm = algorithm;
|
||||
ResultHandler = resultHandler;
|
||||
TimeMonitor = new TimeMonitor(GetTimeMonitorTimeout());
|
||||
IsolatorLimitProvider = isolatorLimitProvider;
|
||||
|
||||
if (job.Language == Language.CSharp)
|
||||
{
|
||||
var method = Algorithm.GetType().GetMethod("OnEndOfDay", new[] { typeof(Symbol) });
|
||||
var method2 = Algorithm.GetType().GetMethod("OnEndOfDay", new[] { typeof(string) });
|
||||
if (method != null && method.DeclaringType != typeof(QCAlgorithm)
|
||||
|| method2 != null && method2.DeclaringType != typeof(QCAlgorithm))
|
||||
{
|
||||
_implementsOnEndOfDaySymbol = true;
|
||||
}
|
||||
|
||||
// Also determine if we are using the soon to be deprecated EOD so we don't use it
|
||||
// unnecessarily and post messages about its deprecation to the user
|
||||
var eodMethod = Algorithm.GetType().GetMethod("OnEndOfDay", Type.EmptyTypes);
|
||||
if (eodMethod != null && eodMethod.DeclaringType != typeof(QCAlgorithm))
|
||||
{
|
||||
_implementsOnEndOfDay = true;
|
||||
}
|
||||
}
|
||||
else if (job.Language == Language.Python)
|
||||
{
|
||||
var wrapper = Algorithm as AlgorithmPythonWrapper;
|
||||
if (wrapper != null)
|
||||
{
|
||||
_implementsOnEndOfDaySymbol = wrapper.IsOnEndOfDaySymbolImplemented;
|
||||
_implementsOnEndOfDay = wrapper.IsOnEndOfDayImplemented;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(nameof(job.Language));
|
||||
}
|
||||
|
||||
// Here to maintain functionality until deprecation in August 2021
|
||||
AddAlgorithmEndOfDayEvent(start: algorithm.Time, end: algorithm.EndDate, currentUtcTime: algorithm.UtcTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new scheduled event unique id
|
||||
/// </summary>
|
||||
/// <remarks>This value is used to order scheduled events in a deterministic way</remarks>
|
||||
protected int GetScheduledEventUniqueId()
|
||||
{
|
||||
return Interlocked.Increment(ref _scheduledEventUniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the timeout the scheduled task time monitor should use
|
||||
/// </summary>
|
||||
protected virtual int GetTimeMonitorTimeout()
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
|
||||
/// </summary>
|
||||
/// <param name="start">The date to start the events</param>
|
||||
/// <param name="end">The date to end the events</param>
|
||||
/// <param name="currentUtcTime">Specifies the current time in UTC, before which,
|
||||
/// no events will be scheduled. Specify null to skip this filter.</param>
|
||||
[Obsolete("This method is deprecated. It will add ScheduledEvents for the deprecated IAlgorithm.OnEndOfDay()")]
|
||||
private void AddAlgorithmEndOfDayEvent(DateTime start, DateTime end, DateTime? currentUtcTime = null)
|
||||
{
|
||||
// If the algorithm didn't implement it no need to support it.
|
||||
if (!_implementsOnEndOfDay) { return; }
|
||||
|
||||
if (_algorithmOnEndOfDay != null)
|
||||
{
|
||||
// if we already set it once we remove the previous and
|
||||
// add a new one, we don't want to keep both
|
||||
Remove(_algorithmOnEndOfDay);
|
||||
}
|
||||
|
||||
// add end of day events for each tradeable day
|
||||
_algorithmOnEndOfDay = ScheduledEventFactory.EveryAlgorithmEndOfDay(
|
||||
Algorithm,
|
||||
ResultHandler,
|
||||
start,
|
||||
end,
|
||||
ScheduledEvent.AlgorithmEndOfDayDelta,
|
||||
currentUtcTime);
|
||||
|
||||
Add(_algorithmOnEndOfDay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScheduledEvent"/> that will fire before market
|
||||
/// close by the specified time for each provided securities.
|
||||
/// </summary>
|
||||
/// <param name="securities">The securities for which we want to add the OnEndOfDay event</param>
|
||||
/// <param name="start">The date to start the events</param>
|
||||
/// <param name="end">The date to end the events</param>
|
||||
/// <param name="currentUtcTime">Specifies the current time in UTC, before which,
|
||||
/// no events will be scheduled. Specify null to skip this filter.</param>
|
||||
private void AddSecurityDependentEndOfDayEvents(
|
||||
IEnumerable<Security> securities,
|
||||
DateTime start,
|
||||
DateTime end,
|
||||
DateTime? currentUtcTime = null)
|
||||
{
|
||||
// add end of trading day events for each security
|
||||
foreach (var security in securities)
|
||||
{
|
||||
var scheduledEvent = ScheduledEventFactory.EverySecurityEndOfDay(
|
||||
Algorithm, ResultHandler, security, start, end, ScheduledEvent.SecurityEndOfDayDelta, currentUtcTime);
|
||||
|
||||
// we keep separate track so we can remove it later
|
||||
_securityOnEndOfDay[security.Symbol] = scheduledEvent;
|
||||
|
||||
// assumes security.Exchange has been updated with today's hours via RefreshMarketHoursToday
|
||||
Add(scheduledEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time that we add/remove securities from the data feed
|
||||
/// </summary>
|
||||
public void OnSecuritiesChanged(SecurityChanges changes)
|
||||
{
|
||||
if (changes != SecurityChanges.None)
|
||||
{
|
||||
if (_implementsOnEndOfDaySymbol)
|
||||
{
|
||||
// we only add and remove on end of day for non internal securities
|
||||
changes = new SecurityChanges(changes) { FilterInternalSecurities = true };
|
||||
AddSecurityDependentEndOfDayEvents(changes.AddedSecurities,
|
||||
Algorithm.UtcTime,
|
||||
Algorithm.EndDate,
|
||||
Algorithm.UtcTime);
|
||||
|
||||
foreach (var security in changes.RemovedSecurities)
|
||||
{
|
||||
ScheduledEvent scheduledEvent;
|
||||
if (_securityOnEndOfDay.TryRemove(security.Symbol, out scheduledEvent))
|
||||
{
|
||||
// we remove the schedule events of the securities that were removed
|
||||
Remove(scheduledEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we re add the algorithm end of day event because it depends on the securities
|
||||
// tradable dates
|
||||
// Here to maintain functionality until deprecation in August 2021
|
||||
AddAlgorithmEndOfDayEvent(Algorithm.UtcTime, Algorithm.EndDate, Algorithm.UtcTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the real time thread
|
||||
/// </summary>
|
||||
public virtual void Exit()
|
||||
{
|
||||
TimeMonitor.DisposeSafely();
|
||||
TimeMonitor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Scheduling;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.RealTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Real time event handler, trigger functions at regular or pretimed intervals
|
||||
/// </summary>
|
||||
[InheritedExport(typeof(IRealTimeHandler))]
|
||||
public interface IRealTimeHandler : IEventSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread status flag.
|
||||
/// </summary>
|
||||
bool IsActive
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the real time handler for the specified algorithm and job
|
||||
/// </summary>
|
||||
void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Set the current time for the event scanner (so we can use same code for backtesting and live events)
|
||||
/// </summary>
|
||||
/// <param name="time">Current real or backtest time.</param>
|
||||
void SetTime(DateTime time);
|
||||
|
||||
/// <summary>
|
||||
/// Scan for past events that didn't fire because there was no data at the scheduled time.
|
||||
/// </summary>
|
||||
/// <param name="time">Current time.</param>
|
||||
void ScanPastEvents(DateTime time);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger and exit signal to terminate real time event scanner.
|
||||
/// </summary>
|
||||
void Exit();
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time that we add/remove securities from the data feed
|
||||
/// </summary>
|
||||
void OnSecuritiesChanged(SecurityChanges changes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.RealTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Live trading realtime event processing.
|
||||
/// </summary>
|
||||
public class LiveTradingRealTimeHandler : BacktestingRealTimeHandler
|
||||
{
|
||||
private Thread _realTimeThread;
|
||||
private CancellationTokenSource _cancellationTokenSource = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current market hours database instance
|
||||
/// </summary>
|
||||
protected MarketHoursDatabase MarketHoursDatabase { get; set; } = MarketHoursDatabase.FromDataFolder();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current symbol properties database instance
|
||||
/// </summary>
|
||||
protected SymbolPropertiesDatabase SymbolPropertiesDatabase { get; set; } = SymbolPropertiesDatabase.FromDataFolder();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time provider
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be fixed to RealTimeHandler, but made a protected property for testing purposes
|
||||
/// </remarks>
|
||||
protected virtual ITimeProvider TimeProvider { get; } = RealTimeProvider.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean flag indicating thread state.
|
||||
/// </summary>
|
||||
public override bool IsActive { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the real time handler for the specified algorithm and job
|
||||
/// </summary>
|
||||
public override void Setup(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler, IApi api, IIsolatorLimitResultProvider isolatorLimitProvider)
|
||||
{
|
||||
base.Setup(algorithm, job, resultHandler, api, isolatorLimitProvider);
|
||||
|
||||
var utcNow = TimeProvider.GetUtcNow();
|
||||
var todayInAlgorithmTimeZone = utcNow.ConvertFromUtc(Algorithm.TimeZone).Date;
|
||||
|
||||
// set up an scheduled event to refresh market hours and symbol properties every certain period of time
|
||||
var times = Time.DateTimeRange(utcNow.Date, Time.EndOfTime, Algorithm.Settings.DatabasesRefreshPeriod).Where(date => date > utcNow);
|
||||
|
||||
Add(new ScheduledEvent("RefreshMarketHoursAndSymbolProperties", times, (name, triggerTime) =>
|
||||
{
|
||||
ResetMarketHoursDatabase();
|
||||
ResetSymbolPropertiesDatabase();
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's the timeout the scheduled task time monitor should use
|
||||
/// </summary>
|
||||
protected override int GetTimeMonitorTimeout()
|
||||
{
|
||||
return 500;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the live realtime event thread montioring.
|
||||
/// It scans every second monitoring for an event trigger.
|
||||
/// </summary>
|
||||
private void Run()
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
// continue thread until cancellation is requested
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var time = TimeProvider.GetUtcNow();
|
||||
WaitTillNextSecond(time);
|
||||
|
||||
// poke each event to see if it should fire, we order by unique id to be deterministic
|
||||
foreach (var kvp in ScheduledEvents.OrderBySafe(pair => pair.Value))
|
||||
{
|
||||
var scheduledEvent = kvp.Key;
|
||||
try
|
||||
{
|
||||
IsolatorLimitProvider.Consume(scheduledEvent, time, TimeMonitor);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Algorithm.SetRuntimeError(exception, $"Scheduled event: '{scheduledEvent.Name}' at {time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsActive = false;
|
||||
Log.Trace("LiveTradingRealTimeHandler.Run(): Exiting thread... Exit triggered: " + _cancellationTokenSource.IsCancellationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the current time. If the date changes re-start the realtime event setup routines.
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
public override void SetTime(DateTime time)
|
||||
{
|
||||
if (Algorithm.IsWarmingUp)
|
||||
{
|
||||
base.SetTime(time);
|
||||
}
|
||||
else if (_realTimeThread == null)
|
||||
{
|
||||
// in live mode we use current time for our time keeping
|
||||
// this method is used by backtesting to set time based on the data
|
||||
_realTimeThread = new Thread(Run) { IsBackground = true, Name = "RealTime Thread" };
|
||||
_realTimeThread.Start(); // RealTime scan time for time based events
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan for past events that didn't fire because there was no data at the scheduled time.
|
||||
/// </summary>
|
||||
/// <param name="time">Current time.</param>
|
||||
public override void ScanPastEvents(DateTime time)
|
||||
{
|
||||
if (Algorithm.IsWarmingUp)
|
||||
{
|
||||
base.ScanPastEvents(time);
|
||||
}
|
||||
// in live mode we use current time for our time keeping
|
||||
// this method is used by backtesting to scan for past events based on the data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the real time thread
|
||||
/// </summary>
|
||||
public override void Exit()
|
||||
{
|
||||
_realTimeThread.StopSafely(TimeSpan.FromMinutes(1), _cancellationTokenSource);
|
||||
_cancellationTokenSource.DisposeSafely();
|
||||
base.Exit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to wait until the second passes, useful to testing
|
||||
/// </summary>
|
||||
protected virtual void WaitTillNextSecond(DateTime time)
|
||||
{
|
||||
// pause until the next second
|
||||
var nextSecond = time.RoundUp(TimeSpan.FromSeconds(1));
|
||||
var delay = Convert.ToInt32((nextSecond - time).TotalMilliseconds);
|
||||
Thread.Sleep(delay < 0 ? 1 : delay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the market hours database, forcing a reload when reused.
|
||||
/// Called in tests where multiple algorithms are run sequentially,
|
||||
/// and we need to guarantee that every test starts with the same environment.
|
||||
/// </summary>
|
||||
protected virtual void ResetMarketHoursDatabase()
|
||||
{
|
||||
MarketHoursDatabase.UpdateDataFolderDatabase();
|
||||
Log.Trace("LiveTradingRealTimeHandler.ResetMarketHoursDatabase(): Updated market hours database.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the symbol properties database, forcing a reload when reused.
|
||||
/// </summary>
|
||||
protected virtual void ResetSymbolPropertiesDatabase()
|
||||
{
|
||||
SymbolPropertiesDatabase.UpdateDataFolderDatabase();
|
||||
Log.Trace("LiveTradingRealTimeHandler.ResetSymbolPropertiesDatabase(): Updated symbol properties database.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Lean.Engine.RealTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for creating common scheduled events
|
||||
/// </summary>
|
||||
public static class ScheduledEventFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScheduledEvent"/> that will fire at the specified <paramref name="timeOfDay"/> for every day in
|
||||
/// <paramref name="dates"/>
|
||||
/// </summary>
|
||||
/// <param name="name">An identifier for this event</param>
|
||||
/// <param name="dates">The dates to set events for at the specified time. These act as a base time to which
|
||||
/// the <paramref name="timeOfDay"/> is added to, that is, the implementation does not use .Date before
|
||||
/// the addition</param>
|
||||
/// <param name="timeOfDay">The time each tradeable date to fire the event</param>
|
||||
/// <param name="callback">The delegate to call when an event fires</param>
|
||||
/// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
|
||||
/// <returns>A new <see cref="ScheduledEvent"/> instance that fires events each tradeable day from the start to the finish at the specified time</returns>
|
||||
public static ScheduledEvent EveryDayAt(string name, IEnumerable<DateTime> dates, TimeSpan timeOfDay, Action<string, DateTime> callback, DateTime? currentUtcTime = null)
|
||||
{
|
||||
var eventTimes = dates.Select(x => x.Date + timeOfDay);
|
||||
if (currentUtcTime.HasValue)
|
||||
{
|
||||
eventTimes = eventTimes.Where(x => x < currentUtcTime.Value);
|
||||
}
|
||||
return new ScheduledEvent(name, eventTimes, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance the event is fo</param>
|
||||
/// <param name="resultHandler">The result handler, used to communicate run time errors</param>
|
||||
/// <param name="start">The date to start the events</param>
|
||||
/// <param name="end">The date to end the events</param>
|
||||
/// <param name="endOfDayDelta">The time difference between the market close and the event, positive time will fire before market close</param>
|
||||
/// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
|
||||
/// <returns>The new <see cref="ScheduledEvent"/> that will fire near market close each tradeable dat</returns>
|
||||
[Obsolete("This method is deprecated. It will generate ScheduledEvents for the deprecated IAlgorithm.OnEndOfDay()")]
|
||||
public static ScheduledEvent EveryAlgorithmEndOfDay(IAlgorithm algorithm, IResultHandler resultHandler, DateTime start, DateTime end, TimeSpan endOfDayDelta, DateTime? currentUtcTime = null)
|
||||
{
|
||||
if (endOfDayDelta >= Time.OneDay)
|
||||
{
|
||||
throw new ArgumentException("Delta must be less than a day", nameof(endOfDayDelta));
|
||||
}
|
||||
|
||||
// set up an event to fire every tradeable date for the algorithm as a whole
|
||||
var eodEventTime = Time.OneDay.Subtract(endOfDayDelta);
|
||||
|
||||
// create enumerable of end of day in algorithm's time zone
|
||||
var times =
|
||||
// for every date any exchange is open in the algorithm
|
||||
from date in Time.EachTradeableDay(algorithm.Securities.Values, start, end)
|
||||
// define the time of day we want the event to fire, a little before midnight
|
||||
let eventTime = date + eodEventTime
|
||||
// convert the event time into UTC
|
||||
let eventUtcTime = eventTime.ConvertToUtc(algorithm.TimeZone)
|
||||
// perform filter to verify it's not before the current time
|
||||
where !currentUtcTime.HasValue || eventUtcTime > currentUtcTime.Value
|
||||
select eventUtcTime;
|
||||
|
||||
// Log a message warning the user this EOD will be deprecated soon
|
||||
algorithm.Debug("Usage of QCAlgorithm.OnEndOfDay() without a symbol will be deprecated August 2021. Always use a symbol when overriding this method: OnEndOfDay(symbol)");
|
||||
|
||||
return new ScheduledEvent(CreateEventName("Algorithm", "EndOfDay"), times, (name, triggerTime) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
algorithm.OnEndOfDay();
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
resultHandler.RuntimeError($"Runtime error in {name} event: {err.Message}", err.StackTrace);
|
||||
Log.Error(err, $"ScheduledEvent.{name}:");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScheduledEvent"/> that will fire before market close by the specified time
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance the event is fo</param>
|
||||
/// <param name="resultHandler">The result handler, used to communicate run time errors</param>
|
||||
/// <param name="security">The security used for defining tradeable dates</param>
|
||||
/// <param name="start">The first date for the events</param>
|
||||
/// <param name="end">The date to end the events</param>
|
||||
/// <param name="endOfDayDelta">The time difference between the market close and the event, positive time will fire before market close</param>
|
||||
/// <param name="currentUtcTime">Specfies the current time in UTC, before which, no events will be scheduled. Specify null to skip this filter.</param>
|
||||
/// <returns>The new <see cref="ScheduledEvent"/> that will fire near market close each tradeable dat</returns>
|
||||
public static ScheduledEvent EverySecurityEndOfDay(IAlgorithm algorithm, IResultHandler resultHandler, Security security, DateTime start, DateTime end, TimeSpan endOfDayDelta, DateTime? currentUtcTime = null)
|
||||
{
|
||||
if (endOfDayDelta >= Time.OneDay)
|
||||
{
|
||||
throw new ArgumentException("Delta must be less than a day", nameof(endOfDayDelta));
|
||||
}
|
||||
|
||||
var isMarketAlwaysOpen = security.Exchange.Hours.IsMarketAlwaysOpen;
|
||||
|
||||
// define all the times we want this event to be fired, every tradeable day for the securtiy
|
||||
// at the delta time before market close expressed in UTC
|
||||
var times =
|
||||
// for every date the exchange is open for this security
|
||||
from date in Time.EachTradeableDay(security, start, end)
|
||||
// get the next market close for the specified date if the market closes at some point.
|
||||
// Otherwise, use the given date at midnight
|
||||
let marketClose = isMarketAlwaysOpen ?
|
||||
date.Date.AddDays(1) : security.Exchange.Hours.GetLastDailyMarketClose(date, security.IsExtendedMarketHours)
|
||||
// define the time of day we want the event to fire before marketclose
|
||||
let eventTime = isMarketAlwaysOpen ? marketClose : marketClose.Subtract(endOfDayDelta)
|
||||
// convert the event time into UTC
|
||||
let eventUtcTime = eventTime.ConvertToUtc(security.Exchange.TimeZone)
|
||||
// perform filter to verify it's not before the current time
|
||||
where !currentUtcTime.HasValue || eventUtcTime > currentUtcTime
|
||||
select eventUtcTime;
|
||||
|
||||
return new ScheduledEvent(CreateEventName(security.Symbol.ToString(), "EndOfDay"), times, (name, triggerTime) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
algorithm.OnEndOfDay(security.Symbol);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
resultHandler.RuntimeError($"Runtime error in {name} event: {err.Message}", err.StackTrace);
|
||||
Log.Error(err, $"ScheduledEvent.{name}:");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the format of event names generated by this system.
|
||||
/// </summary>
|
||||
/// <param name="scope">The scope of the event, example, 'Algorithm' or 'Security'</param>
|
||||
/// <param name="name">A name for this specified event in this scope, example, 'EndOfDay'</param>
|
||||
/// <returns>A string representing a fully scoped event name</returns>
|
||||
public static string CreateEventName(string scope, string name)
|
||||
{
|
||||
return $"{scope}.{name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user