Files
quantconnect--lean/Tests/Engine/DataFeeds/Enumerators/LiveFillForwardEnumeratorTests.cs
T
2026-07-13 13:02:50 +08:00

578 lines
29 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.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class LiveFillForwardEnumeratorTests
{
[Test]
public void FillsForwardOnNulls()
{
var reference = new DateTime(2015, 10, 08);
var period = Time.OneSecond;
var underlying = new List<BaseData>
{
// 0 seconds
new TradeBar(reference, Symbols.SPY, 10, 20, 5, 15, 123456, period),
// 1 seconds
null,
// 3 seconds
new TradeBar(reference.AddSeconds(2), Symbols.SPY, 100, 200, 50, 150, 1234560, period),
null,
null,
null,
null
};
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(reference);
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
var fillForward = new LiveFillForwardEnumerator(timeProvider, underlying.GetEnumerator(), exchange, Ref.Create(Time.OneSecond), false, reference, Time.EndOfTime, Resolution.Second, exchange.TimeZone, false);
// first point is always emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[0], fillForward.Current);
Assert.AreEqual(123456, ((TradeBar)fillForward.Current).Volume);
// stepping again without advancing time does nothing, but we'll still
// return true as per IEnumerator contract
Assert.IsTrue(fillForward.MoveNext());
Assert.IsNull(fillForward.Current);
timeProvider.SetCurrentTime(reference.AddSeconds(2));
// non-null next will fill forward in between
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[0].EndTime, fillForward.Current.Time);
Assert.AreEqual(underlying[0].Value, fillForward.Current.Value);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
// even without stepping the time this will advance since non-null data is ready
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2], fillForward.Current);
Assert.AreEqual(1234560, ((TradeBar)fillForward.Current).Volume);
// step ahead into null data territory
timeProvider.SetCurrentTime(reference.AddSeconds(4));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
Assert.IsTrue(fillForward.MoveNext());
Assert.IsNull(fillForward.Current);
timeProvider.SetCurrentTime(reference.AddSeconds(5));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
timeProvider.SetCurrentTime(reference.AddSeconds(6));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
fillForward.Dispose();
}
[Test]
public void HandlesDaylightSavingTimeChange()
{
// In 2018, Daylight Saving Time (DST) began at 2 AM on Sunday, March 11
// This means that clocks were moved forward one hour on March 11
var reference = new DateTime(2018, 3, 10);
var period = Time.OneDay;
var underlying = new List<TradeBar>
{
new TradeBar(reference, Symbols.SPY, 10, 20, 5, 15, 123456, period),
// Daylight Saving Time change, the data still goes from midnight to midnight
new TradeBar(reference.AddDays(1), Symbols.SPY, 100, 200, 50, 150, 1234560, period)
};
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(reference);
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
var fillForward = new LiveFillForwardEnumerator(
timeProvider,
underlying.GetEnumerator(),
exchange,
Ref.Create(Time.OneDay),
false,
reference,
Time.EndOfTime,
Resolution.Daily,
exchange.TimeZone, false);
// first point is always emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.IsFalse(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[0], fillForward.Current);
//Assert.AreEqual(underlying[0].EndTime, fillForward.Current.EndTime);
Assert.AreEqual(123456, ((TradeBar)fillForward.Current).Volume);
// Daylight Saving Time change -> add 1 hour
timeProvider.SetCurrentTime(reference.AddDays(1).AddHours(1));
// second data point emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.IsFalse(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[1], fillForward.Current);
//Assert.AreEqual(underlying[1].EndTime, fillForward.Current.EndTime);
Assert.AreEqual(1234560, ((TradeBar)fillForward.Current).Volume);
Assert.IsTrue(fillForward.MoveNext());
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[1].EndTime, fillForward.Current.Time);
Assert.AreEqual(underlying[1].Value, fillForward.Current.Value);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
fillForward.Dispose();
}
[Test]
public void LiveFillForwardEnumeratorDoesNotStall()
{
var reference = new DateTime(2020, 5, 21, 9, 40, 0, 100);
var timeProvider = new ManualTimeProvider(reference, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, reference.Date, Resolution.Minute, out var enqueueableEnumerator, false);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
EndTime = new DateTime(2020, 5, 21, 9, 40, 0),
Symbol = Symbols.AAPL
};
var secondBar = new TradeBar
{
Open = 1m,
High = 2m,
Low = 1m,
Close = 2m,
Volume = 100,
EndTime = new DateTime(2020, 5, 21, 9, 42, 0),
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we expect a fill-forward bar.
timeProvider.SetCurrentTime(new DateTime(2020, 5, 21, 9, 41, 0, 100) + LiveFillForwardEnumerator.GetMaximumDataTimeout(Resolution.Minute));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
timeProvider.SetCurrentTime(new DateTime(2020, 5, 21, 9, 42, 0, 100));
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
enqueueableEnumerator.Dispose();
}
private static IEnumerable<TestCaseData> TimeOutTestCases
{
get
{
// Hour resolution, fill forward to market open
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 10, 0, 0), true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 10, 0, 0), false, false);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 10, 0, 0), null, true, false);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 10, 0, 0), null, false, false);
// over a weekend (22th is a Friday and 25th Monday is a holiday)
yield return new(Resolution.Hour, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 10, 0, 0), true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 10, 0, 0), false, false);
// market close
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 15, 0, 0), null, true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 15, 0, 0), null, false, false);
// Minute resolution, fill forward to market open
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 31, 0), true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 31, 0), false, false);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 9, 31, 0), null, true, false);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 9, 31, 0), null, false, false);
// over a weekend
yield return new(Resolution.Minute, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 31, 0), true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 31, 0), false, false);
// market close
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 15, 59, 0), null, true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 15, 59, 0), null, false, false);
// Second resolution, fill forward to market open
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 30, 1), true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 30, 1), false, false);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 9, 30, 1), null, true, false);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 9, 30, 1), null, false, false);
// over a weekend
yield return new(Resolution.Second, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 30, 1), true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 30, 1), false, false);
// market close
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 15, 59, 59), null, true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 15, 59, 59), null, false, false);
}
}
[TestCaseSource(nameof(TimeOutTestCases))]
public void TakesIntoAccountTimeOut(Resolution resolution, DateTime previousDataEndTime, DateTime? expectedNextBarEndTime, bool dataArrivedLate, bool shouldHaveTimeout)
{
var timeProvider = new ManualTimeProvider(previousDataEndTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, previousDataEndTime.Date, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled: false);
var period = resolution.ToTimeSpan();
var openingBar = new TradeBar(previousDataEndTime.Subtract(period), Symbols.AAPL, 0.01m, 0.01m, 0.01m, 0.01m, 1, period);
expectedNextBarEndTime ??= previousDataEndTime.Add(resolution.ToTimeSpan());
var secondBar = new TradeBar(openingBar.EndTime, Symbols.AAPL, 1m, 2m, 1m, 2m, 100, period);
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we don't expect a fill-forward bar because the timeout amount has not passed yet
timeProvider.SetCurrentTime(expectedNextBarEndTime.Value);
if (dataArrivedLate)
{
Assert.IsTrue(fillForwardEnumerator.MoveNext());
if (shouldHaveTimeout)
{
Assert.IsNull(fillForwardEnumerator.Current);
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
}
Assert.IsNotNull(fillForwardEnumerator.Current);
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(expectedNextBarEndTime, fillForwardEnumerator.Current.EndTime);
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(expectedNextBarEndTime, fillForwardEnumerator.Current.EndTime);
}
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
enqueueableEnumerator.Dispose();
}
[Test]
public void TakesIntoAccountTimeOutWhenThereAreBigGaps([Values(Resolution.Second, Resolution.Minute, Resolution.Hour)] Resolution resolution)
{
var start = new DateTime(2020, 5, 20, 12, 0, 0);
var end = new DateTime(2020, 5, 22, 16, 0, 0);
var timeProvider = new ManualTimeProvider(start, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, start, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled: false);
var period = resolution.ToTimeSpan();
var openingBar = new TradeBar(start.Subtract(period), Symbols.AAPL, 0.01m, 0.01m, 0.01m, 0.01m, 1, period);
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
timeProvider.Advance(period);
var previous = (TradeBar)fillForwardEnumerator.Current;
var currentIsMarketOpen = false;
var currentIsMarketClose = false;
while (previous.EndTime < end)
{
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
Assert.IsTrue(fillForwardEnumerator.MoveNext(), $"Previous: {previous.EndTime}");
if (currentIsMarketOpen || currentIsMarketClose)
{
Assert.IsNull(fillForwardEnumerator.Current, $"Previous: {previous.EndTime}");
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext(), $"Previous: {previous.EndTime}");
}
Assert.IsNotNull(fillForwardEnumerator.Current, $"Previous: {previous.EndTime}");
var current = (TradeBar)fillForwardEnumerator.Current;
Assert.IsTrue(current.IsFillForward, $"Current: {previous.EndTime}");
Assert.AreEqual(previous.Open, current.Open, $"Current: {previous.EndTime}");
Assert.AreEqual(previous.Price, current.Price, $"Current: {previous.EndTime}");
var expectedEndTime = previous.EndTime.Add(period);
if (currentIsMarketOpen)
{
expectedEndTime = expectedEndTime.Date.AddDays(1).AddHours(9).AddMinutes(30).Add(period);
if (resolution == Resolution.Hour)
{
expectedEndTime = expectedEndTime.RoundDown(period);
}
}
Assert.AreEqual(expectedEndTime, current.EndTime, $"Current: {previous.EndTime}");
currentIsMarketOpen = current.EndTime.TimeOfDay == TimeSpan.FromHours(16);
currentIsMarketClose = current.EndTime.TimeOfDay == TimeSpan.FromHours(16) - period;
previous = current;
// Advance the time provider
var nextTime = current.EndTime.Add(period);
if (nextTime.TimeOfDay > TimeSpan.FromHours(16))
{
// If the next time is after market close, we need to advance to the next day
nextTime = nextTime.Date.AddDays(1).AddHours(9).AddMinutes(30).Add(period);
if (resolution == Resolution.Hour)
{
nextTime = nextTime.RoundDown(period);
}
}
timeProvider.SetCurrentTime(nextTime);
}
enqueueableEnumerator.Dispose();
}
[TestCase(true, true)]
[TestCase(false, true)]
[TestCase(true, false)]
[TestCase(false, false)]
public void TakesIntoAccountTimeOutDaily(bool dailyStrictEndTimeEnabled, bool dataArrivedLate)
{
var resolution = Resolution.Daily;
var referenceOpenTime = new DateTime(2020, 5, 21, 9, 30, 0);
var referenceTime = new DateTime(2020, 5, 21, 16, 0, 0);
if (!dailyStrictEndTimeEnabled)
{
referenceOpenTime = referenceOpenTime.Date;
referenceTime = referenceTime.Date.AddDays(1);
}
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
var secondBar = new TradeBar
{
Open = 1m,
High = 2m,
Low = 1m,
Close = 2m,
Volume = 100,
Time = referenceOpenTime.AddDays(1),
EndTime = referenceTime.AddDays(1),
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we don't expect a fill-forward bar because the timeout amount has not passed yet
timeProvider.SetCurrentTime(secondBar.EndTime);
if (dataArrivedLate)
{
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsNull(fillForwardEnumerator.Current);
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(secondBar.EndTime, fillForwardEnumerator.Current.EndTime);
}
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(secondBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(secondBar.EndTime, fillForwardEnumerator.Current.EndTime);
enqueueableEnumerator.Dispose();
}
[TestCase(Resolution.Minute)]
[TestCase(Resolution.Hour)]
public void MultiResolutionSmallerFillForwardResolution(Resolution resolution)
{
var ffResolution = Resolution.Second;
var referenceOpenTime = new DateTime(2020, 5, 21, 14, 0, 0);
if (resolution == Resolution.Minute)
{
referenceOpenTime = new DateTime(2020, 5, 21, 15, 59, 0);
}
var referenceTime = new DateTime(2020, 5, 21, 15, 0, 0);
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, true, ffResolution);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we expect a fill-forward bar
for (var i = 0; i < 60; i++)
{
timeProvider.Advance(Time.OneSecond);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork), fillForwardEnumerator.Current.EndTime);
}
enqueueableEnumerator.Dispose();
}
[TestCase(Resolution.Daily, Resolution.Minute)]
[TestCase(Resolution.Hour, Resolution.Minute)]
[TestCase(Resolution.Daily, Resolution.Second)]
[TestCase(Resolution.Hour, Resolution.Second)]
public void MultiResolutionMarketClose(Resolution resolution, Resolution ffResolution)
{
var referenceOpenTime = new DateTime(2020, 5, 21, 9, 30, 0);
if (resolution == Resolution.Hour)
{
referenceOpenTime = new DateTime(2020, 5, 21, 15, 0, 0);
}
var referenceTime = new DateTime(2020, 5, 21, 16, 0, 0);
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, true, ffResolution);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
for (var i = 0; i < 600; i++)
{
// Advance the time, we don't expect a fill-forward bar, we've emitted our daily bar already and market is closed
timeProvider.Advance(ffResolution.ToTimeSpan());
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsNull(fillForwardEnumerator.Current);
}
enqueueableEnumerator.Dispose();
}
private static LiveFillForwardEnumerator GetLiveFillForwardEnumerator(ITimeProvider timeProvider, DateTime startTime, Resolution resolution,
out EnqueueableEnumerator<BaseData> enqueueableEnumerator, bool dailyStrictEndTimeEnabled, Resolution? ffResolution = null)
{
enqueueableEnumerator = new EnqueueableEnumerator<BaseData>();
var fillForwardEnumerator = new LiveFillForwardEnumerator(
timeProvider,
enqueueableEnumerator,
new SecurityExchange(MarketHoursDatabase.FromDataFolder()
.ExchangeHoursListing
.First(kvp => kvp.Key.Market == Market.USA && kvp.Key.SecurityType == SecurityType.Equity)
.Value
.ExchangeHours),
Ref.CreateReadOnly(() => (ffResolution ?? resolution).ToTimeSpan()),
false,
startTime,
Time.EndOfTime,
resolution,
TimeZones.NewYork,
dailyStrictEndTimeEnabled: dailyStrictEndTimeEnabled
);
return fillForwardEnumerator;
}
}
}