chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
/*
* 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 NodaTime;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Base rule scheduler
/// </summary>
public class BaseScheduleRules
{
private bool _sentImplicitWarning;
private readonly IAlgorithm _algorithm;
/// <summary>
/// The algorithm's default time zone
/// </summary>
protected DateTimeZone TimeZone { get; set; }
/// <summary>
/// The security manager
/// </summary>
protected SecurityManager Securities { get; set; }
/// <summary>
/// The market hours database instance to use
/// </summary>
protected MarketHoursDatabase MarketHoursDatabase { get; set; }
/// <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 BaseScheduleRules(IAlgorithm algorithm, SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
{
TimeZone = timeZone;
_algorithm = algorithm;
Securities = securities;
MarketHoursDatabase = marketHoursDatabase;
}
/// <summary>
/// Helper method to fetch the security exchange hours
/// </summary>
protected SecurityExchangeHours GetSecurityExchangeHours(Symbol symbol)
{
if (!Securities.TryGetValue(symbol, out var security))
{
return MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType).ExchangeHours;
}
return security.Exchange.Hours;
}
/// <summary>
/// Helper method to fetch the exchange hours of the securities currently in <see cref="Securities"/>
/// whose markets are not always open. If no such securities are present, falls back to US equities (SPY).
/// </summary>
protected IEnumerable<SecurityExchangeHours> GetMarketOpenCloseExchangeHours()
{
// Pre-seed with SPY's exchange hours: this guarantees a fallback when no eligible
// security is subscribed and implicitly covers every US equity, which shares the
// same exchange hours — so we can skip US equities below to save the lookup.
var hours = new HashSet<SecurityExchangeHours>
{
MarketHoursDatabase.GetEntry(Market.USA, "SPY", SecurityType.Equity).ExchangeHours
};
foreach (var (symbol, security) in Securities)
{
if (security.Type == SecurityType.Equity && symbol.ID.Market == Market.USA)
{
continue;
}
var exchangeHours = security.Exchange.Hours;
if (!exchangeHours.IsMarketAlwaysOpen)
{
hours.Add(exchangeHours);
}
}
return hours;
}
protected Symbol GetSymbol(string ticker)
{
if (SymbolCache.TryGetSymbol(ticker, out var symbolCache))
{
return symbolCache;
}
if (!_sentImplicitWarning)
{
_sentImplicitWarning = true;
_algorithm?.Debug($"Warning: no existing symbol found for ticker {ticker}, it will be created with {SecurityType.Equity} type.");
}
symbolCache = Symbol.Create(ticker, SecurityType.Equity, Market.USA);
SymbolCache.Set(ticker, symbolCache);
return symbolCache;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
/*
* 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 QuantConnect.Util;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Combines multiple time rules into a single rule that emits for each rule
/// </summary>
public class CompositeTimeRule : ITimeRule
{
/// <summary>
/// Gets the individual rules for this composite rule
/// </summary>
public IReadOnlyList<ITimeRule> Rules { get; init; }
/// <summary>
/// Initializes a new instance of the <see cref="CompositeTimeRule"/> class
/// </summary>
/// <param name="timeRules">The time rules to compose</param>
public CompositeTimeRule(params ITimeRule[] timeRules)
: this((IEnumerable<ITimeRule>) timeRules)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CompositeTimeRule"/> class
/// </summary>
/// <param name="timeRules">The time rules to compose</param>
public CompositeTimeRule(IEnumerable<ITimeRule> timeRules)
{
Rules = timeRules.ToList();
}
/// <summary>
/// Gets a name for this rule
/// </summary>
public string Name
{
get { return string.Join(",", Rules.Select(x => x.Name)); }
}
/// <summary>
/// Creates the event times for the specified dates in UTC
/// </summary>
/// <param name="dates">The dates to apply times to</param>
/// <returns>An enumerable of date times that is the result
/// of applying this rule to the specified dates</returns>
public IEnumerable<DateTime> CreateUtcEventTimes(IEnumerable<DateTime> dates)
{
foreach (var date in dates)
{
// make unqiue times and order the events before yielding
var enumerable = new[] {date};
var times = Rules.SelectMany(time => time.CreateUtcEventTimes(enumerable)).ToHashSet().OrderBy(x => x);
foreach (var time in times)
{
yield return time;
}
}
}
}
}
+731
View File
@@ -0,0 +1,731 @@
/*
* 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 System.Globalization;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Helper class used to provide better syntax when defining date rules
/// </summary>
public class DateRules : BaseScheduleRules
{
/// <summary>
/// Initializes a new instance of the <see cref="DateRules"/> 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 DateRules(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 only on the specified day
/// </summary>
/// <param name="year">The year</param>
/// <param name="month">The month</param>
/// <param name="day">The day</param>
/// <returns></returns>
public IDateRule On(int year, int month, int day)
{
// make sure they're date objects
var dates = new[] {new DateTime(year, month, day)};
return new FuncDateRule(string.Join(",", dates.Select(x => x.ToShortDateString())), (start, end) => dates.Where(x => x >= start && x <= end));
}
/// <summary>
/// Specifies an event should fire only on the specified days
/// </summary>
/// <param name="dates">The dates the event should fire</param>
public IDateRule On(params DateTime[] dates)
{
// make sure they're date objects
dates = dates.Select(x => x.Date).ToArray();
return new FuncDateRule(string.Join(",", dates.Select(x => x.ToShortDateString())), (start, end) => dates.Where(x => x >= start && x <= end));
}
/// <summary>
/// Specifies an event should only fire today in the algorithm's time zone
/// using _securities.UtcTime instead of 'start' since ScheduleManager backs it up a day
/// </summary>
public IDateRule Today => new FuncDateRule("TodayOnly",
(start, e) => {
return new[] { Securities.UtcTime.ConvertFromUtc(TimeZone).Date };
}
);
/// <summary>
/// Specifies an event should only fire tomorrow in the algorithm's time zone
/// using _securities.UtcTime instead of 'start' since ScheduleManager backs it up a day
/// </summary>
public IDateRule Tomorrow => new FuncDateRule("TomorrowOnly",
(start, e) => new[] {Securities.UtcTime.ConvertFromUtc(TimeZone).Date.AddDays(1)}
);
/// <summary>
/// Specifies an event should fire on each of the specified days of week
/// </summary>
/// <param name="day">The day the event should fire</param>
/// <returns>A date rule that fires on every specified day of week</returns>
public IDateRule Every(DayOfWeek day) => Every(new[] { day });
/// <summary>
/// Specifies an event should fire on each of the specified days of week
/// </summary>
/// <param name="days">The days the event should fire</param>
/// <returns>A date rule that fires on every specified day of week</returns>
public IDateRule Every(params DayOfWeek[] days)
{
var hash = days.ToHashSet();
return new FuncDateRule(string.Join(",", days), (start, end) => Time.EachDay(start, end).Where(date => hash.Contains(date.DayOfWeek)));
}
/// <summary>
/// Specifies an event should fire every day
/// </summary>
/// <returns>A date rule that fires every day</returns>
public IDateRule EveryDay()
{
return new FuncDateRule("EveryDay", Time.EachDay);
}
/// <summary>
/// Specifies an event should fire every day the symbol is trading
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine tradable dates</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires every day the specified symbol trades</returns>
public IDateRule EveryDay(string symbol, bool extendedMarketHours = false) => EveryDay(GetSymbol(symbol), extendedMarketHours);
/// <summary>
/// Specifies an event should fire every day the symbol is trading
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine tradable dates</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires every day the specified symbol trades</returns>
public IDateRule EveryDay(Symbol symbol, bool extendedMarketHours = false)
{
var securitySchedule = GetSecurityExchangeHours(symbol);
return new FuncDateRule($"{symbol.Value}: EveryDay", (start, end) => Time.EachTradeableDay(securitySchedule, start, end, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the first of each year + offset
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 365.</param>
/// <returns>A date rule that fires on the first of each year + offset</returns>
public IDateRule YearStart(int daysOffset = 0)
{
return YearStart((Symbol)null, daysOffset, false);
}
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each year
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the year</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 365</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each year</returns>
public IDateRule YearStart(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => YearStart(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each year
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the year</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 365</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each year</returns>
public IDateRule YearStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 365 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.YearStart() : Offset must be between 0 and 365");
}
SecurityExchangeHours securityExchangeHours = null;
if (symbol != null)
{
securityExchangeHours = GetSecurityExchangeHours(symbol);
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "YearStart", daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset, true, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the last of each year
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 365</param>
/// <returns>A date rule that fires on the last of each year - offset</returns>
public IDateRule YearEnd(int daysOffset = 0)
{
return YearEnd((Symbol)null, daysOffset, false);
}
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each year
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the year</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 365.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each year</returns>
public IDateRule YearEnd(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => YearEnd(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each year
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the year</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 365.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each year</returns>
public IDateRule YearEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 365 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.YearEnd() : Offset must be between 0 and 365");
}
SecurityExchangeHours securityExchangeHours = null;
if (symbol != null)
{
securityExchangeHours = GetSecurityExchangeHours(symbol);
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "YearEnd", -daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset, false, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the first of each quarter + offset
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 92.</param>
/// <returns>A date rule that fires on the first of each quarter + offset</returns>
public IDateRule QuarterStart(int daysOffset = 0)
{
return QuarterStart((Symbol)null, daysOffset, false);
}
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each quarter
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the quarter</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 92</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each quarter</returns>
public IDateRule QuarterStart(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => QuarterStart(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each quarter
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the quarter</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 92</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each quarter</returns>
public IDateRule QuarterStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 92 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.QuarterStart() : Offset must be between 0 and 92");
}
SecurityExchangeHours securityExchangeHours = null;
if (symbol != null)
{
securityExchangeHours = GetSecurityExchangeHours(symbol);
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "QuarterStart", daysOffset), (start, end) => QuarterIterator(securityExchangeHours, start, end, daysOffset, true, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the last of each quarter
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 92</param>
/// <returns>A date rule that fires on the last of each quarter - offset</returns>
public IDateRule QuarterEnd(int daysOffset = 0)
{
return QuarterEnd((Symbol)null, daysOffset, false);
}
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each quarter
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the quarter</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 92.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each quarter</returns>
public IDateRule QuarterEnd(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => QuarterEnd(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each quarter
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the quarter</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 92.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each quarter</returns>
public IDateRule QuarterEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 92 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.QuarterEnd() : Offset must be between 0 and 92");
}
SecurityExchangeHours securityExchangeHours = null;
if (symbol != null)
{
securityExchangeHours = GetSecurityExchangeHours(symbol);
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "QuarterEnd", -daysOffset), (start, end) => QuarterIterator(securityExchangeHours, start, end, daysOffset, false, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the first of each month + offset
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 30.</param>
/// <returns>A date rule that fires on the first of each month + offset</returns>
public IDateRule MonthStart(int daysOffset = 0)
{
return new FuncDateRule(GetName(null, "MonthStart", daysOffset), (start, end) => MonthIterator(null, start, end, daysOffset, true, false));
}
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each month
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the month</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 30</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each month</returns>
public IDateRule MonthStart(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => MonthStart(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified symbol of each month
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the month</param>
/// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 30</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the first tradable date + offset for the
/// specified security each month</returns>
public IDateRule MonthStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 30 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.MonthStart() : Offset must be between 0 and 30");
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "MonthStart", daysOffset), (start, end) => MonthIterator(GetSecurityExchangeHours(symbol), start, end, daysOffset, true, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on the last of each month
/// </summary>
/// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 30</param>
/// <returns>A date rule that fires on the last of each month - offset</returns>
public IDateRule MonthEnd(int daysOffset = 0)
{
return new FuncDateRule(GetName(null, "MonthEnd", -daysOffset), (start, end) => MonthIterator(null, start, end, daysOffset, false, false));
}
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each month
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the month</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 30.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each month</returns>
public IDateRule MonthEnd(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => MonthEnd(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the last tradable date - offset for the specified symbol of each month
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the month</param>
/// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 30.</param>
/// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
/// <returns>A date rule that fires on the last tradable date - offset for the specified security each month</returns>
public IDateRule MonthEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
// Check that our offset is allowed
if (daysOffset < 0 || 30 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.MonthEnd() : Offset must be between 0 and 30");
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "MonthEnd", -daysOffset), (start, end) => MonthIterator(GetSecurityExchangeHours(symbol), start, end, daysOffset, false, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on Monday + offset each week
/// </summary>
/// <param name="daysOffset">The amount of days to offset monday by; must be between 0 and 6</param>
/// <returns>A date rule that fires on Monday + offset each week</returns>
public IDateRule WeekStart(int daysOffset = 0)
{
// Check that our offset is allowed
if (daysOffset < 0 || 6 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.WeekStart() : Offset must be between 0 and 6");
}
return new FuncDateRule(GetName(null, "WeekStart", daysOffset), (start, end) => WeekIterator(null, start, end, daysOffset, true, false));
}
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified
/// symbol each week
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first
/// tradeable date of the week</param>
/// <param name="daysOffset">The amount of tradable days to offset the first tradable day by</param>
/// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
/// <returns>A date rule that fires on the first + offset tradable date for the specified
/// security each week</returns>
public IDateRule WeekStart(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => WeekStart(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the first tradable date + offset for the specified
/// symbol each week
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the first
/// tradeable date of the week</param>
/// <param name="daysOffset">The amount of tradable days to offset the first tradable day by</param>
/// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
/// <returns>A date rule that fires on the first + offset tradable date for the specified
/// security each week</returns>
public IDateRule WeekStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
var securitySchedule = GetSecurityExchangeHours(symbol);
var tradingDays = securitySchedule.MarketHours.Values
.Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
// Limit offsets to securities weekly schedule
if (daysOffset > tradingDays.Count - 1)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset),
$"DateRules.WeekStart() : {tradingDays.First().DayOfWeek}+{daysOffset} is out of range for {symbol}'s schedule," +
$" please use an offset between 0 - {tradingDays.Count - 1}; Schedule : {string.Join(", ", tradingDays.Select(x => x.DayOfWeek))}");
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "WeekStart", daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset, true, extendedMarketHours));
}
/// <summary>
/// Specifies an event should fire on Friday - offset
/// </summary>
/// <param name="daysOffset"> The amount of days to offset Friday by; must be between 0 and 6 </param>
/// <returns>A date rule that fires on Friday each week</returns>
public IDateRule WeekEnd(int daysOffset = 0)
{
// Check that our offset is allowed
if (daysOffset < 0 || 6 < daysOffset)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.WeekEnd() : Offset must be between 0 and 6");
}
return new FuncDateRule(GetName(null, "WeekEnd", -daysOffset), (start, end) => WeekIterator(null, start, end, daysOffset, false, false));
}
/// <summary>
/// Specifies an event should fire on the last - offset tradable date for the specified
/// symbol of each week
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last
/// tradable date of the week</param>
/// <param name="daysOffset"> The amount of tradable days to offset the last tradable day by each week</param>
/// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
/// <returns>A date rule that fires on the last - offset tradable date for the specified security each week</returns>
public IDateRule WeekEnd(string symbol, int daysOffset = 0, bool extendedMarketHours = true) => WeekEnd(GetSymbol(symbol), daysOffset, extendedMarketHours);
/// <summary>
/// Specifies an event should fire on the last - offset tradable date for the specified
/// symbol of each week
/// </summary>
/// <param name="symbol">The symbol whose exchange is used to determine the last
/// tradable date of the week</param>
/// <param name="daysOffset"> The amount of tradable days to offset the last tradable day by each week</param>
/// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
/// <returns>A date rule that fires on the last - offset tradable date for the specified security each week</returns>
public IDateRule WeekEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
{
var securitySchedule = GetSecurityExchangeHours(symbol);
var tradingDays = securitySchedule.MarketHours.Values
.Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
// Limit offsets to securities weekly schedule
if (daysOffset > tradingDays.Count - 1)
{
throw new ArgumentOutOfRangeException(nameof(daysOffset),
$"DateRules.WeekEnd() : {tradingDays.Last().DayOfWeek}-{daysOffset} is out of range for {symbol}'s schedule," +
$" please use an offset between 0 - {tradingDays.Count - 1}; Schedule : {string.Join(", ", tradingDays.Select(x => x.DayOfWeek))}");
}
// Create the new DateRule and return it
return new FuncDateRule(GetName(symbol, "WeekEnd", -daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset, false, extendedMarketHours));
}
/// <summary>
/// Determine the string representation for a given rule
/// </summary>
/// <param name="symbol">Symbol for the rule</param>
/// <param name="ruleType">Rule type in string form</param>
/// <param name="offset">The amount of offset on this rule</param>
/// <returns></returns>
private static string GetName(Symbol symbol, string ruleType, int offset)
{
// Convert our offset to +#, -#, or empty string if 0
var offsetString = offset.ToString("+#;-#;''", CultureInfo.InvariantCulture);
var name = symbol == null ? $"{ruleType}{offsetString}" : $"{symbol.Value}: {ruleType}{offsetString}";
return name;
}
/// <summary>
/// Get the closest trading day to a given DateTime for a given <see cref="SecurityExchangeHours"/>.
/// </summary>
/// <param name="securityExchangeHours"><see cref="SecurityExchangeHours"/> object with schedule for this Security</param>
/// <param name="baseDay">The day to base our search from</param>
/// <param name="offset">Amount to offset the schedule by tradable days</param>
/// <param name="searchForward">Search into the future for the closest day if true; into the past if false</param>
/// <param name="boundary">The boundary DateTime on the resulting day</param>
/// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
private static DateTime GetScheduledDay(SecurityExchangeHours securityExchangeHours, DateTime baseDay, int offset, bool searchForward, bool extendedMarketHours, DateTime? boundary = null)
{
// By default the scheduled date is the given day
var scheduledDate = baseDay;
// If its not open on this day find the next trading day by searching in the given direction
if (!securityExchangeHours.IsDateOpen(scheduledDate, extendedMarketHours))
{
scheduledDate = searchForward
? securityExchangeHours.GetNextTradingDay(scheduledDate)
: securityExchangeHours.GetPreviousTradingDay(scheduledDate);
}
// Offset the scheduled day accordingly
for (var i = 0; i < offset; i++)
{
scheduledDate = searchForward
? securityExchangeHours.GetNextTradingDay(scheduledDate)
: securityExchangeHours.GetPreviousTradingDay(scheduledDate);
}
// If there is a boundary ensure we enforce it
if (boundary.HasValue)
{
// If we are searching forward and the resulting date is after this boundary we
// revert to the last tradable day equal to or less than boundary
if (searchForward && scheduledDate > boundary)
{
scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0, false, extendedMarketHours);
}
// If we are searching backward and the resulting date is after this boundary we
// revert to the last tradable day equal to or greater than boundary
if (!searchForward && scheduledDate < boundary)
{
scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0, true, extendedMarketHours);
}
}
return scheduledDate;
}
private static IEnumerable<DateTime> BaseIterator(
SecurityExchangeHours securitySchedule,
DateTime start,
DateTime end,
int offset,
bool searchForward,
DateTime periodBegin,
DateTime periodEnd,
Func<DateTime, DateTime> baseDateFunc,
Func<DateTime, DateTime> boundaryDateFunc,
bool extendedMarketHours)
{
// No schedule means no security, set to open everyday
if (securitySchedule == null)
{
securitySchedule = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
}
foreach (var date in Time.EachDay(periodBegin, periodEnd))
{
var baseDate = baseDateFunc(date);
var boundaryDate = boundaryDateFunc(date);
// Determine the scheduled day for this period
if (date == baseDate)
{
var scheduledDay = GetScheduledDay(securitySchedule, baseDate, offset, searchForward, extendedMarketHours, boundaryDate);
// Ensure the date is within our schedules range
if (scheduledDay >= start && scheduledDay <= end)
{
yield return scheduledDay;
}
}
}
}
private static IEnumerable<DateTime> MonthIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
{
// Iterate all days between the beginning of "start" month, through end of "end" month.
// Necessary to ensure we schedule events in the month we start and end.
var beginningOfStartMonth = new DateTime(start.Year, start.Month, 1);
var endOfEndMonth = new DateTime(end.Year, end.Month, DateTime.DaysInMonth(end.Year, end.Month));
// Searching forward the first of the month is baseDay, with boundary being the last
// Searching backward the last of the month is baseDay, with boundary being the first
Func<DateTime, DateTime> baseDateFunc = date => searchForward ? new DateTime(date.Year, date.Month, 1) : new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
Func<DateTime, DateTime> boundaryDateFunc = date => searchForward ? new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month)) : new DateTime(date.Year, date.Month, 1);
return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartMonth, endOfEndMonth, baseDateFunc, boundaryDateFunc, extendedMarketHours);
}
private static IEnumerable<DateTime> QuarterIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
{
// Iterate all days between the beginning of "start" quarter, through end of "end" quarter.
// Necessary to ensure we schedule events in the quarter we start and end.
var startQuarterFirstMonth = ((start.Month - 1) / 3) * 3 + 1;
var beginningOfStartQuarter = new DateTime(start.Year, startQuarterFirstMonth, 1);
var endQuarterLastMonth = ((end.Month - 1) / 3) * 3 + 3;
var endOfEndQuarter = new DateTime(end.Year, endQuarterLastMonth, DateTime.DaysInMonth(end.Year, endQuarterLastMonth));
// Searching forward the first day of the quarter is baseDay, with boundary being the last day
// Searching backward the last day of the quarter is baseDay, with boundary being the first day
Func<DateTime, DateTime> baseDateFunc = date =>
{
var quarterFirstMonth = ((date.Month - 1) / 3) * 3 + 1;
if (searchForward)
{
return new DateTime(date.Year, quarterFirstMonth, 1);
}
var quarterLastMonth = quarterFirstMonth + 2;
return new DateTime(date.Year, quarterLastMonth, DateTime.DaysInMonth(date.Year, quarterLastMonth));
};
Func<DateTime, DateTime> boundaryDateFunc = date =>
{
var quarterFirstMonth = ((date.Month - 1) / 3) * 3 + 1;
if (searchForward)
{
var quarterLastMonth = quarterFirstMonth + 2;
return new DateTime(date.Year, quarterLastMonth, DateTime.DaysInMonth(date.Year, quarterLastMonth));
}
return new DateTime(date.Year, quarterFirstMonth, 1);
};
return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartQuarter, endOfEndQuarter, baseDateFunc, boundaryDateFunc, extendedMarketHours);
}
private static IEnumerable<DateTime> YearIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
{
// Iterate all days between the beginning of "start" year, through end of "end" year
// Necessary to ensure we schedule events in the year we start and end.
var beginningOfStartOfYear = new DateTime(start.Year, start.Month, 1);
var endOfEndYear = new DateTime(end.Year, end.Month, DateTime.DaysInMonth(end.Year, end.Month));
// Searching forward the first of the year is baseDay, with boundary being the last
// Searching backward the last of the year is baseDay, with boundary being the first
Func<DateTime, DateTime> baseDateFunc = date => searchForward ? new DateTime(date.Year, 1, 1) : new DateTime(date.Year, 12, 31);
Func<DateTime, DateTime> boundaryDateFunc = date => searchForward ? new DateTime(date.Year, 12, 31) : new DateTime(date.Year, 1, 1);
return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartOfYear, endOfEndYear, baseDateFunc, boundaryDateFunc, extendedMarketHours);
}
private static IEnumerable<DateTime> WeekIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
{
// Determine the weekly base day and boundary to schedule off of
DayOfWeek weeklyBaseDay;
DayOfWeek weeklyBoundaryDay;
if (securitySchedule == null)
{
// No schedule means no security, set to open everyday
securitySchedule = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
// Searching forward Monday is baseDay, with boundary being the following Sunday
// Searching backward Friday is baseDay, with boundary being the previous Saturday
weeklyBaseDay = searchForward ? DayOfWeek.Monday : DayOfWeek.Friday;
weeklyBoundaryDay = searchForward ? DayOfWeek.Saturday + 1 : DayOfWeek.Sunday - 1;
}
else
{
// Fetch the securities schedule
var weeklySchedule = securitySchedule.MarketHours.Values
.Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
// Determine our weekly base day and boundary for this security
weeklyBaseDay = searchForward ? weeklySchedule.First().DayOfWeek : weeklySchedule.Last().DayOfWeek;
weeklyBoundaryDay = searchForward ? weeklySchedule.Last().DayOfWeek : weeklySchedule.First().DayOfWeek;
}
// Iterate all days between the beginning of "start" week, through end of "end" week.
// Necessary to ensure we schedule events in the week we start and end.
// Also if we have a sunday for start/end we need to adjust for it being the front of the week when we want it as the end of the week.
var startAdjustment = start.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
var beginningOfStartWeek = start.AddDays(-(int)start.DayOfWeek + 1 + startAdjustment); // Date - DayOfWeek + 1
var endAdjustment = end.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
var endOfEndWeek = end.AddDays(-(int)end.DayOfWeek + 7 + endAdjustment); // Date - DayOfWeek + 7
// Determine the schedule for each week in this range
foreach (var date in Time.EachDay(beginningOfStartWeek, endOfEndWeek).Where(x => x.DayOfWeek == weeklyBaseDay))
{
var boundary = date.AddDays(weeklyBoundaryDay - weeklyBaseDay);
var scheduledDay = GetScheduledDay(securitySchedule, date, offset, searchForward, extendedMarketHours, boundary);
// Ensure the date is within our schedules range
if (scheduledDay >= start && scheduledDay <= end)
{
yield return scheduledDay;
}
}
}
}
}
@@ -0,0 +1,402 @@
/*
* 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
{
/// <summary>
/// Provides a builder class to allow for fluent syntax when constructing new events
/// </summary>
/// <remarks>
/// 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
/// </remarks>
public class FluentScheduledEventBuilder : IFluentSchedulingDateSpecifier, IFluentSchedulingRunnable
{
private IDateRule _dateRule;
private ITimeRule _timeRule;
private Func<DateTime, bool> _predicate;
private readonly string _name;
private readonly ScheduleManager _schedule;
private readonly SecurityManager _securities;
/// <summary>
/// Initializes a new instance of the <see cref="FluentScheduledEventBuilder"/> class
/// </summary>
/// <param name="schedule">The schedule to send created events to</param>
/// <param name="securities">The algorithm's security manager</param>
/// <param name="name">A specific name for this event</param>
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
/// <summary>
/// Creates events on each of the specified day of week
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.Every(params DayOfWeek[] days)
{
_dateRule = _schedule.DateRules.Every(days);
return this;
}
/// <summary>
/// Creates events on every day of the year
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.EveryDay()
{
_dateRule = _schedule.DateRules.EveryDay();
return this;
}
/// <summary>
/// Creates events on every trading day of the year for the symbol
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.EveryDay(Symbol symbol)
{
_dateRule = _schedule.DateRules.EveryDay(symbol);
return this;
}
/// <summary>
/// Creates events on the first day of the month
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.MonthStart()
{
_dateRule = _schedule.DateRules.MonthStart();
return this;
}
/// <summary>
/// Creates events on the first trading day of the month
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.MonthStart(Symbol symbol)
{
_dateRule = _schedule.DateRules.MonthStart(symbol);
return this;
}
/// <summary>
/// Filters the event times using the predicate
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingDateSpecifier.Where(Func<DateTime, bool> predicate)
{
_predicate = _predicate == null
? predicate
: (time => _predicate(time) && predicate(time));
return this;
}
/// <summary>
/// Creates events that fire at the specific time of day in the algorithm's time zone
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.At(TimeSpan timeOfDay)
{
return SetTimeRule(_schedule.TimeRules.At(timeOfDay));
}
/// <summary>
/// Creates events that fire a specified number of minutes after market open
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.AfterMarketOpen(Symbol symbol, double minutesAfterOpen, bool extendedMarketOpen)
{
return SetTimeRule(_schedule.TimeRules.AfterMarketOpen(symbol, minutesAfterOpen, extendedMarketOpen));
}
/// <summary>
/// Creates events that fire a specified numer of minutes before market close
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.BeforeMarketClose(Symbol symbol, double minuteBeforeClose, bool extendedMarketClose)
{
return SetTimeRule(_schedule.TimeRules.BeforeMarketClose(symbol, minuteBeforeClose, extendedMarketClose));
}
/// <summary>
/// Creates events that fire on a period define by the specified interval
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingTimeSpecifier.Every(TimeSpan interval)
{
return SetTimeRule(_schedule.TimeRules.Every(interval));
}
/// <summary>
/// Filters the event times using the predicate
/// </summary>
IFluentSchedulingTimeSpecifier IFluentSchedulingTimeSpecifier.Where(Func<DateTime, bool> predicate)
{
_predicate = _predicate == null
? predicate
: (time => _predicate(time) && predicate(time));
return this;
}
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent IFluentSchedulingRunnable.Run(Action callback)
{
return ((IFluentSchedulingRunnable)this).Run((name, time) => callback());
}
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent IFluentSchedulingRunnable.Run(Action<DateTime> callback)
{
return ((IFluentSchedulingRunnable)this).Run((name, time) => callback(time));
}
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent IFluentSchedulingRunnable.Run(Action<string, DateTime> 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;
}
/// <summary>
/// Filters the event times using the predicate
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingRunnable.Where(Func<DateTime, bool> predicate)
{
_predicate = _predicate == null
? predicate
: (time => _predicate(time) && predicate(time));
return this;
}
/// <summary>
/// Filters the event times to only include times where the symbol's market is considered open
/// </summary>
IFluentSchedulingRunnable IFluentSchedulingRunnable.DuringMarketHours(Symbol symbol, bool extendedMarket)
{
var security = GetSecurity(symbol);
Func<DateTime, bool> 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
}
/// <summary>
/// Specifies the date rule component of a scheduled event
/// </summary>
public interface IFluentSchedulingDateSpecifier
{
/// <summary>
/// Filters the event times using the predicate
/// </summary>
IFluentSchedulingTimeSpecifier Where(Func<DateTime, bool> predicate);
/// <summary>
/// Creates events only on the specified date
/// </summary>
IFluentSchedulingTimeSpecifier On(int year, int month, int day);
/// <summary>
/// Creates events only on the specified dates
/// </summary>
IFluentSchedulingTimeSpecifier On(params DateTime[] dates);
/// <summary>
/// Creates events on each of the specified day of week
/// </summary>
IFluentSchedulingTimeSpecifier Every(params DayOfWeek[] days);
/// <summary>
/// Creates events on every day of the year
/// </summary>
IFluentSchedulingTimeSpecifier EveryDay();
/// <summary>
/// Creates events on every trading day of the year for the symbol
/// </summary>
IFluentSchedulingTimeSpecifier EveryDay(Symbol symbol);
/// <summary>
/// Creates events on the first day of the month
/// </summary>
IFluentSchedulingTimeSpecifier MonthStart();
/// <summary>
/// Creates events on the first trading day of the month
/// </summary>
IFluentSchedulingTimeSpecifier MonthStart(Symbol symbol);
}
/// <summary>
/// Specifies the time rule component of a scheduled event
/// </summary>
public interface IFluentSchedulingTimeSpecifier
{
/// <summary>
/// Filters the event times using the predicate
/// </summary>
IFluentSchedulingTimeSpecifier Where(Func<DateTime, bool> predicate);
/// <summary>
/// Creates events that fire at the specified time of day in the specified time zone
/// </summary>
IFluentSchedulingRunnable At(int hour, int minute, int second = 0);
/// <summary>
/// Creates events that fire at the specified time of day in the specified time zone
/// </summary>
IFluentSchedulingRunnable At(int hour, int minute, DateTimeZone timeZone);
/// <summary>
/// Creates events that fire at the specified time of day in the specified time zone
/// </summary>
IFluentSchedulingRunnable At(int hour, int minute, int second, DateTimeZone timeZone);
/// <summary>
/// Creates events that fire at the specified time of day in the specified time zone
/// </summary>
IFluentSchedulingRunnable At(TimeSpan timeOfDay, DateTimeZone timeZone);
/// <summary>
/// Creates events that fire at the specific time of day in the algorithm's time zone
/// </summary>
IFluentSchedulingRunnable At(TimeSpan timeOfDay);
/// <summary>
/// Creates events that fire on a period define by the specified interval
/// </summary>
IFluentSchedulingRunnable Every(TimeSpan interval);
/// <summary>
/// Creates events that fire a specified number of minutes after market open
/// </summary>
IFluentSchedulingRunnable AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false);
/// <summary>
/// Creates events that fire a specified numer of minutes before market close
/// </summary>
IFluentSchedulingRunnable BeforeMarketClose(Symbol symbol, double minuteBeforeClose = 0, bool extendedMarketClose = false);
}
/// <summary>
/// Specifies the callback component of a scheduled event, as well as final filters
/// </summary>
public interface IFluentSchedulingRunnable : IFluentSchedulingTimeSpecifier
{
/// <summary>
/// Filters the event times using the predicate
/// </summary>
new IFluentSchedulingRunnable Where(Func<DateTime, bool> predicate);
/// <summary>
/// Filters the event times to only include times where the symbol's market is considered open
/// </summary>
IFluentSchedulingRunnable DuringMarketHours(Symbol symbol, bool extendedMarket = false);
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent Run(Action callback);
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent Run(Action<DateTime> callback);
/// <summary>
/// Register the defined event with the callback
/// </summary>
ScheduledEvent Run(Action<string, DateTime> callback);
}
}
+74
View File
@@ -0,0 +1,74 @@
/*
* 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 Python.Runtime;
using System;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Uses a function to define an enumerable of dates over a requested start/end period
/// </summary>
public class FuncDateRule : IDateRule
{
private readonly Func<DateTime, DateTime, IEnumerable<DateTime>> _getDatesFunction;
/// <summary>
/// Initializes a new instance of the <see cref="FuncDateRule"/> class
/// </summary>
/// <param name="name">The name of this rule</param>
/// <param name="getDatesFunction">The time applicator function</param>
public FuncDateRule(string name, Func<DateTime, DateTime, IEnumerable<DateTime>> getDatesFunction)
{
Name = name;
_getDatesFunction = getDatesFunction;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncDateRule"/> class using a Python function
/// </summary>
/// <param name="name">The name of this rule</param>
/// <param name="getDatesFunction">The time applicator function in Python</param>
public FuncDateRule(string name, PyObject getDatesFunction)
{
Name = name;
if (!getDatesFunction.TrySafeAs(out _getDatesFunction))
{
throw new ArgumentException("Python DateRule provided is not a function");
}
}
/// <summary>
/// Gets a name for this rule
/// </summary>
public string Name
{
get; private set;
}
/// <summary>
/// Gets the dates produced by this date rule between the specified times
/// </summary>
/// <param name="start">The start of the interval to produce dates for</param>
/// <param name="end">The end of the interval to produce dates for</param>
/// <returns>All dates in the interval matching this date rule</returns>
public IEnumerable<DateTime> GetDates(DateTime start, DateTime end)
{
return _getDatesFunction(start, end);
}
}
}
+74
View File
@@ -0,0 +1,74 @@
/*
* 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 Python.Runtime;
using System;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Uses a function to define a time rule as a projection of date times to date times
/// </summary>
public class FuncTimeRule : ITimeRule
{
private readonly Func<IEnumerable<DateTime>, IEnumerable<DateTime>> _createUtcEventTimesFunction;
/// <summary>
/// Initializes a new instance of the <see cref="FuncTimeRule"/> class
/// </summary>
/// <param name="name">The name of the time rule</param>
/// <param name="createUtcEventTimesFunction">Function used to transform dates into event date times</param>
public FuncTimeRule(string name, Func<IEnumerable<DateTime>, IEnumerable<DateTime>> createUtcEventTimesFunction)
{
Name = name;
_createUtcEventTimesFunction = createUtcEventTimesFunction;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncTimeRule"/> class using a Python function
/// </summary>
/// <param name="name">The name of the time rule</param>
/// <param name="createUtcEventTimesFunction">Function used to transform dates into event date times in Python</param>
public FuncTimeRule(string name, PyObject createUtcEventTimesFunction)
{
Name = name;
if (!createUtcEventTimesFunction.TrySafeAs(out _createUtcEventTimesFunction))
{
throw new ArgumentException("Python TimeRule provided is not a function");
}
}
/// <summary>
/// Gets a name for this rule
/// </summary>
public string Name
{
get; private set;
}
/// <summary>
/// Creates the event times for the specified dates in UTC
/// </summary>
/// <param name="dates">The dates to apply times to</param>
/// <returns>An enumerable of date times that is the result
/// of applying this rule to the specified dates</returns>
public IEnumerable<DateTime> CreateUtcEventTimes(IEnumerable<DateTime> dates)
{
return _createUtcEventTimesFunction(dates);
}
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Specifies dates that events should be fired, used in conjunction with the <see cref="ITimeRule"/>
/// </summary>
public interface IDateRule
{
/// <summary>
/// Gets a name for this rule
/// </summary>
string Name { get; }
/// <summary>
/// Gets the dates produced by this date rule between the specified times
/// </summary>
/// <param name="start">The start of the interval to produce dates for</param>
/// <param name="end">The end of the interval to produce dates for</param>
/// <returns>All dates in the interval matching this date rule</returns>
IEnumerable<DateTime> GetDates(DateTime start, DateTime end);
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* 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.
*
*/
namespace QuantConnect.Scheduling
{
/// <summary>
/// Provides the ability to add/remove scheduled events from the real time handler
/// </summary>
public interface IEventSchedule
{
/// <summary>
/// Adds the specified event to the schedule
/// </summary>
/// <param name="scheduledEvent">The event to be scheduled, including the date/times the event fires and the callback</param>
void Add(ScheduledEvent scheduledEvent);
/// <summary>
/// Removes the specified event from the schedule
/// </summary>
/// <param name="scheduledEvent">The event to be removed</param>
void Remove(ScheduledEvent scheduledEvent);
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Specifies times times on dates for events, used in conjunction with <see cref="IDateRule"/>
/// </summary>
public interface ITimeRule
{
/// <summary>
/// Gets a name for this rule
/// </summary>
string Name { get; }
/// <summary>
/// Creates the event times for the specified dates in UTC
/// </summary>
/// <param name="dates">The dates to apply times to</param>
/// <returns>An enumerable of date times that is the result
/// of applying this rule to the specified dates</returns>
IEnumerable<DateTime> CreateUtcEventTimes(IEnumerable<DateTime> dates);
}
}
+292
View File
@@ -0,0 +1,292 @@
/*
* 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 Python.Runtime;
using QuantConnect.Logging;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Provides access to the real time handler's event scheduling feature
/// </summary>
public class ScheduleManager : IEventSchedule
{
private IEventSchedule _eventSchedule;
private readonly SecurityManager _securities;
private readonly object _eventScheduleLock = new object();
private readonly List<ScheduledEvent> _preInitializedEvents;
/// <summary>
/// Gets the date rules helper object to make specifying dates for events easier
/// </summary>
public DateRules DateRules { get; }
/// <summary>
/// Gets the time rules helper object to make specifying times for events easier
/// </summary>
public TimeRules TimeRules { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ScheduleManager"/> class
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="securities">Securities manager containing the algorithm's securities</param>
/// <param name="timeZone">The algorithm's time zone</param>
/// <param name="marketHoursDatabase">The market hours database instance to use</param>
public ScheduleManager(IAlgorithm algorithm, SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
{
_securities = securities;
DateRules = new DateRules(algorithm, securities, timeZone, marketHoursDatabase);
TimeRules = new TimeRules(algorithm, securities, timeZone, marketHoursDatabase);
// used for storing any events before the event schedule is set
_preInitializedEvents = new List<ScheduledEvent>();
}
/// <summary>
/// Sets the <see cref="IEventSchedule"/> implementation
/// </summary>
/// <param name="eventSchedule">The event schedule implementation to be used. This is the IRealTimeHandler</param>
public 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);
}
}
}
/// <summary>
/// Adds the specified event to the schedule
/// </summary>
/// <param name="scheduledEvent">The event to be scheduled, including the date/times the event fires and the callback</param>
public void Add(ScheduledEvent scheduledEvent)
{
lock (_eventScheduleLock)
{
if (_eventSchedule != null)
{
_eventSchedule.Add(scheduledEvent);
}
else
{
_preInitializedEvents.Add(scheduledEvent);
}
}
}
/// <summary>
/// Removes the specified event from the schedule
/// </summary>
/// <param name="scheduledEvent">The event to be removed</param>
public void Remove(ScheduledEvent scheduledEvent)
{
lock (_eventScheduleLock)
{
if (_eventSchedule != null)
{
_eventSchedule.Remove(scheduledEvent);
}
else
{
_preInitializedEvents.RemoveAll(se => Equals(se, scheduledEvent));
}
}
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, Action callback)
{
return On(dateRule, timeRule, (name, time) => callback());
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, PyObject callback)
{
return On(dateRule, timeRule, (name, time) => { using (Py.GIL()) callback.Invoke(); });
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(IDateRule dateRule, ITimeRule timeRule, Action<string, DateTime> callback)
{
var name = $"{dateRule.Name}: {timeRule.Name}";
return On(name, dateRule, timeRule, callback);
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="name">The event's unique name</param>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, Action callback)
{
return On(name, dateRule, timeRule, (n, d) => callback());
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="name">The event's unique name</param>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, PyObject callback)
{
return On(name, dateRule, timeRule, (n, d) => { using (Py.GIL()) callback.Invoke(); });
}
/// <summary>
/// Schedules the callback to run using the specified date and time rules
/// </summary>
/// <param name="name">The event's unique name</param>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="callback">The callback to be invoked</param>
public ScheduledEvent On(string name, IDateRule dateRule, ITimeRule timeRule, Action<string, DateTime> 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
/// <summary>
/// Entry point for the fluent scheduled event builder
/// </summary>
public IFluentSchedulingDateSpecifier Event()
{
return new FluentScheduledEventBuilder(this, _securities);
}
/// <summary>
/// Entry point for the fluent scheduled event builder
/// </summary>
public IFluentSchedulingDateSpecifier Event(string name)
{
return new FluentScheduledEventBuilder(this, _securities, name);
}
#endregion
#region Training Events
/// <summary>
/// Schedules the provided training code to execute immediately
/// </summary>
public ScheduledEvent TrainingNow(Action trainingCode)
{
return On($"Training: Now: {_securities.UtcTime:O}", DateRules.Today, TimeRules.Now, trainingCode);
}
/// <summary>
/// Schedules the provided training code to execute immediately
/// </summary>
public ScheduledEvent TrainingNow(PyObject trainingCode)
{
return On($"Training: Now: {_securities.UtcTime:O}", DateRules.Today, TimeRules.Now, trainingCode);
}
/// <summary>
/// Schedules the training code to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="trainingCode">The training code to be invoked</param>
public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, Action trainingCode)
{
var name = $"{dateRule.Name}: {timeRule.Name}";
return On(name, dateRule, timeRule, (n, time) => trainingCode());
}
/// <summary>
/// Schedules the training code to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="trainingCode">The training code to be invoked</param>
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(); });
}
/// <summary>
/// Schedules the training code to run using the specified date and time rules
/// </summary>
/// <param name="dateRule">Specifies what dates the event should run</param>
/// <param name="timeRule">Specifies the times on those dates the event should run</param>
/// <param name="trainingCode">The training code to be invoked</param>
public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, Action<DateTime> trainingCode)
{
var name = $"{dateRule.Name}: {timeRule.Name}";
return On(name, dateRule, timeRule, (n, time) => trainingCode(time));
}
#endregion
/// <summary>
/// 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
/// </summary>
internal static IEnumerable<DateTime> 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;
}
}
}
}
+263
View File
@@ -0,0 +1,263 @@
/*
* 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 QuantConnect.Logging;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Real time self scheduling event
/// </summary>
public class ScheduledEvent : IDisposable
{
/// <summary>
/// Gets the default time before market close end of trading day events will fire
/// </summary>
public static readonly TimeSpan SecurityEndOfDayDelta = TimeSpan.FromMinutes(10);
/// <summary>
/// Gets the default time before midnight end of day events will fire
/// </summary>
public static readonly TimeSpan AlgorithmEndOfDayDelta = TimeSpan.FromMinutes(2);
private bool _needsMoveNext;
private bool _endOfScheduledEvents;
private readonly Action<string, DateTime> _callback;
private readonly IEnumerator<DateTime> _orderedEventUtcTimes;
/// <summary>
/// Event that fires each time this scheduled event happens
/// </summary>
public event Action<string, DateTime> EventFired;
/// <summary>
/// Gets or sets whether this event is enabled
/// </summary>
public bool Enabled
{
get; set;
}
/// <summary>
/// Gets or sets whether this event will log each time it fires
/// </summary>
internal bool IsLoggingEnabled
{
get; set;
}
/// <summary>
/// Gets the next time this scheduled event will fire in UTC
/// </summary>
public DateTime NextEventUtcTime
{
get
{
if (_endOfScheduledEvents)
{
return DateTime.MaxValue;
}
if (_needsMoveNext)
{
_needsMoveNext = false;
_endOfScheduledEvents = !_orderedEventUtcTimes.MoveNext();
return NextEventUtcTime;
}
return _orderedEventUtcTimes.Current;
}
}
/// <summary>
/// Gets an identifier for this event
/// </summary>
public string Name { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledEvent"/> class
/// </summary>
/// <param name="name">An identifier for this event</param>
/// <param name="eventUtcTime">The date time the event should fire</param>
/// <param name="callback">Delegate to be called when the event time passes</param>
public ScheduledEvent(string name, DateTime eventUtcTime, Action<string, DateTime> callback = null)
: this(name, new[] { eventUtcTime }.AsEnumerable().GetEnumerator(), callback)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledEvent"/> class
/// </summary>
/// <param name="name">An identifier for this event</param>
/// <param name="orderedEventUtcTimes">An enumerable that emits event times</param>
/// <param name="callback">Delegate to be called each time an event passes</param>
public ScheduledEvent(string name, IEnumerable<DateTime> orderedEventUtcTimes, Action<string, DateTime> callback = null)
: this(name, orderedEventUtcTimes.GetEnumerator(), callback)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledEvent"/> class
/// </summary>
/// <param name="name">An identifier for this event</param>
/// <param name="orderedEventUtcTimes">An enumerator that emits event times</param>
/// <param name="callback">Delegate to be called each time an event passes</param>
public ScheduledEvent(string name, IEnumerator<DateTime> orderedEventUtcTimes, Action<string, DateTime> callback = null)
{
Name = name;
Enabled = true;
_callback = callback;
// we don't move next until we are requested, this allows the algorithm to support the warmup period correctly
_needsMoveNext = true;
_orderedEventUtcTimes = orderedEventUtcTimes;
}
/// <summary>Serves as the default hash function. </summary>
/// <returns>A hash code for the current object.</returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode()
{
return Name.GetHashCode();
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
/// <param name="obj">The object to compare with the current object. </param>
/// <filterpriority>2</filterpriority>
public override bool Equals(object obj)
{
return !ReferenceEquals(null, obj) && ReferenceEquals(this, obj);
}
/// <summary>
/// Scans this event and fires the callback if an event happened
/// </summary>
/// <param name="utcTime">The current time in UTC</param>
internal void Scan(DateTime utcTime)
{
if (_endOfScheduledEvents)
{
return;
}
do
{
if (_needsMoveNext)
{
// if we've passed an event or are just priming the pump, we need to move next
if (!_orderedEventUtcTimes.MoveNext())
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Completed scheduled events.");
}
_endOfScheduledEvents = true;
return;
}
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Next event: {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)} UTC");
}
}
// if time has passed our event
if (utcTime >= _orderedEventUtcTimes.Current)
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Firing at {utcTime.ToStringInvariant(DateFormat.UI)} UTC " +
$"Scheduled at {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)} UTC"
);
}
// fire the event
OnEventFired(_orderedEventUtcTimes.Current);
_needsMoveNext = true;
}
else
{
// we haven't passed the event time yet, so keep waiting on this Current
_needsMoveNext = false;
}
}
// keep checking events until we pass the current time, this will fire
// all 'skipped' events back to back in order, perhaps this should be handled
// in the real time handler
while (_needsMoveNext);
}
/// <summary>
/// Fast forwards this schedule to the specified time without invoking the events
/// </summary>
/// <param name="utcTime">Frontier time</param>
internal void SkipEventsUntil(DateTime utcTime)
{
do
{
// zoom through the enumerator until we get to the desired time
if (utcTime <= NextEventUtcTime)
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Skipped events before {utcTime.ToStringInvariant(DateFormat.UI)}. " +
$"Next event: {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)}"
);
}
return;
}
}
while (_orderedEventUtcTimes.MoveNext());
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Exhausted event stream during skip until {utcTime.ToStringInvariant(DateFormat.UI)}");
}
_endOfScheduledEvents = true;
}
/// <summary>
/// Will return the ScheduledEvents name
/// </summary>
public override string ToString()
{
return $"{Name}";
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
void IDisposable.Dispose()
{
_orderedEventUtcTimes.Dispose();
}
/// <summary>
/// Event invocator for the <see cref="EventFired"/> event
/// </summary>
/// <param name="triggerTime">The event's time in UTC</param>
protected void OnEventFired(DateTime triggerTime)
{
// don't fire the event if we're turned off
if (!Enabled) return;
_callback?.Invoke(Name, _orderedEventUtcTimes.Current);
EventFired?.Invoke(Name, triggerTime);
}
}
}
@@ -0,0 +1,42 @@
/*
* 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;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Throw this if there is an exception in the callback function of the scheduled event
/// </summary>
public class ScheduledEventException : Exception
{
/// <summary>
/// Gets the name of the scheduled event
/// </summary>
public string ScheduledEventName { get; }
/// <summary>
/// ScheduledEventException constructor
/// </summary>
/// <param name="name">The name of the scheduled event</param>
/// <param name="message">The exception as a string</param>
/// <param name="innerException">The exception that is the cause of the current exception</param>
public ScheduledEventException(string name, string message, Exception innerException) : base(message, innerException)
{
ScheduledEventName = name;
}
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
* 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;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Represents a timer consumer instance
/// </summary>
public class TimeConsumer
{
/// <summary>
/// True if the consumer already finished it's work and no longer consumes time
/// </summary>
public bool Finished { get; set; }
/// <summary>
/// The time provider associated with this consumer
/// </summary>
public ITimeProvider TimeProvider { get; set; }
/// <summary>
/// The isolator limit provider to be used with this consumer
/// </summary>
public IIsolatorLimitResultProvider IsolatorLimitProvider { get; set; }
/// <summary>
/// The next time, base on the <see cref="TimeProvider"/>, that time should be requested
/// to be <see cref="IsolatorLimitProvider"/>
/// </summary>
public DateTime? NextTimeRequest { get; set; }
}
}
+163
View File
@@ -0,0 +1,163 @@
/*
* 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.Threading;
using QuantConnect.Util;
using System.Collections.Generic;
namespace QuantConnect.Scheduling
{
/// <summary>
/// Helper class that will monitor timer consumers and request more time if required.
/// Used by <see cref="IsolatorLimitResultProvider"/>
/// </summary>
public class TimeMonitor : IDisposable
{
private readonly int _monitorIntervalMs;
private readonly Timer _timer;
/// <summary>
/// List to store the coming TimeConsumer objects
/// </summary>
/// <remarks>This field is protected because it's used in a test class
/// in `IsolatorLimitResultProviderTests.cs</remarks>
protected List<TimeConsumer> TimeConsumers { get; init; }
/// <summary>
/// Returns the number of time consumers currently being monitored
/// </summary>
public int Count
{
get
{
lock (TimeConsumers)
{
return TimeConsumers.Count;
}
}
}
/// <summary>
/// Creates a new instance
/// </summary>
public TimeMonitor(int monitorIntervalMs = 100)
{
_monitorIntervalMs = monitorIntervalMs;
TimeConsumers = new List<TimeConsumer>();
_timer = new Timer(state =>
{
lock (TimeConsumers)
{
try
{
RemoveAll();
foreach (var consumer in TimeConsumers)
{
ProcessConsumer(consumer);
}
}
finally
{
if (TimeConsumers.Count > 0)
{
// there's some remaning and we are at the timer callback so let's re schedule
TrySchedule();
}
}
}
}, null, Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// Process the TimeConsumer object in TimeConsumers list
/// </summary>
/// <param name="consumer">The TimeConsumer object to be processed</param>
/// <remarks>This method is protected because it's overrode by a test class
/// in `IsolatorLimitResultProviderTests.cs`</remarks>
protected virtual void ProcessConsumer(TimeConsumer consumer)
{
if (consumer.NextTimeRequest == null)
{
// first time, for performance we register this here and not the time consumer
consumer.NextTimeRequest = consumer.TimeProvider.GetUtcNow().AddMinutes(1);
}
else if (consumer.TimeProvider.GetUtcNow() >= consumer.NextTimeRequest)
{
// each minute request additional time from the isolator
consumer.NextTimeRequest = consumer.NextTimeRequest.Value.AddMinutes(1);
try
{
// this will notify the isolator that we've exceed the limits
consumer.IsolatorLimitProvider.RequestAdditionalTime(minutes: 1);
}
catch
{
// pass
}
}
}
/// <summary>
/// Remove all TimeConsumer objects where the `Finished` field is marked as true
/// </summary>
/// <remarks>This method is protected because it's overrode by a test class in
/// `IsolatorLimitResultProviderTests.cs`</remarks>
protected virtual void RemoveAll()
{
TimeConsumers.RemoveAll(time => time.Finished);
}
/// <summary>
/// Adds a new time consumer element to be monitored
/// </summary>
/// <param name="consumer">Time consumer instance</param>
public void Add(TimeConsumer consumer)
{
lock (TimeConsumers)
{
if (TimeConsumers.Count == 0)
{
// there was none left, schedule is not running, let's schedule it
TrySchedule();
}
TimeConsumers.Add(consumer);
}
}
/// <summary>
/// Disposes of the inner timer
/// </summary>
public void Dispose()
{
_timer.DisposeSafely();
}
/// <summary>
/// Lazy scheduling
/// </summary>
private void TrySchedule()
{
try
{
_timer.Change(Time.GetSecondUnevenWait(_monitorIntervalMs), Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// ignored disposed
}
}
}
}
+411
View File
@@ -0,0 +1,411 @@
/*
* 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);
}
}
}
}
}