Files
2026-07-13 13:02:50 +08:00

168 lines
9.3 KiB
C#

/*
* 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}";
}
}
}