Skip to content

Commit

Permalink
Normalize Date timezone (#8420)
Browse files Browse the repository at this point in the history
* First draft of the solution

* Add unit test

* Nit change
  • Loading branch information
Marinovsky authored Nov 25, 2024
1 parent 469d960 commit 5ac7d00
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Algorithm.Python/BaseFrameworkRegressionAlgorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def initialize(self):
# With this procedure, the Alpha Model will experience multiple universe changes
self.add_universe_selection(ScheduledUniverseSelectionModel(
self.date_rules.every_day(), self.time_rules.midnight,
lambda dt: symbols if dt.replace(tzinfo=None) < self.end_date - timedelta(1) else []))
lambda dt: symbols if dt < self.end_date - timedelta(1) else []))

self.set_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(31), 0.025, None))
self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())
Expand Down
2 changes: 1 addition & 1 deletion Common/Data/UniverseSelection/ScheduledUniverse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public ScheduledUniverse(IDateRule dateRule, ITimeRule timeRule, PyObject select
/// <returns>The data that passes the filter</returns>
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(utcTime);
return _selector(DateTime.SpecifyKind(utcTime, DateTimeKind.Unspecified));
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Common/Scheduling/ScheduleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public ScheduledEvent Training(IDateRule dateRule, ITimeRule timeRule, Action<Da
/// </summary>
internal static IEnumerable<DateTime> GetDatesDeferred(IDateRule dateRule, SecurityManager securities)
{
foreach (var item in dateRule.GetDates(securities.UtcTime.Date.AddDays(-1), Time.EndOfTime))
foreach (var item in dateRule.GetDates(DateTime.SpecifyKind(securities.UtcTime.Date.AddDays(-1), DateTimeKind.Unspecified), Time.EndOfTime))
{
yield return item;
}
Expand Down
69 changes: 56 additions & 13 deletions Tests/Common/Scheduling/ScheduleManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.RealTime;
using QuantConnect.Packets;
using QuantConnect.Scheduling;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util.RateLimit;

Expand Down Expand Up @@ -113,20 +114,8 @@ public void TriggersWeeklyScheduledEventsEachWeekBacktesting()
[Test]
public void TriggersWeeklyScheduledEventsEachWeekLive()
{
var algorithm = new AlgorithmStub();

var handler = new TestableLiveTradingRealTimeHandler();
var time = new DateTime(2024, 02, 10);
handler.ManualTimeProvider.SetCurrentTime(time);
var timeLimitManager = new AlgorithmTimeLimitManager(TokenBucket.Null, TimeSpan.FromMinutes(20));
handler.Setup(algorithm, new LiveNodePacket(), null, null, timeLimitManager);

algorithm.Schedule.SetEventSchedule(handler);

algorithm.SetDateTime(time);

var spy = algorithm.AddEquity("SPY").Symbol;

SetUp(time, out var algorithm, out var handler, out var spy);
var eventTriggerTimes = new List<DateTime>();
var scheduledEvent = algorithm.Schedule.On(algorithm.Schedule.DateRules.WeekStart(spy),
algorithm.Schedule.TimeRules.BeforeMarketClose(spy, 60),
Expand Down Expand Up @@ -174,6 +163,60 @@ public void TriggersWeeklyScheduledEventsEachWeekLive()
CollectionAssert.AreEqual(expectedEventTriggerTimes, eventTriggerTimes);
}

[Test]
public void DatesReturnedAreNormalized()
{
var time = new DateTime(2024, 02, 10);
SetUp(time, out var algorithm, out var handler, out var spy);
var eventTriggerTimes = new List<DateTime>();
using var finished = new ManualResetEventSlim(false);

// Schedule a task to advance time
var timeStep = TimeSpan.FromMinutes(1);
var wasCalled = false;
Func<DateTime, DateTime, IEnumerable<DateTime>> func = (date1, date2) =>
{
Assert.AreEqual(DateTimeKind.Unspecified, date1.Kind);
Assert.AreEqual(DateTimeKind.Unspecified, date2.Kind);
wasCalled = true;
return new List<DateTime> { date1, date2 };
};

algorithm.Schedule.On(new FuncDateRule("Test", func),
algorithm.Schedule.TimeRules.Every(timeStep),
() =>
{
handler.ManualTimeProvider.Advance(timeStep);
var now = handler.ManualTimeProvider.GetUtcNow();
finished.Set();
});

// Start
handler.SetTime(time);

finished.Wait(TimeSpan.FromSeconds(15));

handler.Exit();
Assert.IsTrue(wasCalled);
}

private void SetUp(DateTime time, out QCAlgorithm algorithm, out TestableLiveTradingRealTimeHandler handler, out Symbol spy)
{
algorithm = new AlgorithmStub();

handler = new TestableLiveTradingRealTimeHandler();

handler.ManualTimeProvider.SetCurrentTime(time);
var timeLimitManager = new AlgorithmTimeLimitManager(TokenBucket.Null, TimeSpan.FromMinutes(20));
handler.Setup(algorithm, new LiveNodePacket(), null, null, timeLimitManager);

algorithm.Schedule.SetEventSchedule(handler);

algorithm.SetDateTime(time);

spy = algorithm.AddEquity("SPY").Symbol;
}

private class TestableLiveTradingRealTimeHandler : LiveTradingRealTimeHandler
{
public ManualTimeProvider ManualTimeProvider = new ManualTimeProvider();
Expand Down

0 comments on commit 5ac7d00

Please sign in to comment.