/* * 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 NodaTime; using QuantConnect.Securities; using QuantConnect.Logging; using Python.Runtime; namespace QuantConnect.Scheduling { /// /// Provides access to the real time handler's event scheduling feature /// public class ScheduleManager : IEventSchedule { private IEventSchedule _eventSchedule; private readonly SecurityManager _securities; private readonly object _eventScheduleLock = new object(); private readonly List _preInitializedEvents; /// /// Gets the date rules helper object to make specifying dates for events easier /// public DateRules DateRules { get; } /// /// Gets the time rules helper object to make specifying times for events easier /// public TimeRules TimeRules { get; } /// /// Initializes a new instance of the class /// /// Securities manager containing the algorithm's securities /// The algorithm's time zone /// The market hours database instance to use public ScheduleManager(SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase) { _securities = securities; DateRules = new DateRules(securities, timeZone, marketHoursDatabase); TimeRules = new TimeRules(securities, timeZone, marketHoursDatabase); // used for storing any events before the event schedule is set _preInitializedEvents = new List(); } /// /// Sets the implementation /// /// The event schedule implementation to be used. This is the IRealTimeHandler internal void SetEventSchedule(IEventSchedule eventSchedule) { if (eventSchedule == null) { throw new ArgumentNullException(nameof(eventSchedule)); } lock (_eventScheduleLock) { _eventSchedule = eventSchedule; // load up any events that were added before we were ready to send them to the scheduler foreach (var scheduledEvent in _preInitializedEvents) { _eventSchedule.Add(scheduledEvent); } } } /// /// Adds the specified event to the schedule /// /// The event to be scheduled, including the date/times the event fires and the callback public void Add(ScheduledEvent scheduledEvent) { lock (_eventScheduleLock) { if (_eventSchedule != null) { _eventSchedule.Add(scheduledEvent); } else { _preInitializedEvents.Add(scheduledEvent); } } } /// /// Removes the specified event from the schedule /// /// The event to be removed public void Remove(ScheduledEvent scheduledEvent) { lock (_eventScheduleLock) { if (_eventSchedule != null) { _eventSchedule.Remove(scheduledEvent); } else { _preInitializedEvents.RemoveAll(se => Equals(se, scheduledEvent)); } } } /// /// Schedules the callback to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, Action callback) { return On(dateRule, timeRule, (name, time) => callback()); } /// /// Schedules the callback to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, PyObject callback) { return On(dateRule, timeRule, (name, time) => { using (Py.GIL()) callback.Invoke(); }); } /// /// Schedules the callback to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, Action callback) { var name = $"{dateRule.Name}: {timeRule.Name}"; return On(name, dateRule, timeRule, callback); } /// /// Schedules the callback to run using the specified date and time rules /// /// The event's unique name /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, Action callback) { return On(name, dateRule, timeRule, (n, d) => callback()); } /// /// Schedules the callback to run using the specified date and time rules /// /// The event's unique name /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, PyObject callback) { return On(name, dateRule, timeRule, (n, d) => { using (Py.GIL()) callback.Invoke(); }); } /// /// Schedules the callback to run using the specified date and time rules /// /// The event's unique name /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The callback to be invoked public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, Action callback) { // back the date up to ensure we get all events, the event scheduler will skip past events that whose time has passed var dates = GetDatesDeferred(dateRule, _securities); var eventTimes = timeRule.CreateUtcEventTimes(dates); var scheduledEvent = new ScheduledEvent(name, eventTimes, callback); Add(scheduledEvent); Log.Trace($"Event Name \"{scheduledEvent.Name}\", scheduled to run."); return scheduledEvent; } #region Fluent Scheduling /// /// Entry point for the fluent scheduled event builder /// public IFluentSchedulingDateSpecifier Event() { return new FluentScheduledEventBuilder(this, _securities); } /// /// Entry point for the fluent scheduled event builder /// public IFluentSchedulingDateSpecifier Event(string name) { return new FluentScheduledEventBuilder(this, _securities, name); } #endregion #region Training Events /// /// Schedules the provided training code to execute immediately /// public ScheduledEvent TrainingNow(Action trainingCode) { return On($"Training: Now: {_securities.UtcTime:O}", DateRules.Today, TimeRules.Now, trainingCode); } /// /// Schedules the provided training code to execute immediately /// public ScheduledEvent TrainingNow(PyObject trainingCode) { return On($"Training: Now: {_securities.UtcTime:O}", DateRules.Today, TimeRules.Now, trainingCode); } /// /// Schedules the training code to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The training code to be invoked public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, Action trainingCode) { var name = $"{dateRule.Name}: {timeRule.Name}"; return On(name, dateRule, timeRule, (n, time) => trainingCode()); } /// /// Schedules the training code to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The training code to be invoked public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, PyObject trainingCode) { var name = $"{dateRule.Name}: {timeRule.Name}"; return On(name, dateRule, timeRule, (n, time) => { using (Py.GIL()) trainingCode.Invoke(); }); } /// /// Schedules the training code to run using the specified date and time rules /// /// Specifies what dates the event should run /// Specifies the times on those dates the event should run /// The training code to be invoked public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, Action trainingCode) { var name = $"{dateRule.Name}: {timeRule.Name}"; return On(name, dateRule, timeRule, (n, time) => trainingCode(time)); } #endregion /// /// Helper methods to defer the evaluation of the current time until the dates are enumerated for the first time. /// This allows for correct support for warmup period /// internal static IEnumerable GetDatesDeferred(IDateRule dateRule, SecurityManager securities) { foreach (var item in dateRule.GetDates(DateTime.SpecifyKind(securities.UtcTime.Date.AddDays(-1), DateTimeKind.Unspecified), Time.EndOfTime)) { yield return item; } } } }