chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -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);
}
}
}
}
}
}
+282
View File
@@ -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;
}
}
}
+68
View File
@@ -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.");
}
}
}
+167
View File
@@ -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}";
}
}
}