412 lines
23 KiB
C#
412 lines
23 KiB
C#
/*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// Helper class used to provide better syntax when defining time rules
|
|
/// </summary>
|
|
public class TimeRules : BaseScheduleRules
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TimeRules"/> helper class
|
|
/// </summary>
|
|
/// <param name="algorithm">The algorithm instance</param>
|
|
/// <param name="securities">The security manager</param>
|
|
/// <param name="timeZone">The algorithm's default time zone</param>
|
|
/// <param name="marketHoursDatabase">The market hours database instance to use</param>
|
|
public TimeRules(IAlgorithm algorithm, SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
|
|
: base(algorithm, securities, timeZone, marketHoursDatabase)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the default time zone
|
|
/// </summary>
|
|
/// <param name="timeZone">The time zone to use for helper methods that can't resolve a time zone</param>
|
|
public void SetDefaultTimeZone(DateTimeZone timeZone)
|
|
{
|
|
TimeZone = timeZone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the current time
|
|
/// </summary>
|
|
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;
|
|
});
|
|
});
|
|
|
|
/// <summary>
|
|
/// Convenience property for running a scheduled event at midnight in the algorithm time zone
|
|
/// </summary>
|
|
public ITimeRule Midnight => new FuncTimeRule("Midnight", dates => dates.Select(date => date.ConvertToUtc(TimeZone)));
|
|
|
|
/// <summary>
|
|
/// Convenience property for running a scheduled event at noon in the algorithm time zone
|
|
/// </summary>
|
|
public ITimeRule Noon => new FuncTimeRule("Noon", dates => dates.Select(date => date.ConvertToUtc(TimeZone).AddHours(12)));
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the specified time of day in the algorithm's time zone
|
|
/// </summary>
|
|
/// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
|
|
/// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
|
|
public ITimeRule At(TimeSpan timeOfDay)
|
|
{
|
|
return At(timeOfDay, TimeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the specified time of day in the algorithm's time zone
|
|
/// </summary>
|
|
/// <param name="hour">The hour</param>
|
|
/// <param name="minute">The minute</param>
|
|
/// <param name="second">The second</param>
|
|
/// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
|
|
public ITimeRule At(int hour, int minute, int second = 0)
|
|
{
|
|
return At(new TimeSpan(hour, minute, second), TimeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the specified time of day in the specified time zone
|
|
/// </summary>
|
|
/// <param name="hour">The hour</param>
|
|
/// <param name="minute">The minute</param>
|
|
/// <param name="timeZone">The time zone the event time is represented in</param>
|
|
/// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
|
|
public ITimeRule At(int hour, int minute, DateTimeZone timeZone)
|
|
{
|
|
return At(new TimeSpan(hour, minute, 0), timeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the specified time of day in the specified time zone
|
|
/// </summary>
|
|
/// <param name="hour">The hour</param>
|
|
/// <param name="minute">The minute</param>
|
|
/// <param name="second">The second</param>
|
|
/// <param name="timeZone">The time zone the event time is represented in</param>
|
|
/// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
|
|
public ITimeRule At(int hour, int minute, int second, DateTimeZone timeZone)
|
|
{
|
|
return At(new TimeSpan(hour, minute, second), timeZone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the specified time of day in the specified time zone
|
|
/// </summary>
|
|
/// <param name="timeOfDay">The time of day in the algorithm's time zone the event should fire</param>
|
|
/// <param name="timeZone">The time zone the date time is expressed in</param>
|
|
/// <returns>A time rule that fires at the specified time in the algorithm's time zone</returns>
|
|
public ITimeRule At(TimeSpan timeOfDay, DateTimeZone timeZone)
|
|
{
|
|
var name = string.Join(",", timeOfDay.TotalHours.ToStringInvariant("0.##"));
|
|
Func<IEnumerable<DateTime>, IEnumerable<DateTime>> applicator = dates =>
|
|
from date in dates
|
|
let localEventTime = date + timeOfDay
|
|
let utcEventTime = localEventTime.ConvertToUtc(timeZone)
|
|
select utcEventTime;
|
|
|
|
return new FuncTimeRule(name, applicator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire periodically on the requested interval
|
|
/// </summary>
|
|
/// <param name="interval">The frequency with which the event should fire, can not be zero or less</param>
|
|
/// <returns>A time rule that fires after each interval passes</returns>
|
|
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<DateTime>, IEnumerable<DateTime>> applicator = dates => EveryIntervalIterator(dates, interval, TimeZone);
|
|
return new FuncTimeRule(name, applicator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesBeforeOpen"/>.
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="minutesBeforeOpen">The minutes before market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the earliest market open</returns>
|
|
public ITimeRule BeforeMarketOpen(double minutesBeforeOpen = 0, bool extendedMarketOpen = false)
|
|
{
|
|
return AfterMarketOpen(minutesBeforeOpen * (-1), extendedMarketOpen);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesBeforeOpen"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market open we want an event for</param>
|
|
/// <param name="minutesBeforeOpen">The minutes before market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the symbol's market open</returns>
|
|
public ITimeRule BeforeMarketOpen(string symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false) => BeforeMarketOpen(GetSymbol(symbol), minutesBeforeOpen, extendedMarketOpen);
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesBeforeOpen"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market open we want an event for</param>
|
|
/// <param name="minutesBeforeOpen">The minutes before market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the symbol's market open</returns>
|
|
public ITimeRule BeforeMarketOpen(Symbol symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false)
|
|
{
|
|
return AfterMarketOpen(symbol, minutesBeforeOpen * (-1), extendedMarketOpen);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>.
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the earliest market open</returns>
|
|
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<DateTime> EarliestMarketOpenTimes(IEnumerable<DateTime> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market open we want an event for</param>
|
|
/// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the symbol's market open</returns>
|
|
public ITimeRule AfterMarketOpen(string symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false) => AfterMarketOpen(GetSymbol(symbol), minutesAfterOpen, extendedMarketOpen);
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at market open +- <paramref name="minutesAfterOpen"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market open we want an event for</param>
|
|
/// <param name="minutesAfterOpen">The minutes after market open that the event should fire</param>
|
|
/// <param name="extendedMarketOpen">True to use extended market open, false to use regular market open</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the symbol's market open</returns>
|
|
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<DateTime>, IEnumerable<DateTime>> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesAfterClose"/>.
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="minutesAfterClose">The time after market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the latest market close</returns>
|
|
public ITimeRule AfterMarketClose(double minutesAfterClose = 0, bool extendedMarketClose = false)
|
|
{
|
|
return BeforeMarketClose(minutesAfterClose * (-1), extendedMarketClose);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesAfterClose"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market close we want an event for</param>
|
|
/// <param name="minutesAfterClose">The time after market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the symbol's market close</returns>
|
|
public ITimeRule AfterMarketClose(string symbol, double minutesAfterClose = 0, bool extendedMarketClose = false) => AfterMarketClose(GetSymbol(symbol), minutesAfterClose, extendedMarketClose);
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesAfterClose"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market close we want an event for</param>
|
|
/// <param name="minutesAfterClose">The time after market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes after the symbol's market close</returns>
|
|
public ITimeRule AfterMarketClose(Symbol symbol, double minutesAfterClose = 0, bool extendedMarketClose = false)
|
|
{
|
|
return BeforeMarketClose(symbol, minutesAfterClose * (-1), extendedMarketClose);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>.
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the latest market close</returns>
|
|
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<DateTime> LatestMarketCloseTimes(IEnumerable<DateTime> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market close we want an event for</param>
|
|
/// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the symbol's market close</returns>
|
|
public ITimeRule BeforeMarketClose(string symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false) => BeforeMarketClose(GetSymbol(symbol), minutesBeforeClose, extendedMarketClose);
|
|
|
|
/// <summary>
|
|
/// Specifies an event should fire at the market close +- <paramref name="minutesBeforeClose"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol whose market close we want an event for</param>
|
|
/// <param name="minutesBeforeClose">The time before market close that the event should fire</param>
|
|
/// <param name="extendedMarketClose">True to use extended market close, false to use regular market close</param>
|
|
/// <returns>A time rule that fires the specified number of minutes before the symbol's market close</returns>
|
|
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<DateTime>, IEnumerable<DateTime>> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For each provided date will yield all the time intervals based on the supplied time span
|
|
/// </summary>
|
|
/// <param name="dates">The dates for which we want to create the different intervals</param>
|
|
/// <param name="interval">The interval value to use, can not be zero or less</param>
|
|
/// <param name="timeZone">The time zone the date time is expressed in</param>
|
|
private static IEnumerable<DateTime> EveryIntervalIterator(IEnumerable<DateTime> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|