/* * 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 NodaTime; using System.Linq; using QuantConnect.Interfaces; using QuantConnect.Securities; using System.Collections.Generic; using static QuantConnect.StringExtensions; namespace QuantConnect.Scheduling { /// /// Helper class used to provide better syntax when defining time rules /// public class TimeRules : BaseScheduleRules { /// /// Initializes a new instance of the helper class /// /// The algorithm instance /// The security manager /// The algorithm's default time zone /// The market hours database instance to use public TimeRules(IAlgorithm algorithm, SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase) : base(algorithm, securities, timeZone, marketHoursDatabase) { } /// /// Sets the default time zone /// /// The time zone to use for helper methods that can't resolve a time zone public void SetDefaultTimeZone(DateTimeZone timeZone) { TimeZone = timeZone; } /// /// Specifies an event should fire at the current time /// public ITimeRule Now => new FuncTimeRule("Now", dates => { return dates.Select(date => { // we ignore the given date and just use the current time, why? if the algorithm used 'DateRules.Today' // we get the algorithms first 'Date', which during warmup might not be a complete date, depending on the warmup period // and since Today returns dates we might get a time in the past which get's ignored. See 'WarmupTrainRegressionAlgorithm' // which reproduces GH issue #6410 return Securities.UtcTime; }); }); /// /// Convenience property for running a scheduled event at midnight in the algorithm time zone /// public ITimeRule Midnight => new FuncTimeRule("Midnight", dates => dates.Select(date => date.ConvertToUtc(TimeZone))); /// /// Convenience property for running a scheduled event at noon in the algorithm time zone /// public ITimeRule Noon => new FuncTimeRule("Noon", dates => dates.Select(date => date.ConvertToUtc(TimeZone).AddHours(12))); /// /// Specifies an event should fire at the specified time of day in the algorithm's time zone /// /// The time of day in the algorithm's time zone the event should fire /// A time rule that fires at the specified time in the algorithm's time zone public ITimeRule At(TimeSpan timeOfDay) { return At(timeOfDay, TimeZone); } /// /// Specifies an event should fire at the specified time of day in the algorithm's time zone /// /// The hour /// The minute /// The second /// A time rule that fires at the specified time in the algorithm's time zone public ITimeRule At(int hour, int minute, int second = 0) { return At(new TimeSpan(hour, minute, second), TimeZone); } /// /// Specifies an event should fire at the specified time of day in the specified time zone /// /// The hour /// The minute /// The time zone the event time is represented in /// A time rule that fires at the specified time in the algorithm's time zone public ITimeRule At(int hour, int minute, DateTimeZone timeZone) { return At(new TimeSpan(hour, minute, 0), timeZone); } /// /// Specifies an event should fire at the specified time of day in the specified time zone /// /// The hour /// The minute /// The second /// The time zone the event time is represented in /// A time rule that fires at the specified time in the algorithm's time zone public ITimeRule At(int hour, int minute, int second, DateTimeZone timeZone) { return At(new TimeSpan(hour, minute, second), timeZone); } /// /// Specifies an event should fire at the specified time of day in the specified time zone /// /// The time of day in the algorithm's time zone the event should fire /// The time zone the date time is expressed in /// A time rule that fires at the specified time in the algorithm's time zone public ITimeRule At(TimeSpan timeOfDay, DateTimeZone timeZone) { var name = string.Join(",", timeOfDay.TotalHours.ToStringInvariant("0.##")); Func, IEnumerable> applicator = dates => from date in dates let localEventTime = date + timeOfDay let utcEventTime = localEventTime.ConvertToUtc(timeZone) select utcEventTime; return new FuncTimeRule(name, applicator); } /// /// Specifies an event should fire periodically on the requested interval /// /// The frequency with which the event should fire, can not be zero or less /// A time rule that fires after each interval passes public ITimeRule Every(TimeSpan interval) { if (interval <= TimeSpan.Zero) { throw new ArgumentException("TimeRules.Every(): time span interval can not be zero or less"); } var name = Invariant($"Every {interval.TotalMinutes:0.##} min"); Func, IEnumerable> applicator = dates => EveryIntervalIterator(dates, interval, TimeZone); return new FuncTimeRule(name, applicator); } /// /// Specifies an event should fire at market open +- . /// Picks, per date, the earliest market open across the algorithm's securities, ignoring always-open /// exchanges. Defaults to US equities (SPY) when no eligible security is subscribed. /// /// The minutes before market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes before the earliest market open public ITimeRule BeforeMarketOpen(double minutesBeforeOpen = 0, bool extendedMarketOpen = false) { return AfterMarketOpen(minutesBeforeOpen * (-1), extendedMarketOpen); } /// /// Specifies an event should fire at market open +- /// /// The symbol whose market open we want an event for /// The minutes before market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes before the symbol's market open public ITimeRule BeforeMarketOpen(string symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false) => BeforeMarketOpen(GetSymbol(symbol), minutesBeforeOpen, extendedMarketOpen); /// /// Specifies an event should fire at market open +- /// /// The symbol whose market open we want an event for /// The minutes before market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes before the symbol's market open public ITimeRule BeforeMarketOpen(Symbol symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false) { return AfterMarketOpen(symbol, minutesBeforeOpen * (-1), extendedMarketOpen); } /// /// Specifies an event should fire at market open +- . /// Picks, per date, the earliest market open across the algorithm's securities, ignoring always-open /// exchanges. Defaults to US equities (SPY) when no eligible security is subscribed. /// /// The minutes after market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes after the earliest market open public ITimeRule AfterMarketOpen(double minutesAfterOpen = 0, bool extendedMarketOpen = false) { var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen"; var afterOrBefore = minutesAfterOpen > 0 ? "after" : "before"; var name = Invariant($"{Math.Abs(minutesAfterOpen):0.##} min {afterOrBefore} {type}"); var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen); return new FuncTimeRule(name, dates => EarliestMarketOpenTimes(dates, extendedMarketOpen, timeAfterOpen)); } private IEnumerable EarliestMarketOpenTimes(IEnumerable dates, bool extendedMarketOpen, TimeSpan timeAfterOpen) { var exchangeHoursList = GetMarketOpenCloseExchangeHours(); foreach (var date in dates) { var earliestUtc = default(DateTime); foreach (var exchangeHours in exchangeHoursList) { if (!exchangeHours.IsDateOpen(date, extendedMarketOpen)) { continue; } var marketOpen = exchangeHours.GetFirstDailyMarketOpen((date + Time.OneDay).AddTicks(-1), extendedMarketOpen); if (marketOpen.Date != date.Date) { continue; } var utc = (marketOpen + timeAfterOpen).ConvertToUtc(exchangeHours.TimeZone); if (earliestUtc == default || utc < earliestUtc) { earliestUtc = utc; } } if (earliestUtc != default) { yield return earliestUtc; } } } /// /// Specifies an event should fire at market open +- /// /// The symbol whose market open we want an event for /// The minutes after market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes after the symbol's market open public ITimeRule AfterMarketOpen(string symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false) => AfterMarketOpen(GetSymbol(symbol), minutesAfterOpen, extendedMarketOpen); /// /// Specifies an event should fire at market open +- /// /// The symbol whose market open we want an event for /// The minutes after market open that the event should fire /// True to use extended market open, false to use regular market open /// A time rule that fires the specified number of minutes after the symbol's market open public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false) { var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen"; var afterOrBefore = minutesAfterOpen > 0 ? "after" : "before"; var name = Invariant($"{symbol}: {Math.Abs(minutesAfterOpen):0.##} min {afterOrBefore} {type}"); var exchangeHours = GetSecurityExchangeHours(symbol); var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen); Func, IEnumerable> applicator = dates => from date in dates let marketOpen = exchangeHours.GetFirstDailyMarketOpen((date + Time.OneDay).AddTicks(-1), extendedMarketOpen) // make sure the market open is of this date where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == date.Date let localEventTime = marketOpen + timeAfterOpen let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone) select utcEventTime; return new FuncTimeRule(name, applicator); } /// /// Specifies an event should fire at the market close +- . /// Picks, per date, the latest market close across the algorithm's securities, ignoring always-open /// exchanges. Defaults to US equities (SPY) when no eligible security is subscribed. /// /// The time after market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes after the latest market close public ITimeRule AfterMarketClose(double minutesAfterClose = 0, bool extendedMarketClose = false) { return BeforeMarketClose(minutesAfterClose * (-1), extendedMarketClose); } /// /// Specifies an event should fire at the market close +- /// /// The symbol whose market close we want an event for /// The time after market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes after the symbol's market close public ITimeRule AfterMarketClose(string symbol, double minutesAfterClose = 0, bool extendedMarketClose = false) => AfterMarketClose(GetSymbol(symbol), minutesAfterClose, extendedMarketClose); /// /// Specifies an event should fire at the market close +- /// /// The symbol whose market close we want an event for /// The time after market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes after the symbol's market close public ITimeRule AfterMarketClose(Symbol symbol, double minutesAfterClose = 0, bool extendedMarketClose = false) { return BeforeMarketClose(symbol, minutesAfterClose * (-1), extendedMarketClose); } /// /// Specifies an event should fire at the market close +- . /// Picks, per date, the latest market close across the algorithm's securities, ignoring always-open /// exchanges. Defaults to US equities (SPY) when no eligible security is subscribed. /// /// The time before market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes before the latest market close public ITimeRule BeforeMarketClose(double minutesBeforeClose = 0, bool extendedMarketClose = false) { var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose"; var afterOrBefore = minutesBeforeClose > 0 ? "before" : "after"; var name = Invariant($"{Math.Abs(minutesBeforeClose):0.##} min {afterOrBefore} {type}"); var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose); return new FuncTimeRule(name, dates => LatestMarketCloseTimes(dates, extendedMarketClose, timeBeforeClose)); } private IEnumerable LatestMarketCloseTimes(IEnumerable dates, bool extendedMarketClose, TimeSpan timeBeforeClose) { var exchangeHoursList = GetMarketOpenCloseExchangeHours(); foreach (var date in dates) { var latestUtc = default(DateTime); foreach (var exchangeHours in exchangeHoursList) { if (!exchangeHours.IsDateOpen(date, extendedMarketClose)) { continue; } var marketClose = exchangeHours.GetLastDailyMarketClose(date, extendedMarketClose); var utc = (marketClose - timeBeforeClose).ConvertToUtc(exchangeHours.TimeZone); if (utc > latestUtc) { latestUtc = utc; } } if (latestUtc != default) { yield return latestUtc; } } } /// /// Specifies an event should fire at the market close +- /// /// The symbol whose market close we want an event for /// The time before market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes before the symbol's market close public ITimeRule BeforeMarketClose(string symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false) => BeforeMarketClose(GetSymbol(symbol), minutesBeforeClose, extendedMarketClose); /// /// Specifies an event should fire at the market close +- /// /// The symbol whose market close we want an event for /// The time before market close that the event should fire /// True to use extended market close, false to use regular market close /// A time rule that fires the specified number of minutes before the symbol's market close public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false) { var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose"; var afterOrBefore = minutesBeforeClose > 0 ? "before" : "after"; var name = Invariant($"{symbol}: {Math.Abs(minutesBeforeClose):0.##} min {afterOrBefore} {type}"); var exchangeHours = GetSecurityExchangeHours(symbol); var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose); Func, IEnumerable> applicator = dates => from date in dates let marketClose = exchangeHours.GetLastDailyMarketClose(date, extendedMarketClose) // make sure the market open is of this date where exchangeHours.IsDateOpen(date, extendedMarketClose) let localEventTime = marketClose - timeBeforeClose let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone) select utcEventTime; return new FuncTimeRule(name, applicator); } /// /// For each provided date will yield all the time intervals based on the supplied time span /// /// The dates for which we want to create the different intervals /// The interval value to use, can not be zero or less /// The time zone the date time is expressed in private static IEnumerable EveryIntervalIterator(IEnumerable dates, TimeSpan interval, DateTimeZone timeZone) { if (interval <= TimeSpan.Zero) { throw new ArgumentException("TimeRules.EveryIntervalIterator(): time span interval can not be zero or less"); } foreach (var date in dates) { for (var time = TimeSpan.Zero; time < Time.OneDay; time += interval) { yield return (date + time).ConvertToUtc(timeZone); } } } } }