/* * 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.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 security manager /// The algorithm's default time zone /// The market hours database instance to use public TimeRules(SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase) : base(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 +- /// /// 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 +- /// /// 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 +- /// /// 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 +- /// /// 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); } } } } }