Files
quantconnect--lean/Tests/Engine/RealTime/BacktestingRealTimeHandlerTests.cs
Martin-Molinero 9cdb4a91c5 Refactor live data feed (#4636)
* Live Coarse universe refactor

- Live trading will source Coarse and Fine fundamental data directly
  from disk. Updating unit tests.

* Adds ILiveDataProvider interface

  * Adds wrapper for IDataQueueHandler implementations

  * Replaces IDataQueueHandler with ILiveDataProvider in
    LiveTradingDataFeed

  * Edits IDataQueueHandler documentation

* Maintains aggregation for current IDQH impls and skips for ILDF impls

  * Note: No unit test was created for this method, go back and TODO

* Protobuf Market data

- Adding protobuf support for Ticks, TradeBars and QuoteBars. Adding
  unit tests.

* Adds unit tests for LiveDataAggregator changes

  * Fixes bug where custom data was not handled as it was before
  * Fixes race condition bug because of variable reuse in class

* Add protobuf extension serialization

* Fixes for protobuf serialization

* Refactor

* Fix OptionChainUniverse

* replace BaseDataExchange pumping ticks with consolidators

* AlpacaBrokerage

* BitfinexBrokerage

* GDAXBrokerage

* OandaBrokerage

* InteractiveBrokers

* TradierBrokerage

* FxcmBrokerage

* PaperBrokerage

* etc

* WIP fixes for existing LTDF unit tests

* Fixes more LTDF unit tests

* make IDataAggregator.Update recieving Generic BaseData rather than Tick

* Change IDataQueueHandler.Subscribe method

* Some fixes after adding new commits

* Adds protobuf (de)serialization support for Dividend and Split

* Serialize protobuf with length prefix

* Fix missing LTDF unit tests

* Adds TiingoNews protobuf definitions

* fix comments

* more fixes on IQFeedDataQueueHandler

* disallow putting ticks into enumerator directly

* ScannableEnumerator tests

* fix OandaBrokerage

* AggregationManager unit tests

* fix AlpacaBrokerage tests

* fix InteractiveBrokers

* fix FxcmBrokerage tests

* call AggregationManager.Remove method on unsubscribe

* fix GDAX existing tests

* Fixes, refactor adding more tests for AggregatorManager

* Adds BenzingaNews protobuf definitions and round trip unit test

* Adds missing TiingoNews unit test to Protobuf round trip tests

* Improve sleep sequence of LiveSynchronizer

* need start aggregating first, and then can subscribe

* More test fixes and refactor

- Refactoring AggregationManager and ScannableEnumerator so the last is
  the one that owns the consolidator
- Adding pulse on the main LiveSynchronizer

* Improve performance of LEquityDataSynchronizingEnu

* Add missing Set job packet method

* Minor performance improvements

* Improvements add test timeout

- Improvements adding test timeout to find blocking test in travis

* Improve aggregationManager performance

* Testing improvements for travis

* Remove test timeouts

* More test fixes

- Adding more missing dispose calls and improving determinism

* fix IEXDataQueueHandler and tests

* Final tweaks to LTDF tests

* more AggregationManager tests

* consume and log ticks

* fix test: couldn't subscribe to Forex tickers

* change Resolution for all bar configs

* Improve RealTimeScheduleEventServiceAccuracy

* refactoring: move common code to base class

* fixed bug; unsubscribe SubscriptionDataConfig

* Small performance improvement

* Minor fixes

* Avoid Symbol serialization

* Fixes coarse selection in live mode

* Fix for live coarse

* Adds protobuf (de)serialization support for Robintrack

  * Adds round-trip unit test

* Minor performance improvements

* More minor performance improvements

* pass LiveNodePacket through to OandaBrokerage

* Fixes empty list becoming null value when deserializing with protobuf

* Reverts BZ live trading exception removal and fixes tests

* Refactor WorkQueue making it abstract

* Add try catch for composer

* Adds optional data batching period to LiveFillForwardEnumerator

* Override data-queue-handler with config

* Improve PeriodCountConsolidator.Scan performance

* Move batching delay to main Synchornizer thread

* Reverts addition of Robintrack protobuf definitions

* Give priority to config history provider if set

* Add Estimize protobuffing

- Add Estimize protobuffing support. Adding unit tests

* Always dispose of data queue handler

Co-authored-by: Gerardo Salazar <gsalaz9800@gmail.com>
Co-authored-by: Adalyat Nazirov <aenazirov@gmail.com>
2020-08-18 20:21:10 -03:00

487 lines
19 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.Linq;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.AlgorithmFactory.Python.Wrappers;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.RealTime;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Packets;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
namespace QuantConnect.Tests.Engine.RealTime
{
[TestFixture]
public class BacktestingRealTimeHandlerTests
{
private IResultHandler _resultHandler;
[SetUp]
public void SetUp()
{
_resultHandler = new TestResultHandler();
}
[TearDown]
public void TearDown()
{
_resultHandler.Exit();
}
[Test]
public void SortsEventsAfterSetup()
{
var realTimeHandler = new BacktestingRealTimeHandler();
var algo = new TestAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.AddEquity("SPY");
var startDate = new DateTime(2019, 1, 1);
algo.SetStartDate(startDate);
algo.SetDateTime(startDate);
algo.SetEndDate(2020, 1, 1);
var firstCalled = false;
var secondCalled = false;
var events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> { startDate.AddMinutes(-10), startDate.AddMinutes(5)},
(s, time) => { firstCalled = true; }),
new ScheduledEvent("2", new List<DateTime> { startDate.AddMinutes(1)},
(s, time) => { secondCalled = true; }),
new ScheduledEvent("3", new List<DateTime> { startDate.AddMinutes(10)}, (s, time) => { })
};
foreach (var scheduledEvent in events)
{
realTimeHandler.Add(scheduledEvent);
}
realTimeHandler.Setup(algo,
new AlgorithmNodePacket(PacketType.AlgorithmNode),
new BacktestingResultHandler(),
null,
null);
realTimeHandler.SetTime(startDate.AddMinutes(1));
realTimeHandler.Exit();
Assert.IsTrue(secondCalled);
// 'first' should of been called and should be moved behind 'second' after setup
Assert.IsFalse(firstCalled);
}
[Test]
public void SingleScheduledEventFires_SetTime()
{
var realTimeHandler = new BacktestingRealTimeHandler();
var algo = new TestAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.AddEquity("SPY");
algo.SetStartDate(2019, 1, 1);
algo.SetDateTime(new DateTime(2019, 1, 1));
algo.SetEndDate(2020, 1, 1);
realTimeHandler.Setup(algo,
new AlgorithmNodePacket(PacketType.AlgorithmNode),
new BacktestingResultHandler(),
null,
null);
realTimeHandler.SetTime(DateTime.UtcNow);
realTimeHandler.Exit();
Assert.IsTrue(algo.OnEndOfDayFired);
}
[Test]
public void SingleScheduledEventFires_ScanPastEvents()
{
var realTimeHandler = new BacktestingRealTimeHandler();
var algo = new TestAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
algo.AddEquity("SPY");
algo.SetStartDate(2019, 1, 1);
algo.SetDateTime(new DateTime(2019, 1, 1));
algo.SetEndDate(2020, 1, 1);
realTimeHandler.Setup(algo,
new AlgorithmNodePacket(PacketType.AlgorithmNode),
new BacktestingResultHandler(),
null,
null);
realTimeHandler.ScanPastEvents(DateTime.UtcNow);
realTimeHandler.Exit();
Assert.IsTrue(algo.OnEndOfDayFired);
}
[Test]
public void TriggersScheduledEventsSameTimeInOrder()
{
var realTimeHandler = new BacktestingRealTimeHandler();
realTimeHandler.Setup(new AlgorithmStub(new NullDataFeed()),
new AlgorithmNodePacket(PacketType.AlgorithmNode),
new BacktestingResultHandler(),
null,
null);
var eventTime = DateTime.UtcNow;
var count = 0;
for (var i = 0; i < 100; i++)
{
var id = i;
realTimeHandler.Add(new ScheduledEvent($"{id}", eventTime,
(s, time) =>
{
Assert.AreEqual(id, count);
Assert.AreEqual(s, $"{id}");
count++;
}));
}
realTimeHandler.SetTime(DateTime.UtcNow);
realTimeHandler.Exit();
Assert.AreEqual(100, count);
}
[TestCase(false)]
[TestCase(true)]
public void SetTime(bool oneStep)
{
var realTimeHandler = new BacktestingRealTimeHandler();
realTimeHandler.Setup(new AlgorithmStub(new NullDataFeed()),
new AlgorithmNodePacket(PacketType.AlgorithmNode),
new BacktestingResultHandler(),
null,
null);
var date = new DateTime(2020, 1, 1);
var count = 0;
var asserts = 0;
realTimeHandler.Add(new ScheduledEvent("1",
new List<DateTime> { date, date.AddMinutes(10) },
(s, time) =>
{
count++;
if (count == 1)
{
asserts++;
Assert.AreEqual(date, time);
}
else if (oneStep ? count == 2 : count == 4)
{
asserts++;
Assert.AreEqual(date.AddMinutes(10), time);
}
}));
realTimeHandler.Add(new ScheduledEvent("2",
new List<DateTime> { date.AddMinutes(1), date.AddMinutes(2) },
(s, time) =>
{
count++;
if (oneStep ? count == 3 : count == 2)
{
asserts++;
Assert.AreEqual(date.AddMinutes(1), time);
}
else if (oneStep ? count == 4 : count == 3)
{
asserts++;
Assert.AreEqual(date.AddMinutes(2), time);
}
}));
if (oneStep)
{
realTimeHandler.SetTime(date.AddDays(1));
}
else
{
realTimeHandler.SetTime(date);
realTimeHandler.SetTime(date.AddMinutes(1));
realTimeHandler.SetTime(date.AddMinutes(2));
realTimeHandler.SetTime(date.AddMinutes(10));
}
realTimeHandler.Exit();
Assert.AreEqual(4, count);
Assert.AreEqual(4, asserts);
}
[Test]
public void SortRespectsOriginalOrderSameTime()
{
var date = new DateTime(2020, 1, 1);
var events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(1)}, (s, time) => { }),
new ScheduledEvent("3", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { })
};
BacktestingRealTimeHandler.SortFirstElement(events);
Assert.AreEqual(date.AddMinutes(1), events[0].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(10), events[1].NextEventUtcTime);
// scheduled event 3 and 1 have the same time, 3 should still be next else it would mean 1 executed twice when 3 once
Assert.AreEqual("3", events[1].Name);
Assert.AreEqual(date.AddMinutes(10), events[2].NextEventUtcTime);
Assert.AreEqual("1", events[2].Name);
events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(1)}, (s, time) => { }),
new ScheduledEvent("3", new List<DateTime> {date.AddMinutes(3)}, (s, time) => { }),
new ScheduledEvent("4", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("5", new List<DateTime> {date.AddMinutes(50)}, (s, time) => { })
};
BacktestingRealTimeHandler.SortFirstElement(events);
Assert.AreEqual(date.AddMinutes(1), events[0].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(3), events[1].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(10), events[2].NextEventUtcTime);
// scheduled event 4 and 1 have the same time, 4 should still be next else it would mean 1 executed twice when 4 once
Assert.AreEqual("4", events[2].Name);
Assert.AreEqual(date.AddMinutes(10), events[3].NextEventUtcTime);
Assert.AreEqual("1", events[3].Name);
Assert.AreEqual(date.AddMinutes(50), events[4].NextEventUtcTime);
}
[Test]
public void Sort()
{
var date = new DateTime(2020, 1, 1);
var events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(1)}, (s, time) => { })
};
BacktestingRealTimeHandler.SortFirstElement(events);
Assert.AreEqual(date.AddMinutes(1), events[0].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(10), events[1].NextEventUtcTime);
events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(1)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(3)}, (s, time) => { })
};
BacktestingRealTimeHandler.SortFirstElement(events);
Assert.AreEqual(date.AddMinutes(1), events[0].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(3), events[1].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(10), events[2].NextEventUtcTime);
events = new List<ScheduledEvent>
{
new ScheduledEvent("1", new List<DateTime> {date.AddMinutes(10)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(1)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(3)}, (s, time) => { }),
new ScheduledEvent("2", new List<DateTime> {date.AddMinutes(50)}, (s, time) => { })
};
BacktestingRealTimeHandler.SortFirstElement(events);
Assert.AreEqual(date.AddMinutes(1), events[0].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(3), events[1].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(10), events[2].NextEventUtcTime);
Assert.AreEqual(date.AddMinutes(50), events[3].NextEventUtcTime);
}
[TestCase(false)]
[TestCase(true)]
public void ScanPastEvents(bool oneStep)
{
var realTimeHandler = new BacktestingRealTimeHandler();
realTimeHandler.Setup(new AlgorithmStub(),
new AlgorithmNodePacket(PacketType.AlgorithmNode) { Language = Language.CSharp },
_resultHandler,
null,
new TestTimeLimitManager());
var date = new DateTime(2020, 1, 1);
var count = 0;
var asserts = 0;
realTimeHandler.Add(new ScheduledEvent("1",
new List<DateTime> { date, date.AddMinutes(10) },
(s, time) =>
{
count++;
if (count == 1)
{
asserts++;
Assert.AreEqual(date, time);
}
else if (count == 4)
{
asserts++;
Assert.AreEqual(date.AddMinutes(10), time);
}
}));
realTimeHandler.Add(new ScheduledEvent("2",
new List<DateTime> { date.AddMinutes(1), date.AddMinutes(2) },
(s, time) =>
{
count++;
if (count == 2)
{
asserts++;
Assert.AreEqual(date.AddMinutes(1), time);
}
else if (count == 3)
{
asserts++;
Assert.AreEqual(date.AddMinutes(2), time);
}
}));
if (oneStep)
{
realTimeHandler.ScanPastEvents(date.AddDays(1));
}
else
{
realTimeHandler.ScanPastEvents(date.AddMilliseconds(1));
realTimeHandler.ScanPastEvents(date.AddMinutes(1).AddMilliseconds(1));
realTimeHandler.ScanPastEvents(date.AddMinutes(2).AddMilliseconds(1));
realTimeHandler.ScanPastEvents(date.AddMinutes(10).AddMilliseconds(1));
}
realTimeHandler.Exit();
Assert.AreEqual(4, count);
Assert.AreEqual(4, asserts);
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void DoesNotAddOnEndOfDayEventsIfNotImplemented(Language language)
{
Security security;
IAlgorithm algorithm;
if (language == Language.CSharp)
{
algorithm = new AlgorithmStub();
security = (algorithm as QCAlgorithm).AddEquity("SPY");
}
else
{
algorithm = new AlgorithmPythonWrapper("Test_CustomDataAlgorithm");
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
security = algorithm.AddSecurity(SecurityType.Equity,
"SPY",
Resolution.Daily,
Market.USA,
false,
1,
false);
}
var realTimeHandler = new TestBacktestingRealTimeHandler();
realTimeHandler.Setup(algorithm,
new AlgorithmNodePacket(PacketType.AlgorithmNode) { Language = language },
_resultHandler,
null,
new TestTimeLimitManager());
// the generic OnEndOfDay()
Assert.AreEqual(1, realTimeHandler.GetScheduledEventsCount);
realTimeHandler.OnSecuritiesChanged(
new SecurityChanges(new[] { security }, Enumerable.Empty<Security>()));
Assert.AreEqual(1, realTimeHandler.GetScheduledEventsCount);
realTimeHandler.Exit();
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AddsOnEndOfDayEventsIfImplemented(Language language)
{
Security security;
IAlgorithm algorithm;
if (language == Language.CSharp)
{
algorithm = new TestAlgorithm();
security = (algorithm as QCAlgorithm).AddEquity("SPY");
}
else
{
algorithm = new AlgorithmPythonWrapper("OnEndOfDayRegressionAlgorithm");
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(new MockDataFeed(), algorithm));
security = algorithm.AddSecurity(SecurityType.Equity,
"SPY",
Resolution.Daily,
Market.USA,
false,
1,
false);
}
var realTimeHandler = new TestBacktestingRealTimeHandler();
realTimeHandler.Setup(algorithm,
new AlgorithmNodePacket(PacketType.AlgorithmNode) { Language = language },
_resultHandler,
null,
new TestTimeLimitManager());
// the generic OnEndOfDay()
Assert.AreEqual(1, realTimeHandler.GetScheduledEventsCount);
realTimeHandler.OnSecuritiesChanged(
new SecurityChanges(new[] { security }, Enumerable.Empty<Security>()));
Assert.AreEqual(2, realTimeHandler.GetScheduledEventsCount);
realTimeHandler.Exit();
}
private class TestTimeLimitManager : IIsolatorLimitResultProvider
{
public IsolatorLimitResult IsWithinLimit()
{
throw new NotImplementedException();
}
public void RequestAdditionalTime(int minutes)
{
throw new NotImplementedException();
}
public bool TryRequestAdditionalTime(int minutes)
{
throw new NotImplementedException();
}
}
private class TestBacktestingRealTimeHandler : BacktestingRealTimeHandler
{
public int GetScheduledEventsCount => ScheduledEvents.Count;
}
private class TestAlgorithm : AlgorithmStub
{
public bool OnEndOfDayFired { get; set; }
public override void OnEndOfDay()
{
OnEndOfDayFired = true;
}
public override void OnEndOfDay(Symbol symbol)
{
}
}
}
}