/*
* 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.Securities;
using QuantConnect.Util;
namespace QuantConnect.Scheduling
{
///
/// Helper class used to provide better syntax when defining date rules
///
public class DateRules
{
private readonly SecurityManager _securities;
///
/// Initializes a new instance of the helper class
///
/// The security manager
public DateRules(SecurityManager securities)
{
_securities = securities;
}
///
/// Specifies an event should fire only on the specified day
///
/// The year
/// The month
/// The day
///
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);
}
///
/// Specifies an event should fire only on the specified days
///
/// The dates the event should fire
///
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);
}
///
/// Specifies an event should fire on each of the specified days of week
///
/// The day the event shouls fire
/// A date rule that fires on every specified day of week
public IDateRule Every(DayOfWeek day) => Every(new[] { day });
///
/// Specifies an event should fire on each of the specified days of week
///
/// The days the event shouls fire
/// A date rule that fires on every specified day of week
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)));
}
///
/// Specifies an event should fire every day
///
/// A date rule that fires every day
public IDateRule EveryDay()
{
return new FuncDateRule("EveryDay", Time.EachDay);
}
///
/// Specifies an event should fire every day the symbol is trading
///
/// The symbol whose exchange is used to determine tradeable dates
/// A date rule that fires every day the specified symbol trades
public IDateRule EveryDay(Symbol symbol)
{
var security = GetSecurity(symbol);
return new FuncDateRule(symbol.Value + ": EveryDay", (start, end) => Time.EachTradeableDay(security, start, end));
}
///
/// Specifies an event should fire on the first of each month
///
/// A date rule that fires on the first of each month
public IDateRule MonthStart()
{
return new FuncDateRule("MonthStart", (start, end) => MonthStartIterator(null, start, end));
}
///
/// Specifies an event should fire on the first tradeable date for the specified
/// symbol of each month
///
/// The symbol whose exchange is used to determine the first
/// tradeable date of the month
/// A date rule that fires on the first tradeable date for the specified security each month
public IDateRule MonthStart(Symbol symbol)
{
return new FuncDateRule(symbol.Value + ": MonthStart", (start, end) => MonthStartIterator(GetSecurity(symbol), start, end));
}
///
/// Specifies an event should fire on the last of each month
///
/// A date rule that fires on the last of each month
public IDateRule MonthEnd()
{
return new FuncDateRule("MonthEnd", (start, end) => MonthEndIterator(null, start, end));
}
///
/// Specifies an event should fire on the last tradeable date for the specified
/// symbol of each month
///
/// The symbol whose exchange is used to determine the last
/// tradeable date of the month
/// A date rule that fires on the last tradeable date for the specified security each month
public IDateRule MonthEnd(Symbol symbol)
{
return new FuncDateRule(symbol.Value + ": MonthEnd", (start, end) => MonthEndIterator(GetSecurity(symbol), start, end));
}
///
/// Specifies an event should fire on Monday each week
///
/// A date rule that fires on Monday each week
public IDateRule WeekStart()
{
return new FuncDateRule("WeekStart", (start, end) => WeekStartIterator(null, start, end));
}
///
/// Specifies an event should fire on the first tradeable date for the specified
/// symbol of each week
///
/// The symbol whose exchange is used to determine the first
/// tradeable date of the week
/// A date rule that fires on the first tradeable date for the specified security each week
public IDateRule WeekStart(Symbol symbol)
{
return new FuncDateRule(symbol.Value + ": WeekStart", (start, end) => WeekStartIterator(GetSecurity(symbol), start, end));
}
///
/// Specifies an event should fire on Friday each week
///
/// A date rule that fires on Friday each week
public IDateRule WeekEnd()
{
return new FuncDateRule("WeekEnd", (start, end) => WeekEndIterator(null, start, end));
}
///
/// Specifies an event should fire on the last tradeable date for the specified
/// symbol of each week
///
/// The symbol whose exchange is used to determine the last
/// tradeable date of the week
/// A date rule that fires on the last tradeable date for the specified security each week
public IDateRule WeekEnd(Symbol symbol)
{
return new FuncDateRule(symbol.Value + ": WeekEnd", (start, end) => WeekEndIterator(GetSecurity(symbol), start, end));
}
///
/// Gets the security with the specified symbol, or throws an exception if the symbol is not found
///
/// The security's symbol to search for
/// The security object matching the given symbol
private Security GetSecurity(Symbol symbol)
{
Security security;
if (!_securities.TryGetValue(symbol, out security))
{
throw new Exception(symbol.Value + " not found in portfolio. Request this data when initializing the algorithm.");
}
return security;
}
private static IEnumerable MonthStartIterator(Security security, DateTime start, DateTime end)
{
if (security == null)
{
foreach (var date in Time.EachDay(start, end))
{
// fire on the first of each month
if (date.Day == 1) yield return date;
}
yield break;
}
// start a month back so we can properly resolve the first event (we may have passed it)
var aMonthBeforeStart = start.AddMonths(-1);
int lastMonth = aMonthBeforeStart.Month;
foreach (var date in Time.EachTradeableDay(security, aMonthBeforeStart, end))
{
if (date.Month != lastMonth)
{
if (date >= start)
{
// only emit if the date is on or after the start
// the date may be before here because we backed up a month
// to properly resolve the first tradeable date
yield return date;
}
lastMonth = date.Month;
}
}
}
private static IEnumerable MonthEndIterator(Security security, DateTime start, DateTime end)
{
foreach (var date in Time.EachDay(start, end))
{
if (date.Day == DateTime.DaysInMonth(date.Year, date.Month))
{
if (security == null)
{
// fire on the last of each month
yield return date;
}
else
{
// find previous date when market is open
var currentDate = date;
while (!security.Exchange.Hours.IsDateOpen(currentDate))
{
currentDate = currentDate.AddDays(-1);
}
yield return currentDate;
}
}
}
}
private static IEnumerable WeekStartIterator(Security security, DateTime start, DateTime end)
{
var skippedMarketClosedDay = false;
foreach (var date in Time.EachDay(start, end))
{
if (security == null)
{
// fire on Monday
if (date.DayOfWeek == DayOfWeek.Monday)
{
yield return date;
}
}
else
{
// skip Mondays and following days when market is closed
if (date.DayOfWeek == DayOfWeek.Monday || skippedMarketClosedDay)
{
if (security.Exchange.Hours.IsDateOpen(date))
{
skippedMarketClosedDay = false;
yield return date;
}
else
{
skippedMarketClosedDay = true;
}
}
}
}
}
private static IEnumerable WeekEndIterator(Security security, DateTime start, DateTime end)
{
foreach (var date in Time.EachDay(start, end))
{
if (date.DayOfWeek == DayOfWeek.Friday)
{
if (security == null)
{
// fire on Friday
yield return date;
}
else
{
// find previous date when market is open
var currentDate = date;
while (!security.Exchange.Hours.IsDateOpen(currentDate))
{
currentDate = currentDate.AddDays(-1);
}
yield return currentDate;
}
}
}
}
}
}