2656 lines
127 KiB
C#
2656 lines
127 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 NodaTime;
|
|
using NUnit.Framework;
|
|
using QuantConnect.Algorithm;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Data.UniverseSelection;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
|
|
using QuantConnect.Logging;
|
|
using QuantConnect.Packets;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Securities.Equity;
|
|
using QuantConnect.Securities.Forex;
|
|
using QuantConnect.Securities.Option;
|
|
using QuantConnect.Util;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using QuantConnect.Securities.Future;
|
|
|
|
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
|
|
{
|
|
[TestFixture]
|
|
public class FillForwardEnumeratorTests
|
|
{
|
|
[Test]
|
|
public void FillForwardsUntilSubscriptionEnd()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var fillForwardResolution = Time.OneHour;
|
|
|
|
var time = new DateTime(2017, 7, 20, 0, 0, 0);
|
|
var subscriptionEndTime = time.AddDays(1);
|
|
|
|
var enumerator = new List<BaseData>
|
|
{
|
|
new TradeBar { Time = time, Value = 1, Period = dataResolution, Volume = 100},
|
|
new TradeBar { Time = time.AddDays(5), Value = 1, Period = dataResolution, Volume = 100},
|
|
}.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), false, time, subscriptionEndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
var dataCount = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
dataCount++;
|
|
Assert.IsFalse(fillForwardEnumerator.Current.EndTime > subscriptionEndTime);
|
|
}
|
|
Assert.AreEqual(24, dataCount);
|
|
}
|
|
|
|
[Test]
|
|
public void DelistingEvents()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var fillForwardResolution = Time.OneMinute;
|
|
|
|
var time = new DateTime(2017, 7, 20, 0, 0, 0);
|
|
|
|
var enumerator = new List<BaseData>
|
|
{
|
|
new Delisting(Symbols.SPY, time, 100, DelistingType.Warning),
|
|
new Delisting(Symbols.SPY, time.AddDays(1), 100, DelistingType.Delisted),
|
|
new TradeBar { Time = time.AddDays(2), Value = 1, Period = dataResolution, Volume = 100},
|
|
}.GetEnumerator();
|
|
|
|
var exchange = new OptionExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), false, time, time.AddDays(10), dataResolution, exchange.TimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(DelistingType.Warning, ((Delisting)fillForwardEnumerator.Current).Type);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(DelistingType.Delisted, ((Delisting)fillForwardEnumerator.Current).Type);
|
|
|
|
// even if there's more data emitted in the base enumerator we've passed the delisting date!
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
}
|
|
|
|
[TestCase(true)]
|
|
[TestCase(false)]
|
|
// reproduces GH issue 4392 causing fill forward bars not to advance
|
|
// the nature of the bug was rounding down in exchange tz versus data timezone
|
|
public void GetReferenceDateIntervals_RoundDown(bool strictEndTimes)
|
|
{
|
|
var dataResolution = Time.OneDay;
|
|
var fillForwardResolution = Time.OneMinute;
|
|
|
|
var previous = new DateTime(2017, 7, 20, 20, 0, 0);
|
|
var next = new DateTime(2017, 7, 22, 20, 0, 0);
|
|
var enumerator = new List<BaseData>
|
|
{
|
|
new TradeBar { Time = previous, Value = 1, Period = dataResolution, Volume = 100},
|
|
new TradeBar { Time = next, Value = 2, Period = dataResolution, Volume = 100}
|
|
}.GetEnumerator();
|
|
|
|
var dataTimeZone = TimeZones.Utc;
|
|
var exchange = new ForexExchange();
|
|
// to reproduce this bug it's important for data tz to be UTC and exchange tz NewYork.
|
|
Assert.AreEqual(TimeZones.NewYork, exchange.TimeZone);
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), isExtendedMarketHours, next.Date, next.AddDays(1), dataResolution, dataTimeZone, strictEndTimes);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(previous, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.AreEqual(100, (fillForwardEnumerator.Current as TradeBar).Volume);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
// Time should advance!
|
|
// Time should be 10:01am to 5:01pm, on Sundays the market opens at 5pm, so the market duration is 7 hours
|
|
var expectedTime = strictEndTimes ? new DateTime(2017, 7, 23, 10, 1, 0) : new DateTime(2017, 7, 22, 17, 1, 0);
|
|
Assert.AreEqual(expectedTime, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(new DateTime(2017, 7, 23, 17, 1, 0), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.AreEqual(0, (fillForwardEnumerator.Current as TradeBar).Volume);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardMidDay()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
|
|
var reference = new DateTime(2015, 6, 25, 9, 30, 0);
|
|
var data = Enumerable.Range(0, 2).Select(x => new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(x * 2),
|
|
Value = x,
|
|
Period = dataResolution,
|
|
Volume = (x + 1) * 100
|
|
}).ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardResolution = TimeSpan.FromMinutes(1);
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), isExtendedMarketHours, reference.Date, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:31
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:32 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:33
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardFromPreMarket()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 9, 28, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(4),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:29
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:31 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:32 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(4), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:33
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(5), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardFromPreMarketMinuteToSecond()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2011, 4, 26, 8, 39, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.Date.Add(new TimeSpan(9, 30, 0)),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
const bool isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromSeconds(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 8:40:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:30:01 to 9:30:59 (ff)
|
|
for (var i = 1; i < 60; i++)
|
|
{
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.Date.Add(new TimeSpan(9, 30, i)), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
}
|
|
|
|
// 9:31:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.Date.Add(new TimeSpan(9, 31, 0)), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(2, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardRestOfDay()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 15, 57, 0);
|
|
var data = Enumerable.Range(0, 1).Select(x => new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(x * 2),
|
|
Value = x,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}).ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, reference.AddMinutes(3), dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:58
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 3:59 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardEndOfSubscription()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 15, 57, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, reference.AddMinutes(3), dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:58
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 3:59 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardGapBeforeEndOfSubscription()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 15, 57, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, reference.Date.AddHours(16), dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:58
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 3:39 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardToNextDay()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2015, 6, 25, 14, 0, 0);
|
|
var end = reference.Date.AddDays(1).AddHours(10);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = end - dataResolution,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
bool isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromHours(1)), isExtendedMarketHours, reference, end, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 10:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(end, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void SkipsAfterMarketData()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2015, 6, 25, 14, 0, 0);
|
|
var end = reference.Date.AddDays(1).AddHours(10);
|
|
var data = new BaseData[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddHours(3),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.Date.AddDays(1).AddHours(10) - dataResolution,
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 300
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
bool isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromHours(1)), isExtendedMarketHours, reference, end, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 6:00 - this is raw data, the FF enumerator doesn't try to perform filtering per se, just filtering on when to FF
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(4), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 10:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(end, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(2, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(300, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[TestCase(true)]
|
|
[TestCase(false)]
|
|
public void FillsForwardDailyOnHoursInMarketHours(bool strictEndTimes)
|
|
{
|
|
var symbol = strictEndTimes ? Symbols.SPX : Symbols.SPY;
|
|
var dataResolution = Time.OneDay;
|
|
var reference = new DateTime(2015, 6, 25);
|
|
var expectedBarPeriod = Time.OneDay;
|
|
if (strictEndTimes)
|
|
{
|
|
expectedBarPeriod = new TimeSpan(6, 45, 0);
|
|
reference = reference.AddHours(8.5);
|
|
}
|
|
var data = new BaseData[]
|
|
{
|
|
// thurs 6/25
|
|
new TradeBar{Value = 0, Time = reference, Period = expectedBarPeriod, Volume = 100},
|
|
// fri 6/26
|
|
new TradeBar{Value = 1, Time = reference.AddDays(1), Period = expectedBarPeriod, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType));
|
|
bool isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromHours(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, strictEndTimes);
|
|
|
|
// 12:00am // 15:15
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.Add(expectedBarPeriod), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// NEXT DAY
|
|
var fillForwardReferenceTime = reference.AddDays(1).AddHours(9);
|
|
if (strictEndTimes)
|
|
{
|
|
fillForwardReferenceTime = reference.AddDays(1).AddMinutes(30);
|
|
// 9 hrs
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
}
|
|
|
|
// 10:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 11:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 12:00pm (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(3), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 1:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(4), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 2:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(5), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 3:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(6), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
if (!strictEndTimes)
|
|
{
|
|
// 4:00 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(fillForwardReferenceTime.AddHours(7), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
}
|
|
|
|
// 12:00am
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(strictEndTimes ? reference.AddDays(1).Add(expectedBarPeriod) : reference.AddDays(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(expectedBarPeriod, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[TestCase(true)]
|
|
[TestCase(false)]
|
|
public void FillsForwardDailyMissingDays(bool strictEndTimes)
|
|
{
|
|
var symbol = strictEndTimes ? Symbols.SPX : Symbols.SPY;
|
|
var dataResolution = Time.OneDay;
|
|
var expectedBarPeriod = Time.OneDay;
|
|
var reference = new DateTime(2015, 6, 25);
|
|
if (strictEndTimes)
|
|
{
|
|
expectedBarPeriod = new TimeSpan(6, 45, 0);
|
|
reference = reference.AddHours(8.5);
|
|
}
|
|
var data = new BaseData[]
|
|
{
|
|
// thurs 6/25
|
|
new TradeBar{Value = 0, Time = reference, Period = expectedBarPeriod, Volume = 100},
|
|
// fri 6/26
|
|
new TradeBar{Value = 1, Time = reference.AddDays(5), Period = expectedBarPeriod, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType));
|
|
bool isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromDays(1)), isExtendedMarketHours,
|
|
// we add a tenth of a day to reproduce a bug where we would fill forward beyoned the expected end time
|
|
reference, data.Last().EndTime.AddDays(1.10), dataResolution, exchange.TimeZone, strictEndTimes);
|
|
|
|
var dataReferenceTime = reference.AddDays(1);
|
|
if (strictEndTimes)
|
|
{
|
|
dataReferenceTime = reference.Add(expectedBarPeriod);
|
|
}
|
|
|
|
// 6/25
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.IsTrue(((TradeBar)fillForwardEnumerator.Current).Period == expectedBarPeriod);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 6/26
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime.AddDays(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.IsTrue(((TradeBar)fillForwardEnumerator.Current).Period == expectedBarPeriod);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 6/29
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime.AddDays(4), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.IsTrue(((TradeBar)fillForwardEnumerator.Current).Period == expectedBarPeriod);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 6/30
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime.AddDays(5), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.IsTrue(((TradeBar)fillForwardEnumerator.Current).Period == expectedBarPeriod);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 7/1
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime.AddDays(6), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.IsTrue(((TradeBar)fillForwardEnumerator.Current).Period == expectedBarPeriod);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.AreEqual(!strictEndTimes, fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillForwardsDailyMissingDaysRespectingEarlyClose()
|
|
{
|
|
var symbol = Symbols.SPY;
|
|
var dataResolution = Time.OneDay;
|
|
var commonMarketDuration = new TimeSpan(6, 30, 0);
|
|
var startTimeOfDay = new TimeSpan(9, 30, 0);
|
|
var reference = new DateTime(2015, 11, 25).Add(startTimeOfDay);
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
// wed 11/25
|
|
new TradeBar {Value = 0, Time = reference, Period = commonMarketDuration, Volume = 100},
|
|
// tue 12/1
|
|
new TradeBar {Value = 1, Time = reference.AddDays(6), Period = commonMarketDuration, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType));
|
|
var isExtendedMarketHours = false;
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromDays(1)), isExtendedMarketHours,
|
|
reference, data[^1].EndTime.Date.AddDays(1), dataResolution, exchange.TimeZone, dailyStrictEndTimeEnabled: true);
|
|
|
|
var dataReferenceTime = reference;
|
|
var dataReferenceEndTime = reference.Add(commonMarketDuration);
|
|
|
|
// wed 11/25
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(dataReferenceEndTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(commonMarketDuration, ((TradeBar)fillForwardEnumerator.Current).Period);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// thu 11/26 (no data, holiday)
|
|
|
|
// fri 11/27 (early close: 1pm)
|
|
dataReferenceTime = dataReferenceTime.AddDays(2);
|
|
dataReferenceEndTime = dataReferenceEndTime.AddDays(2);
|
|
var earlyClose = dataReferenceEndTime.Date.Add(TimeSpan.FromHours(13));
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(earlyClose, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(earlyClose - dataReferenceTime, ((TradeBar)fillForwardEnumerator.Current).Period);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// mon 11/30
|
|
dataReferenceTime = dataReferenceTime.AddDays(3);
|
|
dataReferenceEndTime = dataReferenceEndTime.AddDays(3);
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(dataReferenceEndTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(commonMarketDuration, ((TradeBar)fillForwardEnumerator.Current).Period);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// tue 12/1
|
|
dataReferenceTime = dataReferenceTime.AddDays(1);
|
|
dataReferenceEndTime = dataReferenceEndTime.AddDays(1);
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(dataReferenceTime, fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(dataReferenceEndTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(commonMarketDuration, ((TradeBar)fillForwardEnumerator.Current).Period);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardHoursAtEndOfDayByHalfHour()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2015, 6, 25, 14, 0, 0);
|
|
var data = new BaseData[]
|
|
{
|
|
// thurs 6/25
|
|
new TradeBar{Value = 0, Time = reference, Period = dataResolution, Volume = 100},
|
|
// fri 6/26
|
|
new TradeBar{Value = 1, Time = reference.Date.AddDays(1), Period = dataResolution, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
bool isExtendedMarketHours = false;
|
|
var ffResolution = TimeSpan.FromMinutes(30);
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(ffResolution), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 3:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 3:30
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(1.5), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 4:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 12:00am
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data.Last().EndTime, fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardHourlyOnMinutesBeginningOfDay()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2015, 6, 25);
|
|
var data = new BaseData[]
|
|
{
|
|
// thurs 6/25
|
|
new TradeBar{Value = 0, Time = reference, Period = dataResolution, Volume = 100},
|
|
// fri 6/26
|
|
new TradeBar{Value = 1, Time = reference.Date.AddHours(9), Period = dataResolution, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
bool isExtendedMarketHours = false;
|
|
var ffResolution = TimeSpan.FromMinutes(15);
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(ffResolution), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 12:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:45 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(9.75), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 10:00
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddHours(10), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[TestCase(true)]
|
|
[TestCase(false)]
|
|
public void FillsForwardMissingDaysOnFillForwardResolutionOfAnHour(bool strictEndTimes)
|
|
{
|
|
var symbol = strictEndTimes ? Symbols.SPX : Symbols.SPY;
|
|
var reference = new DateTime(2015, 6, 23);
|
|
var dataResolution = Time.OneDay;
|
|
var expectedBarPeriod = Time.OneDay;
|
|
if (strictEndTimes)
|
|
{
|
|
expectedBarPeriod = new TimeSpan(6, 45, 0);
|
|
reference = reference.AddHours(8.5);
|
|
}
|
|
var data = new BaseData[]
|
|
{
|
|
// tues 6/23
|
|
new TradeBar{Value = 0, Time = reference, Period = expectedBarPeriod, Volume = 100},
|
|
// wed 7/1
|
|
new TradeBar{Value = 1, Time = reference.AddDays(8), Period = expectedBarPeriod, Volume = 200},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType));
|
|
bool isExtendedMarketHours = false;
|
|
var ffResolution = TimeSpan.FromHours(1);
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(ffResolution), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, strictEndTimes);
|
|
|
|
int dailyBars = 0;
|
|
int hourlyBars = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Log.Debug($"FillsForwardMissingDaysOnFillForwardResolutionOfAnHour(): {fillForwardEnumerator.Current.EndTime.ToStringInvariant()}");
|
|
var startTime = fillForwardEnumerator.Current.Time.TimeOfDay;
|
|
if (startTime == TimeSpan.Zero || startTime == new TimeSpan(8, 30, 0))
|
|
{
|
|
dailyBars++;
|
|
}
|
|
else
|
|
{
|
|
hourlyBars++;
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
}
|
|
}
|
|
|
|
// we expect 7 daily bars here, beginning tues, wed, thurs, fri, mon, tues, wed
|
|
Assert.AreEqual(7, dailyBars);
|
|
|
|
// we expect 6 days worth of ff hourly bars at 7 bars a day
|
|
Assert.AreEqual(42, hourlyBars);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void OandaFillsForwardDailyForexOnWeekends()
|
|
{
|
|
var dailyBarsEmitted = 0;
|
|
var fillForwardBars = new List<BaseData>();
|
|
|
|
// 3 QuoteBars as they would be read from the EURUSD oanda daily file by QuoteBar.Reader()
|
|
// The conversion from dataTimeZone to exchangeTimeZone has been done by hand
|
|
// dataTimeZone == UTC
|
|
/*
|
|
20120719 00:00,1.22769,1.2324,1.22286,1.22759,0,1.22781,1.23253,1.22298,1.22771,0
|
|
20120720 00:00,1.22757,1.22823,1.21435,1.21542,0,1.22769,1.22835,1.21449,1.21592,0
|
|
20120722 00:00,1.21542,1.21542,1.21037,1.21271,0,1.21592,1.21592,1.21087,1.21283,0
|
|
20120723 00:00,1.21273,1.21444,1.20669,1.21238,0,1.21285,1.21454,1.20685,1.21249,0
|
|
*/
|
|
var data = new BaseData[]
|
|
{
|
|
// fri 7/20
|
|
new QuoteBar{Value = 0, Time = new DateTime(2012, 7, 19, 20, 0, 0), Period = Time.OneDay},
|
|
// sunday 7/22
|
|
new QuoteBar{Value = 1, Time = new DateTime(2012, 7, 21, 20, 0, 0), Period = Time.OneDay},
|
|
// monday 7/23
|
|
new QuoteBar{Value = 2, Time = new DateTime(2012, 7, 22, 20, 0, 0), Period = Time.OneDay},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var market = Market.Oanda;
|
|
var symbol = Symbol.Create("EURUSD", SecurityType.Forex, market);
|
|
|
|
var marketHours = MarketHoursDatabase.FromDataFolder();
|
|
var exchange = new ForexExchange(marketHours.GetExchangeHours(market, symbol, SecurityType.Forex));
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromDays(1)), false, data[0].Time.Date, data.Last().EndTime, Time.OneDay, TimeZones.Utc, false);
|
|
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
fillForwardBars.Add(fillForwardEnumerator.Current);
|
|
Log.Debug(fillForwardEnumerator.Current.Time.DayOfWeek + " " + fillForwardEnumerator.Current.Time + " - " + fillForwardEnumerator.Current.EndTime.DayOfWeek + " " + fillForwardEnumerator.Current.EndTime);
|
|
dailyBarsEmitted++;
|
|
}
|
|
|
|
Assert.AreEqual(3, dailyBarsEmitted);
|
|
Assert.AreEqual(new DateTime(2012, 7, 19, 20, 0, 0), fillForwardBars[0].Time);
|
|
Assert.AreEqual(new DateTime(2012, 7, 21, 20, 0, 0), fillForwardBars[1].Time);
|
|
Assert.AreEqual(new DateTime(2012, 7, 22, 20, 0, 0), fillForwardBars[2].Time);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void HandlesDaylightSavingTimeChange()
|
|
{
|
|
var dailyBarsEmitted = 0;
|
|
var fillForwardBars = new List<BaseData>();
|
|
|
|
// 3 QuoteBars as they would be read from the EURUSD oanda daily file by QuoteBar.Reader()
|
|
// The conversion from dataTimeZone to exchangeTimeZone has been done by hand
|
|
// dataTimeZone == UTC
|
|
/*
|
|
20180311 00:00,1.2308,1.2308,1.2308,1.2308,0,1.23096,1.23096,1.23096,1.23096,0
|
|
20180312 00:00,1.23082,1.23449,1.22898,1.23382,0,1.23097,1.23463,1.22911,1.23396,0
|
|
*/
|
|
var data = new BaseData[]
|
|
{
|
|
// Sunday 3/11
|
|
new QuoteBar{Value = 0, Time = new DateTime(2018, 3, 10, 19, 0, 0), Period = Time.OneDay},
|
|
// Monday 3/12
|
|
new QuoteBar{Value = 1, Time = new DateTime(2018, 3, 11, 20, 0, 0), Period = Time.OneDay},
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var market = Market.Oanda;
|
|
var symbol = Symbol.Create("EURUSD", SecurityType.Forex, market);
|
|
|
|
var marketHours = MarketHoursDatabase.FromDataFolder();
|
|
var exchange = new ForexExchange(marketHours.GetExchangeHours(market, symbol, SecurityType.Forex));
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromDays(1)), false, data[0].Time.Date, data.Last().EndTime, Time.OneDay, TimeZones.Utc, false);
|
|
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
fillForwardBars.Add(fillForwardEnumerator.Current);
|
|
Log.Debug(fillForwardEnumerator.Current.Time.DayOfWeek + " " + fillForwardEnumerator.Current.Time + " - " + fillForwardEnumerator.Current.EndTime.DayOfWeek + " " + fillForwardEnumerator.Current.EndTime);
|
|
dailyBarsEmitted++;
|
|
}
|
|
|
|
Assert.AreEqual(2, dailyBarsEmitted);
|
|
Assert.AreEqual(new DateTime(2018, 3, 10, 19, 0, 0), fillForwardBars[0].Time);
|
|
Assert.AreEqual(new DateTime(2018, 3, 11, 20, 0, 0), fillForwardBars[1].Time);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void HandlesDaylightSavingTimeChange_InifinteLoop()
|
|
{
|
|
var dailyBarsEmitted = 0;
|
|
var fillForwardBars = new List<BaseData>();
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
new QuoteBar{Value = 0, Time = new DateTime(2019, 10, 4, 10, 0, 0), Period = Time.OneDay},
|
|
new QuoteBar{Value = 1, Time = new DateTime(2019, 10, 8, 11, 0, 0), Period = Time.OneDay}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var algo = new AlgorithmStub();
|
|
var market = Market.Oanda;
|
|
var security = algo.AddCfd("AU200AUD", Resolution.Daily, market);
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, security.Exchange, Ref.Create(TimeSpan.FromDays(1)), false, data[0].Time.Date, data.Last().EndTime, Time.OneDay, TimeZones.Utc, false);
|
|
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
fillForwardBars.Add(fillForwardEnumerator.Current);
|
|
Log.Debug(fillForwardEnumerator.Current.Time.DayOfWeek + " " + fillForwardEnumerator.Current.Time + " - " + fillForwardEnumerator.Current.EndTime.DayOfWeek + " " + fillForwardEnumerator.Current.EndTime + " " + fillForwardEnumerator.Current.IsFillForward);
|
|
dailyBarsEmitted++;
|
|
}
|
|
|
|
Assert.AreEqual(4, dailyBarsEmitted);
|
|
Assert.AreEqual(new DateTime(2019, 10, 4, 10, 0, 0), fillForwardBars[0].Time);
|
|
Assert.AreEqual(new DateTime(2019, 10, 6, 11, 0, 0), fillForwardBars[1].Time);
|
|
Assert.AreEqual(new DateTime(2019, 10, 7, 11, 0, 0), fillForwardBars[2].Time);
|
|
Assert.AreEqual(new DateTime(2019, 10, 8, 11, 0, 0), fillForwardBars[3].Time);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardAfterMarketOpen_DataSecond_FillForwardMinute()
|
|
{
|
|
var dataResolution = Time.OneSecond;
|
|
var reference = new DateTime(2015, 6, 25, 9, 49, 59);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(4),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:49:59 -> 9:50
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddSeconds(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:50 -> 9:50:01 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddSeconds(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardOnMarketOpen_DataSecond_FillForwardMinute()
|
|
{
|
|
var dataResolution = Time.OneSecond;
|
|
var reference = new DateTime(2015, 6, 25, 9, 29, 59);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(4),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:29:59 -> 9:30
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddSeconds(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:30 -> 9:30:01 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddSeconds(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardOnMarketOpen_DataMinute_FillForwardSecond()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 9, 29, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(4),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromSeconds(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:29 -> 9:30
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:30 -> 9:30:01 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1).AddSeconds(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardOnMarketOpen_DataMinute_FillForwardMinute()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var reference = new DateTime(2015, 6, 25, 9, 29, 0);
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddMinutes(4),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, reference, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 9:29 -> 9:30
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:30 -> 9:31 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(reference.AddMinutes(2), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[TestCase(Resolution.Second, Resolution.Second)]
|
|
[TestCase(Resolution.Second, Resolution.Minute)]
|
|
[TestCase(Resolution.Minute, Resolution.Second)]
|
|
[TestCase(Resolution.Minute, Resolution.Minute)]
|
|
[TestCase(Resolution.Minute, Resolution.Daily)]
|
|
[TestCase(Resolution.Daily, Resolution.Minute)]
|
|
public void FillsForwardBarsForDifferentResolutions(Resolution resolution, Resolution anotherSymbolResolution)
|
|
{
|
|
FillForwardTestAlgorithm.FillForwardBars.Clear();
|
|
|
|
FillForwardTestAlgorithm.Resolution = resolution;
|
|
FillForwardTestAlgorithm.ResolutionAnotherSymbol = anotherSymbolResolution;
|
|
|
|
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(nameof(FillForwardTestAlgorithm),
|
|
new Dictionary<string, string>(),
|
|
Language.CSharp,
|
|
AlgorithmStatus.Completed);
|
|
|
|
AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
|
|
parameter.Statistics,
|
|
parameter.Language,
|
|
parameter.ExpectedFinalStatus,
|
|
setupHandler: "FillForwardTestSetupHandler");
|
|
|
|
var expectedDataFile = $"ff_{resolution}_{anotherSymbolResolution}.txt";
|
|
|
|
// updates expected data
|
|
if (false)
|
|
{
|
|
#pragma warning disable CS0162 // Unreachable code detected; used to store expected data
|
|
QuantConnect.Compression.ZipCreateAppendData(
|
|
"../../TestData/FillForwardBars.zip", expectedDataFile, FillForwardTestAlgorithm.Result.Value, overrideEntry: true);
|
|
#pragma warning restore CS0162
|
|
}
|
|
QuantConnect.Compression.Unzip("TestData/FillForwardBars.zip", "./", overwrite: true);
|
|
var expected = File.ReadAllLines(expectedDataFile);
|
|
|
|
Assert.AreEqual(expected.Length, FillForwardTestAlgorithm.FillForwardBars.Count);
|
|
Assert.IsTrue(expected.SequenceEqual(FillForwardTestAlgorithm.FillForwardBars));
|
|
}
|
|
|
|
private static TestCaseData[] SubscriptionStarts => new[] {
|
|
new TestCaseData(new DateTime(2011, 1, 21, 0, 0, 0), new ForexExchange()), // no move
|
|
new TestCaseData(new DateTime(2011, 3, 11, 0, 0, 0), new ForexExchange()), // move to EDT
|
|
new TestCaseData(new DateTime(2011, 7, 8, 0, 0, 0), new ForexExchange()), // no move
|
|
new TestCaseData(new DateTime(2011, 11, 4, 0, 0, 0), new ForexExchange()), // move to EST
|
|
|
|
new TestCaseData(new DateTime(2011, 1, 21, 0, 0, 0), new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex))), // no move
|
|
new TestCaseData(new DateTime(2011, 3, 11, 0, 0, 0), new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex))), // move to EDT
|
|
new TestCaseData(new DateTime(2011, 7, 8, 0, 0, 0), new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex))), // no move
|
|
new TestCaseData(new DateTime(2011, 11, 4, 0, 0, 0), new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex))), // move to EST
|
|
|
|
new TestCaseData(new DateTime(2011, 1, 21, 0, 0, 0), new EquityExchange()), // no move
|
|
new TestCaseData(new DateTime(2011, 3, 11, 0, 0, 0), new EquityExchange()), // move to EDT
|
|
new TestCaseData(new DateTime(2011, 7, 8, 0, 0, 0), new EquityExchange()), // no move
|
|
new TestCaseData(new DateTime(2011, 11, 4, 0, 0, 0), new EquityExchange()), // move to EST
|
|
};
|
|
|
|
private static IEnumerable<TestCaseData> DaylightSavingCases(int offsetInHours)
|
|
{
|
|
return SubscriptionStarts.Select(origin =>
|
|
{
|
|
var list = new List<object>(origin.Arguments) { DateTimeZone.ForOffset(Offset.FromHours(offsetInHours)) };
|
|
|
|
return new TestCaseData(list.ToArray());
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(DaylightSavingCases), new object[] { -5 })]
|
|
[TestCaseSource(nameof(DaylightSavingCases), new object[] { 0 })]
|
|
[TestCaseSource(nameof(DaylightSavingCases), new object[] { -3 })]
|
|
public void FillsForwardDaylightSavingTime(DateTime reference, SecurityExchange exchange, DateTimeZone dataTimeZone)
|
|
{
|
|
var dataResolution = Time.OneDay;
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(2).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(3).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[1].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[2].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test, TestCaseSource(nameof(SubscriptionStarts))]
|
|
public void FillsForwardDaylightSavingTimeUtcPlus5(DateTime reference, SecurityExchange exchange)
|
|
{
|
|
var dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(+5));
|
|
var dataResolution = Time.OneDay;
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(2).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(3).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.AddTicks(2 * dataResolution.Ticks), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[1].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[2].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
private static TestCaseData[] NoMoveSubscriptionStarts => new[] {
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5))), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5))), // no move
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new EquityExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5))), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new EquityExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5))), // no move
|
|
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new ForexExchange(), DateTimeZone.Utc), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new ForexExchange(), DateTimeZone.Utc), // no move
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new EquityExchange(), DateTimeZone.Utc), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new EquityExchange(), DateTimeZone.Utc), // no move
|
|
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(+5))), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(+5))), // no move
|
|
new TestCaseData(new DateTime(2011, 7, 4, 0, 0, 0), new EquityExchange(), DateTimeZone.ForOffset(Offset.FromHours(+5))), // no move
|
|
new TestCaseData(new DateTime(2011, 1, 17, 0, 0, 0), new EquityExchange(), DateTimeZone.ForOffset(Offset.FromHours(+5))) // no move
|
|
};
|
|
|
|
[Test, TestCaseSource(nameof(NoMoveSubscriptionStarts))]
|
|
public void FillsForwardMiddleWeek(DateTime reference, SecurityExchange exchange, DateTimeZone dataTimeZone)
|
|
{
|
|
var dataResolution = Time.OneDay;
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(2).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(3).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.AddTicks(2 * dataResolution.Ticks), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[1].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[2].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardFromPreMarketWhenDaylightMove()
|
|
{
|
|
var dataResolution = Time.OneMinute;
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = new DateTime(2008, 3, 7, 16, 20, 0),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = new DateTime(2008, 3, 10, 8, 33, 0),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 200
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = new DateTime(2008, 3, 10, 9, 28, 0),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 300
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = new DateTime(2008, 3, 10, 9, 32, 0),
|
|
Value = 3,
|
|
Period = dataResolution,
|
|
Volume = 400
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var exchange = new EquityExchange();
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(TimeSpan.FromMinutes(1)), isExtendedMarketHours, data[0].Time.Date, data.Last().EndTime, dataResolution, exchange.TimeZone, false);
|
|
|
|
// 2008-03-07 16:50
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[0].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(0, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(100, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 2008-03-10 08:33 (pre-market)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[1].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(1, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(200, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 2008-03-10 09:28 (pre-market)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[2].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(2, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(300, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:30 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(new DateTime(2008, 3, 10, 9, 31, 0), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(2, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:31 (ff)
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(new DateTime(2008, 3, 10, 9, 32, 0), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(2, fillForwardEnumerator.Current.Value);
|
|
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(0, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
// 9:32
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
Assert.AreEqual(data[3].Time.Add(dataResolution), fillForwardEnumerator.Current.EndTime);
|
|
Assert.AreEqual(3, fillForwardEnumerator.Current.Value);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
|
|
Assert.AreEqual(dataResolution, fillForwardEnumerator.Current.EndTime - fillForwardEnumerator.Current.Time);
|
|
Assert.AreEqual(400, ((TradeBar)fillForwardEnumerator.Current).Volume);
|
|
|
|
Assert.IsFalse(fillForwardEnumerator.MoveNext());
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
private static TestCaseData[] ExchangeSet => new[] {
|
|
new TestCaseData(new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5)), Resolution.Minute),
|
|
new TestCaseData(new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5)), Resolution.Hour),
|
|
new TestCaseData(new ForexExchange(), DateTimeZone.ForOffset(Offset.FromHours(-5)), Resolution.Daily),
|
|
|
|
new TestCaseData(new EquityExchange(), TimeZones.NewYork, Resolution.Minute),
|
|
new TestCaseData(new EquityExchange(), TimeZones.NewYork, Resolution.Hour),
|
|
new TestCaseData(new EquityExchange(), TimeZones.NewYork, Resolution.Daily),
|
|
|
|
new TestCaseData(new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex)), DateTimeZone.Utc, Resolution.Minute),
|
|
new TestCaseData(new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex)), DateTimeZone.Utc, Resolution.Hour),
|
|
new TestCaseData(new SecurityExchange(MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.Oanda, null, SecurityType.Forex)), DateTimeZone.Utc, Resolution.Daily)
|
|
};
|
|
|
|
private static IEnumerable<TestCaseData> ExchangeSettings(string daylight, DateTime start, params object[] extra)
|
|
{
|
|
return ExchangeSet.Select(origin =>
|
|
{
|
|
var list = new List<object>(origin.Arguments)
|
|
{
|
|
daylight,
|
|
start
|
|
};
|
|
|
|
if (extra?.Any() == true)
|
|
{
|
|
list.AddRange(extra);
|
|
}
|
|
|
|
return new TestCaseData(list.ToArray());
|
|
});
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> ExchangeDaylightTimeSet(int durationInDays, Resolution fillforwardResolution)
|
|
{
|
|
return ExchangeSettings("DST", new DateTime(2011, 3, 7), durationInDays, fillforwardResolution);
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> ExchangeStandardTimeSet(int durationInDays, Resolution fillforwardResolution)
|
|
{
|
|
return ExchangeSettings("ST", new DateTime(2011, 10, 31), durationInDays, fillforwardResolution);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 6, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 7, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 14, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 6, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 7, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 14, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 7, Resolution.Minute })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 7, Resolution.Hour })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 7, Resolution.Minute })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 7, Resolution.Hour })]
|
|
public void FillsForwardBarsAroundDaylightMovementForDifferentResolutions_Enumerator(SecurityExchange exchange, DateTimeZone dataTimeZone, Resolution resolution, string dst, DateTime reference, int durationInDays, Resolution fillforwardResolution)
|
|
{
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = resolution.ToTimeSpan(),
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(durationInDays).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 1,
|
|
Period = resolution.ToTimeSpan(),
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var isExtendedMarketHours = false;
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(fillforwardResolution.ToTimeSpan()),
|
|
isExtendedMarketHours,
|
|
data[0].Time.Date,
|
|
data.Last().EndTime,
|
|
resolution.ToTimeSpan(),
|
|
dataTimeZone, false);
|
|
|
|
var ffbars = new List<string>();
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
if (fillForwardEnumerator.Current?.IsFillForward == true)
|
|
{
|
|
var bar = fillForwardEnumerator.Current;
|
|
ffbars.Add($"{bar.Time:yyyy.MM.dd H:m:s} - {bar.EndTime:yyyy.MM.dd H:m:s}");
|
|
}
|
|
}
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
|
|
var expectedDataFile = $"enum_{dst}_{durationInDays}_{exchange.TimeZone.Id.Replace("/", "_")}_{dataTimeZone.Id.Replace("/", "_")}_{resolution}_{fillforwardResolution}.txt";
|
|
|
|
// updates expected data
|
|
if (false)
|
|
{
|
|
#pragma warning disable CS0162 // Unreachable code detected; used to store expected data
|
|
QuantConnect.Compression.ZipCreateAppendData(
|
|
"../../TestData/FillForwardBars.zip",
|
|
expectedDataFile,
|
|
string.Join(Environment.NewLine, ffbars),
|
|
overrideEntry: true);
|
|
#pragma warning restore CS0162
|
|
}
|
|
QuantConnect.Compression.Unzip("TestData/FillForwardBars.zip", "./", overwrite: true);
|
|
var expected = File.ReadAllLines(expectedDataFile);
|
|
|
|
Assert.AreEqual(expected.Length, ffbars.Count);
|
|
Assert.IsTrue(expected.SequenceEqual(ffbars));
|
|
}
|
|
|
|
[TestCase(15)]
|
|
[TestCase(18)]
|
|
[TestCase(19)]
|
|
[TestCase(21)]
|
|
public void FillsForwardUntilDelisted(int warningDay)
|
|
{
|
|
var exchange = new OptionExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
DateTimeZone dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
|
|
var reference = new DateTime(2014, 6, 5)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
var dataResolution = Time.OneDay;
|
|
var expiry = new DateTime(2014, 6, warningDay)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
var delisted = new DateTime(2014, 6, 22)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
|
|
var spy = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Put, 2, expiry);
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
new BaseDataCollection(
|
|
reference,
|
|
reference.Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new BaseDataCollection(
|
|
reference.AddDays(1),
|
|
reference.AddDays(1).Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference.AddDays(1),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new Delisting(spy, expiry.Date.ConvertTo(dataTimeZone, exchange.TimeZone), 100, DelistingType.Warning),
|
|
}.ToList();
|
|
|
|
// add intermediate values between warning and delisted
|
|
int intermediateDay = (delisted.Day - expiry.Day) / 2;
|
|
if (intermediateDay > 0)
|
|
{
|
|
data.Add(new BaseDataCollection(
|
|
expiry.AddDays(intermediateDay),
|
|
expiry.AddDays(intermediateDay).Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = expiry.AddDays(intermediateDay),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}));
|
|
}
|
|
|
|
// add delisted
|
|
data.Add(new Delisting(spy, delisted, 100, DelistingType.Delisted));
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext()); // 2014.06.05
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext()); // 2014.06.06
|
|
|
|
var counter = 0;
|
|
var previous = fillForwardEnumerator.Current;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Assert.NotNull(fillForwardEnumerator.Current);
|
|
// we don't care about 'Time' because lean only uses 'EndTime', in case some auxiliary data point comes in 'Time == EndTime'
|
|
// but the enumerator output should always go increasing 'EndTime'
|
|
Assert.GreaterOrEqual(fillForwardEnumerator.Current.EndTime, previous?.EndTime);
|
|
Assert.AreEqual(
|
|
fillForwardEnumerator.Current.DataType != MarketDataType.Auxiliary,
|
|
fillForwardEnumerator.Current.IsFillForward || (intermediateDay != 0 && fillForwardEnumerator.Current.Time.Day == expiry.Day + intermediateDay));
|
|
if (fillForwardEnumerator.Current.IsFillForward)
|
|
{
|
|
Assert.AreNotEqual(MarketDataType.Auxiliary, fillForwardEnumerator.Current.DataType);
|
|
counter++;
|
|
}
|
|
else
|
|
{
|
|
Assert.True(fillForwardEnumerator.Current.DataType == MarketDataType.Auxiliary
|
|
|| fillForwardEnumerator.Current.Time == data[3].Time);
|
|
}
|
|
previous = fillForwardEnumerator.Current;
|
|
}
|
|
|
|
Assert.AreEqual(
|
|
(int)(data.Last().EndTime - data[1].EndTime).TotalDays - (intermediateDay > 0 ? 1 : 0),
|
|
counter);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardUntilDelistedMinuteResolution()
|
|
{
|
|
var exchange = new OptionExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
DateTimeZone dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
|
|
var reference = new DateTime(2014, 6, 5, 10, 10, 0)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
var dataResolution = Time.OneMinute;
|
|
var expiry = new DateTime(2014, 6, 15)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
var delisted = new DateTime(2014, 6, 22)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
|
|
var spy = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Put, 2, expiry);
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
new BaseDataCollection(
|
|
reference,
|
|
reference.Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new BaseDataCollection(
|
|
reference.AddDays(1),
|
|
reference.AddDays(1).Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference.AddDays(1),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new Delisting(spy, expiry.Date, 100, DelistingType.Warning),
|
|
new BaseDataCollection(
|
|
reference.AddDays(12),
|
|
reference.AddDays(12).Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(12),
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new Delisting(spy, delisted, 100, DelistingType.Delisted)
|
|
}.ToList();
|
|
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
// Fast forward 2014.06.05 - 06
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext());
|
|
if (fillForwardEnumerator.Current.Time.Day == 7)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
var dateSet = new HashSet<DateTime>();
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Assert.NotNull(fillForwardEnumerator.Current);
|
|
if (fillForwardEnumerator.Current.IsFillForward)
|
|
{
|
|
Assert.AreNotEqual(MarketDataType.Auxiliary, fillForwardEnumerator.Current.DataType);
|
|
dateSet.Add(fillForwardEnumerator.Current.Time.Date);
|
|
}
|
|
else
|
|
{
|
|
Assert.True(fillForwardEnumerator.Current.DataType == MarketDataType.Auxiliary
|
|
|| fillForwardEnumerator.Current.Time == data[3].Time);
|
|
}
|
|
}
|
|
|
|
// '+1' means receiving not-Auxiliary minute data on last day of period
|
|
Assert.AreEqual(
|
|
(int)(data.Last().EndTime - data[1].EndTime).TotalDays + 1,
|
|
dateSet.Count);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardSymbolChangedDailyResolution()
|
|
{
|
|
var symbol = Symbols.Fut_SPY_Mar19_2016;
|
|
var entry = MarketHoursDatabase.FromDataFolder().GetEntry(symbol.ID.Market, symbol, symbol.SecurityType);
|
|
var reference = new DateTime(2014, 6, 5, 20, 0, 0);
|
|
var dataResolution = Time.OneDay;
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
new TradeBar {
|
|
Time = reference,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100,
|
|
Symbol = symbol
|
|
}, new TradeBar {
|
|
Time = reference.AddDays(1),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 200,
|
|
Symbol = symbol
|
|
},
|
|
new SymbolChangedEvent(symbol, reference.AddDays(3).Date, symbol.Value, symbol.Value),
|
|
new TradeBar {
|
|
Time = reference.AddDays(2),
|
|
Value = 3,
|
|
Period = dataResolution,
|
|
Volume = 300,
|
|
Symbol = symbol
|
|
}}.ToList();
|
|
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(enumerator,
|
|
new FutureExchange(entry.ExchangeHours),
|
|
Ref.Create(dataResolution),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
entry.DataTimeZone, false);
|
|
|
|
BaseData previous = null;
|
|
var counter = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Assert.NotNull(fillForwardEnumerator.Current);
|
|
// we don't care about 'Time' because lean only uses 'EndTime', in case some auxiliary data point comes in 'Time == EndTime'
|
|
// but the enumerator output should always go increasing 'EndTime'
|
|
if (previous != null)
|
|
{
|
|
Assert.GreaterOrEqual(fillForwardEnumerator.Current.EndTime, previous?.EndTime);
|
|
}
|
|
if (fillForwardEnumerator.Current.IsFillForward)
|
|
{
|
|
Assert.AreNotEqual(MarketDataType.Auxiliary, fillForwardEnumerator.Current.DataType);
|
|
counter++;
|
|
}
|
|
previous = fillForwardEnumerator.Current;
|
|
}
|
|
|
|
Assert.AreEqual((int)(data.Last().EndTime - data[1].EndTime).TotalDays - 1, counter);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 6, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 7, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeDaylightTimeSet), new object[] { 14, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 6, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 7, Resolution.Daily })]
|
|
[TestCaseSource(nameof(ExchangeStandardTimeSet), new object[] { 14, Resolution.Daily })]
|
|
public void FillsForwardBarsAroundDaylightMovementForDifferentResolutions_Algorithm(SecurityExchange exchange, DateTimeZone dataTimeZone, Resolution resolution, string dst, DateTime reference, int durationInDays, Resolution ffResolution)
|
|
{
|
|
MarketHoursDatabase MarketHours = MarketHoursDatabase.FromDataFolder();
|
|
MarketHours.SetEntry(
|
|
Market.FXCM,
|
|
"EURUSD",
|
|
SecurityType.Forex,
|
|
exchange.Hours,
|
|
dataTimeZone);
|
|
FillForwardDaylightMovementTestAlgorithm.FillForwardBars.Clear();
|
|
FillForwardDaylightMovementTestAlgorithm.Resolution = resolution;
|
|
FillForwardDaylightMovementTestAlgorithm.RefDateTime = reference;
|
|
FillForwardDaylightMovementTestAlgorithm.DurationInDays = durationInDays;
|
|
|
|
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(nameof(FillForwardDaylightMovementTestAlgorithm),
|
|
new Dictionary<string, string>(),
|
|
Language.CSharp,
|
|
AlgorithmStatus.Completed);
|
|
|
|
AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
|
|
parameter.Statistics,
|
|
parameter.Language,
|
|
parameter.ExpectedFinalStatus,
|
|
setupHandler: "FillForwardDaylightMovementTestSetupHandler");
|
|
|
|
var expectedDataFile = $"alg_{dst}_{durationInDays}_{exchange.TimeZone.Id.Replace("/", "_")}_{dataTimeZone.Id.Replace("/", "_")}_{resolution}.txt";
|
|
|
|
// updates expected data
|
|
if (false)
|
|
{
|
|
#pragma warning disable CS0162 // Unreachable code detected; used to store expected data
|
|
QuantConnect.Compression.ZipCreateAppendData(
|
|
"../../TestData/FillForwardBars.zip",
|
|
expectedDataFile,
|
|
string.Join(Environment.NewLine, FillForwardDaylightMovementTestAlgorithm.Result.Value),
|
|
overrideEntry: true);
|
|
#pragma warning restore CS0162
|
|
}
|
|
QuantConnect.Compression.Unzip("TestData/FillForwardBars.zip", "./", overwrite: true);
|
|
var expected = File.ReadAllLines(expectedDataFile);
|
|
|
|
Assert.AreEqual(expected.Length, FillForwardDaylightMovementTestAlgorithm.FillForwardBars.Count);
|
|
Assert.IsTrue(expected.SequenceEqual(FillForwardDaylightMovementTestAlgorithm.FillForwardBars));
|
|
}
|
|
|
|
[Test]
|
|
public void SkipFF2AMOfSundayDST()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2011, 3, 12);
|
|
var dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
|
|
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(2).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(dataResolution),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
int count = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
if (fillForwardEnumerator.Current?.IsFillForward == true)
|
|
{
|
|
if (fillForwardEnumerator.Current.Time.DayOfWeek == DayOfWeek.Sunday &&
|
|
fillForwardEnumerator.Current.Time.Hour == 2)
|
|
{
|
|
Assert.Fail("Shouldn't fill forward bar of 1AM of Sunday when changed Daylight Saving Time.");
|
|
}
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
Assert.Greater(count, 0);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForward2AMOfSundayST()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var reference = new DateTime(2011, 11, 5);
|
|
var dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
|
|
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
|
|
var data = new[]
|
|
{
|
|
new TradeBar
|
|
{
|
|
Time = reference.ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 0,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
},
|
|
new TradeBar
|
|
{
|
|
Time = reference.AddDays(2).ConvertTo(dataTimeZone, exchange.TimeZone),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}.ToList();
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(dataResolution),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
int count = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
if (fillForwardEnumerator.Current?.IsFillForward == true)
|
|
{
|
|
if (fillForwardEnumerator.Current.Time.DayOfWeek == DayOfWeek.Sunday &&
|
|
fillForwardEnumerator.Current.Time.Hour == 2)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert.AreEqual(1, count);
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillsForwardNotDelistingAuxiliary()
|
|
{
|
|
var exchange = new OptionExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
DateTimeZone dataTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
|
|
var reference = new DateTime(2014, 6, 5)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
var dataResolution = Time.OneDay;
|
|
var expiry = new DateTime(2014, 6, 15)
|
|
.ConvertTo(dataTimeZone, exchange.TimeZone);
|
|
|
|
var spy = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Put, 2, expiry);
|
|
|
|
var data = new BaseData[]
|
|
{
|
|
new BaseDataCollection(
|
|
reference,
|
|
reference.Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference,
|
|
Value = 1,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new BaseDataCollection(
|
|
reference.AddDays(1),
|
|
reference.AddDays(1).Add(dataResolution),
|
|
spy,
|
|
new List<BaseData>{new TradeBar
|
|
{
|
|
Time = reference.AddDays(1),
|
|
Value = 2,
|
|
Period = dataResolution,
|
|
Volume = 100
|
|
}
|
|
}),
|
|
new Dividend
|
|
{
|
|
DataType = MarketDataType.Auxiliary,
|
|
Distribution = 0.5m,
|
|
ReferencePrice = decimal.MaxValue - 10000m,
|
|
|
|
Symbol = spy,
|
|
Time = reference.AddDays(5),
|
|
Value = 0.5m
|
|
}
|
|
}.ToList();
|
|
|
|
var enumerator = data.GetEnumerator();
|
|
|
|
var fillForwardEnumerator = new FillForwardEnumerator(
|
|
enumerator,
|
|
exchange,
|
|
Ref.Create(TimeSpan.FromDays(1)),
|
|
false,
|
|
reference,
|
|
data.Last().EndTime,
|
|
dataResolution,
|
|
dataTimeZone, false);
|
|
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext()); // 2014.06.05
|
|
Assert.IsTrue(fillForwardEnumerator.MoveNext()); // 2014.06.06
|
|
|
|
var counter = 0;
|
|
var previous = fillForwardEnumerator.Current;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
Assert.NotNull(fillForwardEnumerator.Current);
|
|
// we don't care about .Time because lean only uses .EndTime
|
|
// in case some auxiliary data point comes in it will respect endtime being ascendant but it's time == endtime
|
|
Assert.GreaterOrEqual(fillForwardEnumerator.Current.EndTime, previous?.EndTime ?? DateTime.MinValue);
|
|
Assert.AreEqual(
|
|
fillForwardEnumerator.Current.DataType != MarketDataType.Auxiliary,
|
|
fillForwardEnumerator.Current.IsFillForward);
|
|
if (fillForwardEnumerator.Current.IsFillForward)
|
|
{
|
|
counter++;
|
|
}
|
|
|
|
previous = fillForwardEnumerator.Current;
|
|
}
|
|
|
|
Assert.AreEqual(
|
|
(int)(data.Last().EndTime - data[1].EndTime).TotalDays,
|
|
counter);
|
|
|
|
fillForwardEnumerator.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void FillForwardIsSkippedWhenLateOpenAtMarketEnd()
|
|
{
|
|
// Set resolution for data and fill forward to one day
|
|
var dataResolution = Time.OneDay;
|
|
var fillForwardResolution = Time.OneDay;
|
|
|
|
// Define the initial time and subscription end time
|
|
var time = new DateTime(2020, 6, 28, 8, 30, 0);
|
|
var subscriptionEndTime = time.AddDays(30);
|
|
|
|
var enumerator = new List<BaseData>
|
|
{
|
|
new TradeBar { Time = new DateTime(2020, 6, 28, 8, 30, 0), EndTime = new DateTime(2020, 6, 28, 16, 0, 0), Value = 1, Volume = 100},
|
|
new TradeBar { Time = new DateTime(2020, 7, 6, 8, 30, 0), EndTime = new DateTime(2020, 7, 6, 16, 0, 0), Value = 1, Volume = 100},
|
|
}.GetEnumerator();
|
|
|
|
// LateOpen occurs at 4:00 PM meaning the market is closed
|
|
var lateOpenTime = new DateTime(2020, 7, 3, 16, 0, 0);
|
|
var exchangeHours = CreateCustomFutureExchangeHours(new DateTime(), lateOpenTime);
|
|
var exchange = new SecurityExchange(exchangeHours);
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange, Ref.Create(fillForwardResolution), false, time.Date, subscriptionEndTime, dataResolution, exchange.TimeZone, true);
|
|
|
|
// Date to check for late open
|
|
int dataCount = 0;
|
|
|
|
// Set to store unique dates
|
|
SortedSet<DateTime> uniqueDates = new SortedSet<DateTime>();
|
|
|
|
// Iterate through the enumerator
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
var currentValue = fillForwardEnumerator.Current;
|
|
|
|
// Add unique end time to the sorted set and increment data count
|
|
uniqueDates.Add(currentValue.EndTime);
|
|
dataCount++;
|
|
|
|
// Ensure that no fill forward occurs on the late open date (5 PM)
|
|
Assert.AreNotEqual(lateOpenTime.Date, currentValue.EndTime);
|
|
Assert.IsFalse(fillForwardEnumerator.Current.EndTime > subscriptionEndTime);
|
|
}
|
|
|
|
// Ensure there are no duplicate dates in the result
|
|
Assert.AreEqual(dataCount, uniqueDates.Count);
|
|
}
|
|
|
|
[Test]
|
|
public void FillForwardsFromLastTrackedPoint()
|
|
{
|
|
var dataResolution = Time.OneHour;
|
|
var fillForwardResolution = Time.OneHour;
|
|
|
|
var subscriptionStartTime = new DateTime(2025, 6, 13);
|
|
var subscriptionEndTime = subscriptionStartTime.AddDays(1);
|
|
|
|
var firstPoint = new TradeBar { Time = subscriptionStartTime.AddHours(12), Value = 2, Period = dataResolution, Volume = 200 };
|
|
using var enumerator = new List<BaseData> { firstPoint }.GetEnumerator();
|
|
|
|
var lastDataPoint = new LastPointTracker()
|
|
{
|
|
LastDataPoint = new TradeBar { Time = subscriptionStartTime.AddHours(-12), Value = 1, Period = dataResolution, Volume = 100 },
|
|
};
|
|
|
|
var exchange = new EquityExchange();
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange,
|
|
Ref.Create(fillForwardResolution), false, subscriptionStartTime, subscriptionEndTime,
|
|
dataResolution, exchange.TimeZone, false, lastPointTracker: lastDataPoint);
|
|
|
|
var lastTradeBar = lastDataPoint.LastDataPoint as TradeBar;
|
|
var dataCount = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
dataCount++;
|
|
var current = fillForwardEnumerator.Current as TradeBar;
|
|
|
|
if (current.Time == firstPoint.Time)
|
|
{
|
|
Assert.IsFalse(current.IsFillForward);
|
|
}
|
|
else
|
|
{
|
|
Assert.IsTrue(current.IsFillForward);
|
|
|
|
var expectedOriginBar = current.Time < firstPoint.Time ? lastTradeBar : firstPoint;
|
|
Assert.AreEqual(expectedOriginBar.Value, current.Value);
|
|
}
|
|
}
|
|
|
|
Assert.AreEqual(7, dataCount);
|
|
}
|
|
|
|
[Test]
|
|
public void DoesNotEmitFillForwardBarOverlappingNextAvailableBar()
|
|
{
|
|
var dataResolution = Time.OneDay;
|
|
var fillForwardResolution = Time.OneDay;
|
|
|
|
var subscriptionStartTime = new DateTime(2024, 11, 1);
|
|
var subscriptionEndTime = subscriptionStartTime.AddDays(3);
|
|
|
|
var bar1 = new TradeBar { Time = subscriptionStartTime.AddHours(-12), Value = 1, Period = dataResolution };
|
|
var bar2 = new TradeBar { Time = subscriptionEndTime.AddHours(-12), Value = 2, Period = dataResolution };
|
|
using var enumerator = new List<BaseData> { bar1, bar2 }.GetEnumerator();
|
|
|
|
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
|
using var fillForwardEnumerator = new FillForwardEnumerator(enumerator, exchange,
|
|
Ref.Create(fillForwardResolution), false, subscriptionStartTime, subscriptionEndTime,
|
|
dataResolution, exchange.TimeZone, false);
|
|
|
|
var ffBar1 = bar1.Clone(fillForward: true);
|
|
ffBar1.Time = subscriptionStartTime;
|
|
|
|
var ffbar2 = bar1.Clone(fillForward: true);
|
|
ffbar2.Time = subscriptionStartTime.AddDays(1);
|
|
|
|
var expectedData = new List<BaseData>
|
|
{
|
|
bar1,
|
|
ffBar1,
|
|
ffbar2,
|
|
// This test reproduces GH issue #9092:
|
|
// An additional FF bar would be emitted here overlapping bar2
|
|
bar2
|
|
};
|
|
|
|
var i = 0;
|
|
while (fillForwardEnumerator.MoveNext())
|
|
{
|
|
var current = fillForwardEnumerator.Current;
|
|
var expectedCurrent = expectedData[i++];
|
|
|
|
Assert.AreEqual(expectedCurrent.Time, current.Time, $"Failed on index {i - 1}");
|
|
Assert.AreEqual(expectedCurrent.EndTime, current.EndTime, $"Failed on index {i - 1}");
|
|
Assert.AreEqual(expectedCurrent.Value, current.Value, $"Failed on index {i - 1}");
|
|
Assert.AreEqual(expectedCurrent.IsFillForward, current.IsFillForward, $"Failed on index {i - 1}");
|
|
}
|
|
|
|
Assert.AreEqual(expectedData.Count, i);
|
|
}
|
|
|
|
private static SecurityExchangeHours CreateCustomFutureExchangeHours(DateTime earlyClose, DateTime lateOpen)
|
|
{
|
|
var sunday = new LocalMarketHours(
|
|
DayOfWeek.Sunday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(17, 0, 0), new TimeSpan(25, 0, 0)) // 1.00:00:00 = 25 horas
|
|
);
|
|
|
|
var monday = new LocalMarketHours(
|
|
DayOfWeek.Monday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
|
|
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)),
|
|
new MarketHoursSegment(MarketHoursState.PostMarket, new TimeSpan(17, 0, 0), new TimeSpan(25, 0, 0))
|
|
);
|
|
|
|
var tuesday = new LocalMarketHours(
|
|
DayOfWeek.Tuesday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
|
|
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)),
|
|
new MarketHoursSegment(MarketHoursState.PostMarket, new TimeSpan(17, 0, 0), new TimeSpan(25, 0, 0))
|
|
);
|
|
|
|
var wednesday = new LocalMarketHours(
|
|
DayOfWeek.Wednesday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
|
|
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)),
|
|
new MarketHoursSegment(MarketHoursState.PostMarket, new TimeSpan(17, 0, 0), new TimeSpan(25, 0, 0))
|
|
);
|
|
|
|
var thursday = new LocalMarketHours(
|
|
DayOfWeek.Thursday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
|
|
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0)),
|
|
new MarketHoursSegment(MarketHoursState.PostMarket, new TimeSpan(17, 0, 0), new TimeSpan(25, 0, 0))
|
|
);
|
|
|
|
var friday = new LocalMarketHours(
|
|
DayOfWeek.Friday,
|
|
new MarketHoursSegment(MarketHoursState.PreMarket, new TimeSpan(0, 0, 0), new TimeSpan(8, 30, 0)),
|
|
new MarketHoursSegment(MarketHoursState.Market, new TimeSpan(8, 30, 0), new TimeSpan(16, 0, 0))
|
|
);
|
|
|
|
var saturday = LocalMarketHours.ClosedAllDay(DayOfWeek.Saturday);
|
|
|
|
var earlyCloses = new Dictionary<DateTime, TimeSpan>
|
|
{
|
|
{ earlyClose.Date, earlyClose.TimeOfDay }
|
|
};
|
|
|
|
var lateOpens = new Dictionary<DateTime, TimeSpan>
|
|
{
|
|
{ lateOpen.Date, lateOpen.TimeOfDay }
|
|
};
|
|
|
|
var holidays = new List<DateTime>
|
|
{
|
|
new DateTime(2025, 4, 18)
|
|
};
|
|
|
|
var exchangeHours = new SecurityExchangeHours(
|
|
TimeZones.Chicago,
|
|
holidays,
|
|
new[]
|
|
{
|
|
sunday,
|
|
monday,
|
|
tuesday,
|
|
wednesday,
|
|
thursday,
|
|
friday,
|
|
saturday
|
|
}.ToDictionary(x => x.DayOfWeek),
|
|
earlyCloses,
|
|
lateOpens
|
|
);
|
|
|
|
return exchangeHours;
|
|
}
|
|
|
|
public class FillForwardTestAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
|
{
|
|
protected Symbol _symbol { get; set; }
|
|
public static List<string> FillForwardBars { get; set; } = new List<string>();
|
|
public static Lazy<string> Result { get; set; }
|
|
public static Resolution Resolution { get; set; }
|
|
public static Resolution ResolutionAnotherSymbol { get; set; }
|
|
public override void Initialize()
|
|
{
|
|
SetStartDate(2013, 10, 04);
|
|
SetEndDate(2013, 10, 07);
|
|
AddEquity("SPY", ResolutionAnotherSymbol);
|
|
_symbol = AddEquity("AIG", Resolution).Symbol;
|
|
}
|
|
public override void OnData(Slice data)
|
|
{
|
|
if (data.ContainsKey(_symbol))
|
|
{
|
|
var tradeBar = data[_symbol] as TradeBar;
|
|
if (tradeBar != null && tradeBar.IsFillForward)
|
|
{
|
|
FillForwardBars.Add($"{tradeBar.EndTime:d H:m:s} {Time:d H:m:s}");
|
|
}
|
|
}
|
|
}
|
|
public override void OnEndOfAlgorithm()
|
|
{
|
|
Result = new Lazy<string>(() => string.Join(Environment.NewLine, FillForwardBars));
|
|
}
|
|
|
|
public bool CanRunLocally { get; } = true;
|
|
public List<Language> Languages { get; } = new() { Language.CSharp };
|
|
|
|
/// <summary>
|
|
/// Data Points count of all timeslices of algorithm
|
|
/// </summary>
|
|
public long DataPoints => 0;
|
|
|
|
/// </summary>
|
|
/// Data Points count of the algorithm history
|
|
/// </summary>
|
|
public int AlgorithmHistoryDataPoints => 0;
|
|
|
|
/// <summary>
|
|
/// Final status of the algorithm
|
|
/// </summary>
|
|
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
|
|
|
|
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>();
|
|
}
|
|
|
|
public class FillForwardDaylightMovementTestAlgorithm : FillForwardTestAlgorithm
|
|
{
|
|
public static DateTime RefDateTime { get; set; }
|
|
public static int DurationInDays { get; set; }
|
|
|
|
public override void Initialize()
|
|
{
|
|
SetStartDate(RefDateTime);
|
|
SetEndDate(RefDateTime.AddDays(DurationInDays));
|
|
_symbol = AddForex("EURUSD", Resolution, market: Market.FXCM).Symbol;
|
|
}
|
|
|
|
public override void OnData(Slice data)
|
|
{
|
|
if (data.ContainsKey(_symbol))
|
|
{
|
|
var bar = data[_symbol] as QuoteBar;
|
|
if (bar != null && bar.IsFillForward)
|
|
{
|
|
FillForwardBars.Add($"{bar.Time:yyyy.MM.dd H:m:s} - {bar.EndTime:yyyy.MM.dd H:m:s}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FillForwardTestSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
|
|
{
|
|
internal static FillForwardTestAlgorithm TestAlgorithm { get; set; }
|
|
|
|
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
|
|
{
|
|
Algorithm = TestAlgorithm = new FillForwardTestAlgorithm();
|
|
return Algorithm;
|
|
}
|
|
}
|
|
|
|
public class FillForwardDaylightMovementTestSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
|
|
{
|
|
internal static FillForwardTestAlgorithm TestAlgorithm { get; set; }
|
|
|
|
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
|
|
{
|
|
Algorithm = TestAlgorithm = new FillForwardDaylightMovementTestAlgorithm();
|
|
return Algorithm;
|
|
}
|
|
}
|
|
}
|
|
}
|