/* * 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 NodaTime; using QuantConnect.Securities; namespace QuantConnect.Scheduling { /// /// Provides a builder class to allow for fluent syntax when constructing new events /// /// /// This builder follows the following steps for event creation: /// /// 1. Specify an event name (optional) /// 2. Specify an IDateRule /// 3. Specify an ITimeRule /// a. repeat 3. to define extra time rules (optional) /// 4. Specify additional where clause (optional) /// 5. Register event via call to Run /// public class FluentScheduledEventBuilder : IFluentSchedulingDateSpecifier, IFluentSchedulingRunnable { private IDateRule _dateRule; private ITimeRule _timeRule; private Func _predicate; private readonly string _name; private readonly ScheduleManager _schedule; private readonly SecurityManager _securities; /// /// Initializes a new instance of the class /// /// The schedule to send created events to /// The algorithm's security manager /// A specific name for this event public FluentScheduledEventBuilder(ScheduleManager schedule, SecurityManager securities, string name = null) { _name = name; _schedule = schedule; _securities = securities; } private FluentScheduledEventBuilder SetTimeRule(ITimeRule rule) { // if it's not set, just set it if (_timeRule == null) { _timeRule = rule; return this; } // if it's already a composite, open it up and make a new composite // prevent nesting composites var compositeTimeRule = _timeRule as CompositeTimeRule; if (compositeTimeRule != null) { var rules = compositeTimeRule.Rules; _timeRule = new CompositeTimeRule(rules.Concat(new[] { rule })); return this; } // create a composite from the existing rule and the new rules _timeRule = new CompositeTimeRule(_timeRule, rule); return this; } #region DateRules and TimeRules delegation /// /// Creates events on each of the specified day of week /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.Every(params DayOfWeek[] days) { _dateRule = _schedule.DateRules.Every(days); return this; } /// /// Creates events on every day of the year /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.EveryDay() { _dateRule = _schedule.DateRules.EveryDay(); return this; } /// /// Creates events on every trading day of the year for the symbol /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.EveryDay(Symbol symbol) { _dateRule = _schedule.DateRules.EveryDay(symbol); return this; } /// /// Creates events on the first day of the month /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.MonthStart() { _dateRule = _schedule.DateRules.MonthStart(); return this; } /// /// Creates events on the first trading day of the month /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.MonthStart(Symbol symbol) { _dateRule = _schedule.DateRules.MonthStart(symbol); return this; } /// /// Filters the event times using the predicate /// IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.Where(Func predicate) { _predicate = _predicate == null ? predicate : (time => _predicate(time) && predicate(time)); return this; } /// /// Creates events that fire at the specific time of day in the algorithm's time zone /// IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(TimeSpan timeOfDay) { return SetTimeRule(_schedule.TimeRules.At(timeOfDay)); } /// /// Creates events that fire a specified number of minutes after market open /// IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.AfterMarketOpen(Symbol symbol, double minutesAfterOpen, bool extendedMarketOpen) { return SetTimeRule(_schedule.TimeRules.AfterMarketOpen(symbol, minutesAfterOpen, extendedMarketOpen)); } /// /// Creates events that fire a specified numer of minutes before market close /// IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.BeforeMarketClose(Symbol symbol, double minuteBeforeClose, bool extendedMarketClose) { return SetTimeRule(_schedule.TimeRules.BeforeMarketClose(symbol, minuteBeforeClose, extendedMarketClose)); } /// /// Creates events that fire on a period define by the specified interval /// IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.Every(TimeSpan interval) { return SetTimeRule(_schedule.TimeRules.Every(interval)); } /// /// Filters the event times using the predicate /// IFluentSchedulingTimeSpecifier IFluentSchedulingTimeSpecifier.Where(Func predicate) { _predicate = _predicate == null ? predicate : (time => _predicate(time) && predicate(time)); return this; } /// /// Register the defined event with the callback /// ScheduledEvent IFluentSchedulingRunnable.Run(Action callback) { return ((IFluentSchedulingRunnable)this).Run((name, time) => callback()); } /// /// Register the defined event with the callback /// ScheduledEvent IFluentSchedulingRunnable.Run(Action callback) { return ((IFluentSchedulingRunnable)this).Run((name, time) => callback(time)); } /// /// Register the defined event with the callback /// ScheduledEvent IFluentSchedulingRunnable.Run(Action callback) { var name = _name ?? _dateRule.Name + ": " + _timeRule.Name; // back the date up to ensure we get all events, the event scheduler will skip past events that whose time has passed var dates = ScheduleManager.GetDatesDeferred(_dateRule, _securities); var eventTimes = _timeRule.CreateUtcEventTimes(dates); if (_predicate != null) { eventTimes = eventTimes.Where(_predicate); } var scheduledEvent = new ScheduledEvent(name, eventTimes, callback); _schedule.Add(scheduledEvent); return scheduledEvent; } /// /// Filters the event times using the predicate /// IFluentSchedulingRunnable IFluentSchedulingRunnable.Where(Func predicate) { _predicate = _predicate == null ? predicate : (time => _predicate(time) && predicate(time)); return this; } /// /// Filters the event times to only include times where the symbol's market is considered open /// IFluentSchedulingRunnable IFluentSchedulingRunnable.DuringMarketHours(Symbol symbol, bool extendedMarket) { var security = GetSecurity(symbol); Func predicate = time => { var localTime = time.ConvertFromUtc(security.Exchange.TimeZone); return security.Exchange.IsOpenDuringBar(localTime, localTime, extendedMarket); }; _predicate = _predicate == null ? predicate : (time => _predicate(time) && predicate(time)); return this; } IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.On(int year, int month, int day) { _dateRule = _schedule.DateRules.On(year, month, day); return this; } IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.On(params DateTime[] dates) { _dateRule = _schedule.DateRules.On(dates); return this; } IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(int hour, int minute, int second) { return SetTimeRule(_schedule.TimeRules.At(hour, minute, second)); } IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(int hour, int minute, DateTimeZone timeZone) { return SetTimeRule(_schedule.TimeRules.At(hour, minute, 0, timeZone)); } IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(int hour, int minute, int second, DateTimeZone timeZone) { return SetTimeRule(_schedule.TimeRules.At(hour, minute, second, timeZone)); } IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(TimeSpan timeOfDay, DateTimeZone timeZone) { return SetTimeRule(_schedule.TimeRules.At(timeOfDay, timeZone)); } private Security GetSecurity(Symbol symbol) { Security security; if (!_securities.TryGetValue(symbol, out security)) { throw new KeyNotFoundException($"{symbol} not found in portfolio. Request this data when initializing the algorithm."); } return security; } #endregion } /// /// Specifies the date rule component of a scheduled event /// public interface IFluentSchedulingDateSpecifier { /// /// Filters the event times using the predicate /// IFluentSchedulingTimeSpecifier Where(Func predicate); /// /// Creates events only on the specified date /// IFluentSchedulingTimeSpecifier On(int year, int month, int day); /// /// Creates events only on the specified dates /// IFluentSchedulingTimeSpecifier On(params DateTime[] dates); /// /// Creates events on each of the specified day of week /// IFluentSchedulingTimeSpecifier Every(params DayOfWeek[] days); /// /// Creates events on every day of the year /// IFluentSchedulingTimeSpecifier EveryDay(); /// /// Creates events on every trading day of the year for the symbol /// IFluentSchedulingTimeSpecifier EveryDay(Symbol symbol); /// /// Creates events on the first day of the month /// IFluentSchedulingTimeSpecifier MonthStart(); /// /// Creates events on the first trading day of the month /// IFluentSchedulingTimeSpecifier MonthStart(Symbol symbol); } /// /// Specifies the time rule component of a scheduled event /// public interface IFluentSchedulingTimeSpecifier { /// /// Filters the event times using the predicate /// IFluentSchedulingTimeSpecifier Where(Func predicate); /// /// Creates events that fire at the specified time of day in the specified time zone /// IFluentSchedulingRunnable At(int hour, int minute, int second = 0); /// /// Creates events that fire at the specified time of day in the specified time zone /// IFluentSchedulingRunnable At(int hour, int minute, DateTimeZone timeZone); /// /// Creates events that fire at the specified time of day in the specified time zone /// IFluentSchedulingRunnable At(int hour, int minute, int second, DateTimeZone timeZone); /// /// Creates events that fire at the specified time of day in the specified time zone /// IFluentSchedulingRunnable At(TimeSpan timeOfDay, DateTimeZone timeZone); /// /// Creates events that fire at the specific time of day in the algorithm's time zone /// IFluentSchedulingRunnable At(TimeSpan timeOfDay); /// /// Creates events that fire on a period define by the specified interval /// IFluentSchedulingRunnable Every(TimeSpan interval); /// /// Creates events that fire a specified number of minutes after market open /// IFluentSchedulingRunnable AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false); /// /// Creates events that fire a specified numer of minutes before market close /// IFluentSchedulingRunnable BeforeMarketClose(Symbol symbol, double minuteBeforeClose = 0, bool extendedMarketClose = false); } /// /// Specifies the callback component of a scheduled event, as well as final filters /// public interface IFluentSchedulingRunnable : IFluentSchedulingTimeSpecifier { /// /// Filters the event times using the predicate /// new IFluentSchedulingRunnable Where(Func predicate); /// /// Filters the event times to only include times where the symbol's market is considered open /// IFluentSchedulingRunnable DuringMarketHours(Symbol symbol, bool extendedMarket = false); /// /// Register the defined event with the callback /// ScheduledEvent Run(Action callback); /// /// Register the defined event with the callback /// ScheduledEvent Run(Action callback); /// /// Register the defined event with the callback /// ScheduledEvent Run(Action callback); } }