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

4428 lines
187 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Moq;
using NodaTime;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Lean.Engine.TransactionHandlers;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Tests.Common.Securities;
using QuantConnect.Util;
using static QuantConnect.Tests.Engine.DataFeeds.Enumerators.LiveSubscriptionEnumeratorTests;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class LiveTradingDataFeedTests
{
private static bool LogsEnabled = false; // this is for travis log not to fill up and reach the max size.
private ManualTimeProvider _manualTimeProvider;
private AlgorithmStub _algorithm;
private TestableLiveSynchronizer _synchronizer;
private DateTime _startDate;
private TestableLiveTradingDataFeed _feed;
private DataManager _dataManager;
private FuncDataQueueHandler _dataQueueHandler;
private static readonly Dictionary<Type, BaseData> _instances = new Dictionary<Type, BaseData>
{
{typeof(BaseData), typeof(TradeBar).GetBaseDataInstance() },
{typeof(IndexedLinkedData), typeof(IndexedLinkedData).GetBaseDataInstance() },
{typeof(IndexedLinkedData2), typeof(IndexedLinkedData2).GetBaseDataInstance() },
};
[SetUp]
public void SetUp()
{
_manualTimeProvider = new ManualTimeProvider();
_algorithm = new AlgorithmStub(false);
_startDate = new DateTime(2018, 08, 1, 11, 0, 0);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
CustomMockedFileBaseData.StartDate = _startDate;
Interlocked.Exchange(ref TestCustomData.ReaderCallsCount, 0);
TestCustomData.ReturnNull = false;
TestCustomData.ThrowException = false;
FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProviderTrue(), false);
}
[TearDown]
public void TearDown()
{
_dataManager?.RemoveAllSubscriptions();
_feed?.Exit();
_synchronizer.DisposeSafely();
_dataQueueHandler?.DisposeSafely();
}
[TestCase(false)]
[TestCase(true)]
public void EmitsStreamedDailyData(bool strictEndTimes)
{
_startDate = new DateTime(2014, 3, 27, 14, 0, 0);
_algorithm.SetStartDate(_startDate);
_manualTimeProvider.SetCurrentTimeUtc(_startDate.ConvertToUtc(TimeZones.NewYork));
var symbol = Symbols.SPY;
_algorithm.Settings.DailyPreciseEndTime = strictEndTimes;
_algorithm.SetBenchmark(x => 1);
var dqh = new TestDataQueueHandler
{
DataPerSymbol = new Dictionary<Symbol, List<BaseData>>
{
{
symbol, new List<BaseData> { new TradeBar(_algorithm.StartDate, symbol, 1, 5, 1, 3, 100, Time.OneDay) }
}
}
};
var feed = RunDataFeed(Resolution.Daily, dataQueueHandler: dqh, equities: new() { "SPY" });
_algorithm.OnEndOfTimeStep();
DateTime emittedDataTime = default;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.HasData)
{
var data = ts.Slice[Symbols.SPY];
if (data == null)
{
return;
}
emittedDataTime = _algorithm.Time;
// short cut
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, endDate: _startDate.AddDays(1),
secondsTimeStep: 60 * 10);
Assert.AreEqual(strictEndTimes ? _startDate.Date.AddHours(16) : _startDate.Date.AddDays(1), emittedDataTime);
}
[TestCase(false, true)]
[TestCase(true, true)]
[TestCase(false, false)]
[TestCase(true, false)]
public void EmitsLeanAggregatedDailyData(bool strictEndTimes, bool warmup)
{
_startDate = new DateTime(2014, 3, 27, 10, 0, 0);
_algorithm.Settings.DailyPreciseEndTime = strictEndTimes;
_algorithm.SetStartDate(_startDate);
_manualTimeProvider.SetCurrentTimeUtc(_startDate.ConvertToUtc(TimeZones.NewYork));
var endDate = _startDate.AddDays(1);
_algorithm.SetBenchmark(x => 1);
if (warmup)
{
_algorithm.SetWarmUp(TimeSpan.FromDays(2));
}
var feed = RunDataFeed();
_algorithm.AddEquity("SPY", Resolution.Daily);
_algorithm.OnEndOfTimeStep();
List<DateTime> emittedDataTime = new();
List<BaseData> emittedData = new();
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.HasData)
{
var data = ts.Slice[Symbols.SPY];
if (data == null)
{
return;
}
emittedDataTime.Add(_algorithm.Time);
emittedData.Add(data);
if (warmup && emittedData.Count == 3 || !warmup && emittedData.Count == 1)
{
// short cut
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, endDate: endDate,
secondsTimeStep: 60 * 60);
for (var i = 0; i < emittedDataTime.Count; i++)
{
Assert.AreEqual(emittedDataTime[i], emittedData[i].EndTime);
}
if (warmup)
{
Assert.AreEqual(3, emittedData.Count);
Assert.AreEqual(strictEndTimes ? _startDate.Date.AddDays(-2).AddHours(16) : _startDate.Date.AddDays(-1), emittedData[0].EndTime);
Assert.AreEqual(strictEndTimes ? _startDate.Date.AddDays(-1).AddHours(16) : _startDate.Date, emittedData[1].EndTime);
}
else
{
Assert.AreEqual(1, emittedData.Count);
}
Assert.AreEqual(strictEndTimes ? _startDate.Date.AddHours(16) : _startDate.Date.AddDays(1), emittedData.Last().EndTime);
}
[TestCase(SecurityType.Option, Resolution.Daily, 7, true)]
[TestCase(SecurityType.Future, Resolution.Daily, 11, true)]
[TestCase(SecurityType.IndexOption, Resolution.Daily, 14, true)]
[TestCase(SecurityType.Option, Resolution.Daily, 14, true)]
[TestCase(SecurityType.Future, Resolution.Daily, 120, true)]
[TestCase(SecurityType.Option, Resolution.Daily, 7, false)]
[TestCase(SecurityType.Future, Resolution.Daily, 11, false)]
[TestCase(SecurityType.IndexOption, Resolution.Daily, 14, false)]
[TestCase(SecurityType.Option, Resolution.Hour, 7, false)]
[TestCase(SecurityType.Future, Resolution.Hour, 11, false)]
[TestCase(SecurityType.IndexOption, Resolution.Hour, 14, false)]
[TestCase(SecurityType.Option, Resolution.Minute, 7, false)]
[TestCase(SecurityType.Future, Resolution.Minute, 11, false)]
[TestCase(SecurityType.IndexOption, Resolution.Minute, 14, false)]
[TestCase(SecurityType.Option, Resolution.Second, 7, false)]
[TestCase(SecurityType.Future, Resolution.Second, 11, false)]
[TestCase(SecurityType.IndexOption, Resolution.Second, 14, false)]
[TestCase(SecurityType.Option, Resolution.Tick, 7, false)]
[TestCase(SecurityType.Future, Resolution.Tick, 11, false)]
[TestCase(SecurityType.IndexOption, Resolution.Tick, 14, false)]
[TestCase(SecurityType.Option, Resolution.Daily, 14, false)]
[TestCase(SecurityType.Future, Resolution.Daily, 120, false)]
[TestCase(SecurityType.Option, Resolution.Hour, 14, false)]
[TestCase(SecurityType.Future, Resolution.Hour, 120, false)]
[TestCase(SecurityType.Option, Resolution.Minute, 14, false)]
[TestCase(SecurityType.Future, Resolution.Minute, 120, false)]
[TestCase(SecurityType.Option, Resolution.Second, 14, false)]
[TestCase(SecurityType.Future, Resolution.Second, 120, false)]
[TestCase(SecurityType.Option, Resolution.Tick, 14, false)]
[TestCase(SecurityType.Future, Resolution.Tick, 120, false)]
public void LiveChainSelection(SecurityType securityType, Resolution resolution, int expirationDatesFilter, bool strictEndTimes)
{
_startDate = securityType == SecurityType.IndexOption ? new DateTime(2021, 1, 4) : new DateTime(2014, 6, 9);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(securityType == SecurityType.Future ? 5 : 1);
_algorithm.SetBenchmark(x => 1);
_algorithm.Settings.DailyPreciseEndTime = strictEndTimes;
var feed = RunDataFeed();
var selectionHappened = 0;
if (securityType.IsOption())
{
var chainAsset = securityType == SecurityType.Option
? _algorithm.AddOption("AAPL", resolution)
: _algorithm.AddIndexOption("SPX", resolution);
chainAsset.SetFilter(x =>
{
selectionHappened++;
var count = 0;
var symbols = x.Expiration(0, expirationDatesFilter).IncludeWeeklys().OnlyApplyFilterAtMarketOpen().Where(x => count++ < 2).ToList();
Assert.AreEqual(2, symbols.Count);
return x;
});
}
else
{
var chainAsset = _algorithm.AddFuture("ES", resolution);
chainAsset.SetFilter(x =>
{
selectionHappened++;
var symbols = x.Expiration(0, expirationDatesFilter).IncludeWeeklys().ToList();
Assert.AreEqual(expirationDatesFilter < 30 ? 1 : 2, symbols.Count);
return x;
});
}
_algorithm.OnEndOfTimeStep();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (selectionHappened == 2)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
var expectedSelections = securityType == SecurityType.Future ? 2 : 1;
Assert.AreEqual(expectedSelections, selectionHappened);
}
[Test]
public void ContinuousFuturesImmediateSelection()
{
_startDate = new DateTime(2013, 10, 7, 0, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var esSelectionTime = DateTime.MinValue;
var esFuture = _algorithm.AddFuture("ES", Resolution.Minute, extendedMarketHours: true);
esFuture.SetFilter(x =>
{
esSelectionTime = x.LocalTime.ConvertToUtc(esFuture.Exchange.TimeZone);
Assert.IsNotEmpty(x);
return x;
});
// ES future time zone is Hong kong while ES is New York, we need to assert that both selection happen right away
var hsiSelectionTime = DateTime.MinValue;
var hsiFuture = _algorithm.AddFuture("HSI", Resolution.Minute, extendedMarketHours: true);
hsiFuture.SetFilter(x =>
{
hsiSelectionTime = x.LocalTime.ConvertToUtc(hsiFuture.Exchange.TimeZone);
Assert.IsNotEmpty(x);
return x;
});
_algorithm.PostInitialize();
Assert.IsNull(esFuture.Mapped);
Assert.IsNull(hsiFuture.Mapped);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (esFuture.Mapped != null && hsiFuture.Mapped != null
&& hsiSelectionTime != DateTime.MinValue && esSelectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
// Continuous futures should select the first contract immediately
Assert.IsNotNull(esFuture.Mapped);
Assert.IsNotNull(hsiFuture.Mapped);
Assert.AreEqual(startDateUtc.Date, esSelectionTime.Date);
Assert.AreEqual(startDateUtc.Date, hsiSelectionTime.Date);
Assert.AreEqual(3, timeSliceCount);
}
[Test]
public void ETFsImmediateSelection()
{
_startDate = new DateTime(2020, 12, 1, 1, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var spy = _algorithm.AddEquity("SPY").Symbol;
var selectionTime = DateTime.MinValue;
List<Symbol> constituents = null;
var universe = _algorithm.AddUniverse(_algorithm.Universe.ETF(spy, constituentsData =>
{
selectionTime = _algorithm.UtcTime;
constituents = constituentsData.Select(x => x.Symbol).ToList();
return constituents;
}));
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreEqual(startDateUtc, selectionTime);
Assert.AreEqual(1, timeSliceCount);
Assert.IsNotNull(constituents);
Assert.IsNotEmpty(constituents);
CollectionAssert.AreEquivalent(constituents, universe.Members.Keys);
// The algorithm's security collection has all constituents and SPY (added manually)
constituents.Add(spy);
CollectionAssert.AreEquivalent(constituents, _algorithm.Securities.Keys);
}
[TestCase(true)]
[TestCase(false)]
public void FundamentalScheduleSelection(bool warmup)
{
_startDate = new DateTime(2014, 3, 27, 9, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(10);
_algorithm.SetStartDate(_startDate);
_algorithm.SetBenchmark(x => 1);
if (warmup)
{
_algorithm.SetWarmUp(TimeSpan.FromDays(2));
}
_algorithm.UniverseSettings.Schedule.On(_algorithm.DateRules.On(new DateTime(2014, 3, 24), new DateTime(2014, 3, 25),
new DateTime(2014, 3, 28), new DateTime(2014, 4, 3)));
var feed = RunDataFeed(runPostInitialize: false);
var selectionTime = DateTime.MinValue;
var selectionAlgoTime = new List<DateTime>();
var selectionDataTime = new List<DateTime>();
IEnumerable<Symbol> Filter(IEnumerable<Fundamental> fundamentals)
{
selectionAlgoTime.Add(_algorithm.Time.Date);
var dataPoint = fundamentals.Take(1);
selectionDataTime.Add(dataPoint.First().EndTime);
return dataPoint.Select(x => x.Symbol);
}
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
var universe = _algorithm.AddUniverse(Filter);
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
ConsumeBridge(feed, TimeSpan.FromSeconds(500), true, ts =>
{
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60 * 12);
Assert.AreEqual(3, selectionAlgoTime.Count, string.Join(",", selectionAlgoTime));
var index = 0;
if (warmup)
{
// warmup start time
Assert.AreEqual(new DateTime(2014, 3, 25), selectionAlgoTime[index++]);
}
else
{
// triggers right away, outside of schedule
Assert.AreEqual(new DateTime(2014, 3, 27), selectionAlgoTime[index++]);
}
Assert.AreEqual(new DateTime(2014, 3, 28), selectionAlgoTime[index++]);
Assert.AreEqual(new DateTime(2014, 4, 3), selectionAlgoTime[index++]);
}
[Test]
public void CoarseFundamentalsImmediateSelection()
{
_startDate = new DateTime(2014, 03, 26, 9, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var selectionTime = DateTime.MinValue;
List<Symbol> selectedSymbols = null;
IEnumerable<Symbol> CoarseFilter(IEnumerable<CoarseFundamental> coarse)
{
selectionTime = _algorithm.UtcTime;
selectedSymbols = coarse.Select(x => x.Symbol).ToList();
return selectedSymbols;
}
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
var universe = _algorithm.AddUniverse(CoarseFilter);
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreEqual(startDateUtc, selectionTime);
Assert.AreEqual(1, timeSliceCount);
Assert.IsNotNull(selectedSymbols);
Assert.IsNotEmpty(selectedSymbols);
CollectionAssert.AreEquivalent(selectedSymbols, universe.Members.Keys);
CollectionAssert.AreEquivalent(selectedSymbols, _algorithm.Securities.Keys);
}
[Test]
public void FutureChainsImmediateSelection()
{
_startDate = new DateTime(2014, 6, 9, 12, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var firstSelectionTimeUtc = DateTime.MinValue;
List<Symbol> selectedSymbols = null;
var future = _algorithm.AddFuture("ES");
future.SetFilter(universe =>
{
firstSelectionTimeUtc = universe.LocalTime.ConvertToUtc(future.Exchange.TimeZone);
selectedSymbols = universe.Data.Select(x => x.Symbol).ToList();
return universe;
});
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (firstSelectionTimeUtc != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreEqual(startDateUtc, firstSelectionTimeUtc);
Assert.AreEqual(1, timeSliceCount);
Assert.IsNotNull(selectedSymbols);
Assert.IsNotEmpty(selectedSymbols);
}
[TestCase(SecurityType.Option)]
[TestCase(SecurityType.IndexOption)]
public void OptionChainImmediateSelection(SecurityType securityType)
{
_startDate = securityType == SecurityType.Option
? new DateTime(2015, 12, 24, 12, 0, 0)
: new DateTime(2021, 01, 04, 12, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var firstSelectionTimeUtc = DateTime.MinValue;
List<Symbol> selectedSymbols = null;
var option = securityType == SecurityType.Option
? _algorithm.AddOption("GOOG")
: _algorithm.AddIndexOption("SPX");
option.SetFilter(universe =>
{
firstSelectionTimeUtc = universe.LocalTime.ConvertToUtc(option.Exchange.TimeZone);
selectedSymbols = (List<Symbol>)universe;
return universe;
});
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(10), true, ts =>
{
timeSliceCount++;
if (firstSelectionTimeUtc != default)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60);
Assert.AreEqual(startDateUtc, firstSelectionTimeUtc);
Assert.GreaterOrEqual(timeSliceCount, 1);
Assert.IsNotNull(selectedSymbols);
Assert.IsNotEmpty(selectedSymbols);
}
[Test]
public void CustomUniverseImmediateSelection()
{
_startDate = new DateTime(2013, 10, 07);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var selectionTime = DateTime.MinValue;
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
var universe = _algorithm.AddUniverse(SecurityType.Equity,
"my-custom-universe",
Resolution.Daily,
Market.USA,
_algorithm.UniverseSettings,
time =>
{
selectionTime = _algorithm.UtcTime;
return new[] { "SPY", "GOOG", "APPL" };
});
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
var expectedSymbols = new List<Symbol>()
{
Symbol.Create("SPY", SecurityType.Equity, Market.USA),
Symbol.Create("GOOG", SecurityType.Equity, Market.USA),
Symbol.Create("APPL", SecurityType.Equity, Market.USA)
};
Assert.AreEqual(startDateUtc, selectionTime);
Assert.AreEqual(1, timeSliceCount);
CollectionAssert.AreEquivalent(expectedSymbols, universe.Members.Keys);
CollectionAssert.AreEquivalent(expectedSymbols, _algorithm.Securities.Keys);
}
[Test]
public void CustomDataUniverseImmediateSelection()
{
_startDate = new DateTime(2014, 03, 26, 11, 0, 0);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var selectionTime = DateTime.MinValue;
List<Symbol> selectedSymbols = null;
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
var universe = _algorithm.AddUniverse<CoarseFundamental>("my-custom-coarse-universe", stockDataSource =>
{
selectionTime = _algorithm.UtcTime;
selectedSymbols = stockDataSource.Select(x => x.Symbol).ToList();
return selectedSymbols;
});
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreEqual(startDateUtc, selectionTime);
Assert.AreEqual(1, timeSliceCount);
Assert.IsNotNull(selectedSymbols);
Assert.IsNotEmpty(selectedSymbols);
CollectionAssert.AreEquivalent(selectedSymbols, universe.Members.Keys);
CollectionAssert.AreEquivalent(selectedSymbols, _algorithm.Securities.Keys);
}
[Test]
public void ConstituentsImmediateSelection()
{
_startDate = new DateTime(2013, 10, 08);
var startDateUtc = _startDate.ConvertToUtc(_algorithm.TimeZone);
_manualTimeProvider.SetCurrentTimeUtc(startDateUtc);
var endDate = _startDate.AddDays(5);
_algorithm.SetBenchmark(x => 1);
var feed = RunDataFeed(runPostInitialize: false);
var selectionTime = DateTime.MinValue;
List<Symbol> constituents = null;
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
var customUniverseSymbol = new Symbol(
SecurityIdentifier.GenerateConstituentIdentifier(
"constituents-universe-qctest",
SecurityType.Equity,
Market.USA),
"constituents-universe-qctest");
using var constituentsUniverse = new ConstituentsUniverse(customUniverseSymbol, _algorithm.UniverseSettings, x =>
{
selectionTime = _algorithm.UtcTime;
constituents = x.Select(x => x.Symbol).ToList();
return constituents;
});
var universe = _algorithm.AddUniverse(constituentsUniverse);
_algorithm.PostInitialize();
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var timeSliceCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
timeSliceCount++;
if (selectionTime != DateTime.MinValue)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreEqual(startDateUtc, selectionTime);
Assert.AreEqual(1, timeSliceCount);
Assert.IsNotNull(constituents);
Assert.IsNotEmpty(constituents);
CollectionAssert.AreEquivalent(constituents, universe.Members.Keys);
CollectionAssert.AreEquivalent(constituents, _algorithm.Securities.Keys);
}
[TestCase(false, SecurityType.Option, Resolution.Hour, false)]
[TestCase(true, SecurityType.Option, Resolution.Hour, false)]
[TestCase(false, SecurityType.IndexOption, Resolution.Hour, false)]
[TestCase(true, SecurityType.IndexOption, Resolution.Hour, false)]
[TestCase(false, SecurityType.Option, Resolution.Daily, false)]
[TestCase(true, SecurityType.Option, Resolution.Daily, false)]
[TestCase(false, SecurityType.IndexOption, Resolution.Daily, false)]
[TestCase(true, SecurityType.IndexOption, Resolution.Daily, false)]
[TestCase(false, SecurityType.Option, Resolution.Daily, true)]
[TestCase(true, SecurityType.Option, Resolution.Daily, true)]
[TestCase(false, SecurityType.IndexOption, Resolution.Daily, true)]
[TestCase(true, SecurityType.IndexOption, Resolution.Daily, true)]
public void WarmupOptionSelection(bool useWarmupResolution, SecurityType securityType, Resolution resolution, bool strictEndTimes)
{
_startDate = securityType == SecurityType.Option ? new DateTime(2014, 6, 9) : new DateTime(2021, 1, 4);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(3);
_algorithm.SetBenchmark(x => 1);
_algorithm.Settings.DailyPreciseEndTime = strictEndTimes;
if (useWarmupResolution)
{
_algorithm.SetWarmup(2, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(2));
}
_algorithm.UniverseSettings.Resolution = resolution;
var feed = RunDataFeed();
// after algorithm initialization let's set the time provider time to reflect warmup window
_manualTimeProvider.SetCurrentTimeUtc(_algorithm.UtcTime);
var es = securityType == SecurityType.Option
? _algorithm.AddOption("AAPL", resolution)
: _algorithm.AddIndexOption("SPX", resolution);
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var countWarmup = 0;
var countLive = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.UniverseData?.Count > 0)
{
Assert.IsNotNull(ts.UniverseData.Select(x => x.Value.Underlying));
Assert.IsNotEmpty(ts.UniverseData.Select(x => x.Value.FilteredContracts));
if (_algorithm.IsWarmingUp)
{
countWarmup++;
}
else
{
countLive++;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
if (_algorithm.IsWarmingUp && countWarmup == 0 || !_algorithm.IsWarmingUp && countLive == 0)
{
Thread.Sleep(50);
}
},
endDate: endDate,
secondsTimeStep: 60 * 60 * 4);
Assert.AreNotEqual(0, countWarmup);
Assert.AreNotEqual(0, countLive);
}
[Test]
public void FutureLiveHoldingsFutureMapping()
{
_startDate = new DateTime(2013, 12, 15);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(1);
_algorithm.SetBenchmark(x => 1);
_algorithm.UniverseSettings.Resolution = Resolution.Hour;
var feed = RunDataFeed();
// after algorithm initialization let's set the time provider time to reflect warmup window
_manualTimeProvider.SetCurrentTimeUtc(_algorithm.UtcTime);
var es = _algorithm.AddFuture("ES");
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var assertedHoldings = false;
var securityChanges = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(7), true, ts =>
{
if (ts.SecurityChanges != SecurityChanges.None)
{
securityChanges++;
}
// let's wait till it's remapped
if (securityChanges == 3)
{
Assert.IsNotNull(_algorithm.Securities.Total.SingleOrDefault(sec => sec.IsTradable));
Assert.AreEqual(3, _algorithm.Securities.Total.Count);
var result = LiveTradingResultHandler.GetHoldings(_algorithm.Securities.Total, _algorithm.SubscriptionManager.SubscriptionDataConfigService);
// old future mapped contract is removed
Assert.AreEqual(2, result.Count);
Assert.IsTrue(result.TryGetValue(es.Symbol.ID.ToString(), out var holding));
Assert.IsTrue(result.TryGetValue(es.Mapped.ID.ToString(), out holding));
Assert.AreEqual(0, LiveTradingResultHandler.GetHoldings(_algorithm.Securities.Total, _algorithm.SubscriptionManager.SubscriptionDataConfigService, onlyInvested: true).Count);
_algorithm.RemoveSecurity(es.Symbol);
// allow time for the exchange to pick up the selection point
Thread.Sleep(150);
}
else if (securityChanges == 4)
{
Assert.IsTrue(_algorithm.Securities.Total.All(sec => !sec.IsTradable));
Assert.AreEqual(3, _algorithm.Securities.Total.Count);
var result = LiveTradingResultHandler.GetHoldings(_algorithm.Securities.Total, _algorithm.SubscriptionManager.SubscriptionDataConfigService);
Assert.AreEqual(0, result.Count);
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
assertedHoldings = true;
}
},
endDate: _startDate.AddDays(10),
secondsTimeStep: 60 * 60 * 8);
Assert.IsTrue(assertedHoldings);
Assert.AreEqual(4, securityChanges);
}
[Test]
public void FutureLiveHoldings()
{
_startDate = new DateTime(2013, 10, 10);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(1);
_algorithm.SetBenchmark(x => 1);
_algorithm.UniverseSettings.Resolution = Resolution.Hour;
var feed = RunDataFeed();
// after algorithm initialization let's set the time provider time to reflect warmup window
_manualTimeProvider.SetCurrentTimeUtc(_algorithm.UtcTime);
var es = _algorithm.AddFuture("ES");
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var assertedHoldings = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.SecurityChanges != SecurityChanges.None)
{
Assert.IsNotNull(_algorithm.Securities.Values.SingleOrDefault(sec => sec.IsTradable));
var result = LiveTradingResultHandler.GetHoldings(_algorithm.Securities.Values, _algorithm.SubscriptionManager.SubscriptionDataConfigService);
Assert.AreEqual(2, result.Count);
Assert.IsTrue(result.TryGetValue(es.Symbol.ID.ToString(), out var holding));
Assert.IsTrue(result.TryGetValue(es.Mapped.ID.ToString(), out holding));
Assert.AreEqual(0, LiveTradingResultHandler.GetHoldings(_algorithm.Securities.Values, _algorithm.SubscriptionManager.SubscriptionDataConfigService, onlyInvested: true).Count);
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
assertedHoldings = true;
}
},
endDate: _startDate.AddDays(1),
secondsTimeStep: 60 * 60);
Assert.IsTrue(assertedHoldings);
}
[TestCase(false)]
[TestCase(true)]
public void WarmupFutureSelection(bool useWarmupResolution)
{
_startDate = new DateTime(2013, 10, 10);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(1);
_algorithm.SetBenchmark(x => 1);
if (useWarmupResolution)
{
_algorithm.SetWarmup(2, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(2));
}
_algorithm.UniverseSettings.Resolution = Resolution.Hour;
var feed = RunDataFeed();
// after algorithm initialization let's set the time provider time to reflect warmup window
_manualTimeProvider.SetCurrentTimeUtc(_algorithm.UtcTime);
var es = _algorithm.AddFuture("ES");
// allow time for the exchange to pick up the selection point
Thread.Sleep(50);
var countWarmup = 0;
var countLive = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.UniverseData?.Count > 0)
{
Assert.IsNotEmpty(ts.UniverseData.Select(x => x.Value.FilteredContracts));
if (_algorithm.IsWarmingUp)
{
countWarmup++;
}
else
{
countLive++;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
},
endDate: endDate,
secondsTimeStep: 60 * 60);
Assert.AreNotEqual(0, countWarmup);
Assert.AreNotEqual(0, countLive);
}
[TestCase(false)]
[TestCase(true)]
public void WarmupExpiredContinuousFuture(bool useWarmupResolution)
{
_startDate = new DateTime(2014, 12, 1);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(30);
var futureChainProvider = new BacktestingFutureChainProvider();
futureChainProvider.Initialize(new(TestGlobals.MapFileProvider, TestGlobals.HistoryProvider));
_algorithm.SetFutureChainProvider(futureChainProvider);
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
if (useWarmupResolution)
{
_algorithm.SetWarmup(365, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(365));
}
var feed = RunDataFeed(runPostInitialize: false);
var continuousContract = _algorithm.AddFuture(Futures.Indices.SP500EMini, Resolution.Daily,
dataNormalizationMode: DataNormalizationMode.BackwardsRatio,
dataMappingMode: DataMappingMode.LastTradingDay,
contractDepthOffset: 0
);
// the expiration of this option contract is before the start date of the algorithm but we should still get some data during warmup
continuousContract.SetFilter(0, 182);
// Post initialize after securities are added (Initialize)
_algorithm.PostInitialize();
var emittedChainData = false;
var emittedContinuousData = false;
var assertedSubscriptions = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (_algorithm.IsWarmingUp)
{
Assert.IsFalse(_dataQueueHandler.SubscriptionDataConfigs.Any(
// the data queue handler shouldn't of seen the expired subscription at any point
x => !x.Symbol.IsCanonical() && x.Symbol.SecurityType == SecurityType.Future && x.Symbol.ID.Date < _algorithm.StartDate));
if (ts.Slice.HasData)
{
emittedContinuousData |= ts.Slice.Keys.Any(s => s == continuousContract.Symbol
// let's assert that during warmup the continuous future got data of expired future
&& continuousContract.Mapped.ID.Date < _algorithm.StartDate);
emittedChainData |= ts.Slice.Keys.Any(s => !s.IsCanonical() && s.SecurityType == SecurityType.Future
// let's assert that during warmup we got chain data of expired futures
&& s.ID.Date < _algorithm.StartDate);
}
}
else
{
Assert.IsTrue(_dataQueueHandler.SubscriptionDataConfigs.Any(
// the data queue handler should of seen the Non expired subscription at any point
x => !x.Symbol.IsCanonical() && x.Symbol.SecurityType == SecurityType.Future && x.Symbol.ID.Date >= _algorithm.StartDate));
assertedSubscriptions = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
},
endDate: endDate,
secondsTimeStep: 60);
Assert.IsTrue(assertedSubscriptions);
Assert.IsTrue(emittedContinuousData);
Assert.IsTrue(emittedChainData);
}
[TestCase(false)]
[TestCase(true)]
public void WarmupExpiredOption(bool useWarmupResolution)
{
_startDate = new DateTime(2014, 6, 14);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(1);
_algorithm.SetBenchmark(x => 1);
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
if (useWarmupResolution)
{
_algorithm.SetWarmup(10, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(10));
}
var feed = RunDataFeed();
var aapl = _algorithm.AddEquity("AAPL");
// the expiration of this option contract is before the start date of the algorithm but we should still get some data during warmup
var option = Symbol.CreateOption(aapl.Symbol, Market.USA, OptionStyle.American, OptionRight.Call, 925, new DateTime(2014, 06, 13));
_algorithm.AddOptionContract(option, Resolution.Minute);
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
// the data queue handler shouldn't of seen the expired option subscription at any point
Assert.IsFalse(_dataQueueHandler.SubscriptionDataConfigs.Any(x => !x.Symbol.IsCanonical() && x.Symbol.SecurityType.IsOption()));
if (ts.Slice.HasData)
{
if (_algorithm.IsWarmingUp && ts.Slice.Keys.Any(s => s == option))
{
emittedData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
},
endDate: endDate,
secondsTimeStep: 5);
Assert.IsTrue(emittedData);
}
[TestCase(false)]
[TestCase(true)]
public void WarmupAddSecurity(bool useWarmupResolution)
{
_startDate = new DateTime(2014, 5, 8);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var endDate = _startDate.AddDays(10);
if (useWarmupResolution)
{
_algorithm.SetWarmup(1, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(1));
}
var feed = RunDataFeed(forex: new List<string> { Symbols.EURUSD.ToString() }, resolution: Resolution.Minute);
var emittedData = false;
var emittedDataDuringWarmup = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.ContainsKey(Symbols.EURUSD))
{
if (_algorithm.IsWarmingUp)
{
emittedDataDuringWarmup = true;
}
else
{
emittedData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, endDate: endDate);
Assert.IsTrue(emittedData);
Assert.IsTrue(emittedDataDuringWarmup);
}
[Test]
public void EmitsData()
{
var endDate = _startDate.AddDays(10);
var feed = RunDataFeed(forex: new List<string> { Symbols.EURUSD.ToString() });
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.HasData)
{
emittedData = true;
var data = ts.Slice[Symbols.EURUSD];
ConsoleWriteLine("HasData: " + data);
ConsoleWriteLine();
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, endDate: endDate);
Assert.IsTrue(emittedData);
}
[Test]
public void HandlesMultipleSecurities()
{
var endDate = _startDate.AddDays(10);
var equities = new List<string> { "SPY", "IBM", "AAPL", "GOOG", "MSFT", "BAC", "GS" };
var forex = new List<string> { "EURUSD", "USDJPY", "GBPJPY", "AUDUSD", "NZDUSD" };
var feed = RunDataFeed(equities: equities, forex: forex);
// allow the feed to create a data point for all
Thread.Sleep(25);
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(2), ts =>
{
var delta = (DateTime.UtcNow - ts.Time).TotalMilliseconds;
var values = ts.Slice.Keys.Select(x => x.Value).ToList();
ConsoleWriteLine(((decimal)delta).SmartRounding().ToStringInvariant() + "ms : " + string.Join(",", values));
Assert.IsTrue(equities.All(x => values.Contains(x)));
Assert.IsTrue(forex.All(x => values.Contains(x)));
emittedData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}, endDate: endDate);
Assert.IsTrue(emittedData);
}
[Test]
public void PerformanceBenchmark()
{
var symbolCount = 600;
var count = new Count();
var stopwatch = Stopwatch.StartNew();
var feed = RunDataFeed(Resolution.Tick, equities: Enumerable.Range(0, symbolCount).Select(x => "E" + x.ToStringInvariant()).ToList(),
getNextTicksFunction: fdqh => ProduceBenchmarkTicks(fdqh, count));
var securitiesCount = _algorithm.Securities.Count;
var expected = _algorithm.Securities.Keys.ToHashSet();
Log.Trace("Securities.Count: " + securitiesCount);
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
ConsoleWriteLine("Count: " + ts.Slice.Keys.Count + " " + DateTime.UtcNow.ToStringInvariant("o"));
if (ts.Slice.Keys.Count != securitiesCount)
{
var included = ts.Slice.Keys.ToHashSet();
expected.ExceptWith(included);
ConsoleWriteLine("Missing: " + string.Join(",", expected.OrderBy(x => x.Value)));
}
});
stopwatch.Stop();
Log.Trace("Total ticks: " + count.Value);
Assert.GreaterOrEqual(count.Value, 700000);
Log.Trace("Elapsed time: " + stopwatch.Elapsed);
var ticksPerSec = count.Value / stopwatch.Elapsed.TotalSeconds;
Log.Trace("Ticks/sec: " + ticksPerSec);
Assert.GreaterOrEqual(ticksPerSec, 70000);
var ticksPerSecPerSymbol = (count.Value / stopwatch.Elapsed.TotalSeconds) / symbolCount;
Log.Trace("Ticks/sec/symbol: " + ticksPerSecPerSymbol);
Assert.GreaterOrEqual(ticksPerSecPerSymbol, 100);
}
[Test]
public void DoesNotSubscribeToCustomData()
{
var endDate = _startDate.AddDays(10);
// Current implementation only sends equity/forex subscriptions to the queue handler,
// new impl sends all, the restriction shouldn't live in the feed, but rather in the
// queue handler impl
var feed = RunDataFeed(equities: new List<string> { "SPY" }, forex: new List<string> { "EURUSD" });
_algorithm.AddData<CustomMockedFileBaseData>("CustomMockedFileBaseData");
var customMockedFileBaseData = SymbolCache.GetSymbol("CustomMockedFileBaseData");
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(2), ts =>
{
ConsoleWriteLine("Count: " + ts.Slice.Keys.Count + " " + DateTime.UtcNow.ToStringInvariant("o"));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
Assert.IsFalse(_dataQueueHandler.Subscriptions.Contains(customMockedFileBaseData));
emittedData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}, endDate: endDate);
Assert.IsTrue(emittedData);
}
[Test]
public void AddsSubscription_NewUserUniverse()
{
var endDate = _startDate.AddDays(10);
var feed = RunDataFeed(equities: new List<string> { "SPY" });
var forexFxcmUserUniverse = UserDefinedUniverse.CreateSymbol(SecurityType.Forex, Market.Oanda);
var emittedData = false;
var newDataCount = 0;
var securityChanges = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
securityChanges += ts.SecurityChanges.Count;
if (!emittedData)
{
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
if (ts.Data.Count > 0)
{
Assert.IsTrue(ts.Slice.Keys.Contains(Symbols.SPY));
}
// SPY benchmark
Assert.AreEqual(1, _dataQueueHandler.Subscriptions.Count);
_algorithm.AddSecurities(forex: new List<string> { "EURUSD" });
_algorithm.OnEndOfTimeStep();
emittedData = true;
}
else
{
// SPY benchmark and EURUSD
if (_dataQueueHandler.Subscriptions.Count != 2)
{
// The custom exchange has to pick up the universe selection data point and push it into the universe subscription to
// trigger adding EURUSD in the next loop
Thread.Sleep(50);
return;
}
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
if (ts.Data.Count > 0)
{
Assert.IsTrue(ts.Slice.Keys.Contains(Symbols.SPY));
}
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD)
|| _dataQueueHandler.Subscriptions.Contains(forexFxcmUserUniverse));
// Might delay a couple of Slices to send over the data, so we will count them
// and assert a minimum amount
if (ts.Slice.Keys.Contains(Symbols.EURUSD))
{
newDataCount++;
if (newDataCount >= 5)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}
}, endDate: endDate);
Log.Trace("newDataCount: " + newDataCount);
Assert.AreEqual(2, securityChanges);
Assert.GreaterOrEqual(newDataCount, 5);
Assert.IsTrue(emittedData);
}
[Test]
public void AddsNewUniverse()
{
var endDate = _startDate.AddDays(10);
_algorithm.UniverseSettings.Resolution = Resolution.Second; // Default is Minute and we need something faster
_algorithm.UniverseSettings.ExtendedMarketHours = true; // Current _startDate is at extended market hours
var feed = RunDataFeed(forex: new List<string> { "EURUSD" });
var firstTime = false;
var securityChanges = 0;
var newDataCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
securityChanges += ts.SecurityChanges.Count;
if (!firstTime)
{
// benchmark SPY, EURUSD
Assert.AreEqual(2, _dataQueueHandler.Subscriptions.Count);
_algorithm.AddUniverse("TestUniverse", time => new List<string> { "AAPL", "SPY" });
firstTime = true;
}
else
{
if (_dataQueueHandler.Subscriptions.Count == 2)
{
Assert.AreEqual(1, _dataQueueHandler.Subscriptions.Count(x => x.Value.Contains("TESTUNIVERSE")));
}
else if (_dataQueueHandler.Subscriptions.Count == 3)
{
// Coarse universe isn't added to the data queue handler
Assert.AreNotEqual(1, _dataQueueHandler.Subscriptions.Count(x => x.Value.Contains("TESTUNIVERSE")));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.AAPL));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
// Might delay a couple of Slices to send over the data, so we will count them and assert a minimum amount
if (ts.Slice.Keys.Contains(Symbols.AAPL)
&& ts.Slice.Keys.Contains(Symbols.SPY))
{
newDataCount++;
if (newDataCount >= 5)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}
else
{
Assert.Fail($"Subscriptions.Count: {_dataQueueHandler.Subscriptions.Count}");
}
}
}, endDate: endDate);
Log.Trace("newDataCount: " + newDataCount);
Assert.AreEqual(3, securityChanges);
Assert.GreaterOrEqual(newDataCount, 5);
Assert.IsTrue(firstTime);
}
[Test]
public void AddsSubscription_SameUserUniverse()
{
var endDate = _startDate.AddDays(10);
var feed = RunDataFeed(equities: new List<string> { "SPY" });
var emittedData = false;
var newDataCount = 0;
var changes = new List<SecurityChanges>();
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.SecurityChanges != SecurityChanges.None)
{
changes.Add(ts.SecurityChanges);
return;
}
if (!emittedData)
{
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
if (ts.Data.Count > 0)
{
Assert.IsTrue(ts.Slice.Keys.Contains(Symbols.SPY));
}
// SPY benchmark
Assert.AreEqual(1, _dataQueueHandler.Subscriptions.Count);
_algorithm.AddSecurities(equities: new List<string> { "AAPL" });
_algorithm.OnEndOfTimeStep();
emittedData = true;
}
else
{
if (_dataQueueHandler.Subscriptions.Count != 2)
{
// SPY benchmark and AAPL, retry it might not being picked up yet
Thread.Sleep(50);
return;
}
// there could be some slices with no data
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
if (ts.Data.Count > 0)
{
Assert.IsTrue(ts.Slice.Keys.Contains(Symbols.SPY));
}
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.AAPL));
// Might delay a couple of Slices to send over the data, so we will count them
// and assert a minimum amount
if (ts.Slice.Keys.Contains(Symbols.AAPL))
{
newDataCount++;
if (newDataCount >= 5)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}
}, endDate: endDate);
Assert.GreaterOrEqual(newDataCount, 5);
Assert.IsTrue(emittedData);
Assert.AreEqual(2, changes.Count);
Assert.AreEqual(Symbols.SPY, changes[0].AddedSecurities.Single().Symbol);
Assert.AreEqual(Symbols.AAPL, changes[1].AddedSecurities.Single().Symbol);
}
[Test]
public void Unsubscribes()
{
var endDate = _startDate.AddDays(10);
var feed = RunDataFeed(equities: new List<string> { "SPY" }, forex: new List<string> { "EURUSD" });
_algorithm.AddData<CustomMockedFileBaseData>("CustomMockedFileBaseData");
var customMockedFileBaseData = SymbolCache.GetSymbol("CustomMockedFileBaseData");
var emittedData = false;
var currentSubscriptionCount = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), false, ts =>
{
Assert.IsFalse(_dataQueueHandler.Subscriptions.Contains(customMockedFileBaseData));
if (!emittedData)
{
currentSubscriptionCount = _dataQueueHandler.SubscriptionDataConfigs.Count;
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
var subscriptions = _dataManager.DataFeedSubscriptions
.Where(subscription => !subscription.Configuration.IsInternalFeed && subscription.Configuration.Symbol == Symbols.SPY);
foreach (var subscription in subscriptions)
{
_dataManager.RemoveSubscription(subscription.Configuration);
}
emittedData = true;
}
else
{
// should of remove trade and quote bar subscription for both (4)
Assert.AreEqual(currentSubscriptionCount - 2, _dataQueueHandler.SubscriptionDataConfigs.Count);
// internal subscription should still be there
Assert.AreEqual(0, _dataQueueHandler.SubscriptionDataConfigs
.Where(config => !config.IsInternalFeed)
.Count(config => config.Symbol == Symbols.SPY));
// Should be 1 left because of internal subscription trade hour
Assert.AreEqual(1, _dataQueueHandler.SubscriptionDataConfigs.Count(config => config.Symbol == Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, endDate: endDate);
Assert.IsTrue(emittedData);
}
[Test]
public void RemoveSecurity()
{
var endDate = _startDate.AddDays(10);
_algorithm.SetFinishedWarmingUp();
_algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
var feed = RunDataFeed(equities: new List<string> { "SPY" }, forex: new List<string> { "EURUSD" });
_algorithm.AddData<CustomMockedFileBaseData>("CustomMockedFileBaseData");
var customMockedFileBaseData = SymbolCache.GetSymbol("CustomMockedFileBaseData");
var emittedData = false;
var currentSubscriptionCount = 0;
var changes = new List<SecurityChanges>();
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.SecurityChanges != SecurityChanges.None)
{
changes.Add(ts.SecurityChanges);
}
Assert.IsFalse(_dataQueueHandler.Subscriptions.Contains(customMockedFileBaseData));
if (!emittedData)
{
currentSubscriptionCount = _dataQueueHandler.SubscriptionDataConfigs.Count;
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
_algorithm.RemoveSecurity(Symbols.SPY);
emittedData = true;
}
else
{
// should of remove trade and quote bar subscription for both (4)
Assert.AreEqual(currentSubscriptionCount - 2, _dataQueueHandler.SubscriptionDataConfigs.Count);
// internal subscription should still be there
Assert.AreEqual(0, _dataQueueHandler.SubscriptionDataConfigs
.Where(config => !config.IsInternalFeed)
.Count(config => config.Symbol == Symbols.SPY));
// Should be 1 left because of internal subscription trade hour
Assert.AreEqual(1, _dataQueueHandler.SubscriptionDataConfigs.Count(config => config.Symbol == Symbols.SPY));
Assert.IsTrue(_dataQueueHandler.Subscriptions.Contains(Symbols.EURUSD));
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, endDate: endDate);
Assert.IsTrue(emittedData);
Assert.AreEqual(4, changes.Aggregate(0, (i, securityChanges) => i + securityChanges.Count));
Assert.AreEqual(Symbols.SPY, changes[1].RemovedSecurities.Single().Symbol);
}
[Test]
public void BenchmarkTicksPerSecondWithTwentySymbols()
{
// this ran at ~25k ticks/per symbol for 20 symbols
var feed = RunDataFeed(Resolution.Tick, equities: Enumerable.Range(0, 20).Select(x => x.ToStringInvariant()).ToList());
int ticks = 0;
var averages = new List<decimal>();
var timer = new Timer(state =>
{
var avg = ticks / 20m;
Interlocked.Exchange(ref ticks, 0);
Log.Trace("Average ticks per symbol: " + avg.SmartRounding());
averages.Add(avg);
}, null, Time.OneSecond, Time.OneSecond);
ConsumeBridge(feed, TimeSpan.FromSeconds(3), false, ts =>
{
Interlocked.Add(ref ticks, ts.Slice.Ticks.Sum(x => x.Value.Count));
});
timer.Dispose();
var average = averages.Average();
Log.Trace("\r\nAverage ticks per symbol per second: " + average);
Assert.That(average, Is.GreaterThan(40));
}
[Test]
public void EmitsForexDataWithRoundedUtcTimes()
{
var feed = RunDataFeed(forex: new List<string> { "EURUSD" });
var emittedData = false;
var lastTime = DateTime.UtcNow;
ConsumeBridge(feed, TimeSpan.FromSeconds(3), ts =>
{
if (!emittedData)
{
emittedData = true;
lastTime = ts.Time;
return;
}
var delta = (DateTime.UtcNow - ts.Time).TotalMilliseconds;
Assert.AreEqual(lastTime.Add(Time.OneSecond), ts.Time);
Assert.AreEqual(1, ts.Slice.QuoteBars.Count);
lastTime = ts.Time;
});
Assert.IsTrue(emittedData);
}
[Test]
public void HandlesManyCustomDataSubscriptions()
{
var feed = RunDataFeed();
for (int i = 0; i < 100; i++)
{
_algorithm.AddData<CustomMockedFileBaseData>((100 + i).ToStringInvariant(), Resolution.Second, fillForward: false);
}
int count = 0;
var emittedData = false;
var stopwatch = Stopwatch.StartNew();
var previousTime = DateTime.Now;
Log.Trace("start: " + previousTime.ToStringInvariant("o"));
ConsumeBridge(feed, TimeSpan.FromSeconds(3), false, ts =>
{
// because this is a remote file we may skip data points while the newest
// version of the file is downloading [internet speed] and also we decide
// not to emit old data
stopwatch.Stop();
if (ts.Slice.Count == 0) return;
emittedData = true;
count++;
// make sure within 2 seconds
var delta = DateTime.Now.Subtract(previousTime);
previousTime = DateTime.Now;
Assert.IsTrue(delta <= TimeSpan.FromSeconds(2), delta.ToString());
ConsoleWriteLine($"TimeProvider now: {_manualTimeProvider.GetUtcNow().ToStringInvariant()} Count: {ts.Slice.Count}. " +
$"Delta (ms): {((decimal)delta.TotalMilliseconds).SmartRounding().ToStringInvariant()}{Environment.NewLine}"
);
});
Log.Trace("Count: " + count);
Log.Trace("Spool up time: " + stopwatch.Elapsed);
Assert.That(count, Is.GreaterThan(5));
Assert.IsTrue(emittedData);
}
[TestCase(FileFormat.Csv, true, false)]
[TestCase(FileFormat.UnfoldingCollection, true, false)]
[TestCase(FileFormat.Csv, false, false)]
[TestCase(FileFormat.UnfoldingCollection, false, false)]
[TestCase(FileFormat.Csv, false, true)]
[TestCase(FileFormat.UnfoldingCollection, false, true)]
public void RestCustomDataReturningNullDoesNotInfinitelyPoll(FileFormat fileFormat, bool returnsNull, bool throwsException)
{
TestCustomData.FileFormat = fileFormat;
var feed = RunDataFeed();
_algorithm.AddData<TestCustomData>("Pinocho", Resolution.Minute, fillForward: false);
TestCustomData.ReturnNull = returnsNull;
TestCustomData.ThrowException = throwsException;
ConsumeBridge(feed, TimeSpan.FromSeconds(2), alwaysInvoke: true, ts =>
{
// we request every 30min, so let's make sure time doesn't advance beyond 30 min, we want to test we are not requesting in a tight loop in the data stack
Thread.Sleep(100);
if (ts.DataPointCount > 0)
{
Log.Debug("Emitted data");
}
});
Assert.AreEqual(1, TestCustomData.ReaderCallsCount);
}
[Test, Ignore("These tests depend on a remote server")]
public void HandlesRestApi()
{
var resolution = Resolution.Second;
var feed = RunDataFeed();
_algorithm.AddData<RestApiBaseData>("RestApi", resolution);
var symbol = SymbolCache.GetSymbol("RestApi");
var count = 0;
var receivedData = false;
var timeZone = _algorithm.Securities[symbol].Exchange.TimeZone;
RestApiBaseData last = null;
using var cancellationTokenSource = new CancellationTokenSource();
foreach (var ts in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (!ts.Slice.ContainsKey(symbol)) return;
count++;
receivedData = true;
var data = (RestApiBaseData)ts.Slice[symbol];
var time = data.EndTime.ConvertToUtc(timeZone);
ConsoleWriteLine(DateTime.UtcNow + ": Data time: " + time.ConvertFromUtc(TimeZones.NewYork) + Environment.NewLine);
if (last != null)
{
Assert.AreEqual(last.EndTime, data.EndTime.Subtract(resolution.ToTimeSpan()));
}
last = data;
}
feed.Exit();
Assert.That(count, Is.GreaterThanOrEqualTo(8));
Assert.IsTrue(receivedData);
Assert.That(RestApiBaseData.ReaderCount, Is.LessThanOrEqualTo(30)); // we poll at 10x frequency
Log.Trace("Count: " + count + " ReaderCount: " + RestApiBaseData.ReaderCount);
}
[TestCase(DataNormalizationMode.Raw, true)]
[TestCase(DataNormalizationMode.BackwardsRatio, true)]
[TestCase(DataNormalizationMode.BackwardsPanamaCanal, true)]
[TestCase(DataNormalizationMode.ForwardPanamaCanal, true)]
[TestCase(DataNormalizationMode.Raw, false)]
[TestCase(DataNormalizationMode.BackwardsRatio, false)]
[TestCase(DataNormalizationMode.BackwardsPanamaCanal, false)]
[TestCase(DataNormalizationMode.ForwardPanamaCanal, false)]
public void LivePriceScaling(DataNormalizationMode dataNormalizationMode, bool warmup)
{
_startDate = new DateTime(2013, 10, 10);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
_algorithm.SetBenchmark(x => 1);
if (warmup)
{
_algorithm.SetWarmup(TimeSpan.FromDays(2));
}
else
{
_algorithm.SetFinishedWarmingUp();
}
var feed = RunDataFeed(runPostInitialize: false);
var security = _algorithm.AddFuture("ES",
dataNormalizationMode: dataNormalizationMode);
var symbol = security.Symbol;
_algorithm.PostInitialize();
var receivedSecurityChanges = false;
var receivedData = false;
var assertPrice = new Action<decimal>((decimal price) =>
{
ConsoleWriteLine($"assertPrice: {price} for {symbol} @{security.LocalTime}");
if (_algorithm.IsWarmingUp)
{
if (dataNormalizationMode == DataNormalizationMode.ForwardPanamaCanal && Math.Abs(price - 1760m) > 10)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.Raw && Math.Abs(price -1660m) > 10)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.BackwardsPanamaCanal && Math.Abs(price - 1510m) > 10)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.BackwardsRatio && Math.Abs(price - 1560m) > 10m)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
}
else
{
if (dataNormalizationMode == DataNormalizationMode.ForwardPanamaCanal && price < 90)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.Raw && Math.Abs(price - 2m) > 1)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.BackwardsPanamaCanal && price < -160)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
else if (dataNormalizationMode == DataNormalizationMode.BackwardsRatio && Math.Abs(price - 1.48m) > price * 0.1m)
{
throw new RegressionTestException($"unexpected price {price} for {symbol} @{security.LocalTime}");
}
}
});
var lastPrice = 0m;
ConsumeBridge(feed, TimeSpan.FromSeconds(10), ts =>
{
foreach (var addedSecurity in ts.SecurityChanges.AddedSecurities)
{
if (addedSecurity.Symbol == symbol)
{
receivedSecurityChanges = true;
}
}
if (warmup != _algorithm.IsWarmingUp)
{
return;
}
if (ts.Slice.Bars.ContainsKey(symbol))
{
receivedData = true;
assertPrice(ts.Slice.Bars[symbol].Price);
}
if (lastPrice != security.Price && security.HasData)
{
lastPrice = security.Price;
// assert realtime prices too
assertPrice(lastPrice);
}
},
alwaysInvoke: true,
secondsTimeStep: 60 * 60 * 8,
endDate: _startDate.AddDays(7));
Assert.IsTrue(receivedSecurityChanges, "Did not add symbol!");
Assert.IsTrue(receivedData, "Did not get any symbol data!");
}
[TestCase("AAPL", SecurityType.Equity)]
[TestCase("BTCUSD", SecurityType.Crypto)]
[TestCase("SPX500USD", SecurityType.Cfd)]
[TestCase("ES", SecurityType.Future)]
[TestCase("ES", SecurityType.FutureOption)]
[TestCase("AAPL", SecurityType.Option)]
public void UserDefinedUniverseSelection(string ticker, SecurityType securityType)
{
var feed = RunDataFeed();
_algorithm.SetFinishedWarmingUp();
Symbol symbol = null;
if (securityType == SecurityType.Cfd)
{
symbol = _algorithm.AddCfd(ticker).Symbol;
}
else if (securityType == SecurityType.Equity)
{
symbol = _algorithm.AddEquity(ticker).Symbol;
}
else if (securityType == SecurityType.Crypto)
{
symbol = _algorithm.AddCrypto(ticker).Symbol;
}
else if (securityType == SecurityType.Option)
{
symbol = Symbol.CreateOption(Symbols.AAPL, Symbols.AAPL.ID.Market, OptionStyle.American,
OptionRight.Call, 1, _manualTimeProvider.GetUtcNow().AddDays(20));
_algorithm.AddOptionContract(symbol);
}
else if (securityType == SecurityType.Future)
{
symbol = _algorithm.AddFuture(ticker).Symbol;
}
else if (securityType == SecurityType.FutureOption)
{
var expiration = _manualTimeProvider.GetUtcNow().AddDays(20);
symbol = Symbol.CreateFuture("ES", Market.CME, expiration);
symbol = Symbol.CreateOption(symbol, symbol.ID.Market, OptionStyle.American, OptionRight.Call, 1, expiration);
_algorithm.AddFutureOptionContract(symbol);
}
_algorithm.OnEndOfTimeStep();
var receivedSecurityChanges = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(3), ts =>
{
foreach (var addedSecurity in ts.SecurityChanges.AddedSecurities)
{
if (addedSecurity.Symbol == symbol)
{
receivedSecurityChanges = true;
// we got what we wanted, end unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
},
alwaysInvoke: true,
// need to give time for future universe selection to trigger, midnight exchange tz
secondsTimeStep: 60,
endDate: _startDate.AddDays(1));
Assert.IsTrue(receivedSecurityChanges, "Did not add symbol!");
}
[Test]
public void DelistedEventEmitted_Equity()
{
_startDate = new DateTime(2007, 05, 17);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var feed = RunDataFeed();
var symbol = _algorithm.AddEquity("AAA.1").Symbol;
_algorithm.OnEndOfTimeStep();
_algorithm.SetFinishedWarmingUp();
var receivedDelistedWarning = 0;
var receivedDelisted = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
foreach (var delistingEvent in ts.Slice.Delistings)
{
if (delistingEvent.Key != symbol)
{
throw new RegressionTestException($"Unexpected delisting for symbol {delistingEvent.Key}");
}
if (delistingEvent.Value.Type == DelistingType.Warning)
{
Interlocked.Increment(ref receivedDelistedWarning);
}
if (delistingEvent.Value.Type == DelistingType.Delisted)
{
Interlocked.Increment(ref receivedDelisted);
// we got what we wanted, end unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
},
alwaysInvoke: false,
secondsTimeStep: 3600 * 8,
endDate: _startDate.AddDays(3));
Assert.AreEqual(1, receivedDelistedWarning, $"Did not receive {DelistingType.Warning}");
Assert.AreEqual(1, receivedDelisted, $"Did not receive {DelistingType.Delisted}");
}
[Test]
public void DelistedEventEmitted()
{
_startDate = new DateTime(2016, 2, 18);
var delistingDate = Symbols.SPY_C_192_Feb19_2016.GetDelistingDate();
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var feed = RunDataFeed();
var option = _algorithm.AddOptionContract(Symbols.SPY_C_192_Feb19_2016);
_algorithm.OnEndOfTimeStep();
_algorithm.SetFinishedWarmingUp();
var receivedDelistedWarning = 0;
var receivedDelisted = 0;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
foreach (var delisting in ts.Slice.Delistings)
{
if (delisting.Key != Symbols.SPY_C_192_Feb19_2016)
{
throw new RegressionTestException($"Unexpected delisting for symbol {delisting.Key}");
}
if (delisting.Value.Type == DelistingType.Warning)
{
Interlocked.Increment(ref receivedDelistedWarning);
}
if (delisting.Value.Type == DelistingType.Delisted)
{
Interlocked.Increment(ref receivedDelisted);
// we got what we wanted, end unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
},
alwaysInvoke: false,
secondsTimeStep: 3600 * 8,
endDate: delistingDate.AddDays(2));
Assert.AreEqual(1, receivedDelistedWarning, $"Did not receive {DelistingType.Warning}");
Assert.AreEqual(1, receivedDelisted, $"Did not receive {DelistingType.Delisted}");
Assert.IsTrue(option.IsDelisted);
Assert.IsFalse(option.IsTradable);
Assert.IsFalse(_algorithm.Securities.Any(x => x.Key == option.Symbol));
}
[TestCase("20140325", typeof(CoarseFundamental))]
[TestCase("20201202", typeof(ETFConstituentUniverse))]
public void UniverseDataIsHoldUntilTimeIsRight(string dateTime, Type universeData)
{
_startDate = Time.ParseDate(dateTime);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
Log.Debug($"StartTime {_manualTimeProvider.GetUtcNow()}");
var feed = RunDataFeed(getNextTicksFunction: fdqh => Enumerable.Empty<BaseData>());
if (universeData == typeof(CoarseFundamental))
{
_algorithm.AddUniverse(coarse => coarse.Take(10).Select(x => x.Symbol));
}
else
{
_algorithm.AddUniverse(_algorithm.Universe.ETF("SPY", Market.USA, _algorithm.UniverseSettings,
constituentData => constituentData.Take(10).Select(x => x.Symbol)));
}
// will add the universe
_algorithm.OnEndOfTimeStep();
var receivedUniverseData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
if (ts.UniverseData.Count > 0 &&
ts.UniverseData.First().Value.Data.First().GetType().IsAssignableTo(universeData))
{
var now = _manualTimeProvider.GetUtcNow();
Log.Trace($"Received BaseDataCollection {now}");
// Assert data got hold until time was right
Assert.IsTrue(now.Hour < 23 && now.Hour > 5, $"Unexpected now value: {now}");
receivedUniverseData = true;
// we got what we wanted, end unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, sendUniverseData: true,
alwaysInvoke: true,
secondsTimeStep: 3600,
endDate: _startDate.AddDays(1));
Log.Debug($"EndTime {_manualTimeProvider.GetUtcNow()}");
Assert.IsTrue(receivedUniverseData, "Did not receive universe data.");
}
[Test]
public void CustomUniverseFineFundamentalDataGetsPipedCorrectly()
{
_startDate = new DateTime(2014, 10, 07, 15, 0, 0);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
// we use test ConstituentsUniverse, we have daily data for it
var customUniverseSymbol = new Symbol(
SecurityIdentifier.GenerateConstituentIdentifier(
"constituents-universe-qctest",
SecurityType.Equity,
Market.USA),
"constituents-universe-qctest");
using var customUniverse = new ConstituentsUniverse(customUniverseSymbol,
new UniverseSettings(Resolution.Daily, 1, false, true, TimeSpan.Zero));
var feed = RunDataFeed();
var fineWasCalled = false;
_algorithm.AddUniverse(customUniverse,
fine =>
{
var symbol = fine.First().Symbol;
if (symbol == Symbols.AAPL)
{
fineWasCalled = true;
}
return new[] { symbol };
});
_algorithm.OnEndOfTimeStep();
// allow time for the base exchange to pick up the universe selection point
Thread.Sleep(100);
SecurityChanges securityChanges = null;
var receivedFundamentalsData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(10), ts =>
{
if (ts.UniverseData.Count > 0 &&
ts.UniverseData.First().Value.Data.First() is Fundamental)
{
securityChanges = ts.SecurityChanges;
receivedFundamentalsData = true;
// short cut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, secondsTimeStep: 60 * 60,
alwaysInvoke: true,
sendUniverseData: true,
endDate: _startDate.AddDays(10));
Assert.IsNotNull(securityChanges);
Assert.IsTrue(securityChanges.AddedSecurities.Single().Symbol.Value == "AAPL");
Assert.IsTrue(receivedFundamentalsData);
Assert.IsTrue(fineWasCalled);
}
[TestCase(false)]
[TestCase(true)]
public void FineCoarseFundamentalDataGetsPipedCorrectlyWarmup(bool useWarmupResolution)
{
_startDate = new DateTime(2014, 3, 27);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
if (useWarmupResolution)
{
_algorithm.SetWarmup(1, Resolution.Daily);
}
else
{
_algorithm.SetWarmup(TimeSpan.FromDays(1));
}
var fineWasCalled = false;
var fineWasCalledDuringWarmup = false;
_algorithm.UniverseSettings.Resolution = Resolution.Second;
_algorithm.AddUniverse(coarse => coarse
.Where(x => x.Symbol.ID.Symbol.Contains("AAPL")).Select((fundamental, _) => fundamental.Symbol),
fine =>
{
var symbol = fine.FirstOrDefault()?.Symbol;
if (symbol == Symbols.AAPL)
{
if (_algorithm.IsWarmingUp)
{
fineWasCalledDuringWarmup = true;
}
else
{
fineWasCalled = true;
}
return new[] { symbol };
}
return Enumerable.Empty<Symbol>();
});
var feed = RunDataFeed(getNextTicksFunction: fdqh => Enumerable.Empty<BaseData>());
var receivedFundamentalsData = false;
var receivedFundamentalsDataDuringWarmup = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
if (ts.UniverseData.Count > 0 &&
ts.UniverseData.First().Value.Data.First() is Fundamental)
{
if (_algorithm.IsWarmingUp)
{
receivedFundamentalsDataDuringWarmup = true;
}
else
{
receivedFundamentalsData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, sendUniverseData: true, alwaysInvoke: true, secondsTimeStep: 3600, endDate: _startDate.AddDays(10));
Assert.IsTrue(fineWasCalledDuringWarmup);
Assert.IsTrue(fineWasCalled);
Assert.IsTrue(receivedFundamentalsData);
Assert.IsTrue(receivedFundamentalsDataDuringWarmup);
}
[TestCase("BTCUSD")]
[TestCase("ADAUSDT")]
public void MarginInterestDataGetsPipedCorrectly(string cryptoFuture)
{
_startDate = new DateTime(2022, 12, 12);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var feed = RunDataFeed(getNextTicksFunction: fdqh => Enumerable.Empty<BaseData>());
var asset = _algorithm.AddCryptoFuture(cryptoFuture);
var receivedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
var interestRates = ts.Slice.Get<MarginInterestRate>();
foreach (var interestRate in interestRates)
{
receivedData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
Assert.AreEqual(asset.Symbol, interestRate.Key);
}
}, secondsTimeStep: 60 * 60 * 3, endDate: _startDate.AddDays(2));
Assert.IsTrue(receivedData);
}
[TestCase(1)]
[TestCase(2)]
public void FineCoarseFundamentalDataGetsPipedCorrectly(int numberOfUniverses)
{
_startDate = new DateTime(2014, 3, 25);
CustomMockedFileBaseData.StartDate = _startDate;
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var feed = RunDataFeed(getNextTicksFunction: fdqh => Enumerable.Empty<BaseData>());
var fineWasCalled = new List<bool> { false, false };
for (var i = 0; i < numberOfUniverses; i++)
{
var index = i;
_algorithm.AddUniverse(coarse => coarse
.Where(x => x.Symbol.ID.Symbol.Contains("AAPL")).Select((fundamental, i) => fundamental.Symbol),
fine =>
{
var symbol = fine.First().Symbol;
if (symbol == Symbols.AAPL)
{
fineWasCalled[index] = true;
}
return new[] { symbol };
});
}
var receivedFundamentalsData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), ts =>
{
if (ts.UniverseData.Count > 0 &&
ts.UniverseData.First().Value.Data.First() is Fundamental)
{
receivedFundamentalsData = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, sendUniverseData: true, alwaysInvoke: true, secondsTimeStep: 1200, endDate: _startDate.AddDays(10));
Assert.IsTrue(receivedFundamentalsData);
for (var i = 0; i < numberOfUniverses; i++)
{
Assert.IsTrue(fineWasCalled[i]);
}
}
[TestCase(SecurityType.Future, true)]
[TestCase(SecurityType.Option, true)]
[TestCase(SecurityType.IndexOption, true)]
[TestCase(SecurityType.Future, false)]
[TestCase(SecurityType.Option, false)]
[TestCase(SecurityType.IndexOption, false)]
public void AddChainUniverseCanNotAdvanceTime(SecurityType securityType, bool strictEndTimes)
{
_algorithm.Settings.DailyPreciseEndTime = strictEndTimes;
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
_algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
// this reproduces GH issue #5245 where time can not advance and will keep it's default value
var feed = RunDataFeed(lookupSymbolsFunction: null, canPerformSelection: () => false);
if (securityType == SecurityType.Future)
{
_algorithm.AddFuture(Futures.Indices.SP500EMini);
}
else if (securityType == SecurityType.IndexOption)
{
_algorithm.AddIndexOption("SPX");
}
else
{
_algorithm.AddOption("AAPL");
}
// will add the universe
_algorithm.OnEndOfTimeStep();
ConsumeBridge(feed, TimeSpan.FromSeconds(2), ts =>
{
if (ts.UniverseData.Count > 0)
{
}
}, secondsTimeStep: 60 * 60 * 3, // 3 hour time step
alwaysInvoke: true);
Assert.AreNotEqual(AlgorithmStatus.RuntimeError, _algorithm.Status);
}
[Test]
public void ConstituentsUniverse()
{
var qqq = Symbol.Create("QQQ", SecurityType.Equity, Market.USA);
// Set a date for which we have the test data.
// Note the date is a Tuesday
_startDate = new DateTime(2013, 10, 07);
var endDate = new DateTime(2013, 10, 10);
_manualTimeProvider.SetCurrentTimeUtc(_startDate.AddHours(20));
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
_algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
var yieldedSymbols = false;
var yieldedNoneSymbol = false;
var feed = RunDataFeed();
using var constituentsUniverse = new ConstituentsUniverse(
new Symbol(
SecurityIdentifier.GenerateConstituentIdentifier(
"constituents-universe-qctest",
SecurityType.Equity,
Market.USA),
"constituents-universe-qctest"),
_algorithm.UniverseSettings);
_algorithm.AddUniverse(constituentsUniverse);
// will add the universe
_algorithm.OnEndOfTimeStep();
// allow time for the base exchange to pick up the universe selection point
Thread.Sleep(100);
ConsumeBridge(feed, TimeSpan.FromSeconds(10), ts =>
{
if (ts.UniverseData.Count > 0)
{
var data = ts.UniverseData.Values.First();
if (data.EndTime >= new DateTime(2013, 10, 09))
{
Assert.AreEqual(1, data.Data.Count);
Assert.IsTrue(data.Data.Any(baseData => baseData.Symbol == Symbol.None));
yieldedNoneSymbol = true;
}
else if (data.EndTime >= new DateTime(2013, 10, 08))
{
Assert.AreEqual(2, data.Data.Count);
Assert.IsTrue(data.Data.Any(baseData => baseData.Symbol == Symbols.AAPL));
Assert.IsTrue(data.Data.Any(baseData => baseData.Symbol == qqq));
yieldedSymbols = true;
}
if (yieldedSymbols && yieldedNoneSymbol)
{
// we got what we wanted, end unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, secondsTimeStep: 60 * 60,
alwaysInvoke: true,
endDate: endDate);
Assert.IsTrue(yieldedSymbols, "Did not yielded Symbols");
Assert.IsTrue(yieldedNoneSymbol, "Did not yield NoneSymbol");
}
[Test]
public void ThrowingDataQueueHandlerRuntimeError()
{
_algorithm.UniverseSettings.Resolution = Resolution.Daily;
_algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
var feed = RunDataFeed(dataQueueHandler: new ThrowingDataQueueHandler());
_algorithm.AddEquity("SPY");
_algorithm.OnEndOfTimeStep();
ConsumeBridge(feed, TimeSpan.FromSeconds(2), ts =>
{
if (_algorithm.Status == AlgorithmStatus.RuntimeError)
{
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, secondsTimeStep: 60 * 60 * 3);
Assert.AreEqual(AlgorithmStatus.RuntimeError, _algorithm.Status);
}
[Test]
public void FastExitsDoNotThrowUnhandledExceptions()
{
var algorithm = new AlgorithmStub();
// job is used to send into DataQueueHandler
var job = new LiveNodePacket();
// result handler is used due to dependency in SubscriptionDataReader
var resultHandler = new BacktestingResultHandler();
_feed = new TestableLiveTradingDataFeed(algorithm.Settings);
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(
algorithm.Portfolio.CashBook,
marketHoursDatabase,
symbolPropertiesDataBase,
algorithm,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(algorithm.Portfolio),
algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(_feed,
new UniverseSelection(algorithm, securityService, dataPermissionManager, TestGlobals.DataProvider),
algorithm,
algorithm.TimeKeeper,
marketHoursDatabase,
true,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
algorithm.SubscriptionManager.SetDataManager(dataManager);
_synchronizer = new TestableLiveSynchronizer();
_synchronizer.Initialize(algorithm, dataManager, new());
algorithm.AddSecurities(Resolution.Tick, Enumerable.Range(0, 20).Select(x => x.ToStringInvariant()).ToList());
var getNextTicksFunction = Enumerable.Range(0, 20).Select(x => new Tick { Symbol = SymbolCache.GetSymbol(x.ToStringInvariant()) }).ToList();
_feed.DataQueueHandler = new FuncDataQueueHandler(handler => getNextTicksFunction, new RealTimeProvider(), _algorithm.Settings);
_feed.Initialize(
algorithm,
job,
resultHandler,
TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider,
TestGlobals.DataProvider,
dataManager,
_synchronizer,
new TestDataChannelProvider());
var unhandledExceptionWasThrown = false;
try
{
_feed.Exit();
}
catch (Exception ex)
{
QuantConnect.Logging.Log.Error(ex.ToString());
unhandledExceptionWasThrown = true;
}
Thread.Sleep(500);
Assert.IsFalse(unhandledExceptionWasThrown);
}
[Test]
public void HandlesAllTickTypesAtTickResolution()
{
var symbol = Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Coinbase);
// setting func benchmark so we don't add SPY
_algorithm.SetBenchmark(time => 1);
var feed = RunDataFeed(
Resolution.Tick,
crypto: new List<string> { symbol.Value },
getNextTicksFunction: dqh => Enumerable.Range(1, 2)
.Select(x => new Tick
{
Symbol = symbol,
TickType = x % 2 == 0 ? TickType.Trade : TickType.Quote
})
.ToList());
var tradeCount = 0;
var quoteCount = 0;
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(1), true, ts =>
{
if (ts.Slice.HasData)
{
emittedData = true;
tradeCount += ts.Slice.Ticks[symbol].Count(tick => tick.TickType == TickType.Trade);
quoteCount += ts.Slice.Ticks[symbol].Count(tick => tick.TickType == TickType.Quote);
}
});
Assert.IsTrue(emittedData, "No data was emitted");
Assert.AreNotEqual(0, quoteCount);
Assert.AreNotEqual(0, tradeCount);
}
[Test]
public void SuspiciousTicksAreNotFilteredAtTickResolution()
{
var endDate = _startDate.AddDays(10);
var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
var feed = RunDataFeed(
Resolution.Tick,
equities: new List<string> { symbol.Value },
getNextTicksFunction: dqh => Enumerable.Range(0, 1)
.Select(
x => new Tick
{
Symbol = symbol,
TickType = TickType.Trade,
Suspicious = x % 2 == 0
})
.ToList());
var emittedData = false;
var suspiciousTicksReceived = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(3), true, ts =>
{
if (ts.Slice.HasData)
{
emittedData = true;
foreach (var kvp in ts.Slice.Ticks)
{
foreach (var tick in kvp.Value)
{
if (tick.Suspicious)
{
suspiciousTicksReceived = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}
}
}, endDate: endDate);
Assert.IsTrue(emittedData);
Assert.IsTrue(suspiciousTicksReceived);
}
[TestCase(SecurityType.Equity, TickType.Trade)]
[TestCase(SecurityType.Forex, TickType.Quote)]
[TestCase(SecurityType.Crypto, TickType.Trade)]
[TestCase(SecurityType.Crypto, TickType.Quote)]
public void SuspiciousTicksAreFilteredAtNonTickResolution(SecurityType securityType, TickType tickType)
{
var lastTime = _manualTimeProvider.GetUtcNow();
var feed = RunDataFeed(Resolution.Minute,
equities: securityType == SecurityType.Equity ? new List<string> { Symbols.SPY.ToString() } : new List<string>(),
forex: securityType == SecurityType.Forex ? new List<string> { Symbols.EURUSD.ToString() } : new List<string>(),
crypto: securityType == SecurityType.Crypto ? new List<string> { Symbols.BTCUSD.ToString() } : new List<string>(),
getNextTicksFunction: (fdqh =>
{
var time = _manualTimeProvider.GetUtcNow();
if (time == lastTime) return Enumerable.Empty<BaseData>();
lastTime = time;
var tickTime = lastTime.AddMinutes(-1).ConvertFromUtc(TimeZones.NewYork);
return fdqh.Subscriptions.Where(symbol => !_algorithm.UniverseManager.ContainsKey(symbol)) // its not a universe
.Select(symbol => new Tick(tickTime, symbol, 1, 2)
{
Quantity = 1,
TickType = tickType,
Suspicious = true
}).ToList();
}));
var emittedData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(2), true, ts =>
{
if (ts.Slice.HasData)
{
emittedData = true;
}
});
Assert.IsFalse(emittedData);
}
[TestCase(false)]
[TestCase(true)]
public void SkipLiveDividend(bool warmup)
{
var symbol = Symbols.AAPL;
// aapl has a dividend on the 6th
if (warmup)
{
_startDate = new DateTime(2013, 11, 09);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
_algorithm.SetWarmup(5);
}
else
{
_startDate = new DateTime(2013, 11, 05);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
}
var startPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
var feed = RunDataFeed(Resolution.Daily, equities: new List<string> { symbol.Value },
getNextTicksFunction: delegate
{
return Enumerable.Empty<BaseData>();
});
var emittedDividend = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.Dividends.ContainsKey(symbol))
{
Assert.AreEqual(warmup, _algorithm.IsWarmingUp);
emittedDividend = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}, secondsTimeStep: warmup ? 60 * 60 : 60 * 60 * 5,
endDate: _startDate.AddDays(30));
Assert.IsTrue(emittedDividend);
// we do not handle dividends in live trading, we leave it for the cash sync
Assert.AreEqual(startPortfolioValue, _algorithm.Portfolio.TotalPortfolioValue);
}
[TestCase(false)]
[TestCase(true)]
public void LiveSplitHandling(bool warmup)
{
// there's an split starting on the 7th
var symbol = Symbols.AAPL;
if (warmup)
{
_startDate = new DateTime(2014, 06, 10);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
_algorithm.SetWarmup(6);
}
else
{
_startDate = new DateTime(2014, 06, 5);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
}
var feed = RunDataFeed(Resolution.Daily, equities: new List<string> { symbol.Value },
getNextTicksFunction: delegate
{
return Enumerable.Empty<BaseData>();
});
var holdings = _algorithm.Securities[symbol].Holdings;
holdings.SetHoldings(10, quantity: 100);
var startPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
var emittedSplit = false;
var emittedSplitWarning = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.Splits.TryGetValue(symbol, out var split))
{
Assert.AreEqual(warmup, _algorithm.IsWarmingUp);
if (split.Type == SplitType.SplitOccurred)
{
emittedSplit = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
else
{
emittedSplitWarning = true;
}
}
}, secondsTimeStep: warmup ? 60 * 60 : 60 * 60 * 12,
endDate: _startDate.AddDays(30));
Assert.IsTrue(emittedSplit);
Assert.IsTrue(emittedSplitWarning);
Assert.AreEqual((double)startPortfolioValue, (double)_algorithm.Portfolio.TotalPortfolioValue, delta: (double)(0.1m * _algorithm.Portfolio.TotalPortfolioValue));
if (!warmup)
{
Assert.AreNotEqual(10, holdings.Quantity);
Assert.AreNotEqual(100, holdings.AveragePrice);
}
else
{
// during warmup they shouldn't change
Assert.AreEqual(100, holdings.Quantity);
Assert.AreEqual(10, holdings.AveragePrice);
}
}
[Test]
public void HandlesAuxiliaryDataAtTickResolution()
{
// aapl has a dividend on the 6th
_startDate = new DateTime(2013, 11, 05);
_manualTimeProvider.SetCurrentTimeUtc(_startDate);
var symbol = Symbols.AAPL;
var feed = RunDataFeed(
Resolution.Tick,
equities: new List<string> { symbol.Value },
getNextTicksFunction: delegate
{
return new[] { (BaseData)new Tick { Symbol = symbol, TickType = TickType.Trade } };
});
var emittedTicks = false;
var emittedAuxData = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(1), true, ts =>
{
if (ts.Slice.HasData)
{
if (ts.Slice.Ticks.ContainsKey(symbol))
{
emittedTicks = true;
}
if (ts.Slice.Dividends.ContainsKey(symbol))
{
emittedAuxData = true;
}
if (emittedAuxData && emittedTicks)
{
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, secondsTimeStep: 60 * 60 * 4,
endDate: _startDate.AddDays(2));
Assert.IsTrue(emittedTicks);
Assert.IsTrue(emittedAuxData);
}
[Test]
public void AggregatesTicksToTradeBar()
{
var symbol = Symbols.AAPL;
var feed = RunDataFeed(Resolution.Second, equities: new List<string> { symbol.Value });
var emittedTradeBars = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(3), true, ts =>
{
if (ts.Slice.HasData)
{
if (ts.Slice.Bars.ContainsKey(symbol))
{
emittedTradeBars = true;
// we got what we wanted shortcut unit test
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}, endDate: _startDate.AddDays(1));
Assert.IsTrue(emittedTradeBars);
}
[Test]
public void DoesNotAggregateTicksToTradeBar()
{
var symbol = Symbols.AAPL;
var feed = RunDataFeed(
Resolution.Tick,
equities: new List<string> { symbol.Value },
getNextTicksFunction: delegate
{
return Enumerable.Range(0, 2)
.Select(_ => (BaseData)new Tick { Symbol = symbol, TickType = TickType.Trade })
.ToList();
});
var emittedTradebars = false;
ConsumeBridge(feed, TimeSpan.FromSeconds(1), true, ts =>
{
if (ts.Slice.HasData)
{
if (ts.Slice.Bars.ContainsKey(symbol))
{
emittedTradebars = true;
}
}
});
Assert.IsFalse(emittedTradebars);
}
[Test]
public void FillForwardsWarmUpDataToLiveFeed(
[Values(Resolution.Minute, Resolution.Daily)] Resolution warmupResolution,
[Values] bool fromHistoryProviderWarmUp,
[Values] bool withLiveDataPoint)
{
var symbol = Symbols.SPY;
TradeBar lastHistoryWarmUpBar = null;
if (fromHistoryProviderWarmUp)
{
_startDate = new DateTime(2025, 06, 12);
var historyBarTime = warmupResolution == Resolution.Minute ? _startDate.AddHours(-12) : _startDate.AddDays(-2);
lastHistoryWarmUpBar = new TradeBar(historyBarTime, symbol, 1, 1, 1, 1, 100, warmupResolution.ToTimeSpan());
var historyProvider = new Mock<IHistoryProvider>();
historyProvider
.Setup(m => m.GetHistory(It.IsAny<IEnumerable<Data.HistoryRequest>>(), It.IsAny<DateTimeZone>()))
.Returns(new List<Slice>
{
new Slice(lastHistoryWarmUpBar.EndTime,
new List<BaseData> { lastHistoryWarmUpBar },
lastHistoryWarmUpBar.EndTime.ConvertToUtc(TimeZones.NewYork))
});
_algorithm.SetHistoryProvider(historyProvider.Object);
}
else
{
_startDate = new DateTime(2013, 10, 12);
}
_algorithm.Settings.DailyPreciseEndTime = false;
_algorithm.SetStartDate(_startDate);
_manualTimeProvider.SetCurrentTimeUtc(_algorithm.Time.ConvertToUtc(TimeZones.NewYork));
_algorithm.SetBenchmark(_ => 0);
_algorithm.SetWarmUp(warmupResolution == Resolution.Minute ? 60 * 8 : 10, warmupResolution);
var firstLiveBarTime = warmupResolution == Resolution.Minute
? _startDate.AddHours(8)
: _startDate.AddHours(0.25);
var firstLiveBar = new TradeBar(firstLiveBarTime, symbol, 1, 5, 1, 3, 100, Time.OneMinute);
var liveData = withLiveDataPoint ? new List<BaseData> { firstLiveBar } : new List<BaseData>();
var dqh = new TestDataQueueHandler { DataPerSymbol = new() { { symbol, liveData } } };
var feed = RunDataFeed(Resolution.Minute, dataQueueHandler: dqh, equities: new() { "SPY" });
_algorithm.OnEndOfTimeStep();
TradeBar lastWarmupTradeBar = null;
TradeBar lastTradeBar = null;
var dataFillForwardedFromWarmupCount = 0;
var dataFillForwardedFromLiveCount = 0;
var gotLivePoint = false;
var stopTime = withLiveDataPoint ? firstLiveBar.EndTime.AddHours(0.25) : _startDate.AddHours(0.5);
if (warmupResolution == Resolution.Minute)
{
stopTime = withLiveDataPoint? firstLiveBar.EndTime.AddHours(1) : _startDate.AddHours(8);
}
ConsumeBridge(feed, TimeSpan.FromSeconds(5), true, ts =>
{
if (ts.Slice.HasData)
{
Assert.IsTrue(ts.Slice.Bars.TryGetValue(symbol, out var tradeBar));
if (_algorithm.IsWarmingUp)
{
lastWarmupTradeBar = tradeBar;
}
else
{
lastTradeBar = tradeBar;
if (lastTradeBar.EndTime == firstLiveBar.EndTime && withLiveDataPoint)
{
Assert.IsFalse(lastTradeBar.IsFillForward);
gotLivePoint = true;
}
else
{
Assert.IsTrue(lastTradeBar.IsFillForward);
if (!withLiveDataPoint || lastTradeBar.EndTime < firstLiveBar.EndTime)
{
dataFillForwardedFromWarmupCount++;
}
else if (withLiveDataPoint && lastTradeBar.EndTime > firstLiveBar.EndTime)
{
dataFillForwardedFromLiveCount++;
}
}
if (tradeBar.EndTime >= stopTime)
{
// short cut
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
}
}
}
},
endDate: _startDate.AddDays(60),
secondsTimeStep: 60);
// Assert we actually got warmup data
Assert.IsNotNull(lastWarmupTradeBar);
// Assert we got normal data
Assert.IsNotNull(lastTradeBar);
// Assert we got fill-forwarded data before the actual live data
Assert.Greater(dataFillForwardedFromWarmupCount, 0);
// Assert we got fill-forwarded data after the actual live data
if (withLiveDataPoint)
{
Assert.IsTrue(gotLivePoint);
Assert.Greater(dataFillForwardedFromLiveCount, 0);
}
else
{
Assert.AreEqual(0, dataFillForwardedFromLiveCount);
}
}
[TestCase(0, 13)]
[TestCase(2, 13)]
[TestCase(10, 12)]
[TestCase(100, 12)]
[TestCase(280, 13)]
public void UniverseScheduleUtcShitft(int dateShitft, int expectedTimeShift)
{
var result = LiveTradingDataFeed.GetScheduledUniverseUtcTimeShift(new DateTime(2026, 3, 1).AddDays(dateShitft));
Assert.AreEqual(expectedTimeShift, (int)result.TotalHours);
}
private IDataFeed RunDataFeed(Resolution resolution = Resolution.Second, List<string> equities = null, List<string> forex = null, List<string> crypto = null,
Func<FuncDataQueueHandler, IEnumerable<BaseData>> getNextTicksFunction = null,
Func<Symbol, bool, string, IEnumerable<Symbol>> lookupSymbolsFunction = null,
Func<bool> canPerformSelection = null, IDataQueueHandler dataQueueHandler = null,
bool runPostInitialize = true)
{
_algorithm.SetStartDate(_startDate);
_algorithm.SetDateTime(_manualTimeProvider.GetUtcNow());
var lastTime = _manualTimeProvider.GetUtcNow();
getNextTicksFunction ??= (fdqh =>
{
var time = _manualTimeProvider.GetUtcNow();
if (time == lastTime) return Enumerable.Empty<BaseData>();
lastTime = time;
var tickTimeUtc = lastTime.AddMinutes(-1);
return fdqh.SubscriptionDataConfigs.Where(config => !_algorithm.UniverseManager.ContainsKey(config.Symbol)) // its not a universe
.SelectMany(config =>
{
if (_algorithm.IsWarmingUp)
{
return Enumerable.Empty<Tick>();
}
var ticks = new List<Tick>
{
new Tick(tickTimeUtc.ConvertFromUtc(config.ExchangeTimeZone), config.Symbol, 1, 2)
{
Quantity = 1,
// Symbol could not be in the Securities collections for the custom Universe tests. AlgorithmManager is in charge of adding them, and we are not executing that code here.
TickType = config.TickType
}
};
return ticks;
}
).ToList();
});
// job is used to send into DataQueueHandler
var job = new LiveNodePacket();
// result handler is used due to dependency in SubscriptionDataReader
var resultHandler = new BacktestingResultHandler();
if (dataQueueHandler == null)
{
_dataQueueHandler = new FuncDataQueueHandlerUniverseProvider(getNextTicksFunction,
lookupSymbolsFunction ?? ((symbol, _, _) =>
{
var date = _manualTimeProvider.GetUtcNow()
.ConvertFromUtc(MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType).TimeZone)
.Date;
var symbols = new List<Symbol>();
for (var i = 0; i < 4; i++)
{
if (symbol.SecurityType.IsOption())
{
foreach (var optionRight in new[] { OptionRight.Call, OptionRight.Put })
{
symbols.Add(Symbol.CreateOption(symbol.Underlying ?? symbol,
symbol.ID.Market,
symbol.SecurityType.DefaultOptionStyle(),
optionRight,
i,
date.AddDays(i)));
}
}
else
{
symbols.Add(Symbol.CreateFuture(symbol.ID.Symbol, symbol.ID.Market, date.AddDays(i)));
}
}
return symbols;
}),
canPerformSelection ?? (() => true), _manualTimeProvider, _algorithm.Settings);
}
_feed = new TestableLiveTradingDataFeed(_algorithm.Settings, dataQueueHandler ?? _dataQueueHandler);
_feed.TestDataQueueHandlerManager.TimeProvider = _manualTimeProvider;
var fileProvider = TestGlobals.DataProvider;
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(_algorithm.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDataBase, _algorithm, RegisteredSecurityDataTypesProvider.Null, new SecurityCacheProvider(_algorithm.Portfolio), algorithm: _algorithm);
_algorithm.Securities.SetSecurityService(securityService);
var dataPermissionManager = new DataPermissionManager();
_dataManager = new DataManager(_feed,
new UniverseSelection(_algorithm, securityService, dataPermissionManager, fileProvider),
_algorithm,
_algorithm.TimeKeeper,
marketHoursDatabase,
true,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
_algorithm.SubscriptionManager.SetDataManager(_dataManager);
_algorithm.AddSecurities(resolution, equities, forex, crypto);
_synchronizer = new TestableLiveSynchronizer(_manualTimeProvider, 10);
_synchronizer.Initialize(_algorithm, _dataManager, new());
_feed.Initialize(_algorithm, job, resultHandler, TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider, fileProvider, _dataManager, _synchronizer, new TestDataChannelProvider());
if (runPostInitialize)
{
_algorithm.PostInitialize();
}
return _feed;
}
private void ConsumeBridge(IDataFeed feed, TimeSpan timeout, Action<TimeSlice> handler, bool sendUniverseData = false,
int secondsTimeStep = 1, bool alwaysInvoke = false, DateTime endDate = default(DateTime))
{
ConsumeBridge(feed, timeout, alwaysInvoke, handler, sendUniverseData: sendUniverseData, secondsTimeStep: secondsTimeStep, endDate: endDate);
}
private void ConsumeBridge(IDataFeed feed,
TimeSpan timeout,
bool alwaysInvoke,
Action<TimeSlice> handler,
bool noOutput = true,
bool sendUniverseData = false,
int secondsTimeStep = 1,
DateTime endDate = default(DateTime))
{
var endTime = DateTime.UtcNow.Add(timeout);
bool startedReceivingata = false;
using var cancellationTokenSource = new CancellationTokenSource(timeout * 2);
_algorithm.SetLocked();
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
_algorithm.ProcessSecurityChanges(timeSlice.SecurityChanges);
_algorithm.SetDateTime(timeSlice.Time);
if (!noOutput)
{
ConsoleWriteLine("\r\n" + $"Now (EDT): {DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork):o}" +
$". TimeSlice.Time (EDT): {timeSlice.Time.ConvertFromUtc(TimeZones.NewYork):o}. HasData {timeSlice.Slice?.HasData}");
}
if (timeSlice.IsTimePulse)
{
continue;
}
AlgorithmManager.HandleDividends(timeSlice, _algorithm, liveMode: true);
AlgorithmManager.HandleSplits(timeSlice, _algorithm, liveMode: true);
if (!startedReceivingata
&& (timeSlice.Slice.Count != 0
|| sendUniverseData && timeSlice.UniverseData.Count > 0))
{
startedReceivingata = true;
}
if (startedReceivingata || alwaysInvoke)
{
handler(timeSlice);
}
_algorithm.OnEndOfTimeStep();
_manualTimeProvider.AdvanceSeconds(secondsTimeStep);
if (!_algorithm.IsWarmingUp)
{
Thread.Sleep(10);
}
if (endDate != default(DateTime) && _manualTimeProvider.GetUtcNow() > endDate
|| endTime <= DateTime.UtcNow)
{
feed.Exit();
cancellationTokenSource.Cancel();
// allow LTDF tasks to finish
Thread.Sleep(10);
return;
}
}
}
private class Count
{
public int Value;
}
private static IEnumerable<BaseData> ProduceBenchmarkTicks(FuncDataQueueHandler fdqh, Count count)
{
for (int i = 0; i < 10000; i++)
{
foreach (var symbol in fdqh.Subscriptions)
{
count.Value++;
yield return new Tick { Symbol = symbol };
}
}
}
private void ConsoleWriteLine(string line = "")
{
if (LogsEnabled)
{
Log.Trace(line);
}
}
private static TestCaseData[] DataTypeTestCases => new[]
{
// Equity - Hourly resolution
// We expect 7 hourly bars for 6.5 hours in open market hours
new TestCaseData(Symbols.SPY, Resolution.Hour, 1, 0, 7, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Equity - Minute resolution
// We expect 30 minute bars for 0.5 hours in open market hours
new TestCaseData(Symbols.SPY, Resolution.Minute, 1, 0, (int)(0.5 * 60), (int)(0.5 * 60), 0, 0, false, _instances[typeof(BaseData)]),
// Equity - Tick resolution
// In this test we only emit ticks once per hour
// We expect only 6 ticks -- the 4 PM tick is not received because it's outside market hours -> times 2 (quote/trade bar)
new TestCaseData(Symbols.SPY, Resolution.Tick, 1, (7 - 1) * 2, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Forex - FXCM
new TestCaseData(Symbols.EURUSD, Resolution.Hour, 1, 0, 0, 24, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbols.EURUSD, Resolution.Minute, 1, 0, 0, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
// emit at the start and end time
new TestCaseData(Symbols.EURUSD, Resolution.Tick, 1, 24, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Forex - Oanda
new TestCaseData(Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda), Resolution.Hour, 1, 0, 0, 24, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda), Resolution.Minute, 1, 0, 0, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
// emit at the start and end time
new TestCaseData(Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda), Resolution.Tick, 1, 24, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// CFD - FXCM
new TestCaseData(Symbol.Create("DE30EUR", SecurityType.Cfd, Market.FXCM), Resolution.Hour, 1, 0, 0, 14, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbol.Create("DE30EUR", SecurityType.Cfd, Market.FXCM), Resolution.Minute, 1, 0, 0, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbol.Create("DE30EUR", SecurityType.Cfd, Market.FXCM), Resolution.Tick, 1, 14, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// CFD - Oanda
new TestCaseData(Symbols.DE30EUR, Resolution.Hour, 1, 0, 0, 21, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbols.DE30EUR, Resolution.Minute, 1, 0, 0, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbols.DE30EUR, Resolution.Tick, 1, 21, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Crypto
new TestCaseData(Symbols.BTCUSD, Resolution.Hour, 1, 0, 24, 24, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbols.BTCUSD, Resolution.Minute, 1, 0, 1 * 60, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
// x2 because counting trades and quotes. Emit at the start and end time
new TestCaseData(Symbols.BTCUSD, Resolution.Tick, 1, 25 * 2, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Futures
// ES has two session breaks totalling 1h 15m, so total trading hours = 22.75
new TestCaseData(Symbols.Future_ESZ18_Dec2018, Resolution.Hour, 1, 0, 23, 23, 0, 0, false, _instances[typeof(BaseData)]),
new TestCaseData(Symbols.Future_ESZ18_Dec2018, Resolution.Minute, 1, 0, 1 * 60, 1 * 60, 0, 0, false, _instances[typeof(BaseData)]),
// x2 because counting trades and quotes. Emit at the start and end time
new TestCaseData(Symbols.Future_ESZ18_Dec2018, Resolution.Tick, 1, 24 * 2, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Options
new TestCaseData(Symbols.SPY_C_192_Feb19_2016, Resolution.Hour, 1, 0, 7, 7, 0, 0, false, _instances[typeof(BaseData)]),
// We expect 30 minute bars for 0.5 hours in open market hours
new TestCaseData(Symbols.SPY_C_192_Feb19_2016, Resolution.Minute, 1, 0, (int)(0.5 * 60), (int)(0.5 * 60), 0, 0, false, _instances[typeof(BaseData)]),
// x2 because counting trades and quotes
new TestCaseData(Symbols.SPY_C_192_Feb19_2016, Resolution.Tick, 1, (7 - 1) * 2, 0, 0, 0, 0, false, _instances[typeof(BaseData)]),
// Custom data not supported
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData2), Symbols.AAPL, Market.USA), Resolution.Hour, 1, 0, 0, 0, 0, 24 * 2, true, _instances[typeof(IndexedLinkedData2)]),
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData2), Symbols.AAPL, Market.USA), Resolution.Minute, 1, 0, 0, 0, 0, 60 * 2, true, _instances[typeof(IndexedLinkedData2)]),
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData2), Symbols.AAPL, Market.USA), Resolution.Tick, 1, 0, 0, 0, 0, 24, true, _instances[typeof(IndexedLinkedData2)]),
//// Custom data streamed
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData), Symbols.AAPL, Market.USA), Resolution.Hour, 1, 0, 0, 0, 0, 24, false, _instances[typeof(IndexedLinkedData)]),
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData), Symbols.AAPL, Market.USA), Resolution.Minute, 1, 0, 0, 0, 0, 60, false, _instances[typeof(IndexedLinkedData)]),
new TestCaseData(Symbol.CreateBase(typeof(IndexedLinkedData), Symbols.AAPL, Market.USA), Resolution.Tick, 1, 0, 0, 0, 0, 24, false, _instances[typeof(IndexedLinkedData)])
};
[TestCaseSource(nameof(DataTypeTestCases))]
public void HandlesAllTypes<T>(
Symbol symbol,
Resolution resolution,
int days,
int expectedTicksReceived,
int expectedTradeBarsReceived,
int expectedQuoteBarsReceived,
int expectedAuxPointsReceived,
int expectedCustomPointsReceived,
bool shouldThrowException,
T customDataType) where T : BaseData, new()
{
// startDate and endDate are in algorithm time zone. Start date has to be before the expiration of symbol
var startDate = new DateTime(2015, 6, 8);
var endDate = startDate.AddDays(days);
if (resolution == Resolution.Minute)
{
// for faster test execution time
startDate = startDate.AddHours(9);
endDate = startDate.AddHours(1);
}
var algorithmTimeZone = TimeZones.NewYork;
DateTimeZone exchangeTimeZone = null;
var timeProvider = new ManualTimeProvider(algorithmTimeZone);
timeProvider.SetCurrentTime(startDate);
var actualPricePointsEnqueued = 0;
var actualAuxPointsEnqueued = 0;
var lastTime = DateTime.MinValue;
using var emittedData = new ManualResetEvent(false);
var algorithm = new QCAlgorithm();
using var dataQueueStarted = new ManualResetEvent(false);
_dataQueueHandler = new FuncDataQueueHandler(fdqh =>
{
dataQueueStarted.Set();
if (exchangeTimeZone == null)
{
return Enumerable.Empty<BaseData>();
}
var utcTime = timeProvider.GetUtcNow();
var exchangeTime = utcTime.ConvertFromUtc(exchangeTimeZone);
var ended = exchangeTime > endDate.ConvertTo(algorithmTimeZone, exchangeTimeZone);
if (exchangeTime == lastTime || ended)
{
if (ended)
{
emittedData.Set();
}
return Enumerable.Empty<BaseData>();
}
lastTime = exchangeTime;
var algorithmTime = utcTime.ConvertFromUtc(algorithmTimeZone);
var dataPoints = new List<BaseData>();
if (symbol.SecurityType == SecurityType.Base)
{
var dataPoint = new T
{
Symbol = symbol,
EndTime = exchangeTime,
Value = actualPricePointsEnqueued++
};
dataPoints.Add(dataPoint);
ConsoleWriteLine(
$"{algorithmTime} - FuncDataQueueHandler emitted custom data point: {dataPoint}");
}
else
{
var tickType = TickType.Quote;
var dataPoint = new Tick
{
Symbol = symbol,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = tickType,
Value = actualPricePointsEnqueued
};
if (symbol.SecurityType != SecurityType.Equity
|| resolution != Resolution.Daily
|| resolution != Resolution.Hour)
{
actualPricePointsEnqueued++;
// equity has minute/second/tick quote data
dataPoints.Add(dataPoint);
}
ConsoleWriteLine(
$"{algorithmTime} - FuncDataQueueHandler emitted {tickType} tick: {dataPoint}");
dataPoint = new Tick
{
Symbol = symbol,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = TickType.Trade,
Value = actualPricePointsEnqueued++
};
dataPoints.Add(dataPoint);
ConsoleWriteLine(
$"{algorithmTime} - FuncDataQueueHandler emitted Trade tick: {dataPoint}");
}
emittedData.Set();
return dataPoints;
}, timeProvider, algorithm.Settings);
_feed = new TestableLiveTradingDataFeed(algorithm.Settings, _dataQueueHandler);
algorithm.SetDateTime(timeProvider.GetUtcNow());
var historyProvider = new Mock<IHistoryProvider>();
historyProvider.Setup(
m => m.GetHistory(It.IsAny<IEnumerable<Data.HistoryRequest>>(), It.IsAny<DateTimeZone>()))
.Returns(Enumerable.Empty<Slice>());
algorithm.SetHistoryProvider(historyProvider.Object);
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(
algorithm.Portfolio.CashBook,
marketHoursDatabase,
symbolPropertiesDataBase,
algorithm,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(algorithm.Portfolio),
algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(_feed,
new UniverseSelection(algorithm, securityService, dataPermissionManager, TestGlobals.DataProvider),
algorithm,
algorithm.TimeKeeper,
marketHoursDatabase,
true,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
algorithm.SubscriptionManager.SetDataManager(dataManager);
algorithm.SetLiveMode(true);
var mock = new Mock<ITransactionHandler>();
mock.Setup(m => m.GetOpenOrders(It.IsAny<Func<Order, bool>>())).Returns(new List<Order>());
algorithm.Transactions.SetOrderProcessor(mock.Object);
_synchronizer = new TestableLiveSynchronizer(timeProvider, 10);
_synchronizer.Initialize(algorithm, dataManager, new());
Security security;
switch (symbol.SecurityType)
{
case SecurityType.Base:
algorithm.AddEquity(symbol.Underlying.Value, resolution, symbol.ID.Market,
fillForward: false);
if (customDataType.RequiresMapping())
{
security = algorithm.AddData<T>(symbol.Value, resolution,
fillForward: false);
}
else
{
throw new NotImplementedException($"Custom data not implemented: {symbol}");
}
break;
case SecurityType.Future:
security = algorithm.AddFutureContract(symbol, resolution, fillForward: false, extendedMarketHours: true);
break;
case SecurityType.Option:
security = algorithm.AddOptionContract(symbol, resolution, fillForward: false);
break;
default:
security = algorithm.AddSecurity(symbol.SecurityType, symbol.Value, resolution,
symbol.ID.Market, false, 1, false);
break;
}
_feed.Initialize(algorithm, new LiveNodePacket(), new BacktestingResultHandler(),
TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider,
dataManager, _synchronizer, new TestDataChannelProvider());
if (!dataQueueStarted.WaitOne(TimeSpan.FromMilliseconds(5000)))
{
throw new TimeoutException("Timeout waiting for IDQH to start");
}
using var cancellationTokenSource = new CancellationTokenSource();
// for tick resolution, we advance one hour at a time for less unit test run time
TimeSpan advanceTimeSpan;
switch (resolution)
{
case Resolution.Tick:
default:
advanceTimeSpan = TimeSpan.FromHours(1);
break;
case Resolution.Second:
advanceTimeSpan = TimeSpan.FromSeconds(1);
break;
case Resolution.Minute:
advanceTimeSpan = TimeSpan.FromSeconds(60);
break;
case Resolution.Hour:
advanceTimeSpan = TimeSpan.FromMinutes(60);
break;
case Resolution.Daily:
advanceTimeSpan = TimeSpan.FromHours(24);
break;
}
try
{
algorithm.PostInitialize();
var actualTicksReceived = 0;
var actualTradeBarsReceived = 0;
var actualQuoteBarsReceived = 0;
var actualAuxPointsReceived = 0;
var actualCustomPointsReceived = 0;
var sliceCount = 0;
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.IsTimePulse)
{
algorithm.OnEndOfTimeStep();
exchangeTimeZone = security.Exchange.TimeZone;
continue;
}
sliceCount++;
// give enough time to the producer to emit
if (sliceCount == 1 && !emittedData.WaitOne(300))
{
Assert.Fail("Timeout waiting for data generation");
}
if (resolution == Resolution.Tick)
{
if (timeSlice.Slice.Ticks.ContainsKey(symbol))
{
foreach (var tick in timeSlice.Slice.Ticks[symbol].ToList())
{
actualTicksReceived++;
ConsoleWriteLine(
$"{algorithm.Time} - Tick received, value: {tick.Value} {tick.TickType} (count: {actualTicksReceived})"
);
}
}
if (timeSlice.Slice.Dividends.ContainsKey(symbol))
{
actualAuxPointsReceived++;
ConsoleWriteLine(
$"{algorithm.Time} - Dividend received, value: {timeSlice.Slice.Dividends[symbol].Value} (count: {actualAuxPointsReceived})"
);
}
var customDataCount = timeSlice.Slice.Get<T>().Count;
if (customDataCount > 0)
{
actualCustomPointsReceived += customDataCount;
ConsoleWriteLine(
$"{algorithm.Time} - Custom received, value: {timeSlice.Slice.Get<T>().First().Value} (count: {actualCustomPointsReceived})"
);
}
}
else
{
if (timeSlice.Slice.Bars.ContainsKey(symbol))
{
actualTradeBarsReceived++;
ConsoleWriteLine(
$"{algorithm.Time} - TradeBar received, value: {timeSlice.Slice.Bars[symbol].Value} (count: {actualTradeBarsReceived})"
);
}
if (timeSlice.Slice.Dividends.ContainsKey(symbol))
{
actualAuxPointsReceived++;
ConsoleWriteLine(
$"{algorithm.Time} - Dividend received, value: {timeSlice.Slice.Dividends[symbol].Value} (count: {actualAuxPointsReceived})"
);
}
if (timeSlice.Slice.QuoteBars.ContainsKey(symbol))
{
actualQuoteBarsReceived++;
ConsoleWriteLine(
$"{algorithm.Time} - QuoteBar received, value: {timeSlice.Slice.QuoteBars[symbol].Value} (count: {actualQuoteBarsReceived})"
);
}
if (symbol.SecurityType == SecurityType.Base)
{
var customDataCount = timeSlice.Slice.Get<T>().Count;
if (customDataCount > 0)
{
actualCustomPointsReceived += customDataCount;
ConsoleWriteLine(
$"{algorithm.Time} - Custom received, value: {timeSlice.Slice.Get<T>().First().Value} (count: {actualCustomPointsReceived})"
);
}
}
}
algorithm.OnEndOfTimeStep();
_synchronizer.NewDataEvent.Reset();
emittedData.Reset();
timeProvider.Advance(advanceTimeSpan);
// give enough time to the producer to emit
if (!emittedData.WaitOne(300))
{
Assert.Fail($"Timeout waiting for data generation at {algorithm.Time} algorithm tz");
}
var currentTime = timeProvider.GetUtcNow();
algorithm.SetDateTime(currentTime);
ConsoleWriteLine($"Algorithm time set to {currentTime.ConvertFromUtc(algorithmTimeZone)}");
if (shouldThrowException && algorithm.Status == AlgorithmStatus.RuntimeError)
{
// expected
return;
}
if (currentTime.ConvertFromUtc(algorithmTimeZone) > endDate)
{
_feed.Exit();
cancellationTokenSource.Cancel();
break;
}
if (resolution != Resolution.Tick)
{
var amount = currentTime.Ticks % resolution.ToTimeSpan().Ticks;
if (amount == 0)
{
// let's avoid race conditions and give time for the funDataQueueHandler thread to distribute the data among the consolidators
if (!_synchronizer.NewDataEvent.Wait(500))
{
if (!shouldThrowException || algorithm.Status != AlgorithmStatus.RuntimeError)
{
Assert.Fail("Timeout waiting for data generation");
}
}
}
}
else
{
_synchronizer.NewDataEvent.Wait(300);
}
}
emittedData.DisposeSafely();
dataQueueStarted.DisposeSafely();
Log.Trace(
$"SliceCount:{sliceCount} - PriceData: Enqueued:{actualPricePointsEnqueued} TicksReceived:{actualTicksReceived}"
);
Log.Trace(
$"SliceCount:{sliceCount} - PriceData: Enqueued:{actualPricePointsEnqueued} TradeBarsReceived:{actualTradeBarsReceived}"
);
Log.Trace(
$"SliceCount:{sliceCount} - PriceData: Enqueued:{actualPricePointsEnqueued} QuoteBarsReceived:{actualQuoteBarsReceived}"
);
Log.Trace(
$"SliceCount:{sliceCount} - AuxData: Enqueued:{actualAuxPointsEnqueued} Received:{actualAuxPointsReceived}"
);
Log.Trace(
$"SliceCount:{sliceCount} - AuxData: Enqueued:{actualPricePointsEnqueued} Received:{actualCustomPointsReceived}"
);
Assert.IsTrue(actualPricePointsEnqueued > 0);
if (resolution == Resolution.Tick)
{
if (symbol.SecurityType == SecurityType.Base)
{
Assert.IsTrue(actualTicksReceived == 0);
}
else
{
Assert.IsTrue(actualTicksReceived > 0);
}
}
else
{
switch (symbol.SecurityType)
{
case SecurityType.Equity:
Assert.IsTrue(actualTradeBarsReceived > 0);
if (resolution == Resolution.Daily || resolution == Resolution.Hour)
{
Assert.IsTrue(actualQuoteBarsReceived == 0);
}
else
{
Assert.IsTrue(actualQuoteBarsReceived > 0);
}
break;
case SecurityType.Forex:
case SecurityType.Cfd:
Assert.IsTrue(actualQuoteBarsReceived > 0);
break;
case SecurityType.Crypto:
case SecurityType.Option:
case SecurityType.Future:
Assert.IsTrue(actualTradeBarsReceived > 0);
Assert.IsTrue(actualQuoteBarsReceived > 0);
break;
case SecurityType.Base:
Assert.IsTrue(actualCustomPointsReceived > 0);
break;
}
}
Assert.AreEqual(expectedTicksReceived, actualTicksReceived);
Assert.AreEqual(expectedTradeBarsReceived, actualTradeBarsReceived);
Assert.AreEqual(expectedQuoteBarsReceived, actualQuoteBarsReceived);
Assert.AreEqual(expectedAuxPointsReceived, actualAuxPointsReceived);
Assert.AreEqual(expectedCustomPointsReceived, actualCustomPointsReceived);
dataManager.RemoveAllSubscriptions();
_dataQueueHandler.DisposeSafely();
}
catch (Exception exception)
{
Log.Error(exception);
if (!shouldThrowException)
{
throw;
}
}
finally
{
dataManager?.RemoveAllSubscriptions();
if (!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
_dataQueueHandler.DisposeSafely();
_feed?.Exit();
}
}
[TestCase(SecurityType.Future, 4)]
[TestCase(SecurityType.Option, 1232)]
[TestCase(SecurityType.IndexOption, 6)]
public void HandlesFutureAndOptionChainUniverse(SecurityType securityType, int expectedContractsCount)
{
Log.DebuggingEnabled = LogsEnabled;
// startDate and endDate are in algorithm time zone. Midnight so selection happens right away
var startDate = securityType switch
{
SecurityType.Option => new DateTime(2015, 12, 24),
SecurityType.IndexOption => new DateTime(2021, 01, 04),
SecurityType.Future => new DateTime(2013, 07, 11),
_ => throw new ArgumentOutOfRangeException(nameof(securityType), securityType, null)
};
var endDate = startDate.AddDays(2.3);
var algorithmTimeZone = TimeZones.NewYork;
DateTimeZone exchangeTimeZone = null;
var timeProvider = new ManualTimeProvider(algorithmTimeZone);
timeProvider.SetCurrentTime(startDate);
var lastTime = DateTime.MinValue;
var timeAdvanceStep = TimeSpan.FromMinutes(180);
using var timeAdvanced = new AutoResetEvent(true);
using var started = new ManualResetEvent(false);
var futureSelectionCount = 0;
var selectedFutureSymbols = new HashSet<Symbol>();
Symbol canonicalOptionSymbol = null;
Exception lookupSymbolsException = null;
var futureSymbols = new HashSet<Symbol>();
var optionSymbols = new HashSet<Symbol>();
var algorithm = new QCAlgorithm();
_dataQueueHandler = new FuncDataQueueHandlerUniverseProvider(
fdqh =>
{
started.Set();
if (!timeAdvanced.WaitOne(TimeSpan.FromMilliseconds(5000)))
{
Log.Error("Timeout waiting for time to advance");
return Enumerable.Empty<BaseData>();
}
if (exchangeTimeZone == null)
{
return Enumerable.Empty<BaseData>();
}
var utcTime = timeProvider.GetUtcNow();
var exchangeTime = utcTime.ConvertFromUtc(exchangeTimeZone);
if (exchangeTime == lastTime ||
exchangeTime > endDate.ConvertTo(algorithmTimeZone, exchangeTimeZone))
{
return Enumerable.Empty<BaseData>();
}
lastTime = exchangeTime;
var dataPoints = new List<BaseData>();
if (securityType.IsOption())
{
dataPoints.Add(new Tick
{
Symbol = canonicalOptionSymbol,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = TickType.Trade,
Value = 100,
Quantity = 1
});
dataPoints.Add(new Tick
{
Symbol = canonicalOptionSymbol.Underlying,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = TickType.Trade,
Value = 100,
Quantity = 1
});
dataPoints.AddRange(
optionSymbols.Select(
symbol => new Tick
{
Symbol = symbol,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = TickType.Trade,
Value = 100,
Quantity = 1
}));
}
else if (securityType == SecurityType.Future)
{
if (selectedFutureSymbols.Count > 0)
{
var canonicalFutureSymbol = selectedFutureSymbols.First().Canonical;
var mappedSymbol = (algorithm.Securities[canonicalFutureSymbol] as Future).Mapped;
dataPoints.AddRange(
selectedFutureSymbols.Union(new[] { canonicalFutureSymbol, mappedSymbol }).Select(
symbol => new Tick
{
Symbol = symbol,
Time = exchangeTime,
EndTime = exchangeTime,
TickType = TickType.Trade,
Value = 100,
Quantity = 1
}));
}
}
Log.Debug($"DQH: Emitting data point(s) at {utcTime.ConvertFromUtc(algorithmTimeZone)} ({algorithmTimeZone})");
return dataPoints;
},
// LookupSymbols
(symbol, includeExpired, securityCurrency) => Enumerable.Empty<Symbol>(),
// CanAdvanceTime
() =>
{
var time = timeProvider.GetUtcNow().ConvertFromUtc(algorithmTimeZone);
var result = time.Hour >= 1 && time.Hour < 23 && time.Day != 21;
Log.Debug($"CanPerformSelection() called at {time} ({algorithmTimeZone}), returning {result}");
return result;
},
timeProvider, algorithm.Settings);
_feed = new TestableLiveTradingDataFeed(_algorithm.Settings, _dataQueueHandler);
algorithm.SetDateTime(timeProvider.GetUtcNow());
algorithm.SetBenchmark(t => 0);
var historyProvider = new Mock<IHistoryProvider>();
historyProvider.Setup(
m => m.GetHistory(It.IsAny<IEnumerable<Data.HistoryRequest>>(), It.IsAny<DateTimeZone>()))
.Returns(Enumerable.Empty<Slice>());
algorithm.SetHistoryProvider(historyProvider.Object);
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(
algorithm.Portfolio.CashBook,
marketHoursDatabase,
symbolPropertiesDataBase,
algorithm,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(algorithm.Portfolio),
algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(_feed,
new UniverseSelection(algorithm, securityService, dataPermissionManager, TestGlobals.DataProvider),
algorithm,
algorithm.TimeKeeper,
marketHoursDatabase,
true,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
algorithm.SubscriptionManager.SetDataManager(dataManager);
algorithm.SetLiveMode(true);
var mock = new Mock<ITransactionHandler>();
mock.Setup(m => m.GetOpenOrders(It.IsAny<Func<Order, bool>>())).Returns(new List<Order>());
algorithm.Transactions.SetOrderProcessor(mock.Object);
_synchronizer = new TestableLiveSynchronizer(timeProvider, 10);
_synchronizer.Initialize(algorithm, dataManager, new());
if (securityType == SecurityType.Option)
{
algorithm.AddEquity("GOOG", Resolution.Minute);
var option = algorithm.AddOption("GOOG", Resolution.Minute, Market.USA);
option.SetFilter(x => x.StandardsOnly());
exchangeTimeZone = option.Exchange.TimeZone;
canonicalOptionSymbol = option.Symbol;
}
else if (securityType == SecurityType.IndexOption)
{
algorithm.AddIndex("SPX", Resolution.Minute);
var option = algorithm.AddIndexOption("SPX", Resolution.Minute, Market.USA);
option.SetFilter(x => x);
exchangeTimeZone = option.Exchange.TimeZone;
canonicalOptionSymbol = option.Symbol;
}
else if (securityType == SecurityType.Future)
{
var future = algorithm.AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, extendedMarketHours: true, fillForward: false);
future.SetFilter(u =>
{
futureSelectionCount++;
var result = u.IncludeWeeklys().Contracts(x => x.Take(2));
selectedFutureSymbols.UnionWith(result.Take(2).Select(x => x.Symbol));
return result;
});
exchangeTimeZone = future.Exchange.TimeZone;
}
else
{
throw new NotSupportedException($"Unsupported security type: {securityType}");
}
_feed.Initialize(algorithm, new LiveNodePacket(), new BacktestingResultHandler(),
TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider,
dataManager, _synchronizer, new TestDataChannelProvider());
using var cancellationTokenSource = new CancellationTokenSource();
algorithm.PostInitialize();
DateTime? lastSecurityChangedTime = null;
if (!started.WaitOne(TimeSpan.FromMilliseconds(5000)))
{
throw new TimeoutException("Timeout waiting for IDQH to start");
}
var interval = TimeSpan.FromMilliseconds(100);
Timer timer = null;
timer = new Timer(
_ =>
{
try
{
// stop the timer to prevent reentrancy
timer.Change(Timeout.Infinite, Timeout.Infinite);
timeProvider.Advance(timeAdvanceStep);
Log.Debug($"Time advanced to {timeProvider.GetUtcNow()} (UTC)");
timeAdvanced.Set();
// restart the timer
timer.Change(interval, interval);
}
catch (ObjectDisposedException)
{
}
}, null, interval, interval);
// We should wait for the base exchange to pick up the universe and push a selection data point
Thread.Sleep(100);
bool IsPastEndTime(out DateTime currentTime)
{
currentTime = timeProvider.GetUtcNow();
if (currentTime.ConvertFromUtc(algorithmTimeZone) > endDate)
{
_feed.Exit();
cancellationTokenSource.Cancel();
return true;
}
return false;
}
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.IsTimePulse || !timeSlice.Slice.HasData && timeSlice.SecurityChanges == SecurityChanges.None)
{
if (IsPastEndTime(out _)) break;
continue;
}
var futureContractCount = 0;
var optionContractCount = 0;
if (securityType == SecurityType.Future)
{
foreach (var futureChain in timeSlice.Slice.FutureChains.Values)
{
var symbols = futureChain.Contracts.Values.Select(x => x.Symbol).ToList();
futureContractCount += symbols.Count;
Log.Debug($"{timeSlice.Time} - future contracts: {string.Join(",", symbols)}");
}
Log.Debug($"{timeSlice.Time} - future symbols: {string.Join(",", futureSymbols)}");
}
else if (securityType.IsOption())
{
foreach (var optionChain in timeSlice.Slice.OptionChains.Values)
{
var symbols = optionChain.Contracts.Values.Select(x => x.Symbol).ToList();
optionContractCount += symbols.Count;
Log.Debug($"{timeSlice.Time} - option contracts: {string.Join(",", symbols)}");
}
Log.Debug($"{timeSlice.Time} - option symbols: {string.Join(",", optionSymbols)}");
}
if (lastSecurityChangedTime != null &&
timeSlice.Time > lastSecurityChangedTime.Value.Add(timeAdvanceStep)
&& timeSlice.Slice.HasData)
{
if (securityType == SecurityType.Future)
{
// -2 to remove canonical & internal since it's not part of the chain
Assert.AreEqual(futureSymbols.Count - 2, futureContractCount);
foreach (var symbol in futureSymbols)
{
// only assert there is data for non internal subscriptions
if (algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(symbol).Any())
{
Assert.IsTrue(timeSlice.Slice.ContainsKey(symbol), $"{symbol} was not found, has [{string.Join(",", timeSlice.Slice.Keys)}]");
}
}
}
if (securityType.IsOption() && timeSlice.Slice.OptionChains.Values.Count > 0)
{
Assert.AreEqual(optionSymbols.Count, optionContractCount);
foreach (var symbol in optionSymbols)
{
Assert.IsTrue(timeSlice.Slice.ContainsKey(symbol));
}
}
}
foreach (var security in timeSlice.SecurityChanges.AddedSecurities)
{
if (security.Symbol.SecurityType == SecurityType.Future)
{
lastSecurityChangedTime = timeSlice.Time;
Log.Debug($"{timeSlice.Time} - Adding future symbol: {security.Symbol}");
futureSymbols.Add(security.Symbol);
}
else if (security.Symbol.SecurityType.IsOption())
{
lastSecurityChangedTime = timeSlice.Time;
Log.Debug($"{timeSlice.Time} - Adding option symbol: {security.Symbol}");
optionSymbols.Add(security.Symbol);
}
}
foreach (var security in timeSlice.SecurityChanges.RemovedSecurities)
{
if (security.Symbol.SecurityType == SecurityType.Future)
{
lastSecurityChangedTime = timeSlice.Time;
Log.Debug($"{timeSlice.Time} - Removing future symbol: {security.Symbol}");
futureSymbols.Remove(security.Symbol);
}
else if (security.Symbol.SecurityType.IsOption())
{
lastSecurityChangedTime = timeSlice.Time;
Log.Debug($"{timeSlice.Time} - Removing option symbol: {security.Symbol}");
optionSymbols.Remove(security.Symbol);
}
}
algorithm.OnEndOfTimeStep();
// We should wait for the base exchange to pick up the universe and push a selection data point
Thread.Sleep(150);
foreach (var baseDataCollection in timeSlice.UniverseData.Values)
{
var symbols = string.Join(",", baseDataCollection.Data.Select(x => x.Symbol));
Log.Debug($"{timeSlice.Time} - universe data: {symbols}");
}
// Get current time and check if we should stop the algorithm
IsPastEndTime(out var currentTime);
algorithm.SetDateTime(currentTime);
Log.Debug($"{timeSlice.Time} - Algorithm time set to {currentTime.ConvertFromUtc(algorithmTimeZone)} ({algorithmTimeZone})");
}
if (lookupSymbolsException != null)
{
throw lookupSymbolsException;
}
if (securityType == SecurityType.Future)
{
Assert.AreEqual(2, futureSelectionCount);
// we add 2 symbols + 1 continuous future + 1 continuous future mapped symbol
Assert.AreEqual(4, futureSymbols.Count, "Future symbols count mismatch");
}
else if (securityType.IsOption())
{
Assert.AreEqual(expectedContractsCount, optionSymbols.Count, "Option symbols count mismatch");
}
dataManager.RemoveAllSubscriptions();
_dataQueueHandler.DisposeSafely();
timeAdvanced.DisposeSafely();
started.DisposeSafely();
timer.Dispose();
}
// Reproduces https://github.com/QuantConnect/Lean/issues/8363
[TestCase(Resolution.Second)]
[TestCase(Resolution.Minute)]
[TestCase(Resolution.Hour)]
[TestCase(Resolution.Daily)]
public void UsesFullPeriodDataForConsolidation(Resolution resolution)
{
_startDate = new DateTime(2014, 3, 27);
_algorithm.SetStartDate(_startDate);
_algorithm.Settings.DailyPreciseEndTime = false;
// Add a few milliseconds to the start date to mimic a real world live scenario, where the time provider
// will not always return an perfect rounded-down to second time
_manualTimeProvider.SetCurrentTimeUtc(_startDate.AddMilliseconds(1).ConvertToUtc(TimeZones.NewYork));
var symbol = Symbols.SPY;
_algorithm.SetBenchmark(x => 0);
var data = new[]
{
new [] { 108, 109, 90, 109, 72 },
new [] { 105, 105, 94, 100, 175 },
new [] { 93, 109, 90, 90, 170 },
new [] { 95, 105, 90, 91, 19 },
new [] { 91, 109, 91, 93, 132 },
new [] { 98, 109, 94, 102, 175 },
new [] { 107, 107, 91, 96, 97 },
new [] { 105, 108, 91, 101, 124 },
new [] { 105, 107, 91, 107, 81 },
new [] { 91, 109, 91, 101, 168 },
new [] { 93, 107, 90, 107, 199 },
new [] { 101, 108, 90, 90, 169 },
new [] { 101, 109, 90, 103, 14 },
new [] { 92, 109, 90, 105, 55 },
new [] { 96, 107, 92, 92, 176 },
new [] { 94, 105, 90, 94, 28 },
new [] { 105, 109, 91, 93, 172 },
new [] { 107, 109, 93, 93, 137 },
new [] { 95, 109, 91, 97, 168 },
new [] { 103, 109, 91, 107, 178 },
new [] { 96, 109, 96, 100, 168 },
new [] { 90, 108, 90, 102, 63 },
new [] { 100, 109, 96, 102, 134 },
new [] { 95, 103, 90, 94, 39 },
new [] { 105, 109, 91, 108, 117 },
new [] { 106, 106, 91, 103, 20 },
new [] { 95, 109, 93, 107, 7 },
new [] { 104, 108, 90, 102, 150 },
new [] { 94, 109, 90, 99, 178 },
new [] { 99, 109, 90, 106, 150 },
};
var seconds = 0;
var timeSpan = resolution.ToTimeSpan();
using var dataQueueHandler = new TestDataQueueHandler
{
DataPerSymbol = new Dictionary<Symbol, List<BaseData>>
{
{
symbol,
data
.Select(prices => new TradeBar(_startDate.Add(timeSpan * seconds++),
symbol,
prices[0],
prices[1],
prices[2],
prices[3],
prices[4],
timeSpan))
.Cast<BaseData>()
.ToList()
}
}
};
var feed = RunDataFeed(
resolution: resolution,
equities: new() { "SPY" },
dataQueueHandler: dataQueueHandler);
var consolidatedData = new List<TradeBar>();
var consolidatorUpdateData = new List<TradeBar>();
const int consolidatorBarCountSpan = 6;
var consolidatedCount = 0;
var dataCountUsedForFirstConsolidatedBar = 0;
_algorithm.Consolidate<TradeBar>(symbol, timeSpan * consolidatorBarCountSpan, (consolidatedBar) =>
{
_algorithm.Debug($"Consolidated: {_algorithm.Time} - {consolidatedBar}");
// The first consolidated bar will be consolidated from 1 to consolidatorSpanSeconds second bars,
// from the start time to the next multiple of consolidatorSpanSeconds
var dataCountToTake = 0;
if (consolidatedCount++ == 0)
{
Assert.LessOrEqual(consolidatorUpdateData.Count, consolidatorBarCountSpan);
dataCountToTake = dataCountUsedForFirstConsolidatedBar = consolidatorUpdateData.Count;
}
else
{
Assert.AreEqual(dataCountUsedForFirstConsolidatedBar + consolidatorBarCountSpan * (consolidatedCount - 1),
consolidatorUpdateData.Count);
dataCountToTake = consolidatorBarCountSpan;
}
var dataForCurrentConsolidatedBar = consolidatorUpdateData
.Skip(consolidatorBarCountSpan * (consolidatedCount - 1))
.Take(dataCountToTake)
.ToList();
Assert.AreEqual(consolidatedBar.Time, dataForCurrentConsolidatedBar[0].Time);
Assert.AreEqual(consolidatedBar.EndTime, dataForCurrentConsolidatedBar[^1].EndTime);
var expectedOpen = dataForCurrentConsolidatedBar[0].Open;
Assert.AreEqual(expectedOpen, consolidatedBar.Open);
var expectedClose = dataForCurrentConsolidatedBar[^1].Close;
Assert.AreEqual(expectedClose, consolidatedBar.Close);
var expectedHigh = dataForCurrentConsolidatedBar.Max(x => x.High);
Assert.AreEqual(expectedHigh, consolidatedBar.High);
var expectedLow = dataForCurrentConsolidatedBar.Min(x => x.Low);
Assert.AreEqual(expectedLow, consolidatedBar.Low);
var expectedVolume = dataForCurrentConsolidatedBar.Sum(x => x.Volume);
Assert.AreEqual(expectedVolume, consolidatedBar.Volume);
});
ConsumeBridge(feed,
TimeSpan.FromSeconds(5),
true,
timeSlice =>
{
if (consolidatorUpdateData.Count >= data.Length)
{
// Ran out of data, stop the feed
_manualTimeProvider.SetCurrentTimeUtc(Time.EndOfTime);
return;
}
// Mimic the algorithm manager consolidators scan:
// First, scan for consolidators that need to be updated
// NOTE: Rounding time down to mimic the algorithm manager consolidators scan
_algorithm.SubscriptionManager.ScanPastConsolidators(timeSlice.Time.RoundDown(Time.OneSecond), _algorithm);
// Then, update the consolidators with the new data
if (timeSlice.ConsolidatorUpdateData.Count > 0)
{
var timeKeeper = _algorithm.TimeKeeper;
foreach (var update in timeSlice.ConsolidatorUpdateData)
{
var localTime = timeKeeper.GetLocalTimeKeeper(update.Target.ExchangeTimeZone).LocalTime;
var consolidators = update.Target.Consolidators;
foreach (var consolidator in consolidators)
{
foreach (var dataPoint in update.Data)
{
if (consolidator is TradeBarConsolidator tradeBarConsolidator)
{
consolidatorUpdateData.Add(dataPoint as TradeBar);
}
consolidator.Update(dataPoint);
}
// scan for time after we've pumped all the data through for this consolidator
consolidator.Scan(localTime);
}
}
}
},
endDate: _startDate.Date.AddDays(60),
secondsTimeStep: (int)timeSpan.TotalSeconds);
Assert.AreEqual(dataQueueHandler.DataPerSymbol.Values.Single().Count / consolidatorBarCountSpan, consolidatedCount);
}
private class TestFundamentalDataProviderTrue : IFundamentalDataProvider
{
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
if (securityIdentifier == SecurityIdentifier.Empty)
{
return default;
}
return Get(time, securityIdentifier, name);
}
private dynamic Get(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty enumName)
{
var name = Enum.GetName(enumName);
switch (name)
{
case "HasFundamentalData":
return true;
}
return null;
}
public void Initialize(IDataProvider dataProvider, bool liveMode)
{
}
}
private class ThrowingDataQueueHandler : IDataQueueHandler
{
public bool IsConnected => true;
public void Dispose()
{ }
public void SetJob(LiveNodePacket job)
{ }
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
throw new NotImplementedException();
}
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
throw new NotImplementedException();
}
}
}
internal class TestableLiveTradingDataFeed : LiveTradingDataFeed
{
public IDataQueueHandler DataQueueHandler;
public TestDataQueueHandlerManager TestDataQueueHandlerManager;
public TestableLiveTradingDataFeed(IAlgorithmSettings settings, IDataQueueHandler dataQueueHandler = null)
{
DataQueueHandler = dataQueueHandler;
TestDataQueueHandlerManager = new(new[] { DataQueueHandler }, settings);
}
protected override BaseDataExchange GetBaseDataExchange()
{
var result = base.GetBaseDataExchange();
result.SleepInterval = 10;
return result;
}
protected override IDataQueueHandler GetDataQueueHandler()
{
return TestDataQueueHandlerManager;
}
public override void Exit()
{
base.Exit();
DataQueueHandler.DisposeSafely();
}
}
internal class TestDataQueueHandlerManager : DataQueueHandlerManager
{
public ITimeProvider TimeProvider { get; set; }
public TestDataQueueHandlerManager(IEnumerable<IDataQueueHandler> dataQueueHandlers, IAlgorithmSettings settings)
: base(settings)
{
DataHandlers = dataQueueHandlers.ToList();
}
protected override ITimeProvider InitializeFrontierTimeProvider()
{
return TimeProvider;
}
}
internal class TestDataChannelProvider : DataChannelProvider
{
public override bool ShouldStreamSubscription(SubscriptionDataConfig config)
{
if (config.Type == typeof(IndexedLinkedData))
{
return true;
}
return base.ShouldStreamSubscription(config);
}
}
internal class TestableLiveSynchronizer : LiveSynchronizer
{
private readonly ITimeProvider _timeProvider;
private readonly int _newLiveDataTimeout;
public ManualResetEventSlim NewDataEvent { get; set; }
public TestableLiveSynchronizer(ITimeProvider timeProvider = null, int? newLiveDataTimeout = null)
{
NewDataEvent = new ManualResetEventSlim(true);
_timeProvider = timeProvider ?? new RealTimeProvider();
_newLiveDataTimeout = newLiveDataTimeout ?? 10;
}
protected override int GetPulseDueTime(DateTime now)
{
return _newLiveDataTimeout;
}
protected override ITimeProvider GetTimeProvider()
{
return _timeProvider;
}
protected override void OnSubscriptionNewDataAvailable(object sender, EventArgs args)
{
base.OnSubscriptionNewDataAvailable(sender, args);
NewDataEvent.Set();
}
}
internal class TestCustomData : BaseData
{
public static int ReaderCallsCount;
public static bool ReturnNull { get; set; }
public static bool ThrowException { get; set; }
public static FileFormat FileFormat { get; set; }
static TestCustomData()
{
ReaderCallsCount = 0;
FileFormat = FileFormat.Csv;
}
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
Interlocked.Increment(ref ReaderCallsCount);
if (ThrowException)
{
throw new RegressionTestException("Custom data Reader threw exception");
}
else if (ReturnNull)
{
return null;
}
else
{
var data = new TestCustomData
{
// return not null but 'old data' -> there is no data yet available for today
Time = date.AddHours(-100),
Value = 1,
Symbol = config.Symbol
};
if (FileFormat == FileFormat.UnfoldingCollection)
{
return new BaseDataCollection
{
Time = date.AddHours(-100),
// return not null but 'old data' -> there is no data yet available for today
EndTime = date.AddHours(-99),
Value = 1,
Symbol = config.Symbol,
Data = new List<BaseData> { data }
};
}
return data;
}
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("localhost:1232/fake",
SubscriptionTransportMedium.Rest,
FileFormat);
}
}
}