Files
2026-07-13 13:02:50 +08:00

1200 lines
46 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using NodaTime;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;
namespace QuantConnect.Tests.Common.Scheduling
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class DateRulesTests
{
private static DateTime _utcNow = new DateTime(2021, 07, 27, 1, 10, 10, 500);
[Test]
public void EveryDayDateRuleEmitsEveryDay()
{
var rules = GetDateRules();
var rule = rules.EveryDay();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
DateTime previous = DateTime.MinValue;
foreach (var date in dates)
{
count++;
if (previous != DateTime.MinValue)
{
Assert.AreEqual(Time.OneDay, date - previous);
}
previous = date;
}
// leap year
Assert.AreEqual(366, count);
}
[Test]
public void EverySymbolDayRuleEmitsOnTradeableDates()
{
var rules = GetDateRules();
var rule = rules.EveryDay(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
}
Assert.AreEqual(252, count);
}
[Test]
public void StartOfMonthNoSymbol()
{
var rules = GetDateRules();
var rule = rules.MonthStart();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(1, date.Day);
}
Assert.AreEqual(12, count);
}
[Test]
public void StartOfMonthNoSymbolMidMonthStart()
{
var rules = GetDateRules();
var rule = rules.MonthStart();
var dates = rule.GetDates(new DateTime(2000, 01, 04), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(1, date.Day);
}
Assert.AreEqual(11, count);
}
[Test]
public void StartOfMonthNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.MonthStart(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(new DateTime(2000, count, 6), date);
Assert.AreEqual(6, date.Day);
}
Assert.AreEqual(12, count);
}
[TestCase(2, false)] // Before 11th
[TestCase(4, false)]
[TestCase(8, false)]
[TestCase(12, true)] // After 11th
[TestCase(16, true)]
[TestCase(20, true)]
public void StartOfMonthSameMonthSchedule(int startingDateDay, bool expectNone)
{
// Reproduces issue #5678, Assert that even though start is not first of month,
// we still schedule for that month.
var startingDate = new DateTime(2000, 12, startingDateDay);
var endingDate = new DateTime(2000, 12, 31);
var rules = GetDateRules();
var rule = rules.MonthStart(10); // 12/11/2000
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(expectNone, dates.IsNullOrEmpty());
if (!expectNone)
{
Assert.AreEqual(new DateTime(2000, 12, 11), dates.First());
}
}
[Test]
public void StartOfMonthWithSymbol()
{
var rules = GetDateRules();
var rule = rules.MonthStart(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.IsTrue(date.Day <= 3);
Log.Trace(date.Day.ToString(CultureInfo.InvariantCulture));
}
Assert.AreEqual(12, count);
}
[Test]
public void StartOfMonthWithSymbolMidMonthStart()
{
var rules = GetDateRules();
var rule = rules.MonthStart(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 04), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.IsTrue(date.Day <= 3);
Log.Trace(date.Day.ToString(CultureInfo.InvariantCulture));
}
Assert.AreEqual(11, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 10, 8, 8, 10, 8, 8 }, 5)]
[TestCase(Symbols.SymbolsKey.SPY, new[] { 20, 17, 17, 19, 17, 19 }, 12)] // Contains holiday 1/17
[TestCase(Symbols.SymbolsKey.SPY, new[] { 31, 29, 31, 28, 31, 30 }, 25)] // Always last trading day of the month (25 > than trading days)
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 6, 6, 6, 6, 6, 6 }, 5)]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 7, 7, 7, 7, 7, 7 }, 5)]
public void StartOfMonthWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays, int offset)
{
var rules = GetDateRules();
var rule = rules.MonthStart(Symbols.Lookup(symbolKey), offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 6, 30)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new {date, expectedDay});
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[Test]
public void EndOfMonthNoSymbol()
{
var rules = GetDateRules();
var rule = rules.MonthEnd();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(DateTime.DaysInMonth(date.Year, date.Month), date.Day);
}
Assert.AreEqual(12, count);
}
[Test]
public void EndOfMonthNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.MonthEnd(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(DateTime.DaysInMonth(date.Year, date.Month) - 5, date.Day);
}
Assert.AreEqual(12, count);
}
[TestCase(5, true)] // Before 21th
[TestCase(10, true)]
[TestCase(15, true)]
[TestCase(21, false)] // After 21th
[TestCase(25, false)]
[TestCase(30, false)]
public void EndOfMonthSameMonthSchedule(int endingDateDay, bool expectNone)
{
// Related to issue #5678, Assert that even though end date is not end of month,
// we still schedule for that month.
var startingDate = new DateTime(2000, 12, 1);
var endingDate = new DateTime(2000, 12, endingDateDay);
var rules = GetDateRules();
var rule = rules.MonthEnd(10); // 12/21/2000
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(expectNone, dates.IsNullOrEmpty());
if (!expectNone)
{
Assert.AreEqual(new DateTime(2000, 12, 21), dates.First());
}
}
[Test]
public void EndOfMonthWithSymbol()
{
var rules = GetDateRules();
var rule = rules.MonthEnd(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.IsTrue(date.Day >= 28);
Log.Trace(date + " " + date.DayOfWeek);
}
Assert.AreEqual(12, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 24, 22, 24, 20, 23, 23 }, 5)] // This case contains two Holidays 4/21 & 5/29
[TestCase(Symbols.SymbolsKey.SPY, new[] { 12, 10, 15, 11, 12, 14 }, 12)] // Contains holiday 1/17
[TestCase(Symbols.SymbolsKey.SPY, new[] { 3, 1, 1, 3, 1, 1 }, 25)] // Always first trading day of the month (25 > than trading days)
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 26, 24, 26, 25, 26, 25 }, 5)]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 25, 23, 26, 24, 25, 25 }, 5)]
public void EndOfMonthWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays, int offset)
{
var rules = GetDateRules();
var rule = rules.MonthEnd(Symbols.Lookup(symbolKey), offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 6, 30)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[Test]
public void StartOfQuarterNoSymbol()
{
var rules = GetDateRules();
var rule = rules.QuarterStart();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
CollectionAssert.AreEqual(
new[]
{
new DateTime(2000, 1, 1),
new DateTime(2000, 4, 1),
new DateTime(2000, 7, 1),
new DateTime(2000, 10, 1)
},
dates);
}
[Test]
public void StartOfQuarterNoSymbolMidQuarterStart()
{
var rules = GetDateRules();
var rule = rules.QuarterStart();
var dates = rule.GetDates(new DateTime(2000, 02, 15), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(3, dates.Count);
CollectionAssert.AreEqual(
new[]
{
new DateTime(2000, 4, 1),
new DateTime(2000, 7, 1),
new DateTime(2000, 10, 1)
},
dates);
}
[Test]
public void StartOfQuarterNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.QuarterStart(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
CollectionAssert.AreEqual(
new[]
{
new DateTime(2000, 1, 6),
new DateTime(2000, 4, 6),
new DateTime(2000, 7, 6),
new DateTime(2000, 10, 6)
},
dates);
}
[Test]
public void StartOfQuarterWithSymbol()
{
var rules = GetDateRules();
var rule = rules.QuarterStart(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
foreach (var date in dates)
{
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.Contains(date.Month, new[] { 1, 4, 7, 10 });
Assert.IsTrue(date.Day <= 3);
}
}
[Test]
public void EndOfQuarterNoSymbol()
{
var rules = GetDateRules();
var rule = rules.QuarterEnd();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
CollectionAssert.AreEqual(
new[]
{
new DateTime(2000, 3, 31),
new DateTime(2000, 6, 30),
new DateTime(2000, 9, 30),
new DateTime(2000, 12, 31)
},
dates);
}
[Test]
public void EndOfQuarterNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.QuarterEnd(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
CollectionAssert.AreEqual(
new[]
{
new DateTime(2000, 3, 26),
new DateTime(2000, 6, 25),
new DateTime(2000, 9, 25),
new DateTime(2000, 12, 26)
},
dates);
}
[Test]
public void EndOfQuarterWithSymbol()
{
var rules = GetDateRules();
var rule = rules.QuarterEnd(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31)).ToList();
Assert.AreEqual(4, dates.Count);
foreach (var date in dates)
{
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.Contains(date.Month, new[] { 3, 6, 9, 12 });
}
}
[TestCase(-1)]
[TestCase(93)]
public void QuarterStartOffsetOutOfRangeThrows(int offset)
{
var rules = GetDateRules();
Assert.Throws<ArgumentOutOfRangeException>(() => rules.QuarterStart(offset));
Assert.Throws<ArgumentOutOfRangeException>(() => rules.QuarterStart(Symbols.SPY, offset));
}
[TestCase(-1)]
[TestCase(93)]
public void QuarterEndOffsetOutOfRangeThrows(int offset)
{
var rules = GetDateRules();
Assert.Throws<ArgumentOutOfRangeException>(() => rules.QuarterEnd(offset));
Assert.Throws<ArgumentOutOfRangeException>(() => rules.QuarterEnd(Symbols.SPY, offset));
}
[Test]
public void StartOfYearNoSymbol()
{
var rules = GetDateRules();
var rule = rules.YearStart();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(1, date.Day);
Assert.AreEqual(1, date.Month);
}
Assert.AreEqual(11, count);
}
[Test]
public void StartOfYearNoSymbolMidYearStart()
{
var rules = GetDateRules();
var rule = rules.YearStart();
var dates = rule.GetDates(new DateTime(2000, 06, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(2000, date.Year);
Assert.AreEqual(1, date.Month);
Assert.AreEqual(1, date.Day);
}
Assert.AreEqual(10, count);
}
[Test]
public void StartOfYearNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.YearStart(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(1, date.Month);
Assert.AreEqual(6, date.Day);
}
Assert.AreEqual(11, count);
}
[TestCase(2)] // Before 11th
[TestCase(4)]
[TestCase(8)]
[TestCase(12)] // After 11th
[TestCase(16)]
[TestCase(20)]
public void StartOfYearSameYearSchedule(int startingDateDay)
{
var startingDate = new DateTime(2000, 1, startingDateDay);
var endingDate = new DateTime(2000, 12, 31);
var rules = GetDateRules();
var rule = rules.YearStart(10); // 11/1/2000
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(startingDateDay > 11, dates.IsNullOrEmpty());
if (startingDateDay <= 11)
{
Assert.AreEqual(new DateTime(2000, 1, 11), dates.Single());
}
}
[Test]
public void StartOfYearWithSymbol()
{
var rules = GetDateRules();
var rule = rules.YearStart(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.IsTrue(date.Day <= 4);
Log.Debug(date.Day.ToString(CultureInfo.InvariantCulture));
}
Assert.AreEqual(11, count);
}
[Test]
public void StartOfYearWithSymbolMidYearStart()
{
var rules = GetDateRules();
var rule = rules.YearStart(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 06, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(2000, date.Year);
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.AreEqual(1, date.Month);
Assert.IsTrue(date.Day <= 4);
Log.Debug(date.Day.ToString(CultureInfo.InvariantCulture));
}
Assert.AreEqual(10, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 10, 9, 9, 9, 9}, 5)]
[TestCase(Symbols.SymbolsKey.SPY, new[] { 20, 19, 18, 21, 21}, 12)] // Contains holiday 1/17
[TestCase(Symbols.SymbolsKey.SPY, new[] { 29, 31, 31, 31, 31}, 348)] // Always last trading day of the year
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 6, 6, 6, 6, 6}, 5)]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 7, 7, 7, 7, 7}, 5)]
public void StartOfYearWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays, int offset)
{
var rules = GetDateRules();
var rule = rules.YearStart(Symbols.Lookup(symbolKey), offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2004, 12, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[Test]
public void EndOfYearNoSymbol()
{
var rules = GetDateRules();
var rule = rules.YearEnd();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(12, date.Month);
Assert.AreEqual(DateTime.DaysInMonth(date.Year, date.Month), date.Day);
}
Assert.AreEqual(11, count);
}
[Test]
public void EndOfYearNoSymbolWithOffset()
{
var rules = GetDateRules();
var rule = rules.YearEnd(5);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(12, date.Month);
Assert.AreEqual(DateTime.DaysInMonth(date.Year, date.Month) - 5, date.Day);
}
Assert.AreEqual(11, count);
}
[TestCase(5)] // Before 21th
[TestCase(10)]
[TestCase(15)]
[TestCase(21)] // After 21th
[TestCase(25)]
[TestCase(30)]
public void EndOfYearSameMonthSchedule(int endingDateDay)
{
var startingDate = new DateTime(2000, 1, 1);
var endingDate = new DateTime(2000, 12, endingDateDay);
var rules = GetDateRules();
var rule = rules.YearEnd(10); // 12/21/2000
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(endingDateDay < 21, dates.IsNullOrEmpty());
if (endingDateDay >= 21)
{
Assert.AreEqual(new DateTime(2000, 12, 21), dates.Single());
}
}
[Test]
public void EndOfYearWithSymbol()
{
var rules = GetDateRules();
var rule = rules.YearEnd(Symbols.SPY);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2010, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreNotEqual(DayOfWeek.Saturday, date.DayOfWeek);
Assert.AreNotEqual(DayOfWeek.Sunday, date.DayOfWeek);
Assert.AreEqual(12, date.Month);
Assert.IsTrue(date.Day >= 28);
Log.Debug(date + " " + date.DayOfWeek);
}
Assert.AreEqual(11, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 21, 21, 23, 23, 23 }, 5)] // This case contains two Holidays 12/25 & 12/29
[TestCase(Symbols.SymbolsKey.SPY, new[] { 12, 12, 12, 12, 14 }, 12)] // Contains holiday 1/25
[TestCase(Symbols.SymbolsKey.SPY, new[] { 1, 3, 3, 3, 3 }, 19)] // Always first trading day of the month (25 > than trading days)
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 26, 26, 26, 26, 26 }, 5)]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 25, 25, 25, 25, 26 }, 5)]
public void EndOfYearWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays, int offset)
{
var rules = GetDateRules();
var rule = rules.YearEnd(Symbols.Lookup(symbolKey), offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2004, 12, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[Test]
public void StartOfWeekNoSymbol()
{
var rules = GetDateRules();
var rule = rules.WeekStart();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(DayOfWeek.Monday, date.DayOfWeek);
}
Assert.AreEqual(52, count);
}
[TestCase(0, DayOfWeek.Monday)]
[TestCase(1, DayOfWeek.Tuesday)]
[TestCase (2, DayOfWeek.Wednesday)]
[TestCase(3, DayOfWeek.Thursday)]
[TestCase(4, DayOfWeek.Friday)]
[TestCase(5, DayOfWeek.Saturday)]
[TestCase(6, DayOfWeek.Sunday)]
public void StartOfWeekNoSymbolWithOffset(int offset, DayOfWeek expectedDayOfWeek)
{
var rules = GetDateRules();
var rule = rules.WeekStart(offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(expectedDayOfWeek, date.DayOfWeek);
}
// There are 53 saturday and sundays in 2000, otherwise we expect only 52
int expected = expectedDayOfWeek == DayOfWeek.Saturday || expectedDayOfWeek == DayOfWeek.Sunday ? 53 : 52;
Assert.AreEqual(expected, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 3, 10, 18, 24, 31 })]
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 2, 9, 16, 23, 30 })]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 2, 9, 16, 23, 30 })]
[TestCase(Symbols.SymbolsKey.Fut_SPY_Feb19_2016, new int[] { 2, 9, 16, 23, 30 })]
public void StartOfWeekWithSymbol(Symbols.SymbolsKey symbolKey, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.WeekStart(Symbols.Lookup(symbolKey));
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 1, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[TestCase(false, new int[] { 3, 10, 18, 24, 31 })]
[TestCase(true, new int[] { 2, 9, 16, 23, 30 })]
public void StartOfWeekWithSymbolUsingExtendedMarketHoursParameter(bool extendedMarketHours, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.WeekStart(Symbols.Lookup(Symbols.SymbolsKey.Fut_SPY_Feb19_2016), extendedMarketHours: extendedMarketHours);
AssertDateRule(rule, new DateTime(2000, 01, 01), new DateTime(2000, 1, 31), expectedDays);
}
[TestCase(false, new int[] { 3, 3, 3, 2, 2 })]
[TestCase(true, new int[] { 3, 1, 3, 2, 2 })]
public void StartOfYearWithSymbolUsingExtendedMarketHoursParameter(bool extendedMarketHours, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.YearStart(Symbols.Lookup(Symbols.SymbolsKey.Fut_SPY_Feb19_2016), extendedMarketHours: extendedMarketHours);
AssertDateRule(rule, new DateTime(2005, 01, 01), new DateTime(2009, 1, 31), expectedDays);
}
[TestCase(false, new int[] { 30, 29, 31, 31, 31})]
[TestCase(true, new int[] { 30, 31, 31, 31, 31 })]
public void EndOfYearWithSymbolUsingExtendedMarketHoursParameter(bool extendedMarketHours, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.YearEnd(Symbols.Lookup(Symbols.SymbolsKey.Fut_SPY_Feb19_2016), extendedMarketHours: extendedMarketHours);
AssertDateRule(rule, new DateTime(2005, 01, 01), new DateTime(2010, 1, 1), expectedDays);
}
[TestCase(false, new int[] { 1, 1, 2, 1, 1 })]
[TestCase(true, new int[] { 1, 1, 1, 1, 1 })]
public void StartOfMonthWithSymbolUsingExtendedMarketHoursParameter(bool extendedMarketHours, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.MonthStart(Symbols.Lookup(Symbols.SymbolsKey.Fut_SPY_Feb19_2016), extendedMarketHours: extendedMarketHours);
AssertDateRule(rule, new DateTime(2000, 08, 01), new DateTime(2000, 12, 31), expectedDays);
}
[TestCase(false, new int[] { 31, 29, 31, 30, 29 })]
[TestCase(true, new int[] { 31, 29, 31, 30, 31 })]
public void EndOfMonthWithSymbolUsingExtendedMarketHoursParameter(bool extendedMarketHours, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.MonthEnd(Symbols.Lookup(Symbols.SymbolsKey.Fut_SPY_Feb19_2016), extendedMarketHours: extendedMarketHours);
AssertDateRule(rule, new DateTime(2000, 08, 01), new DateTime(2000, 12, 31), expectedDays);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 5, 12, 20, 26 })] // Set contains holiday on 1/17
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 4, 11, 18, 25 })]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 4, 11, 18, 25 })]
public void StartOfWeekWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.WeekStart(Symbols.Lookup(symbolKey), 2);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 1, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[TestCase(3, false)] // Start before the 6th
[TestCase(4, false)]
[TestCase(5, false)]
[TestCase(6, false)]
[TestCase(7, true)] // Start after the 6th
[TestCase(8, true)]
[TestCase(9, true)]
public void StartOfWeekSameWeekSchedule(int startingDateDay, bool expectNone)
{
// Related to issue #5678, Assert that even though starting date may not be
// not monday we still schedule for that week.
// For this test and the EndOfWeek counterpart we will use the week of
// 1/3/2000 Monday to 1/9/2000 Sunday; with our variable date applied.
var startingDate = new DateTime(2000, 1, startingDateDay);
var endingDate = new DateTime(2000, 1, 9);
var rules = GetDateRules();
var rule = rules.WeekStart(3); // 1/6/2000
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(expectNone, dates.IsNullOrEmpty());
if (!expectNone)
{
Assert.AreEqual(new DateTime(2000, 1, 6), dates.First());
}
}
[TestCase(5)] // Monday + 5 = Saturday
[TestCase(6)] // Monday + 6 = Sunday
public void StartOfWeekWithSymbolOffsetToNonTradableDay(int offset)
{
var rules = GetDateRules();
// We expect it to throw because Spy does not trade on the weekends
Assert.Throws<ArgumentOutOfRangeException>(() => rules.WeekStart(Symbols.SPY, offset));
}
[Test]
public void EndOfWeekNoSymbol()
{
var rules = GetDateRules();
var rule = rules.WeekEnd();
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(DayOfWeek.Friday, date.DayOfWeek);
}
Assert.AreEqual(52, count);
}
[TestCase(0, DayOfWeek.Friday)]
[TestCase(1, DayOfWeek.Thursday)]
[TestCase(2, DayOfWeek.Wednesday)]
[TestCase(3, DayOfWeek.Tuesday)]
[TestCase(4, DayOfWeek.Monday)]
[TestCase(5, DayOfWeek.Sunday)]
[TestCase(6, DayOfWeek.Saturday)]
public void EndOfWeekNoSymbolWithOffset(int offset, DayOfWeek expectedDayOfWeek)
{
var rules = GetDateRules();
var rule = rules.WeekEnd(offset);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 12, 31));
int count = 0;
foreach (var date in dates)
{
count++;
Assert.AreEqual(expectedDayOfWeek, date.DayOfWeek);
}
Assert.AreEqual(52, count);
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 7, 14, 21, 28 })]
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 1, 8, 15, 22, 29 })]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 7, 14, 21, 28 })]
[TestCase(Symbols.SymbolsKey.Fut_SPY_Feb19_2016, new int[] { 7, 14, 21, 28 })]
public void EndOfWeekWithSymbol(Symbols.SymbolsKey symbolKey, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.WeekEnd(Symbols.Lookup(symbolKey));
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 1, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[TestCase(Symbols.SymbolsKey.SPY, new[] { 5, 12, 19, 26 })]
[TestCase(Symbols.SymbolsKey.BTCUSD, new[] { 6, 13, 20, 27 })]
[TestCase(Symbols.SymbolsKey.EURUSD, new[] { 5, 12, 19, 26 })]
public void EndOfWeekWithSymbolWithOffset(Symbols.SymbolsKey symbolKey, int[] expectedDays)
{
var rules = GetDateRules();
var rule = rules.WeekEnd(Symbols.Lookup(symbolKey), 2);
var dates = rule.GetDates(new DateTime(2000, 01, 01), new DateTime(2000, 1, 31)).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
[TestCase(3, true)] // End before the 6th
[TestCase(4, true)]
[TestCase(5, true)]
[TestCase(6, false)] // End after the 6th
[TestCase(7, false)]
[TestCase(8, false)]
[TestCase(9, false)]
public void EndOfWeekSameWeekSchedule(int endDateDay, bool expectNone)
{
// Related to issue #5678, Assert that even though starting date may not be
// not monday we still schedule for that week.
// For this test and the EndOfWeek counterpart we will use the week of
// 1/3/2000 Monday to 1/9/2000 Sunday; with our variable date applied.
var startingDate = new DateTime(2000, 1, 3);
var endingDate = new DateTime(2000, 1, endDateDay);
var rules = GetDateRules();
var rule = rules.WeekEnd(1); // 1/6/2000 (For weekEnd, Friday is the base day)
var dates = rule.GetDates(startingDate, endingDate);
Assert.AreEqual(expectNone, dates.IsNullOrEmpty());
if (!expectNone)
{
Assert.AreEqual(new DateTime(2000, 1, 6), dates.First());
}
}
[TestCase(5)] // Friday - 5 = Sunday
[TestCase(6)] // Friday - 6 = Saturday
public void EndOfWeekWithSymbolOffsetToNonTradableDay(int offset)
{
var rules = GetDateRules();
// We expect it to throw because Spy does not trade on the weekends
Assert.Throws<ArgumentOutOfRangeException>(() => rules.WeekEnd(Symbols.SPY, offset));
}
[Test]
public void DateRulesExpectedNames()
{
var rules = GetDateRules();
IDateRule rule;
// WeekEnd Rules
rule = rules.WeekEnd();
Assert.AreEqual("WeekEnd", rule.Name);
rule = rules.WeekEnd(1);
Assert.AreEqual("WeekEnd-1", rule.Name);
rule = rules.WeekEnd(Symbols.SPY);
Assert.AreEqual("SPY: WeekEnd", rule.Name);
rule = rules.WeekEnd(Symbols.SPY, 2);
Assert.AreEqual("SPY: WeekEnd-2", rule.Name);
// WeekStart rules
rule = rules.WeekStart();
Assert.AreEqual("WeekStart", rule.Name);
rule = rules.WeekStart(1);
Assert.AreEqual("WeekStart+1", rule.Name);
rule = rules.WeekStart(Symbols.SPY);
Assert.AreEqual("SPY: WeekStart", rule.Name);
rule = rules.WeekStart(Symbols.SPY, 3);
Assert.AreEqual("SPY: WeekStart+3", rule.Name);
}
[Test]
public void SetTimeZone()
{
var rules = GetDateRules();
var nowNewYork = rules.Today.GetDates(_utcNow, _utcNow).Single();
rules.SetDefaultTimeZone(TimeZones.Utc);
var nowUtc = rules.Today.GetDates(_utcNow, _utcNow).Single();
Assert.AreEqual(_utcNow.Date, nowUtc);
Assert.AreEqual(nowUtc.Date, nowNewYork.AddDays(1));
}
[Test]
public void SetFuncDateRuleInPythonWorksAsExpected()
{
using (Py.GIL())
{
var pythonModule = PyModule.FromString("testModule", @"
from AlgorithmImports import *
def CustomDateRule(start, end):
return [start + (end - start)/2]
");
dynamic pythonCustomDateRule = pythonModule.GetAttr("CustomDateRule");
var funcDateRule = new FuncDateRule("PythonFuncDateRule", pythonCustomDateRule);
Assert.AreEqual("PythonFuncDateRule", funcDateRule.Name);
Assert.AreEqual(new DateTime(2023, 1, 16, 12, 0, 0), funcDateRule.GetDates(new DateTime(2023, 1, 1), new DateTime(2023, 2, 1)).First());
}
}
[Test]
public void SetFuncDateRuleInPythonWorksAsExpectedWithCSharpFunc()
{
using (Py.GIL())
{
var pythonModule = PyModule.FromString("testModule", @"
from AlgorithmImports import *
def GetFuncDateRule(csharpFunc):
return FuncDateRule(""CSharp"", csharpFunc)
");
dynamic getFuncDateRule = pythonModule.GetAttr("GetFuncDateRule");
Func<DateTime, DateTime, IEnumerable<DateTime>> csharpFunc = (start, end) => { return new List<DateTime>() { new DateTime(2001, 3, 18) }; };
var funcDateRule = getFuncDateRule(csharpFunc);
Assert.AreEqual("CSharp", (funcDateRule.Name as PyObject).GetAndDispose<string>());
Assert.AreEqual(new DateTime(2001, 3, 18),
(funcDateRule.GetDates(new DateTime(2023, 1, 1), new DateTime(2023, 2, 1)) as PyObject).GetAndDispose<List<DateTime>>().First());
}
}
[Test]
public void SetFuncDateRuleInPythonFailsWhenDateRuleIsInvalid()
{
using (Py.GIL())
{
var pythonModule = PyModule.FromString("testModule", @"
from AlgorithmImports import *
wrongCustomDateRule = 1
");
dynamic pythonCustomDateRule = pythonModule.GetAttr("wrongCustomDateRule");
Assert.Throws<ArgumentException>(() => new FuncDateRule("PythonFuncDateRule", pythonCustomDateRule));
}
}
[Test]
public void DateRuleDoesNotConflictWithTimeRuleDueToExtendedMarketHours()
{
var algorithm = new AlgorithmStub();
algorithm.SetDateTime(new DateTime(2024, 02, 10));
var es = algorithm.AddFuture("ES").Symbol;
var scheduledEvent = algorithm.Schedule.On(algorithm.Schedule.DateRules.WeekStart(es, extendedMarketHours: false),
algorithm.Schedule.TimeRules.AfterMarketOpen(es),
() => { });
Assert.AreEqual(new DateTime(2024, 02, 12, 14, 30, 0), scheduledEvent.NextEventUtcTime);
}
private static void AssertDateRule(IDateRule rule, DateTime start, DateTime end, int[] expectedDays)
{
var dates = rule.GetDates(start, end).ToList();
// Assert we have as many dates as expected
Assert.AreEqual(expectedDays.Length, dates.Count);
// Verify the days match up
var datesAndExpectedDays = dates.Zip(expectedDays, (date, expectedDay) => new { date, expectedDay });
foreach (var pair in datesAndExpectedDays)
{
Assert.AreEqual(pair.expectedDay, pair.date.Day);
}
}
private static DateRules GetDateRules()
{
var mhdb = MarketHoursDatabase.FromDataFolder();
var timeKeeper = new TimeKeeper(_utcNow, new List<DateTimeZone>());
var manager = new SecurityManager(timeKeeper);
// Add SPY for Equity testing
var securityExchangeHours = mhdb.GetExchangeHours(Market.USA, null, SecurityType.Equity);
var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, true, false, false);
manager.Add(
Symbols.SPY,
new Security(
securityExchangeHours,
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
)
);
// Add BTC for Crypto testing
securityExchangeHours = mhdb.GetExchangeHours(Market.Bitfinex, Symbols.BTCUSD, SecurityType.Crypto);
config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.BTCUSD, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, true, false, false);
manager.Add(
Symbols.BTCUSD,
new Security(
securityExchangeHours,
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
)
);
// Add EURUSD for Forex testing
securityExchangeHours = mhdb.GetExchangeHours(Market.FXCM, Symbols.EURUSD, SecurityType.Forex);
config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.EURUSD, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, true, false, false);
manager.Add(
Symbols.EURUSD,
new Security(
securityExchangeHours,
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
)
);
// Add Fut_SPY_Feb19_2016 for testing
securityExchangeHours = mhdb.GetExchangeHours(Market.CME, Symbols.Fut_SPY_Feb19_2016, SecurityType.Future);
config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.Fut_SPY_Feb19_2016, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, true, false, false);
manager.Add(
Symbols.Fut_SPY_Feb19_2016,
new Security(
securityExchangeHours,
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
)
);
var rules = new DateRules(null, manager, TimeZones.NewYork, mhdb);
return rules;
}
}
}