chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user