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