chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,98 @@
/*
* 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 Newtonsoft.Json;
using NUnit.Framework;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class AuxiliaryDataSerializationTests
{
private readonly JsonSerializerSettings _settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
[Test]
public void DeserializesSplitWarning()
{
var splitWarning = new Split(Symbols.AAPL, new DateTime(2014, 6, 9), 645.57m, 0.142857m, SplitType.Warning);
var json = JsonConvert.SerializeObject(splitWarning, _settings);
var deserialized = (Split)JsonConvert.DeserializeObject(json, _settings);
Assert.AreEqual(splitWarning.Symbol, deserialized.Symbol);
Assert.AreEqual(splitWarning.Time, deserialized.Time);
Assert.AreEqual(splitWarning.Type, deserialized.Type);
Assert.AreEqual(splitWarning.ReferencePrice, deserialized.ReferencePrice);
Assert.AreEqual(splitWarning.SplitFactor, deserialized.SplitFactor);
}
[Test]
public void DeserializesSplit()
{
var split = new Split(Symbols.AAPL, new DateTime(2014, 6, 9), 645.57m, 0.142857m, SplitType.SplitOccurred);
var json = JsonConvert.SerializeObject(split, _settings);
var deserialized = (Split)JsonConvert.DeserializeObject(json, _settings);
Assert.AreEqual(split.Symbol, deserialized.Symbol);
Assert.AreEqual(split.Time, deserialized.Time);
Assert.AreEqual(split.Type, deserialized.Type);
Assert.AreEqual(split.ReferencePrice, deserialized.ReferencePrice);
Assert.AreEqual(split.SplitFactor, deserialized.SplitFactor);
}
[Test]
public void DeserializesDividend()
{
var dividend = new Dividend(Symbols.AAPL, new DateTime(2014, 11, 6), 0.47m, 108.60m);
var json = JsonConvert.SerializeObject(dividend, _settings);
var deserialized = (Dividend)JsonConvert.DeserializeObject(json, _settings);
Assert.AreEqual(dividend.Symbol, deserialized.Symbol);
Assert.AreEqual(dividend.Time, deserialized.Time);
Assert.AreEqual(dividend.Distribution, deserialized.Distribution);
}
[Test]
public void DeserializesDelistingWarning()
{
var delistingWarning = new Delisting(Symbols.AAPL, new DateTime(2999, 12, 31), 100m, DelistingType.Warning);
var json = JsonConvert.SerializeObject(delistingWarning, _settings);
var deserialized = (Delisting)JsonConvert.DeserializeObject(json, _settings);
Assert.AreEqual(delistingWarning.Symbol, deserialized.Symbol);
Assert.AreEqual(delistingWarning.Time, deserialized.Time);
Assert.AreEqual(delistingWarning.Type, deserialized.Type);
}
[Test]
public void DeserializesDelisting()
{
var delisting = new Delisting(Symbols.AAPL, new DateTime(2999, 12, 31), 100m, DelistingType.Delisted);
var json = JsonConvert.SerializeObject(delisting, _settings);
var deserialized = (Delisting)JsonConvert.DeserializeObject(json, _settings);
Assert.AreEqual(delisting.Symbol, deserialized.Symbol);
Assert.AreEqual(delisting.Time, deserialized.Time);
Assert.AreEqual(delisting.Type, deserialized.Type);
}
}
}
@@ -0,0 +1,55 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class FactorFileRowTests
{
[Test]
public void ToCsv()
{
var row = new CorporateFactorRow(new DateTime(2000, 01, 01), 1m, 2m, 123m);
var actual = row.GetFileFormat("source");
var expected = "20000101,1,2,123,source";
Assert.AreEqual(expected, actual);
}
[Test]
public void AppliesDividendWithPreviousTradingDateEqualToRowDate()
{
var row = new CorporateFactorRow(new DateTime(2018, 08, 23), 1m, 2m, 123m);
var dividend = new Dividend(Symbols.SPY, row.Date.AddDays(1), 1m, 123m);
var updated = row.Apply(dividend, SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
Assert.AreEqual("20180823,0.9918699,2,123", updated.GetFileFormat());
}
[Test]
public void AppliesSplitWithPreviousTradingDateEqualToRowDate()
{
var row = new CorporateFactorRow(new DateTime(2018, 08, 23), 1m, 2m, 123m);
var dividend = new Split(Symbols.SPY, row.Date.AddDays(1), 123m, 2m, SplitType.SplitOccurred);
var updated = row.Apply(dividend, SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
Assert.AreEqual("20180823,1,4,123", updated.GetFileFormat());
}
}
}
@@ -0,0 +1,552 @@
/*
* 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.IO;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class FactorFileTests
{
[Test]
public void ReadsFactorFileWithoutInfValues()
{
var PermTick = "AAPL";
var Market = "usa";
var _symbol = new Symbol(SecurityIdentifier.GenerateEquity(PermTick, Market), PermTick);
var factorFile = TestGlobals.FactorFileProvider.Get(_symbol) as CorporateFactorProvider;
Assert.AreEqual(41, factorFile.SortedFactorFileData.Count);
Assert.AreEqual(new DateTime(1998, 01, 01), factorFile.FactorFileMinimumDate.Value);
}
[Test]
public void ReadsFactorFileWithExponentialNotation()
{
// Source NEWL factor file at 2019-12-09
var lines = new[]
{
"19980102,0.8116779,1e+07",
"20051108,0.8116779,1e+07",
"20060217,0.8416761,1e+07",
"20060516,0.8644420,1e+07",
"20060814,0.8747766,1e+07",
"20061115,0.8901232,1e+07",
"20070314,0.9082148,1e+07",
"20070522,0.9166239,1e+07",
"20070814,0.9306799,1e+07",
"20071120,0.9534326,1e+07",
"20080520,0.9830510,1e+07",
"20100802,1.0000000,1e+07",
"20131016,1.0000000,1.11111e+06",
"20131205,1.0000000,75188",
"20140305,1.0000000,25000",
"20140514,1.0000000,2500",
"20140714,1.0000000,50",
"20501231,1.0000000,1"
};
var factorFile = PriceScalingExtensions.SafeRead("PermTick", lines, SecurityType.Equity);
Assert.AreEqual(5, factorFile.Count());
Assert.IsNotNull(factorFile.FactorFileMinimumDate);
Assert.AreEqual(new DateTime(2013, 12, 04), factorFile.FactorFileMinimumDate.Value);
}
[Test]
public void ReadsFactorFileWithInfValues()
{
var lines = new[]
{
"19980102,1.0000000,inf",
"20151211,1.0000000,inf",
"20160330,1.0000000,2500",
"20160915,1.0000000,80",
"20501231,1.0000000,1"
};
DateTime? factorFileMinimumDate;
var factorFile = PriceScalingExtensions.SafeRead("PermTick", lines, SecurityType.Equity);
Assert.AreEqual(3, factorFile.Count());
Assert.IsNotNull(factorFile.FactorFileMinimumDate);
Assert.AreEqual(new DateTime(2016, 3, 29), factorFile.FactorFileMinimumDate.Value);
}
[Test]
public void CorrectlyDeterminesTimePriceFactors()
{
var reference = DateTime.Today;
const string symbol = "n/a";
var file = GetTestFactorFile(symbol, reference);
// time price factors should be the price factor * split factor
Assert.AreEqual(1, file.GetPriceFactor(reference, DataNormalizationMode.Adjusted));
Assert.AreEqual(1, file.GetPriceFactor(reference.AddDays(-6), DataNormalizationMode.Adjusted));
Assert.AreEqual(.9, file.GetPriceFactor(reference.AddDays(-7), DataNormalizationMode.Adjusted));
Assert.AreEqual(.9, file.GetPriceFactor(reference.AddDays(-13), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8, file.GetPriceFactor(reference.AddDays(-14), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8, file.GetPriceFactor(reference.AddDays(-20), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8m * .5m, file.GetPriceFactor(reference.AddDays(-21), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8m * .5m, file.GetPriceFactor(reference.AddDays(-22), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8m * .5m, file.GetPriceFactor(reference.AddDays(-89), DataNormalizationMode.Adjusted));
Assert.AreEqual(.8m * .25m, file.GetPriceFactor(reference.AddDays(-91), DataNormalizationMode.Adjusted));
}
[Test]
public void HasDividendEventOnNextTradingDay()
{
var reference = DateTime.Today;
const string symbol = "n/a";
decimal priceFactorRatio;
decimal referencePrice;
var file = GetTestFactorFile(symbol, reference);
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference, out priceFactorRatio, out referencePrice));
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-6), out priceFactorRatio, out referencePrice));
Assert.IsTrue(file.HasDividendEventOnNextTradingDay(reference.AddDays(-7), out priceFactorRatio, out referencePrice));
Assert.AreEqual(.9m/1m, priceFactorRatio);
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-8), out priceFactorRatio, out referencePrice));
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-13), out priceFactorRatio, out referencePrice));
Assert.IsTrue(file.HasDividendEventOnNextTradingDay(reference.AddDays(-14), out priceFactorRatio, out referencePrice));
Assert.AreEqual(.8m / .9m, priceFactorRatio);
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-15), out priceFactorRatio, out referencePrice));
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-364), out priceFactorRatio, out referencePrice));
Assert.IsTrue(file.HasDividendEventOnNextTradingDay(reference.AddDays(-365), out priceFactorRatio, out referencePrice));
Assert.AreEqual(.7m / .8m, priceFactorRatio);
Assert.IsFalse(file.HasDividendEventOnNextTradingDay(reference.AddDays(-366), out priceFactorRatio, out referencePrice));
Assert.IsNull(file.FactorFileMinimumDate);
}
[Test]
public void HasSplitEventOnNextTradingDay()
{
var reference = DateTime.Today;
const string symbol = "n/a";
decimal splitFactor;
decimal referencePrice;
var file = GetTestFactorFile(symbol, reference);
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference, out splitFactor, out referencePrice));
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-20), out splitFactor, out referencePrice));
Assert.IsTrue(file.HasSplitEventOnNextTradingDay(reference.AddDays(-21), out splitFactor, out referencePrice));
Assert.AreEqual(.5, splitFactor);
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-22), out splitFactor, out referencePrice));
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-89), out splitFactor, out referencePrice));
Assert.IsTrue(file.HasSplitEventOnNextTradingDay(reference.AddDays(-90), out splitFactor, out referencePrice));
Assert.AreEqual(.5, splitFactor);
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-91), out splitFactor, out referencePrice));
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-364), out splitFactor, out referencePrice));
Assert.IsTrue(file.HasSplitEventOnNextTradingDay(reference.AddDays(-365), out splitFactor, out referencePrice));
Assert.AreEqual(.5, splitFactor);
Assert.IsFalse(file.HasSplitEventOnNextTradingDay(reference.AddDays(-366), out splitFactor, out referencePrice));
Assert.IsNull(file.FactorFileMinimumDate);
}
[Test]
public void GeneratesCorrectSplitsAndDividends()
{
var reference = new DateTime(2018, 01, 01);
var file = GetTestFactorFile("SPY", reference);
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var splitsAndDividends = file.GetSplitsAndDividends(Symbols.SPY, exchangeHours);
var dividend = (Dividend)splitsAndDividends.Single(d => d.Time == reference.AddDays(-6));
var distribution = Dividend.ComputeDistribution(100m, .9m / 1m, 2);
Assert.AreEqual(distribution, dividend.Distribution);
dividend = (Dividend) splitsAndDividends.Single(d => d.Time == reference.AddDays(-13));
distribution = Math.Round(Dividend.ComputeDistribution(100m, .8m / .9m, 2), 2);
Assert.AreEqual(distribution, dividend.Distribution);
var split = (Split) splitsAndDividends.Single(d => d.Time == reference.AddDays(-20));
var splitFactor = .5m;
Assert.AreEqual(splitFactor, split.SplitFactor);
split = (Split) splitsAndDividends.Single(d => d.Time == reference.AddDays(-89));
splitFactor = .5m;
Assert.AreEqual(splitFactor, split.SplitFactor);
dividend = splitsAndDividends.OfType<Dividend>().Single(d => d.Time == reference.AddDays(-363));
distribution = Dividend.ComputeDistribution(100m, .7m / .8m, 2);
Assert.AreEqual(distribution, dividend.Distribution);
split = splitsAndDividends.OfType<Split>().Single(d => d.Time == reference.AddDays(-363));
splitFactor = .5m;
Assert.AreEqual(splitFactor, split.SplitFactor);
}
[Test]
public void GetsSplitsAndDividends()
{
var factorFile = GetFactorFile_AAPL2018_05_11();
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var splitsAndDividends = factorFile.GetSplitsAndDividends(Symbols.AAPL, exchangeHours).ToList();
foreach (var sad in splitsAndDividends)
{
Log.Trace($"{sad.Time.Date:yyyy-MM-dd}: {sad}");
}
var splits = splitsAndDividends.OfType<Split>().ToList();
var dividends = splitsAndDividends.OfType<Dividend>().ToList();
var dividend = dividends.Single(d => d.Time == new DateTime(2018, 05, 11));
Assert.AreEqual(0.73m, dividend.Distribution.RoundToSignificantDigits(6));
var split = splits.Single(d => d.Time == new DateTime(2014, 06, 09));
Assert.AreEqual((1/7m).RoundToSignificantDigits(6), split.SplitFactor);
}
[Test]
public void AppliesDividend()
{
var factorFileBeforeDividend = GetFactorFile_AAPL2018_05_08();
var factorFileAfterDividend = GetFactorFile_AAPL2018_05_11();
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var dividend = new Dividend(Symbols.AAPL, new DateTime(2018, 05, 11), 0.73m, 190.03m);
var actual = factorFileBeforeDividend.Apply(new List<BaseData> {dividend}, exchangeHours);
Assert.AreEqual(factorFileAfterDividend.Count(), actual.Count());
Assert.True(actual.First().Date == new DateTime(1998, 01, 02),
$"Factor file first row changed from 1998-01-02 to {actual.First().Date:yyyy-MM-dd} after applying new event");
foreach (var item in factorFileAfterDividend.Reverse().Zip(actual.Reverse(), (a,e) => new{actual=a, expected=e}))
{
var expected = (CorporateFactorRow)item.expected;
var actualRow = (CorporateFactorRow)item.actual;
Log.Trace($"expected: {item.expected} actual: {item.actual} diff: {100* (1 - actualRow.PriceFactor/expected.PriceFactor):0.0000}%");
Assert.AreEqual(item.expected.Date, item.actual.Date);
Assert.AreEqual(expected.ReferencePrice, actualRow.ReferencePrice);
Assert.AreEqual(expected.SplitFactor, actualRow.SplitFactor);
var delta = (double)expected.PriceFactor * 1e-5;
Assert.AreEqual((double)expected.PriceFactor, (double)actualRow.PriceFactor, delta);
}
}
[Test]
public void AppliesSplit()
{
var factorFileBeforeSplit = GetFactorFile_LODE20191127();
var factorFileAfterSplit = GetFactorFile_LODE20191129();
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var eventTime = new DateTime(2019, 11, 29);
var split = new Split(Symbols.LODE, eventTime, 0.06m, 5, SplitType.SplitOccurred);
var actual = factorFileBeforeSplit.Apply(new List<BaseData> { split }, exchangeHours);
Assert.AreEqual(factorFileAfterSplit.Count(), actual.Count());
Assert.True(actual.First().Date == new DateTime(1998, 01, 02),
$"Factor file first row changed from 1998-01-02 to {actual.First().Date:yyyy-MM-dd} after applying new event");
Assert.True(((CorporateFactorRow)actual.First()).SplitFactor == 25m, "Factor File split factor is not computed correctly");
foreach (var item in actual.Reverse().Zip(factorFileAfterSplit.Reverse(), (a, e) => new { actual = a, expected = e }))
{
var expected = (CorporateFactorRow)item.expected;
var actualRow = (CorporateFactorRow)item.actual;
Log.Trace($"expected: {item.expected} actual: {item.actual} diff: {100 * (1 - actualRow.PriceFactor / expected.PriceFactor):0.0000}%");
Assert.AreEqual(item.expected.Date, item.actual.Date);
Assert.AreEqual(expected.ReferencePrice, actualRow.ReferencePrice);
Assert.AreEqual(expected.SplitFactor, actualRow.SplitFactor);
var delta = (double)expected.PriceFactor * 1e-5;
Assert.AreEqual((double)expected.PriceFactor, (double)actualRow.PriceFactor, delta);
}
}
[Test]
public void CanHandleRepeatedEventsCorrectly()
{
var factorFileBeforeSplit = GetFactorFile_LODE20191127();
var factorFileAfterSplit = GetFactorFile_LODE20191129();
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var eventTime = new DateTime(2019, 11, 29);
var split = new Split(Symbols.LODE, eventTime, 0.06m, 5, SplitType.SplitOccurred);
var events = new List<BaseData> { split, split, split };
var actual = factorFileBeforeSplit.Apply(events, exchangeHours);
Assert.AreEqual(factorFileAfterSplit.Count(), actual.Count());
Assert.True(actual.First().Date == new DateTime(1998, 01, 02),
$"Factor file first row changed from 1998-01-02 to {actual.First().Date:yyyy-MM-dd} after applying new event");
Assert.True(((CorporateFactorRow)actual.First()).SplitFactor == 25m, "Factor File split factor is not computed correctly");
foreach (var item in actual.Reverse().Zip(factorFileAfterSplit.Reverse(), (a, e) => new { actual = a, expected = e }))
{
var expectedRow = (CorporateFactorRow)item.expected;
var actualRow = (CorporateFactorRow)item.actual;
Log.Trace($"expected: {item.expected} actual: {item.actual} diff: {100 * (1 - actualRow.PriceFactor / expectedRow.PriceFactor):0.0000}%");
Assert.AreEqual(item.expected.Date, item.actual.Date);
Assert.AreEqual(expectedRow.ReferencePrice, actualRow.ReferencePrice);
Assert.AreEqual(expectedRow.SplitFactor, actualRow.SplitFactor);
var delta = (double)expectedRow.PriceFactor * 1e-5;
Assert.AreEqual((double)expectedRow.PriceFactor, (double)actualRow.PriceFactor, delta);
}
}
[Test]
public void AppliesSplitAndDividendAtSameTime()
{
var reference = new DateTime(2018, 08, 01);
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(QuantConnect.Market.USA, Symbols.SPY, SecurityType.Equity);
var expected = GetTestFactorFile("AAPL", reference);
// remove the last entry that contains a split and dividend at the same time
var factorFile = new CorporateFactorProvider("AAPL", expected.SortedFactorFileData.Where(kvp => kvp.Value.Single().PriceFactor >= .8m).Select(kvp => kvp.Value.Single()));
var actual = factorFile.Apply(new List<BaseData>
{
new Split(Symbols.AAPL, reference.AddDays(-364), 100m, 1 / 2m, SplitType.SplitOccurred),
new Dividend(Symbols.AAPL, reference.AddDays(-364), 12.5m, 100m)
}, exchangeHours);
foreach (var item in actual.Reverse().Zip(expected.Reverse(), (a, e) => new {actual = a, expected = e}))
{
var expectedRow = (CorporateFactorRow)item.expected;
var actualRow = (CorporateFactorRow)item.actual;
Log.Trace($"expected: {item.expected} actual: {item.actual} diff: {100 * (1 - actualRow.PriceFactor / expectedRow.PriceFactor):0.0000}%");
Assert.AreEqual(item.expected.Date, item.actual.Date);
Assert.AreEqual(expectedRow.ReferencePrice, actualRow.ReferencePrice);
Assert.AreEqual(expectedRow.SplitFactor, actualRow.SplitFactor);
Assert.AreEqual(expectedRow.PriceFactor.RoundToSignificantDigits(4), actualRow.PriceFactor.RoundToSignificantDigits(4));
}
}
[Test]
public void ReadsOldFactorFileFormat()
{
var lines = new[]
{
"19980102,1.0000000,0.5",
"20130828,1.0000000,0.5",
"20501231,1.0000000,1"
};
var factorFile = PriceScalingExtensions.SafeRead("bno", lines, SecurityType.Equity) as CorporateFactorProvider;
var firstRow = factorFile.SortedFactorFileData[new DateTime(1998, 01, 02)].Single();
Assert.AreEqual(1m, firstRow.PriceFactor);
Assert.AreEqual(0.5m, firstRow.SplitFactor);
Assert.AreEqual(0m, firstRow.ReferencePrice);
var secondRow = factorFile.SortedFactorFileData[new DateTime(2013, 08, 28)].Single();
Assert.AreEqual(1m, secondRow.PriceFactor);
Assert.AreEqual(0.5m, secondRow.SplitFactor);
Assert.AreEqual(0m, firstRow.ReferencePrice);
var thirdRow = factorFile.SortedFactorFileData[Time.EndOfTime].Single();
Assert.AreEqual(1m, thirdRow.PriceFactor);
Assert.AreEqual(1m, thirdRow.SplitFactor);
Assert.AreEqual(0m, firstRow.ReferencePrice);
}
[Test]
public void HandlesUnknownDataMappingModes()
{
var lines = new[]
{
"{\"Date\":\"2010-01-28T00:00:00\",\"BackwardsRatioScale\":[1.1575],\"BackwardsPanamaCanalScale\":[7.06],\"ForwardPanamaCanalScale\":[0.0],\"DataMappingMode\":1}",
"{\"Date\":\"2010-02-25T00:00:00\",\"BackwardsRatioScale\":[1.1575],\"BackwardsPanamaCanalScale\":[7.06],\"ForwardPanamaCanalScale\":[0.0],\"DataMappingMode\":788}"
};
var factorFile = PriceScalingExtensions.SafeRead("cl", lines, SecurityType.Future) as MappingContractFactorProvider;
Assert.AreEqual(1, factorFile.Count());
Assert.AreEqual(DataMappingMode.FirstDayMonth, (factorFile.First() as MappingContractFactorRow).DataMappingMode);
}
[Test]
public void ResolvesCorrectMostRecentFactorChangeDate()
{
var lines = new[]
{
"19980102,1.0000000,0.5",
"20130828,1.0000000,0.5",
"20501231,1.0000000,1"
};
var factorFile = PriceScalingExtensions.SafeRead("bno", lines, SecurityType.Equity) as CorporateFactorProvider;
Assert.AreEqual(new DateTime(2013, 08, 28), factorFile.MostRecentFactorChange);
}
[Test]
[TestCase("")]
[TestCase("20501231,1.0000000,1")]
public void EmptyFactorFileReturnsEmptyListForSplitsAndDividends(string contents)
{
var lines = contents.Split('\n').Where(l => !string.IsNullOrWhiteSpace(l));
var factorFile = PriceScalingExtensions.SafeRead("bno", lines, SecurityType.Equity) as CorporateFactorProvider;
Assert.IsEmpty(factorFile.GetSplitsAndDividends(Symbols.SPY, SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork)));
}
private static CorporateFactorProvider GetTestFactorFile(string symbol, DateTime reference)
{
var file = new CorporateFactorProvider(symbol, new List<CorporateFactorRow>
{
new CorporateFactorRow(reference, 1, 1),
new CorporateFactorRow(reference.AddDays(-7), .9m, 1, 100m), // dividend
new CorporateFactorRow(reference.AddDays(-14), .8m, 1, 100m), // dividend
new CorporateFactorRow(reference.AddDays(-21), .8m, .5m, 100m), // split
new CorporateFactorRow(reference.AddDays(-90), .8m, .25m, 100m), // split
new CorporateFactorRow(reference.AddDays(-365), .7m, .125m, 100m) // split+dividend
});
return file;
}
private static CorporateFactorProvider GetFactorFile_LODE20191127()
{
const string factorFileContents = @"
19980102,1,5,8.5,qq
20171109,1,5,0.12,qq
20501231,1,1,0,qq
";
DateTime? factorFileMinimumDate;
using var reader = new StreamReader(factorFileContents.ToStream());
using var streamReaderEnumerable = new StreamReaderEnumerable(reader);
var enumerable = streamReaderEnumerable.Where(line => line.Length > 0);
var factorFileRows = CorporateFactorRow.Parse(enumerable, out factorFileMinimumDate);
return new CorporateFactorProvider("lode", factorFileRows, factorFileMinimumDate);
}
private static CorporateFactorProvider GetFactorFile_LODE20191129()
{
const string factorFileContents = @"
19980102,1,25,8.5,qq
20171109,1,25,0.12,qq
20191127,1,5,0.06,qq
20501231,1,1,0,qq
";
DateTime? factorFileMinimumDate;
using var reader = new StreamReader(factorFileContents.ToStream());
using var streamReaderEnumerable = new StreamReaderEnumerable(reader);
var enumerable = streamReaderEnumerable.Where(line => line.Length > 0);
var factorFileRows = CorporateFactorRow.Parse(enumerable, out factorFileMinimumDate);
return new CorporateFactorProvider("lode", factorFileRows, factorFileMinimumDate);
}
private static CorporateFactorProvider GetFactorFile_AAPL2018_05_11()
{
const string factorFileContents = @"
19980102,0.8893653,0.0357143,16.25
20000620,0.8893653,0.0357143,101
20050225,0.8893653,0.0714286,88.97
20120808,0.8893653,0.142857,619.85
20121106,0.8931837,0.142857,582.85
20130206,0.8972636,0.142857,457.285
20130508,0.9024937,0.142857,463.71
20130807,0.908469,0.142857,464.94
20131105,0.9144679,0.142857,525.58
20140205,0.9198056,0.142857,512.59
20140507,0.9253111,0.142857,592.34
20140606,0.9304792,0.142857,645.57
20140806,0.9304792,1,94.96
20141105,0.9351075,1,108.86
20150204,0.9391624,1,119.55
20150506,0.9428692,1,125.085
20150805,0.9468052,1,115.4
20151104,0.9510909,1,122.01
20160203,0.9551617,1,96.34
20160504,0.9603451,1,94.19
20160803,0.9661922,1,105.8
20161102,0.9714257,1,111.6
20170208,0.9764128,1,132.04
20170510,0.9806461,1,153.26
20170809,0.9846939,1,161.1
20171109,0.9885598,1,175.87
20180208,0.9921138,1,155.16
20180510,0.9961585,1,190.03
20501231,1,1,0
";
DateTime? factorFileMinimumDate;
using var reader = new StreamReader(factorFileContents.ToStream());
using var streamReaderEnumerable = new StreamReaderEnumerable(reader);
var enumerable = streamReaderEnumerable.Where(line => line.Length > 0);
var factorFileRows = CorporateFactorRow.Parse(enumerable, out factorFileMinimumDate);
return new CorporateFactorProvider("aapl", factorFileRows, factorFileMinimumDate);
}
// AAPL experiences a 0.73 dividend distribution on 2018.05.11
private static CorporateFactorProvider GetFactorFile_AAPL2018_05_08()
{
const string factorFileContents = @"
19980102,0.8927948,0.0357143,16.25
20000620,0.8927948,0.0357143,101
20050225,0.8927948,0.0714286,88.97
20120808,0.8927948,0.142857,619.85
20121106,0.8966279,0.142857,582.85
20130206,0.9007235,0.142857,457.285
20130508,0.9059737,0.142857,463.71
20130807,0.9119721,0.142857,464.94
20131105,0.9179942,0.142857,525.58
20140205,0.9233525,0.142857,512.59
20140507,0.9288793,0.142857,592.34
20140606,0.9340673,0.142857,645.57
20140806,0.9340673,1,94.96
20141105,0.9387135,1,108.86
20150204,0.942784,1,119.55
20150506,0.9465051,1,125.085
20150805,0.9504563,1,115.4
20151104,0.9547586,1,122.01
20160203,0.9588451,1,96.34
20160504,0.9640485,1,94.19
20160803,0.9699181,1,105.8
20161102,0.9751718,1,111.6
20170208,0.9801781,1,132.04
20170510,0.9844278,1,153.26
20170809,0.9884911,1,161.1
20171109,0.992372,1,175.87
20180208,0.9959397,1,155.16
20501231,1,1,0
";
DateTime? factorFileMinimumDate;
using var reader = new StreamReader(factorFileContents.ToStream());
using var streamReaderEnumerable = new StreamReaderEnumerable(reader);
var enumerable = streamReaderEnumerable.Where(line => line.Length > 0);
var factorFileRows = CorporateFactorRow.Parse(enumerable, out factorFileMinimumDate);
return new CorporateFactorProvider("aapl", factorFileRows, factorFileMinimumDate);
}
}
}
@@ -0,0 +1,81 @@
/*
* 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.Globalization;
using System.IO;
using NUnit.Framework;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Interfaces;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class LocalDiskFactorFileProviderTests
{
internal IFactorFileProvider FactorFileProvider;
[OneTimeSetUp]
public virtual void Setup()
{
FactorFileProvider = TestGlobals.FactorFileProvider;
}
[Test]
public void RetrievesFromDisk()
{
var factorFile = FactorFileProvider.Get(Symbols.SPY);
Assert.IsNotNull(factorFile);
}
[Test]
public void CachesValueAndReturnsSameReference()
{
var factorFile1 = FactorFileProvider.Get(Symbols.SPY);
var factorFile2 = FactorFileProvider.Get(Symbols.SPY);
Assert.IsTrue(ReferenceEquals(factorFile1, factorFile2));
}
[Test]
public void ReturnsNullForNotFound()
{
var factorFile = FactorFileProvider.Get(Symbol.Create("not-a-ticker", SecurityType.Equity, QuantConnect.Market.USA)) as CorporateFactorProvider;
Assert.IsNotNull(factorFile);
Assert.IsEmpty(factorFile);
}
[Test, Ignore("This test is meant to be run manually")]
public void FindsFactorFilesWithErrors()
{
var factorFileFolder = Path.Combine(Globals.DataFolder, "equity", QuantConnect.Market.USA, "factor_files");
foreach (var fileName in Directory.EnumerateFiles(factorFileFolder))
{
var ticker = Path.GetFileNameWithoutExtension(fileName).ToUpper(CultureInfo.InvariantCulture);
var symbol = Symbol.Create(ticker, SecurityType.Equity, QuantConnect.Market.USA);
try
{
FactorFileProvider.Get(symbol);
}
catch (Exception exception)
{
Console.WriteLine(ticker + ": " + exception.Message);
}
}
}
}
}
@@ -0,0 +1,44 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class LocalDiskMapFileProviderTests
{
[Test]
public void RetrievesFromDisk()
{
var provider = new LocalDiskMapFileProvider();
provider.Initialize(TestGlobals.DataProvider);
var mapFiles = provider.Get(AuxiliaryDataKey.EquityUsa);
Assert.IsNotEmpty(mapFiles);
}
[Test]
public void CachesValueAndReturnsSameReference()
{
var provider = new LocalDiskMapFileProvider();
provider.Initialize(TestGlobals.DataProvider);
var mapFiles1 = provider.Get(AuxiliaryDataKey.EquityUsa);
var mapFiles2 = provider.Get(AuxiliaryDataKey.EquityUsa);
Assert.IsTrue(ReferenceEquals(mapFiles1, mapFiles2));
}
}
}
@@ -0,0 +1,111 @@
/*
* 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.IO;
using NUnit.Framework;
using System.Threading;
using QuantConnect.Util;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class LocalZipFactorFileProviderTests : LocalDiskFactorFileProviderTests
{
private string _zipFilePath;
[OneTimeSetUp]
public override void Setup()
{
// Take our repo included factor files and zip them up for these tests
var date = DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork).Date.AddDays(-1);
var path = Path.Combine(Globals.DataFolder, $"equity/usa/factor_files/");
var tmp = "./tmp.zip";
_zipFilePath = Path.Combine(Globals.DataFolder, $"equity/usa/factor_files/factor_files_{date:yyyyMMdd}.zip");
// Have to compress to tmp file or else it doesn't finish reading all the files in dir
QuantConnect.Compression.ZipDirectory(path, tmp);
File.Move(tmp, _zipFilePath, true);
FactorFileProvider = new LocalZipFactorFileProvider();
FactorFileProvider.Initialize(TestGlobals.MapFileProvider, TestGlobals.DataProvider);
}
[OneTimeTearDown]
public void TearDown()
{
if (File.Exists(_zipFilePath))
{
File.Delete(_zipFilePath);
}
}
[Test]
public void CacheIsCleared()
{
var fileProviderTest = new LocalZipFactorFileProviderTest();
using var dataProviderTest = new DefaultDataProviderTest();
fileProviderTest.Initialize(TestGlobals.MapFileProvider, dataProviderTest);
fileProviderTest.CacheCleared.Reset();
fileProviderTest.Get(Symbols.AAPL);
Assert.AreEqual(1, dataProviderTest.FetchCount);
Thread.Sleep(50);
fileProviderTest.Get(Symbols.AAPL);
Assert.AreEqual(1, dataProviderTest.FetchCount);
fileProviderTest.CacheCleared.WaitOne(TimeSpan.FromSeconds(2));
fileProviderTest.Get(Symbols.AAPL);
Assert.AreEqual(2, dataProviderTest.FetchCount);
fileProviderTest.Enabled = false;
dataProviderTest.DisposeSafely();
}
private class LocalZipFactorFileProviderTest : LocalZipFactorFileProvider
{
public bool Enabled = true;
public readonly ManualResetEvent CacheCleared = new (false);
protected override TimeSpan CacheRefreshPeriod => TimeSpan.FromMilliseconds(300);
protected override void StartExpirationTask()
{
if (Enabled)
{
base.StartExpirationTask();
CacheCleared.Set();
}
}
}
private class DefaultDataProviderTest : DefaultDataProvider
{
public int FetchCount { get; set; }
public override Stream Fetch(string key)
{
FetchCount++;
return base.Fetch(key);
}
}
}
}
@@ -0,0 +1,119 @@
/*
* 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.IO;
using System.Threading;
using NUnit.Framework;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Util;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class LocalZipMapFileProviderTests
{
private string _zipFilePath;
[OneTimeSetUp]
public void Setup()
{
// Take our repo included map files and zip them up for these tests
var date = DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork).Date.AddDays(-1);
var path = Path.Combine(Globals.DataFolder, $"equity/usa/map_files/");
var tmp = "./tmp.zip";
_zipFilePath = Path.Combine(Globals.DataFolder, $"equity/usa/map_files/map_files_{date:yyyyMMdd}.zip");
// Have to compress to tmp file or else it doesn't finish reading all the files in dir
QuantConnect.Compression.ZipDirectory(path, tmp);
File.Move(tmp, _zipFilePath, true);
}
[OneTimeTearDown]
public void TearDown()
{
if (File.Exists(_zipFilePath))
{
File.Delete(_zipFilePath);
}
}
[Test]
public void Retrieves()
{
var fileProviderTest = new LocalZipMapFileProviderTest();
using var dataProviderTest = new DefaultDataProviderTest();
fileProviderTest.Initialize(dataProviderTest);
var mapFileResolver = fileProviderTest.Get(AuxiliaryDataKey.EquityUsa);
fileProviderTest.Enabled = false;
dataProviderTest.DisposeSafely();
Assert.IsNotEmpty(mapFileResolver);
}
[Test]
public void CacheIsCleared()
{
var fileProviderTest = new LocalZipMapFileProviderTest();
using var dataProviderTest = new DefaultDataProviderTest();
fileProviderTest.Initialize(dataProviderTest);
fileProviderTest.CacheCleared.Reset();
fileProviderTest.Get(AuxiliaryDataKey.EquityUsa);
Assert.AreEqual(1, dataProviderTest.FetchCount);
Thread.Sleep(50);
fileProviderTest.Get(AuxiliaryDataKey.EquityUsa);
Assert.AreEqual(1, dataProviderTest.FetchCount);
fileProviderTest.CacheCleared.WaitOne(TimeSpan.FromSeconds(2));
fileProviderTest.Get(AuxiliaryDataKey.EquityUsa);
Assert.AreEqual(2, dataProviderTest.FetchCount);
fileProviderTest.Enabled = false;
dataProviderTest.DisposeSafely();
}
private class LocalZipMapFileProviderTest : LocalZipMapFileProvider
{
public bool Enabled = true;
public readonly ManualResetEvent CacheCleared = new(false);
protected override TimeSpan CacheRefreshPeriod => TimeSpan.FromMilliseconds(300);
protected override void StartExpirationTask()
{
if (Enabled)
{
base.StartExpirationTask();
CacheCleared.Set();
}
}
}
private class DefaultDataProviderTest : DefaultDataProvider
{
public int FetchCount { get; set; }
public override Stream Fetch(string key)
{
FetchCount++;
return base.Fetch(key);
}
}
}
}
+176
View File
@@ -0,0 +1,176 @@
/*
* 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.Globalization;
using System.IO;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Tests.Common.Data.Auxiliary
{
[TestFixture]
public class MapFileTests
{
[Test]
public void HandlesUnknownMappingMode()
{
var fileName = "testMapFile.csv";
var lines = new string[]
{
"20110221,cl uucg3i0a3zy9,NYMEX,1",
"20110418,cl uvvl4qhqe8xt,NYMEX,999"
};
File.WriteAllLines(fileName, lines);
var result = MapFileRow.Read(fileName, QuantConnect.Market.NYMEX, SecurityType.Future, TestGlobals.DataProvider).ToList();
File.Delete(fileName);
Assert.AreEqual(1, result.Count);
Assert.AreEqual(new DateTime(2011,2,21), result[0].Date);
}
[Test]
public void RowThrowsForUnknownMappingMode()
{
Assert.Throws<ArgumentException>(() => MapFileRow.Parse("20110418,cl uvvl4qhqe8xt,NYMEX,999", QuantConnect.Market.NYMEX, SecurityType.Future));
}
[Test]
public void ResolvesFirstTicker()
{
var mapFile = new MapFile("goog", new List<MapFileRow>
{
new MapFileRow(new DateTime(2014, 03, 27), "goocv"),
new MapFileRow(new DateTime(2014, 04, 02), "goocv"),
new MapFileRow(new DateTime(2050, 12, 31), "goog")
});
Assert.AreEqual("GOOCV", mapFile.FirstTicker);
}
[Test]
[TestCase("abc", "ABC")]
[TestCase("abc.1", "ABC")]
[TestCase("brk.a", "BRK.A")]
[TestCase("brk.a.1", "BRK.A")]
public void ResolvesFirstTickerFromPermtickIfEmptyMapFile(string permtick, string expectedFirstTicker)
{
var mapFile = new MapFile(permtick, new List<MapFileRow>());
Assert.AreEqual(expectedFirstTicker, mapFile.FirstTicker);
}
[Test]
public void ResolvesFirstDate()
{
var mapFile = new MapFile("goog", new List<MapFileRow>
{
new MapFileRow(new DateTime(2014, 03, 27), "goocv"),
new MapFileRow(new DateTime(2014, 04, 02), "goocv"),
new MapFileRow(new DateTime(2050, 12, 31), "goog")
});
Assert.AreEqual(new DateTime(2014, 03, 27), mapFile.FirstDate);
}
[Test]
public void GenerateMapFileCSV()
{
var mapFile = new MapFile("enrn", new List<MapFileRow>()
{
new MapFileRow(new DateTime(2001, 1, 1), "enrn"),
new MapFileRow(new DateTime(2001, 12, 2), "enrnq")
});
var csvData = new List<string>()
{
"20010101,enrn",
"20011202,enrnq"
};
Assert.True(mapFile.ToCsvLines().SequenceEqual(csvData));
}
[Test]
public void ParsesExchangeCorrectly()
{
var mapFile = new MapFile("goog", new List<MapFileRow>
{
new MapFileRow(new DateTime(2014, 03, 27), "goocv", "Q"),
new MapFileRow(new DateTime(2014, 04, 02), "goocv", "Q"),
new MapFileRow(new DateTime(2050, 12, 31), "goog", "Q")
});
Assert.AreEqual(Exchange.NASDAQ, (Exchange) mapFile.Last().PrimaryExchange);
}
[TestCaseSource(nameof(ParsesRowWithExchangesCorrectlyCases))]
public void ParsesRowWithExchangesCorrectly(string mapFileRow, Exchange expectedExchange)
{
// Arrange
var rowParts = mapFileRow.Split(',');
var expectedMapFileRow = new MapFileRow(
DateTime.ParseExact(rowParts[0], DateFormat.EightCharacter, CultureInfo.InvariantCulture),
rowParts[1],
rowParts[2]);
// Act
var actualMapFileRow = MapFileRow.Parse(mapFileRow, QuantConnect.Market.USA, SecurityType.Equity);
// Assert
Assert.AreEqual(expectedExchange, actualMapFileRow.PrimaryExchange);
Assert.AreEqual(expectedMapFileRow, actualMapFileRow);
}
[Test]
public void ParsesRowWithoutExchangesCorrectly()
{
// Arrange
var mapFileRow = "20010213,aapl";
var rowParts = mapFileRow.Split(',');
var expectedMapFileRow = new MapFileRow(
DateTime.ParseExact(rowParts[0], DateFormat.EightCharacter, CultureInfo.InvariantCulture),
rowParts[1]);
// Act
var actualMapFileRow = MapFileRow.Parse(mapFileRow, QuantConnect.Market.USA, SecurityType.Equity);
// Assert
Assert.AreEqual(Exchange.UNKNOWN, actualMapFileRow.PrimaryExchange);
Assert.AreEqual(expectedMapFileRow, actualMapFileRow);
}
private static TestCaseData[] ParsesRowWithExchangesCorrectlyCases()
{
return new[]
{
new TestCaseData("20010213,aapl,Q", Exchange.NASDAQ),
new TestCaseData("20010213,aapl,Z", Exchange.BATS),
new TestCaseData("20010213,aapl,P", Exchange.ARCA),
new TestCaseData("20010213,aapl,N", Exchange.NYSE),
new TestCaseData("20010213,aapl,C", Exchange.NSX),
new TestCaseData("20010213,aapl,D", Exchange.FINRA),
new TestCaseData("20010213,aapl,I", Exchange.ISE),
new TestCaseData("20010213,aapl,M", Exchange.CSE),
new TestCaseData("20010213,aapl,W", Exchange.CBOE),
new TestCaseData("20010213,aapl,A", Exchange.AMEX),
new TestCaseData("20010213,aapl,J", Exchange.EDGA),
new TestCaseData("20010213,aapl,K", Exchange.EDGX),
new TestCaseData("20010213,aapl,B", Exchange.NASDAQ_BX),
new TestCaseData("20010213,aapl,X", Exchange.NASDAQ_PSX),
new TestCaseData("20010213,aapl,Y", Exchange.BATS_Y),
};
}
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public abstract class BaseConsolidatorTests
{
protected abstract IDataConsolidator CreateConsolidator();
protected virtual void AssertConsolidator(IDataConsolidator consolidator)
{
Assert.IsNull(consolidator.Consolidated);
}
protected virtual Func<IBaseData, IBaseData, bool> AssertConsolidatedValues => (first, second) =>
{
Assert.AreEqual(first.Value, second.Value);
Assert.AreEqual(first.Price, second.Price);
Assert.AreEqual(first.DataType, second.DataType);
Assert.AreEqual(first.Symbol, second.Symbol);
Assert.AreEqual(first.EndTime, second.EndTime);
return true;
};
protected virtual dynamic GetTestValues()
{
var time = new DateTime(2016, 1, 1);
return new List<IndicatorDataPoint>()
{
new IndicatorDataPoint(time, 1.38687m),
new IndicatorDataPoint(time.AddSeconds(1), 1.38687m),
new IndicatorDataPoint(time.AddSeconds(2), 1.38688m),
new IndicatorDataPoint(time.AddSeconds(3), 1.38687m),
new IndicatorDataPoint(time.AddSeconds(4), 1.38686m),
new IndicatorDataPoint(time.AddSeconds(5), 1.38685m),
new IndicatorDataPoint(time.AddSeconds(6), 1.38683m),
new IndicatorDataPoint(time.AddSeconds(7), 1.38682m),
new IndicatorDataPoint(time.AddSeconds(8), 1.38682m),
new IndicatorDataPoint(time.AddSeconds(9), 1.38684m),
new IndicatorDataPoint(time.AddSeconds(10), 1.38682m),
new IndicatorDataPoint(time.AddSeconds(11), 1.38680m),
new IndicatorDataPoint(time.AddSeconds(12), 1.38681m),
new IndicatorDataPoint(time.AddSeconds(13), 1.38686m),
new IndicatorDataPoint(time.AddSeconds(14), 1.38688m),
};
}
[Test]
public void ResetWorksAsExpected()
{
// Test Renko bar consistency amongst three consolidators starting at different times
var time = new DateTime(2016, 1, 1);
var testValues = GetTestValues();
var consolidatedBarsBefore = new List<IBaseData>();
var consolidator = CreateConsolidator();
foreach (var data in testValues)
{
consolidator.Update(data);
if (consolidator.Consolidated != null)
{
consolidatedBarsBefore.Add(consolidator.Consolidated);
}
}
consolidator.Reset();
AssertConsolidator(consolidator);
var consolidatedBarsAfter = new List<IBaseData>();
foreach (var data in testValues)
{
consolidator.Update(data);
if (consolidator.Consolidated != null)
{
consolidatedBarsAfter.Add(consolidator.Consolidated);
}
}
Assert.AreEqual(consolidatedBarsBefore.Count, consolidatedBarsAfter.Count);
consolidatedBarsBefore.Zip<IBaseData, IBaseData, bool>(consolidatedBarsAfter, AssertConsolidatedValues);
consolidator.Dispose();
}
}
}
@@ -0,0 +1,364 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class BaseDataConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void AggregatesTickToNewTradeBarProperly()
{
TradeBar newTradeBar = null;
using var creator = new BaseDataConsolidator(4);
creator.DataConsolidated += (sender, tradeBar) =>
{
newTradeBar = tradeBar;
};
var reference = DateTime.Today;
var bar1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
Value = 5,
Quantity = 10
};
creator.Update(bar1);
Assert.IsNull(newTradeBar);
var bar2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
Value = 10,
Quantity = 20
};
creator.Update(bar2);
Assert.IsNull(newTradeBar);
var bar3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(2),
Value = 1,
Quantity = 10
};
creator.Update(bar3);
Assert.IsNull(newTradeBar);
var bar4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(3),
Value = 9,
Quantity = 20
};
creator.Update(bar4);
Assert.IsNotNull(newTradeBar);
Assert.AreEqual(Symbols.SPY, newTradeBar.Symbol);
Assert.AreEqual(bar1.Time, newTradeBar.Time);
Assert.AreEqual(bar1.Value, newTradeBar.Open);
Assert.AreEqual(bar2.Value, newTradeBar.High);
Assert.AreEqual(bar3.Value, newTradeBar.Low);
Assert.AreEqual(bar4.Value, newTradeBar.Close);
Assert.AreEqual(bar4.EndTime, newTradeBar.EndTime);
// base data can't aggregate volume
Assert.AreEqual(0, newTradeBar.Volume);
}
[Test]
public void DoesNotConsolidateDifferentSymbols()
{
using var consolidator = new BaseDataConsolidator(2);
var reference = DateTime.Today;
var tb1 = new Tick
{
Symbol = Symbols.AAPL,
Time = reference,
Value = 5,
Quantity = 10
};
var tb2 = new Tick
{
Symbol = Symbols.ZNGA,
Time = reference,
Value = 2,
Quantity = 5
};
consolidator.Update(tb1);
Exception ex = Assert.Throws<InvalidOperationException>(() => consolidator.Update(tb2));
Assert.IsTrue(ex.Message.Contains("is not the same"));
}
[Test]
public void AggregatesTradeBarsProperly()
{
TradeBar newTradeBar = null;
using var creator = new TradeBarConsolidator(4);
creator.DataConsolidated += (sender, args) =>
{
newTradeBar = args;
};
var time = DateTime.Today;
var period = TimeSpan.FromMinutes(1);
var bar1 = new TradeBar
{
Time = time,
Symbol = Symbols.SPY,
Open = 1,
High = 2,
Low = 0.75m,
Close = 1.25m,
Period = period
};
creator.Update(bar1);
Assert.IsNull(newTradeBar);
var bar2 = new TradeBar
{
Time = time + TimeSpan.FromMinutes(1),
Symbol = Symbols.SPY,
Open = 1.1m,
High = 2.2m,
Low = 0.9m,
Close = 2.1m,
Period = period
};
creator.Update(bar2);
Assert.IsNull(newTradeBar);
var bar3 = new TradeBar
{
Time = time + TimeSpan.FromMinutes(2),
Symbol = Symbols.SPY,
Open = 1,
High = 2,
Low = 0.1m,
Close = 1.75m,
Period = period
};
creator.Update(bar3);
Assert.IsNull(newTradeBar);
var bar4 = new TradeBar
{
Time = time + TimeSpan.FromMinutes(3),
Symbol = Symbols.SPY,
Open = 1,
High = 7,
Low = 0.5m,
Close = 4.4m,
Period = period
};
creator.Update(bar4);
Assert.IsNotNull(newTradeBar);
Assert.AreEqual(bar1.Symbol, newTradeBar.Symbol);
Assert.AreEqual(1, newTradeBar.Open);
Assert.AreEqual(7, newTradeBar.High);
Assert.AreEqual(0.1m, newTradeBar.Low);
Assert.AreEqual(4.4m, newTradeBar.Close);
Assert.AreEqual(newTradeBar.Close, newTradeBar.Value);
Assert.AreEqual(bar4.EndTime, newTradeBar.EndTime);
Assert.AreEqual(TimeSpan.FromMinutes(4), newTradeBar.Period);
Assert.AreEqual(bar1.Volume + bar2.Volume + bar3.Volume + bar4.Volume, newTradeBar.Volume);
}
[Test]
public void AggregatesPeriodInCountModeWithHourlyData()
{
TradeBar consolidated = null;
using var consolidator = new BaseDataConsolidator(2);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddHours(1) });
Assert.IsNotNull(consolidated);
// The EndTime of the consolidated bar should match the EndTime of the last data point
Assert.AreEqual(reference.AddHours(1), consolidated.EndTime);
Assert.AreEqual(TimeSpan.FromHours(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddHours(2) });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddHours(3) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddHours(3), consolidated.EndTime);
Assert.AreEqual(TimeSpan.FromHours(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddHours(4) });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddHours(5) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddHours(5), consolidated.EndTime);
Assert.AreEqual(TimeSpan.FromHours(1), consolidated.Period);
}
[Test]
public void AggregatesPeriodInPeriodModeWithDailyData()
{
TradeBar consolidated = null;
using var consolidator = new BaseDataConsolidator(TimeSpan.FromDays(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddDays(1) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(2) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(3) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
}
[Test]
public void AggregatesPeriodInPeriodModeWithDailyDataAndRoundedTime()
{
TradeBar consolidated = null;
using var consolidator = new BaseDataConsolidator(TimeSpan.FromDays(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference.AddSeconds(45) });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddDays(1).AddMinutes(1) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference, consolidated.Time);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(2).AddHours(1).AddMinutes(1).AddSeconds(1) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference.AddDays(1), consolidated.Time);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(3) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference.AddDays(2), consolidated.Time);
}
[Test]
public void ConsolidatesWithRegisterIndicator()
{
using var consolidator = new BaseDataConsolidator(TimeSpan.FromMinutes(5));
consolidator.DataConsolidated += OnFiveMinutes;
indicator = new SimpleMovingAverage(2);
RegisterIndicator(indicator, consolidator);
var time = DateTime.Today.AddHours(9);
for (var i = 1; i < 100; i++)
{
consolidator.Update(new Tick(time.AddMinutes(i - 1), Symbols.SPY, i, i, i));
}
}
private SimpleMovingAverage indicator;
private void OnFiveMinutes(object sender, TradeBar e)
{
if (!indicator.IsReady) return;
var previous = e.Value - e.Period.Minutes;
var actual = (e.Value + previous) / indicator.Period;
Assert.AreEqual(indicator, actual);
}
/// <summary>
/// Simplified version of QCAlgorithm.RegisterIndicator
/// </summary>
/// <param name="indicator">The indicator to receive data from the consolidator</param>
/// <param name="consolidator">The consolidator to receive raw subscription data</param>
public void RegisterIndicator(IndicatorBase<IndicatorDataPoint> indicator, IDataConsolidator consolidator)
{
consolidator.DataConsolidated += (sender, consolidated) =>
{
indicator.Update(consolidated.EndTime, consolidated.Value);
};
}
protected override IDataConsolidator CreateConsolidator()
{
return new BaseDataConsolidator(4);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2015, 04, 13, 8, 31, 0);
return new List<TradeBar>()
{
new TradeBar(){ Time = time, Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10 },
new TradeBar(){ Time = time.AddMinutes(1), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12, Close = 5 },
new TradeBar(){ Time = time.AddMinutes(2), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10, Close = 7 },
new TradeBar(){ Time = time.AddMinutes(3), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 5, Close = 2 },
new TradeBar(){ Time = time.AddMinutes(4), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 15 , Close = 2 },
new TradeBar(){ Time = time.AddMinutes(5), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 20 , Close = 2 },
new TradeBar(){ Time = time.AddMinutes(6), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 18 , Close = 8 },
new TradeBar(){ Time = time.AddMinutes(7), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12 , Close = 4 },
new TradeBar(){ Time = time.AddMinutes(8), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 25 , Close = 5 },
new TradeBar(){ Time = time.AddMinutes(9), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 30 , Close = 4 },
new TradeBar(){ Time = time.AddMinutes(10), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 26 , Close = 7 },
};
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class BaseDataTests
{
[Test]
public void IsSparseData_ReturnsTrue_WhenSecurityTypeIsBase()
{
var baseData = new DataType {Symbol = Symbol.Create("ticker", SecurityType.Base, QuantConnect.Market.USA)};
Assert.IsTrue(baseData.IsSparseData());
}
[Test]
public void IsSparseData_ReturnsFalse_WhenSecurityTypeIsNotBase()
{
var securityTypes = Enum.GetValues(typeof(SecurityType))
.Cast<SecurityType>()
.Where(type => type != SecurityType.Base);
foreach (var securityType in securityTypes)
{
var baseData = new DataType();
try { baseData.Symbol = Symbol.Create("ticker", securityType, QuantConnect.Market.USA); }
catch (NotImplementedException) { continue; }
Assert.IsFalse(baseData.IsSparseData(), securityType.ToString());
}
}
private class DataType : BaseData { }
}
}
@@ -0,0 +1,558 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class CalendarConsolidatorsTests
{
private Dictionary<Language, dynamic> _dailyFuncDictionary;
private Dictionary<Language, dynamic> _weeklyFuncDictionary;
private Dictionary<Language, dynamic> _monthlyFuncDictionary;
[OneTimeSetUp]
public void SetUp()
{
_dailyFuncDictionary = new Dictionary<Language, dynamic> { { Language.CSharp, TimeSpan.FromDays(1) } };
_weeklyFuncDictionary = new Dictionary<Language, dynamic> { { Language.CSharp, Calendar.Weekly } };
_monthlyFuncDictionary = new Dictionary<Language, dynamic> { { Language.CSharp, Calendar.Monthly } };
using (Py.GIL())
{
var module = PyModule.FromString(
"PythonCalendar",
@"
from AlgorithmImports import *
oneday = timedelta(1)
def Weekly(dt):
value = 8 - dt.isoweekday()
if value == 8: value = 1 # Sunday
start = (dt + timedelta(value)).date() - timedelta(7)
return CalendarInfo(start, timedelta(7))
def Monthly(dt):
start = dt.replace(day=1).date()
end = dt.replace(day=28) + timedelta(4)
end = (end - timedelta(end.day-1)).date()
return CalendarInfo(start, end - start)"
);
_dailyFuncDictionary[Language.Python] = module.GetAttr("oneday");
_weeklyFuncDictionary[Language.Python] = module.GetAttr("Weekly");
_monthlyFuncDictionary[Language.Python] = module.GetAttr("Monthly");
}
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesTradeBarToCalendarTradeBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var bars = new List<TradeBar>
{
new TradeBar(reference.AddDays(1), Symbols.SPY, 9, 11, 8, 10, 100, Time.OneDay),
new TradeBar(reference.AddDays(3), Symbols.SPY, 10, 12, 8, 11, 100, Time.OneDay),
new TradeBar(reference.AddDays(5), Symbols.SPY, 11, 13, 9, 10, 100, Time.OneDay),
new TradeBar(reference.AddDays(7), Symbols.SPY, 11, 13, 9, 11, 100, Time.OneDay),
new TradeBar(reference.AddDays(14), Symbols.SPY, 11, 13, 9, 11, 100, Time.OneDay)
};
using var weeklyConsolidator = new TradeBarConsolidator(_weeklyFuncDictionary[language]);
weeklyConsolidator.DataConsolidated += (s, e) =>
{
AssertTradeBar(
bars.Take(3),
reference,
reference.AddDays(7),
Symbols.SPY,
e);
};
using var monthlyConsolidator = new TradeBarConsolidator(_monthlyFuncDictionary[language]);
monthlyConsolidator.DataConsolidated += (s, e) =>
{
AssertTradeBar(
bars.Take(4),
new DateTime(reference.Year, reference.Month, 1),
new DateTime(reference.Year, reference.Month + 1, 1),
Symbols.SPY,
e);
};
foreach (var bar in bars.Take(4))
{
weeklyConsolidator.Update(bar);
}
foreach (var bar in bars)
{
monthlyConsolidator.Update(bar);
}
}
private void AssertTradeBar(IEnumerable<TradeBar> tradeBars, DateTime openTime, DateTime closeTime, Symbol symbol, TradeBar consolidated)
{
Assert.IsNotNull(consolidated);
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(tradeBars.First().Open, consolidated.Open);
Assert.AreEqual(tradeBars.Max(x => x.High), consolidated.High);
Assert.AreEqual(tradeBars.Min(x => x.Low), consolidated.Low);
Assert.AreEqual(tradeBars.Last().Close, consolidated.Close);
Assert.AreEqual(tradeBars.Sum(x => x.Volume), consolidated.Volume);
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesQuoteBarToCalendarQuoteBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var bars = new List<QuoteBar>
{
new QuoteBar(reference.AddDays(1), Symbols.EURUSD, new Bar(9, 11, 8, 10), 10, new Bar(9, 11, 8, 10), 10, Time.OneDay),
new QuoteBar(reference.AddDays(3), Symbols.EURUSD, new Bar(10, 12, 8, 11), 10, new Bar(10, 12, 8, 11), 10, Time.OneDay),
new QuoteBar(reference.AddDays(5), Symbols.EURUSD, new Bar(11, 13, 9, 10), 10, new Bar(11, 13, 9, 10), 10, Time.OneDay),
new QuoteBar(reference.AddDays(7), Symbols.EURUSD, new Bar(11, 13, 9, 11), 10, new Bar(11, 13, 9, 11), 10, Time.OneDay),
new QuoteBar(reference.AddDays(14), Symbols.EURUSD, new Bar(11, 13, 9, 11), 10, new Bar(11, 13, 9, 11), 10, Time.OneDay)
};
using var weeklyConsolidator = new QuoteBarConsolidator(_weeklyFuncDictionary[language]);
weeklyConsolidator.DataConsolidated += (s, e) =>
{
AssertQuoteBar(
bars.Take(3),
reference,
reference.AddDays(7),
Symbols.EURUSD,
e);
};
using var monthlyConsolidator = new QuoteBarConsolidator(_monthlyFuncDictionary[language]);
monthlyConsolidator.DataConsolidated += (s, e) =>
{
AssertQuoteBar(
bars.Take(4),
new DateTime(reference.Year, reference.Month, 1),
new DateTime(reference.Year, reference.Month + 1, 1),
Symbols.EURUSD,
e);
};
foreach (var bar in bars.Take(4))
{
weeklyConsolidator.Update(bar);
}
foreach (var bar in bars.Take(5))
{
monthlyConsolidator.Update(bar);
}
}
private void AssertQuoteBar(IEnumerable<QuoteBar> quoteBars, DateTime openTime, DateTime closeTime, Symbol symbol, QuoteBar consolidated)
{
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(quoteBars.First().Open, consolidated.Open);
Assert.AreEqual(quoteBars.First().Bid.Open, consolidated.Bid.Open);
Assert.AreEqual(quoteBars.First().Ask.Open, consolidated.Ask.Open);
Assert.AreEqual(quoteBars.Max(x => x.High), consolidated.High);
Assert.AreEqual(quoteBars.Max(x => x.Bid.High), consolidated.Bid.High);
Assert.AreEqual(quoteBars.Max(x => x.Ask.High), consolidated.Ask.High);
Assert.AreEqual(quoteBars.Min(x => x.Low), consolidated.Low);
Assert.AreEqual(quoteBars.Min(x => x.Bid.Low), consolidated.Bid.Low);
Assert.AreEqual(quoteBars.Min(x => x.Ask.Low), consolidated.Ask.Low);
Assert.AreEqual(quoteBars.Last().Close, consolidated.Close);
Assert.AreEqual(quoteBars.Last().Bid.Close, consolidated.Bid.Close);
Assert.AreEqual(quoteBars.Last().Ask.Close, consolidated.Ask.Close);
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesTickToCalendarTradeBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var ticks = new List<Tick>
{
new Tick(reference.AddDays(1), Symbols.SPY, 9, 11, 8){ TickType = TickType.Trade, Quantity = 10 },
new Tick(reference.AddDays(3), Symbols.SPY, 10, 12, 8){ TickType = TickType.Trade, Quantity = 10 },
new Tick(reference.AddDays(5), Symbols.SPY, 11, 13, 9){ TickType = TickType.Trade, Quantity = 10 },
new Tick(reference.AddDays(7), Symbols.SPY, 11, 13, 9){ TickType = TickType.Trade, Quantity = 10 },
new Tick(reference.AddDays(14), Symbols.SPY, 11, 13, 9){ TickType = TickType.Trade, Quantity = 10 }
};
using var weeklyConsolidator = new TickConsolidator(_weeklyFuncDictionary[language]);
weeklyConsolidator.DataConsolidated += (s, e) =>
{
AssertTickTradeBar(
ticks.Take(3),
reference,
reference.AddDays(7),
Symbols.SPY,
e);
};
using var monthlyConsolidator = new TickConsolidator(_monthlyFuncDictionary[language]);
monthlyConsolidator.DataConsolidated += (s, e) =>
{
AssertTickTradeBar(
ticks.Take(4),
new DateTime(reference.Year, reference.Month, 1),
new DateTime(reference.Year, reference.Month + 1, 1),
Symbols.SPY,
e);
};
foreach (var tick in ticks.Take(4))
{
weeklyConsolidator.Update(tick);
}
foreach (var tick in ticks)
{
monthlyConsolidator.Update(tick);
}
}
private void AssertTickTradeBar(IEnumerable<Tick> ticks, DateTime openTime, DateTime closeTime, Symbol symbol, TradeBar consolidated)
{
Assert.IsNotNull(consolidated);
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(ticks.First().Value, consolidated.Open);
Assert.AreEqual(ticks.Max(x => x.Value), consolidated.High);
Assert.AreEqual(ticks.Min(x => x.Value), consolidated.Low);
Assert.AreEqual(ticks.Last().Value, consolidated.Close);
Assert.AreEqual(ticks.Sum(x => x.Quantity), consolidated.Volume);
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesTickToCalendarQuoteBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var ticks = new List<Tick>
{
new Tick(reference.AddDays(1), Symbols.EURUSD, 9, 11, 8){ Quantity = 10 },
new Tick(reference.AddDays(3), Symbols.EURUSD, 10, 12, 8){ Quantity = 10 },
new Tick(reference.AddDays(5), Symbols.EURUSD, 11, 13, 9){ Quantity = 10 },
new Tick(reference.AddDays(7), Symbols.EURUSD, 11, 13, 9){ Quantity = 10 },
new Tick(reference.AddDays(14), Symbols.EURUSD, 11, 13, 9){ Quantity = 10 }
};
using var weeklyConsolidator = new TickQuoteBarConsolidator(_weeklyFuncDictionary[language]);
weeklyConsolidator.DataConsolidated += (s, e) =>
{
AssertTickQuoteBar(
ticks.Take(3),
reference,
reference.AddDays(7),
Symbols.EURUSD,
e);
};
using var monthlyConsolidator = new TickQuoteBarConsolidator(_monthlyFuncDictionary[language]);
monthlyConsolidator.DataConsolidated += (s, e) =>
{
AssertTickQuoteBar(
ticks.Take(4),
new DateTime(reference.Year, reference.Month, 1),
new DateTime(reference.Year, reference.Month + 1, 1),
Symbols.EURUSD,
e);
};
foreach (var tick in ticks.Take(4))
{
weeklyConsolidator.Update(tick);
}
foreach (var tick in ticks)
{
monthlyConsolidator.Update(tick);
}
}
private void AssertTickQuoteBar(IEnumerable<Tick> ticks, DateTime openTime, DateTime closeTime, Symbol symbol, QuoteBar consolidated)
{
Assert.IsNotNull(consolidated);
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(ticks.First().BidPrice, consolidated.Bid.Open);
Assert.AreEqual(ticks.First().AskPrice, consolidated.Ask.Open);
Assert.AreEqual(ticks.Max(x => x.BidPrice), consolidated.Bid.High);
Assert.AreEqual(ticks.Max(x => x.AskPrice), consolidated.Ask.High);
Assert.AreEqual(ticks.Min(x => x.BidPrice), consolidated.Bid.Low);
Assert.AreEqual(ticks.Min(x => x.AskPrice), consolidated.Ask.Low);
Assert.AreEqual(ticks.Last().BidPrice, consolidated.Bid.Close);
Assert.AreEqual(ticks.Last().AskPrice, consolidated.Ask.Close);
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesBaseDataToCalendarTradeBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var ticks = new List<Tick>
{
new Tick(reference.AddDays(1), Symbols.SPY, 9, 11, 8){ Quantity = 10 },
new Tick(reference.AddDays(3), Symbols.SPY, 10, 12, 8){ Quantity = 10 },
new Tick(reference.AddDays(5), Symbols.SPY, 11, 13, 9){ Quantity = 10 },
new Tick(reference.AddDays(7), Symbols.SPY, 11, 13, 9){ Quantity = 10 },
new Tick(reference.AddDays(14), Symbols.SPY, 11, 13, 9){ Quantity = 10 }
};
using var weeklyConsolidator = new BaseDataConsolidator(_weeklyFuncDictionary[language]);
weeklyConsolidator.DataConsolidated += (s, e) =>
{
AssertBaseTradeBar(
ticks.Take(3),
reference,
reference.AddDays(7),
Symbols.SPY,
e);
};
using var monthlyConsolidator = new BaseDataConsolidator(_monthlyFuncDictionary[language]);
monthlyConsolidator.DataConsolidated += (s, e) =>
{
AssertBaseTradeBar(
ticks.Take(4),
new DateTime(reference.Year, reference.Month, 1),
new DateTime(reference.Year, reference.Month + 1, 1),
Symbols.SPY,
e);
};
foreach (var tick in ticks.Take(4))
{
weeklyConsolidator.Update(tick);
}
foreach (var tick in ticks)
{
monthlyConsolidator.Update(tick);
}
}
private void AssertBaseTradeBar(IEnumerable<Tick> ticks, DateTime openTime, DateTime closeTime, Symbol symbol, TradeBar consolidated)
{
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(ticks.First().Value, consolidated.Open);
Assert.AreEqual(ticks.Max(x => x.Value), consolidated.High);
Assert.AreEqual(ticks.Min(x => x.Value), consolidated.Low);
Assert.AreEqual(ticks.Last().Value, consolidated.Close);
Assert.AreEqual(0, consolidated.Volume);
}
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AggregatesTradeBarToDailyTradeBarProperly(Language language)
{
// Monday
var reference = new DateTime(2019, 3, 18);
var bars = new List<TradeBar>
{
new TradeBar(reference.AddHours(6), Symbols.SPY, 9, 11, 8, 10, 100, Time.OneHour),
new TradeBar(reference.AddHours(12), Symbols.SPY, 10, 12, 8, 11, 100, Time.OneHour),
new TradeBar(reference.AddHours(18), Symbols.SPY, 11, 13, 9, 10, 100, Time.OneHour),
new TradeBar(reference.AddHours(21), Symbols.SPY, 11, 13, 9, 11, 100, Time.OneHour),
new TradeBar(reference.AddHours(25), Symbols.SPY, 11, 13, 9, 11, 100, Time.OneHour)
};
using var dailyConsolidator = new TradeBarConsolidator(_dailyFuncDictionary[language]);
dailyConsolidator.DataConsolidated += (s, e) =>
{
AssertTradeBar(
bars.Take(4),
reference,
reference.AddDays(1),
Symbols.SPY,
e);
};
foreach (var bar in bars)
{
dailyConsolidator.Update(bar);
}
}
private void AssertDailyTradeBar(IEnumerable<TradeBar> tradeBars, DateTime openTime, DateTime closeTime, Symbol symbol, TradeBar consolidated)
{
Assert.IsNotNull(consolidated);
Assert.AreEqual(openTime, consolidated.Time);
Assert.AreEqual(closeTime, consolidated.EndTime);
Assert.AreEqual(symbol, consolidated.Symbol);
Assert.AreEqual(tradeBars.First().Open, consolidated.Open);
Assert.AreEqual(tradeBars.Max(x => x.High), consolidated.High);
Assert.AreEqual(tradeBars.Min(x => x.Low), consolidated.Low);
Assert.AreEqual(tradeBars.Last().Close, consolidated.Close);
Assert.AreEqual(tradeBars.Sum(x => x.Volume), consolidated.Volume);
}
private SimpleMovingAverage indicator;
[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void AllCalendarsConsolidatesWithRegisterIndicator(Language language)
{
CalendarConsolidatesWithRegisterIndicator(_weeklyFuncDictionary[language]);
CalendarConsolidatesWithRegisterIndicator(_monthlyFuncDictionary[language]);
}
[Test]
public void Weekly()
{
var quarterly = Calendar.Weekly;
var calendarInfo = quarterly(new DateTime(2020, 2, 20));
Assert.AreEqual(new DateTime(2020, 2, 17), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(7), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 11, 2));
Assert.AreEqual(new DateTime(2018, 10, 29), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(7), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 12, 31));
Assert.AreEqual(new DateTime(2018, 12, 31), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(7), calendarInfo.Period);
}
[Test]
public void Monthly()
{
var quarterly = Calendar.Monthly;
var calendarInfo = quarterly(new DateTime(2020, 5, 11));
Assert.AreEqual(new DateTime(2020, 5, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(31), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 11, 13));
Assert.AreEqual(new DateTime(2018, 11, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(30), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 12, 31));
Assert.AreEqual(new DateTime(2018, 12, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(31), calendarInfo.Period);
}
[Test]
public void Quarterly()
{
var quarterly = Calendar.Quarterly;
var calendarInfo = quarterly(new DateTime(2020, 5, 1));
Assert.AreEqual(new DateTime(2020, 4, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(91), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 11, 13));
Assert.AreEqual(new DateTime(2018, 10, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(92), calendarInfo.Period);
calendarInfo = quarterly(new DateTime(2018, 12, 31));
Assert.AreEqual(new DateTime(2018, 10, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(92), calendarInfo.Period);
}
[Test]
public void Yearly()
{
var quarterly = Calendar.Yearly;
var calendarInfo = quarterly(new DateTime(2020, 5, 1));
Assert.AreEqual(new DateTime(2020, 1, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(366), calendarInfo.Period); // leap year
calendarInfo = quarterly(new DateTime(2021, 11, 1));
Assert.AreEqual(new DateTime(2021, 1, 1), calendarInfo.Start);
Assert.AreEqual(TimeSpan.FromDays(365), calendarInfo.Period);
}
private void CalendarConsolidatesWithRegisterIndicator(dynamic calendarType)
{
using var consolidator = new TradeBarConsolidator(calendarType);
consolidator.DataConsolidated += (s, e) =>
{
if (!indicator.IsReady) return;
var previous = e.Value - e.Period.Days;
var actual = (e.Value + previous) / indicator.Period;
Assert.AreEqual(indicator, actual);
};
indicator = new SimpleMovingAverage(2);
RegisterIndicator(indicator, consolidator);
var reference = new DateTime(2019, 4, 1);
for (var i = 1; i < 100; i++)
{
var bar = new TradeBar(reference.AddDays(i - 1), Symbols.SPY, i, i, i, i, 0);
consolidator.Update(bar);
}
}
/// <summary>
/// Simplified version of QCAlgorithm.RegisterIndicator
/// </summary>
/// <param name="indicator">The indicator to receive data from the consolidator</param>
/// <param name="consolidator">The consolidator to receive raw subscription data</param>
public void RegisterIndicator(IndicatorBase<IndicatorDataPoint> indicator, IDataConsolidator consolidator)
{
consolidator.DataConsolidated += (sender, consolidated) =>
{
indicator.Update(consolidated.EndTime, consolidated.Value);
};
}
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class ChannelTests
{
private static TestCaseData[] Equality => new[]
{
new TestCaseData(new Channel("trade", Symbols.SPY), Symbols.SPY, "trade"),
new TestCaseData(new Channel("quote", Symbols.AAPL), Symbols.AAPL, "quote"),
new TestCaseData(new Channel("quote-trade", Symbols.IBM), Symbols.IBM, "quote-trade")
};
[TestCaseSource(nameof(Equality))]
public void Equal(Channel expected, Symbol symbol, string channelName)
{
var actual = new Channel(channelName, symbol);
Assert.AreNotSame(expected, actual);
Assert.AreEqual(expected, actual);
Assert.AreEqual(expected.GetHashCode(), actual.GetHashCode());
}
private static TestCaseData[] Inequality => new[]
{
new TestCaseData(new Channel("trade", Symbols.SPY), Symbols.SPY, "quote"),
new TestCaseData(new Channel("trade", Symbols.AAPL), Symbols.SPY, "trade"),
new TestCaseData(new Channel("quote-trade", Symbols.IBM), Symbols.IBM, "quote"),
new TestCaseData(new Channel("quote-trade", Symbols.MSFT), Symbols.MSFT, "trade")
};
[TestCaseSource(nameof(Inequality))]
public void NotEqual(Channel expected, Symbol symbol, string channelName)
{
var actual = new Channel(channelName, symbol);
Assert.AreNotSame(expected, actual);
Assert.AreNotEqual(expected, actual);
Assert.AreNotEqual(expected.GetHashCode(), actual.GetHashCode());
}
}
}
@@ -0,0 +1,76 @@
/*
* 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 QuantConnect.Data.Consolidators;
using System.Collections.Generic;
using QuantConnect.Data.Market;
using NUnit.Framework;
using System;
namespace QuantConnect.Tests.Common.Data
{
public class ClassicRangeConsolidatorTests : RangeConsolidatorTests
{
protected override RangeConsolidator CreateRangeConsolidator(int range)
{
return new ClassicRangeConsolidator(range, x => x.Value, x => 10m);
}
/// <summary>
/// This test doesn't work for ClassicRangeConsolidator since this consolidator
/// doesn't create intermediate/phantom bars
/// </summary>
[TestCaseSource(nameof(PriceGapBehaviorIsTheExpectedOneTestCases))]
public override void PriceGapBehaviorIsTheExpectedOne(Symbol symbol, double minimumPriceVariation, double range)
{
}
[TestCaseSource(nameof(ConsolidatorCreatesExpectedBarsTestCases))]
public override void ConsolidatorCreatesExpectedBarsInDifferentScenarios(List<decimal> testValues, RangeBar[] expectedBars)
{
base.ConsolidatorCreatesExpectedBarsInDifferentScenarios(testValues, expectedBars);
}
private static object[] ConsolidatorCreatesExpectedBarsTestCases = new object[]
{
new object[] { new List<decimal>(){ 90m, 94.5m }, new RangeBar[] {
new RangeBar{ Open = 90m, Low = 90m, High = 91m, Close = 91m, Volume = 10m, EndTime = new DateTime(2016, 1, 2) }
}},
new object[] { new List<decimal>(){ 94m, 89.5m }, new RangeBar[] {
new RangeBar { Open = 94m, Low = 93m, High = 94m, Close = 93m, Volume = 10m, EndTime = new DateTime(2016, 1, 2) }
}},
new object[] { new List<decimal>{ 90m, 94.5m, 89.5m }, new RangeBar[] {
new RangeBar { Open = 90m, Low = 90m, High = 91m, Close = 91m, Volume = 10m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 94.5m, Low = 93.50m, High = 94.50m, Close = 93.50m, Volume = 10m, EndTime = new DateTime(2016, 1, 3)}
}},
new object[] { new List<decimal>{ 94.5m, 89.5m, 94.5m }, new RangeBar[] {
new RangeBar { Open = 95m, Low = 94m, High = 95m, Close = 94m, Volume = 10m, EndTime = new DateTime(2016, 1, 2)},
new RangeBar { Open = 89.50m, Low = 89.50m, High = 90.50m, Close = 90.50m, Volume = 10m , EndTime = new DateTime(2016, 1, 3)}
}},
};
protected override decimal[][] GetRangeConsolidatorExpectedValues()
{
return new decimal[][] {
new decimal[]{ 90m, 90m, 91m, 91m, 10m },
new decimal[]{ 94.5m, 93.5m, 94.5m, 93.5m, 20m},
new decimal[]{ 89.5m, 89m, 90m, 90m, 20m},
new decimal[]{ 90.5m, 90m, 91m, 91m, 20m},
new decimal[]{ 91.5m, 90.5m, 91.5m, 90.5m, 10m},
new decimal[]{ 90m, 90m, 91m, 91m, 20m},
};
}
}
}
@@ -0,0 +1,288 @@
/*
* 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 NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class ClassicRenkoConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void ClassicOutputTypeIsRenkoBar()
{
using var consolidator = new ClassicRenkoConsolidator(10, x => x.Value, x => 0);
Assert.AreEqual(typeof(RenkoBar), consolidator.OutputType);
}
[Test]
public void ClassicConsolidatesOnBrickHigh()
{
RenkoBar bar = null;
using var consolidator = new ClassicRenkoConsolidator(10, x => x.Value, x => 0);
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var reference = DateTime.Today;
consolidator.Update(new IndicatorDataPoint(reference, 0m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddHours(1), 5m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddHours(2), 10m));
Assert.IsNotNull(bar);
Assert.AreEqual(0m, bar.Open);
Assert.AreEqual(10m, bar.Close);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void ClassicConsolidatesOnBrickLow()
{
RenkoBar bar = null;
using var consolidator = new ClassicRenkoConsolidator(10, x => x.Value, x => 0);
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var reference = DateTime.Today;
consolidator.Update(new IndicatorDataPoint(reference, 10m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddHours(1), 2m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddHours(2), 0m));
Assert.IsNotNull(bar);
Assert.AreEqual(10m, bar.Open);
Assert.AreEqual(0m, bar.Close);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void ConsistentRenkos()
{
// Test Renko bar consistency amongst three consolidators starting at different times
var time = new DateTime(2016, 1, 1);
var testValues = new List<decimal>
{
1.38687m, 1.38688m, 1.38687m, 1.38686m, 1.38685m, 1.38683m,
1.38682m, 1.38682m, 1.38684m, 1.38682m, 1.38682m, 1.38680m,
1.38681m, 1.38686m, 1.38688m, 1.38688m, 1.38690m, 1.38690m,
1.38691m, 1.38692m, 1.38694m, 1.38695m, 1.38697m, 1.38697m,
1.38700m, 1.38699m, 1.38699m, 1.38699m, 1.38698m, 1.38699m,
1.38697m, 1.38698m, 1.38698m, 1.38697m, 1.38698m, 1.38698m,
1.38697m, 1.38697m, 1.38700m, 1.38702m, 1.38701m, 1.38699m,
1.38697m, 1.38698m, 1.38696m, 1.38698m, 1.38697m, 1.38695m,
1.38695m, 1.38696m, 1.38693m, 1.38692m, 1.38693m, 1.38693m,
1.38692m, 1.38693m, 1.38692m, 1.38690m, 1.38686m, 1.38685m,
1.38687m, 1.38686m, 1.38686m, 1.38686m, 1.38686m, 1.38685m,
1.38684m, 1.38678m, 1.38679m, 1.38680m, 1.38680m, 1.38681m,
1.38685m, 1.38685m, 1.38683m, 1.38682m, 1.38682m, 1.38683m,
1.38682m, 1.38683m, 1.38682m, 1.38681m, 1.38680m, 1.38681m,
1.38681m, 1.38681m, 1.38682m, 1.38680m, 1.38679m, 1.38678m,
1.38675m, 1.38678m, 1.38678m, 1.38678m, 1.38682m, 1.38681m,
1.38682m, 1.38680m, 1.38682m, 1.38683m, 1.38685m, 1.38683m,
1.38683m, 1.38684m, 1.38683m, 1.38683m, 1.38684m, 1.38685m,
1.38684m, 1.38683m, 1.38686m, 1.38685m, 1.38685m, 1.38684m,
1.38685m, 1.38682m, 1.38684m, 1.38683m, 1.38682m, 1.38683m,
1.38685m, 1.38685m, 1.38685m, 1.38683m, 1.38685m, 1.38684m,
1.38686m, 1.38693m, 1.38695m, 1.38693m, 1.38694m, 1.38693m,
1.38692m, 1.38693m, 1.38695m, 1.38697m, 1.38698m, 1.38695m,
1.38696m
};
var consolidator1 = new ClassicRenkoConsolidator(0.0001m);
var consolidator2 = new ClassicRenkoConsolidator(0.0001m);
var consolidator3 = new ClassicRenkoConsolidator(0.0001m);
// Update each of our consolidators starting at different indexes of test values
for (int i = 0; i < testValues.Count; i++)
{
var data = new IndicatorDataPoint(time.AddSeconds(i), testValues[i]);
consolidator1.Update(data);
if (i > 10)
{
consolidator2.Update(data);
}
if (i > 20)
{
consolidator3.Update(data);
}
}
// Assert that consolidator 2 and 3 price is the same as 1. Even though they started at different
// indexes they should be the same
var bar1 = consolidator1.Consolidated as RenkoBar;
var bar2 = consolidator2.Consolidated as RenkoBar;
var bar3 = consolidator3.Consolidated as RenkoBar;
Assert.AreEqual(bar1.Close, bar2.Close);
Assert.AreEqual(bar1.Close, bar3.Close);
consolidator1.Dispose();
consolidator2.Dispose();
consolidator3.Dispose();
}
[Test]
public void ClassicCyclesUpAndDown()
{
RenkoBar bar = null;
int rcount = 0;
using var consolidator = new ClassicRenkoConsolidator(1m, x => x.Value, x => 0);
consolidator.DataConsolidated += (sender, consolidated) =>
{
rcount++;
bar = consolidated;
};
var reference = DateTime.Today;
// opens at 0
consolidator.Update(new IndicatorDataPoint(reference, 0));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(1), .5m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(2), 1m));
Assert.IsNotNull(bar);
Assert.AreEqual(0m, bar.Open);
Assert.AreEqual(1m, bar.Close);
Assert.AreEqual(0, bar.Volume);
Assert.AreEqual(1m, bar.High);
Assert.AreEqual(0m, bar.Low);
Assert.IsTrue(bar.IsClosed);
Assert.AreEqual(reference, bar.Start);
Assert.AreEqual(reference.AddSeconds(2), bar.EndTime);
bar = null;
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(3), 1.5m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(4), 1m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(5), .5m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(6), 0m));
Assert.IsNotNull(bar);
// ReSharper disable HeuristicUnreachableCode - ReSharper doesn't realiz this can be set via the event handler
Assert.AreEqual(1m, bar.Open);
Assert.AreEqual(0m, bar.Close);
Assert.AreEqual(0, bar.Volume);
Assert.AreEqual(1.5m, bar.High);
Assert.AreEqual(0m, bar.Low);
Assert.IsTrue(bar.IsClosed);
Assert.AreEqual(reference.AddSeconds(2), bar.Start);
Assert.AreEqual(reference.AddSeconds(6), bar.EndTime);
bar = null;
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(7), -0.5m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(8), -0.9999999m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(9), -0.01m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(10), 0.25m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(9), 0.75m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(10), 0.9999999m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(10), 0.25m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(9), -0.25m));
Assert.IsNull(bar);
consolidator.Update(new IndicatorDataPoint(reference.AddSeconds(10), -1m));
Assert.IsNotNull(bar);
Assert.AreEqual(0m, bar.Open);
Assert.AreEqual(-1m, bar.Close);
Assert.AreEqual(0, bar.Volume);
Assert.AreEqual(0.9999999m, bar.High);
Assert.AreEqual(-1m, bar.Low);
Assert.IsTrue(bar.IsClosed);
Assert.AreEqual(reference.AddSeconds(6), bar.Start);
Assert.AreEqual(reference.AddSeconds(10), bar.EndTime);
// ReSharper restore HeuristicUnreachableCode
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void SelectorCanBeOptionalWhenVolumeSelectorIsPassed(Language language)
{
if (language == Language.CSharp)
{
Assert.DoesNotThrow(() =>
{
using var consolidator = new ClassicRenkoConsolidator(10, null, x => x.Value);
});
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("test", @"
from AlgorithmImports import *
def getConsolidator():
return ClassicRenkoConsolidator(10, None, lambda x: x.Value)
");
Assert.DoesNotThrow(() =>
{
var consolidator = testModule.GetAttr("getConsolidator").Invoke();
});
}
}
}
protected override IDataConsolidator CreateConsolidator()
{
return new ClassicRenkoConsolidator(0.0001m);
}
}
}
+301
View File
@@ -0,0 +1,301 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class ConsolidatorBaseTests
{
[TestCaseSource(nameof(WindowTestCases))]
public void WindowStoresConsolidatedBars(IDataConsolidator consolidator, IBaseData[] bars, decimal expectedWindow0, decimal expectedWindow1)
{
var windowConsolidator = (ConsolidatorBase)consolidator;
foreach (var bar in bars)
{
consolidator.Update(bar);
}
Assert.AreEqual(2, windowConsolidator.Window.Count);
Assert.AreEqual(expectedWindow0, windowConsolidator.Window[0].Value);
Assert.AreEqual(expectedWindow1, windowConsolidator.Window[1].Value);
Assert.AreEqual(windowConsolidator.Window[0], windowConsolidator.Consolidated);
Assert.AreEqual(expectedWindow0, windowConsolidator[0].Value);
Assert.AreEqual(expectedWindow1, windowConsolidator.Previous.Value);
consolidator.Dispose();
}
[Test]
public void HandlerSeesPreviousConsolidatedBarWhileReceivingTheNewOne()
{
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var consolidator = new IdentityDataConsolidator<TradeBar>();
IBaseData eventArgument = null;
IBaseData consolidatedInsideHandler = null;
consolidator.DataConsolidated += (_, bar) =>
{
eventArgument = bar;
consolidatedInsideHandler = ((ConsolidatorBase)consolidator).Consolidated;
};
// First bar: inside the handler Consolidated is still null (no previous bar yet)
var first = new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute };
consolidator.Update(first);
Assert.AreEqual(first, eventArgument);
Assert.IsNull(consolidatedInsideHandler);
// Second bar: the handler receives the new bar as argument while Consolidated still holds the previous one
var second = new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute };
consolidator.Update(second);
Assert.AreEqual(second, eventArgument);
Assert.AreEqual(first, consolidatedInsideHandler);
// Once the handler returns, the window reflects the latest consolidated bar
Assert.AreEqual(second, ((ConsolidatorBase)consolidator).Consolidated);
consolidator.Dispose();
}
[Test]
public void WindowHoldsTheNewBarInsideTypedHandler()
{
// regression for consolidators that fired their typed event before populating the window:
// inside the handler consolidator[0] must be the bar that was just produced
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var consolidator = new RenkoConsolidator(1m);
var windowConsolidator = (ConsolidatorBase)consolidator;
var handlerCalls = 0;
consolidator.DataConsolidated += (_, bar) =>
{
handlerCalls++;
Assert.AreEqual(bar, windowConsolidator[0]);
Assert.AreEqual(bar, windowConsolidator.Current);
Assert.AreEqual(bar.Value, windowConsolidator.Current.Value);
};
consolidator.Update(new IndicatorDataPoint(spy, reference, 10m));
consolidator.Update(new IndicatorDataPoint(spy, reference.AddMinutes(1), 12.1m));
Assert.Greater(handlerCalls, 0);
consolidator.Dispose();
}
[Test]
public void InterfaceAndConcreteDataConsolidatedShareOneSubscriptionList()
{
// regression for the interface event and the concrete event being two separate handler lists:
// subscribing through one and unsubscribing through the other must cancel out
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var consolidator = new IdentityDataConsolidator<TradeBar>();
IDataConsolidator asInterface = consolidator;
var calls = 0;
DataConsolidatedHandler handler = (_, __) => calls++;
asInterface.DataConsolidated += handler;
consolidator.DataConsolidated -= handler;
consolidator.Update(new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute });
Assert.AreEqual(0, calls);
consolidator.Dispose();
}
[Test]
public void OutOfOrderDataDoesNotClearWindow()
{
// regression for count mode emitting a null bar on an out of order data point, which
// previously reset the whole rolling window through the Consolidated setter
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var consolidator = new TradeBarConsolidator(1);
var windowConsolidator = (ConsolidatorBase)consolidator;
consolidator.Update(new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute });
consolidator.Update(new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute });
Assert.AreEqual(2, windowConsolidator.Window.Count);
consolidator.Update(new TradeBar { Symbol = spy, Time = reference.AddMinutes(-5), Close = 30m, Value = 30m, Period = Time.OneMinute });
Assert.AreEqual(2, windowConsolidator.Window.Count);
Assert.AreEqual(20m, windowConsolidator.Window[0].Value);
Assert.AreEqual(10m, windowConsolidator.Window[1].Value);
consolidator.Dispose();
}
[Test]
public void CurrentAndPreviousAreNullBeforeFirstConsolidation()
{
var consolidator = new TradeBarConsolidator(1);
var windowConsolidator = (ConsolidatorBase)consolidator;
Assert.IsNull(windowConsolidator.Consolidated);
Assert.IsNull(windowConsolidator.Current);
Assert.IsNull(windowConsolidator.Previous);
Assert.IsNull(windowConsolidator[0]);
Assert.AreEqual(0, windowConsolidator.Window.Count);
consolidator.Dispose();
}
[Test]
public void ResetClearsWindowAndConsolidated()
{
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var consolidator = new TradeBarConsolidator(1);
var windowConsolidator = (ConsolidatorBase)consolidator;
consolidator.Update(new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute });
consolidator.Update(new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute });
Assert.AreEqual(2, windowConsolidator.Window.Count);
Assert.IsNotNull(windowConsolidator.Consolidated);
windowConsolidator.Reset();
Assert.AreEqual(0, windowConsolidator.Window.Count);
Assert.IsNull(windowConsolidator.Consolidated);
Assert.IsNull(windowConsolidator.Current);
Assert.IsNull(windowConsolidator.Previous);
consolidator.Dispose();
}
private static IEnumerable<TestCaseData> WindowTestCases()
{
var reference = new DateTime(2015, 4, 13);
var spy = Symbols.SPY;
var ibm = Symbols.IBM;
yield return new TestCaseData(
new TradeBarConsolidator(1),
new IBaseData[]
{
new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
},
20m, 10m
).SetName("TradeBarConsolidator");
yield return new TestCaseData(
new QuoteBarConsolidator(1),
new IBaseData[]
{
new QuoteBar { Symbol = spy, Time = reference, Value = 10m, Period = Time.OneMinute },
new QuoteBar { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, Period = Time.OneMinute }
},
20m, 10m
).SetName("QuoteBarConsolidator");
yield return new TestCaseData(
new TickConsolidator(1),
new IBaseData[]
{
new Tick { Symbol = spy, Time = reference, Value = 10m, TickType = TickType.Trade },
new Tick { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, TickType = TickType.Trade }
},
20m, 10m
).SetName("TickConsolidator");
yield return new TestCaseData(
new TickQuoteBarConsolidator(1),
new IBaseData[]
{
new Tick { Symbol = spy, Time = reference, Value = 10m, TickType = TickType.Quote, BidPrice = 10m, AskPrice = 10m },
new Tick { Symbol = spy, Time = reference.AddMinutes(1), Value = 20m, TickType = TickType.Quote, BidPrice = 20m, AskPrice = 20m }
},
20m, 10m
).SetName("TickQuoteBarConsolidator");
yield return new TestCaseData(
new BaseDataConsolidator(1),
new IBaseData[]
{
new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
},
20m, 10m
).SetName("BaseDataConsolidator");
yield return new TestCaseData(
new IdentityDataConsolidator<TradeBar>(),
new IBaseData[]
{
new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
},
20m, 10m
).SetName("IdentityDataConsolidator");
yield return new TestCaseData(
new ClassicRenkoConsolidator(10),
new IBaseData[]
{
new IndicatorDataPoint(spy, reference, 0m),
new IndicatorDataPoint(spy, reference.AddMinutes(1), 10m),
new IndicatorDataPoint(spy, reference.AddMinutes(2), 20m)
},
20m, 10m
).SetName("ClassicRenkoConsolidator");
yield return new TestCaseData(
new RenkoConsolidator(1m),
new IBaseData[]
{
new IndicatorDataPoint(spy, reference, 10m),
new IndicatorDataPoint(spy, reference.AddMinutes(1), 12.1m)
},
12m, 11m
).SetName("RenkoConsolidator");
yield return new TestCaseData(
new RangeConsolidator(100, x => x.Value, x => 0m),
new IBaseData[]
{
new IndicatorDataPoint(ibm, reference, 90m),
new IndicatorDataPoint(ibm, reference.AddMinutes(1), 94.5m)
},
94.03m, 93.02m
).SetName("RangeConsolidator");
yield return new TestCaseData(
new SequentialConsolidator(new TradeBarConsolidator(1), new TradeBarConsolidator(1)),
new IBaseData[]
{
new TradeBar { Symbol = spy, Time = reference, Close = 10m, Value = 10m, Period = Time.OneMinute },
new TradeBar { Symbol = spy, Time = reference.AddMinutes(1), Close = 20m, Value = 20m, Period = Time.OneMinute }
},
20m, 10m
).SetName("SequentialConsolidator");
}
}
}
@@ -0,0 +1,247 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.Consolidators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class ConsolidatorWrapperTests
{
[TestCase(0)]
[TestCase(1)]
[TestCase(100)]
public void InitialScanTime(int seconds)
{
var time = new DateTime(2024, 2, 16);
var timeKeeper = new TimeKeeper(time, TimeZones.NewYork);
var localtime = timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork);
var increment = TimeSpan.FromSeconds(seconds);
using var consolidator = new TestConsolidator();
using var wrapper = new ConsolidatorWrapper(consolidator, increment, timeKeeper, localtime);
wrapper.AdvanceScanTime();
Assert.AreEqual(time.Add(increment < Time.OneSecond ? Time.OneSecond : increment), wrapper.UtcScanTime);
}
[TestCase(2)]
[TestCase(100)]
public void ScanTimeAfterScanUtcTimeInPast(int seconds)
{
var time = new DateTime(2024, 2, 16);
var timeKeeper = new TimeKeeper(time, TimeZones.NewYork);
var localtime = timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork);
var increment = TimeSpan.FromSeconds(seconds);
using var consolidator = new TestConsolidator();
using var wrapper = new ConsolidatorWrapper(consolidator, increment, timeKeeper, localtime);
wrapper.AdvanceScanTime();
var expected = time.Add(increment < Time.OneSecond ? Time.OneSecond : increment);
Assert.AreEqual(expected, wrapper.UtcScanTime);
timeKeeper.SetUtcDateTime(time.Add(Time.OneSecond));
wrapper.Scan();
Assert.AreEqual(expected, wrapper.UtcScanTime);
}
[TestCase(2)]
[TestCase(100)]
public void ScanTimeAfterScanUtcTimeInFuture(int seconds)
{
var time = new DateTime(2024, 2, 16);
var timeKeeper = new TimeKeeper(time, TimeZones.NewYork);
var localtime = timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork);
var increment = TimeSpan.FromSeconds(seconds);
using var consolidator = new TestConsolidator();
using var wrapper = new ConsolidatorWrapper(consolidator, increment, timeKeeper, localtime);
wrapper.AdvanceScanTime();
var expected = time.Add(increment < Time.OneSecond ? Time.OneSecond : increment);
Assert.AreEqual(expected, wrapper.UtcScanTime);
timeKeeper.SetUtcDateTime(time.Add(Time.OneDay));
wrapper.Scan();
Assert.AreEqual(expected.Add(Time.OneDay), wrapper.UtcScanTime);
}
[TestCase(-1, true)]
[TestCase(0, true)]
[TestCase(1, true)]
[TestCase(2, true)]
[TestCase(3, true)]
[TestCase(24, true)]
[TestCase(-24, true)]
[TestCase(-1, false)]
[TestCase(0, false)]
[TestCase(1, false)]
[TestCase(2, false)]
[TestCase(3, false)]
[TestCase(24, false)]
[TestCase(-24, false)]
public void ScanTimeAfterConsolidationDayLightSavings(int hoursShift, bool savingsStart)
{
var tz = TimeZones.NewYork;
DateTime time;
if (savingsStart)
{
time = new DateTime(2024, 3, 10).AddHours(hoursShift).ConvertToUtc(tz);
}
else
{
time = new DateTime(2024, 11, 3).AddHours(hoursShift).ConvertToUtc(tz);
}
var timeKeeper = new TimeKeeper(time, tz);
var localtime = timeKeeper.GetLocalTimeKeeper(tz);
var increment = Time.OneHour;
using var consolidator = new TestConsolidator();
using var wrapper = new ConsolidatorWrapper(consolidator, increment, timeKeeper, localtime);
wrapper.AdvanceScanTime();
var expected = time.Add(Time.OneHour);
Assert.AreEqual(expected, wrapper.UtcScanTime);
consolidator.Consolidate(new TradeBar { Time = time.AddMinutes(100), Period = Time.OneDay });
Assert.AreEqual(consolidator.Consolidated.EndTime.ConvertToUtc(tz) + Time.OneDay, wrapper.UtcScanTime);
}
[TestCase(-1, true)]
[TestCase(0, true)]
[TestCase(1, true)]
[TestCase(2, true)]
[TestCase(3, true)]
[TestCase(24, true)]
[TestCase(-24, true)]
[TestCase(-1, false)]
[TestCase(0, false)]
[TestCase(1, false)]
[TestCase(2, false)]
[TestCase(3, false)]
[TestCase(24, false)]
[TestCase(-24, false)]
public void ScanTimeOnWorkingBarDayLightSavings(int hoursShift, bool savingsStart)
{
var tz = TimeZones.NewYork;
DateTime time;
if (savingsStart)
{
time = new DateTime(2024, 3, 10).AddHours(hoursShift).ConvertToUtc(tz);
}
else
{
time = new DateTime(2024, 11, 3).AddHours(hoursShift).ConvertToUtc(tz);
}
var timeKeeper = new TimeKeeper(time, tz);
var localtime = timeKeeper.GetLocalTimeKeeper(tz);
var increment = Time.OneHour;
using var consolidator = new TestConsolidator();
using var wrapper = new ConsolidatorWrapper(consolidator, increment, timeKeeper, localtime);
wrapper.AdvanceScanTime();
var expected = time.Add(Time.OneHour);
Assert.AreEqual(expected, wrapper.UtcScanTime);
timeKeeper.SetUtcDateTime(wrapper.UtcScanTime);
// set a working bars
consolidator.WorkingData = new TradeBar { Time = time.AddMinutes(100), Period = Time.OneDay };
wrapper.Scan();
// after the scan we adjust the expected end time to the working bar
Assert.AreEqual(consolidator.WorkingData.EndTime.ConvertToUtc(tz), wrapper.UtcScanTime);
}
[Test]
public void ConsolidatorScanPriorityComparerComparesByUtcScanDate()
{
const int id = 1;
var utcScanTime = new DateTime(2024, 12, 10, 0, 0, 0, DateTimeKind.Utc);
var priority1 = new ConsolidatorScanPriority(utcScanTime, id);
var priority2 = new ConsolidatorScanPriority(utcScanTime.AddSeconds(1), id + 1);
var priority3 = new ConsolidatorScanPriority(utcScanTime, id + 1);
Assert.AreEqual(-1, ConsolidatorScanPriority.Comparer.Compare(priority1, priority2));
Assert.AreEqual(1, ConsolidatorScanPriority.Comparer.Compare(priority2, priority1));
Assert.AreEqual(1, ConsolidatorScanPriority.Comparer.Compare(priority3, priority1));
Assert.AreEqual(-1, ConsolidatorScanPriority.Comparer.Compare(priority3, priority2));
Assert.AreEqual(0, ConsolidatorScanPriority.Comparer.Compare(priority1, priority1));
}
[Test]
public void ConsolidatorScanPriorityComparerComparesByIdIfUtcScanTimesAreEqual()
{
const int id = 1;
var utcScanTime = new DateTime(2024, 12, 10, 0, 0, 0, DateTimeKind.Utc);
var priority1 = new ConsolidatorScanPriority(utcScanTime, id);
var priority2 = new ConsolidatorScanPriority(utcScanTime, id + 1);
Assert.AreEqual(1, ConsolidatorScanPriority.Comparer.Compare(priority2, priority1));
}
[Test]
public void ConsolidatorScanPriorityComparerTreatsNullsRight()
{
const int id = 1;
var utcScanTime = new DateTime(2024, 12, 10, 0, 0, 0, DateTimeKind.Utc);
var priority1 = new ConsolidatorScanPriority(utcScanTime, id);
Assert.AreEqual(1, ConsolidatorScanPriority.Comparer.Compare(priority1, null));
Assert.AreEqual(-1, ConsolidatorScanPriority.Comparer.Compare(null, priority1));
Assert.AreEqual(0, ConsolidatorScanPriority.Comparer.Compare(null, null));
}
private class TestConsolidator : IDataConsolidator
{
public IBaseData Consolidated { get; set; }
public IBaseData WorkingData { get; set; }
public Type InputType => typeof(BaseData);
public Type OutputType => typeof(BaseData);
public event DataConsolidatedHandler DataConsolidated;
public void Dispose()
{
}
public void Scan(DateTime currentLocalTime)
{
}
public void Update(IBaseData data)
{
}
public void Consolidate(BaseData dataPoint)
{
Consolidated = dataPoint;
DataConsolidated?.Invoke(this, dataPoint);
}
public void Reset()
{
}
}
}
}
@@ -0,0 +1,104 @@
/*
* 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.Collections.Generic;
using NUnit.Framework;
using Python.Runtime;
namespace QuantConnect.Tests.Common.Data.Custom
{
[TestFixture]
public class PythonCustomDataTests
{
[Test]
public void IsSparseDataDefaultValue()
{
dynamic instance;
using (Py.GIL())
{
PyObject test = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test(PythonData):
def Pepe(self):
return 1").GetAttr("Test");
instance = test.CreateType().GetBaseDataInstance();
instance.Symbol = Symbol.CreateBase(typeof(decimal), Symbols.SPY, QuantConnect.Market.USA);
}
Assert.IsTrue(instance.IsSparseData());
}
[TestCase("True", true)]
[TestCase("False", false)]
public void OverridesIsSparseData(string value, bool booleanValue)
{
dynamic instance;
using (Py.GIL())
{
PyObject test = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test(PythonData):
def IsSparseData(self):
return " + $"{value}").GetAttr("Test");
instance = test.CreateType().GetBaseDataInstance();
}
Assert.AreEqual(booleanValue, instance.IsSparseData());
}
[Test]
public void OverridesDefaultResolution()
{
dynamic instance;
using (Py.GIL())
{
PyObject test = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test(PythonData):
def DefaultResolution(self):
return Resolution.Tick").GetAttr("Test");
instance = test.CreateType().GetBaseDataInstance();
}
Assert.AreEqual(Resolution.Tick, instance.DefaultResolution());
}
[Test]
public void OverridesSupportedResolutions()
{
dynamic instance;
using (Py.GIL())
{
PyObject test = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test(PythonData):
def SupportedResolutions(self):
return [ Resolution.Tick, Resolution.Daily ]").GetAttr("Test");
instance = test.CreateType().GetBaseDataInstance();
}
var res = instance.SupportedResolutions();
Assert.AreEqual(new List<Resolution> { Resolution.Tick, Resolution.Daily }, res);
}
}
}
@@ -0,0 +1,174 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using System;
using System.Linq;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class DataQueueHandlerSubscriptionManagerTests
{
private DataQueueHandlerSubscriptionManager _subscriptionManager;
[SetUp]
public void SetUp()
{
_subscriptionManager = new FakeDataQueuehandlerSubscriptionManager((t) => "quote-trade");
}
[Test]
public void SubscribeSingleSingleChannel()
{
_subscriptionManager.Subscribe(GetSubscriptionDataConfig<TradeBar>(Symbols.AAPL, Resolution.Minute));
Assert.NotZero(_subscriptionManager.GetSubscribedSymbols().Count());
Assert.Contains(Symbols.AAPL, _subscriptionManager.GetSubscribedSymbols().ToArray());
}
[Test]
public void SubscribeManySingleChannel()
{
for (int i = 0; i < 10; i++)
{
_subscriptionManager.Subscribe(GetSubscriptionDataConfig<TradeBar>(Symbols.AAPL, Resolution.Minute));
Assert.Contains(Symbols.AAPL, _subscriptionManager.GetSubscribedSymbols().ToList());
Assert.IsTrue(_subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
Assert.IsTrue(_subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Trade));
}
for (int i = 9; i >= 0; i--)
{
_subscriptionManager.Unsubscribe(GetSubscriptionDataConfig<QuoteBar>(Symbols.AAPL, Resolution.Minute));
Assert.AreEqual(i > 0, _subscriptionManager.GetSubscribedSymbols().Count() == 1);
Assert.AreEqual(i > 0, _subscriptionManager.GetSubscribedSymbols().Contains(Symbols.AAPL));
Assert.AreEqual(i > 0, _subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
}
}
[TestCase(typeof(TradeBar), TickType.Trade)]
[TestCase(typeof(QuoteBar), TickType.Quote)]
[TestCase(typeof(OpenInterest), TickType.OpenInterest)]
public void SubscribeSinglePerChannel(Type type, TickType tickType)
{
using var subscriptionManager = new FakeDataQueuehandlerSubscriptionManager((t) => t.ToString());
subscriptionManager.Subscribe(GetSubscriptionDataConfig(type, Symbols.AAPL, Resolution.Minute));
Assert.AreEqual(1, subscriptionManager.GetSubscribedSymbols().Count());
Assert.Contains(Symbols.AAPL, subscriptionManager.GetSubscribedSymbols().ToArray());
foreach (var value in Enum.GetValues(typeof(TickType)))
{
Assert.AreEqual(tickType == (TickType)value, subscriptionManager.IsSubscribed(Symbols.AAPL, (TickType)value));
}
}
[Test]
public void SubscribeManyPerChannel()
{
using var subscriptionManager = new FakeDataQueuehandlerSubscriptionManager((t) => t.ToString());
for (int i = 0; i < 5; i++)
{
subscriptionManager.Subscribe(GetSubscriptionDataConfig<TradeBar>(Symbols.AAPL, Resolution.Minute));
}
Assert.IsTrue(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Trade));
Assert.IsFalse(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
subscriptionManager.Subscribe(GetSubscriptionDataConfig<QuoteBar>(Symbols.AAPL, Resolution.Minute));
Assert.IsTrue(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Trade));
Assert.IsTrue(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
for (int i = 0; i < 5; i++)
{
subscriptionManager.Unsubscribe(GetSubscriptionDataConfig<TradeBar>(Symbols.AAPL, Resolution.Minute));
}
Assert.IsFalse(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Trade));
Assert.IsTrue(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
subscriptionManager.Unsubscribe(GetSubscriptionDataConfig<QuoteBar>(Symbols.AAPL, Resolution.Minute));
Assert.IsFalse(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Trade));
Assert.IsFalse(subscriptionManager.IsSubscribed(Symbols.AAPL, TickType.Quote));
}
[TestCase(TickType.Trade, MarketDataType.TradeBar, 1)]
[TestCase(TickType.Trade, MarketDataType.QuoteBar, 0)]
[TestCase(TickType.Quote, MarketDataType.QuoteBar, 1)]
[TestCase(TickType.OpenInterest, MarketDataType.Tick, 1)]
[TestCase(TickType.OpenInterest, MarketDataType.TradeBar, 0)]
public void GetSubscribeSymbolsBySpecificTickType(TickType tickType, MarketDataType dataType, int expectedCount)
{
using var fakeDataQueueHandler = new FakeDataQueuehandlerSubscriptionManager((tickType) => tickType!.ToString());
switch (dataType)
{
case MarketDataType.TradeBar:
fakeDataQueueHandler.Subscribe(GetSubscriptionDataConfig<TradeBar>(Symbols.AAPL, Resolution.Minute));
break;
case MarketDataType.QuoteBar:
fakeDataQueueHandler.Subscribe(GetSubscriptionDataConfig<QuoteBar>(Symbols.AAPL, Resolution.Minute));
break;
case MarketDataType.Tick:
fakeDataQueueHandler.Subscribe(GetSubscriptionDataConfig<OpenInterest>(Symbols.AAPL, Resolution.Minute));
break;
}
var subscribeSymbols = fakeDataQueueHandler.GetSubscribedSymbols(tickType).ToList();
Assert.That(subscribeSymbols.Count, Is.EqualTo(expectedCount));
}
#region helper
private SubscriptionDataConfig GetSubscriptionDataConfig(Type T, Symbol symbol, Resolution resolution, TickType? tickType = null)
{
return new SubscriptionDataConfig(
T,
symbol,
resolution,
TimeZones.Utc,
TimeZones.Utc,
true,
true,
false,
tickType: tickType);
}
protected SubscriptionDataConfig GetSubscriptionDataConfig<T>(Symbol symbol, Resolution resolution)
{
return new SubscriptionDataConfig(
typeof(T),
symbol,
resolution,
TimeZones.Utc,
TimeZones.Utc,
true,
true,
false);
}
#endregion
}
}
@@ -0,0 +1,158 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using System;
using System.Collections.Generic;
using System.Threading;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class DividendYieldProviderTests
{
// Without a price:
[TestCase("19700306", null, 0.0)] // Date in before the first date in file
[TestCase("20191107", null, 0.0118177)] // Dividend on this date
[TestCase("20191108", null, 0.0118177)] // Same dividend yield is fill-forwarded for every day until next dividend
[TestCase("20200205", null, 0.0118177)]
[TestCase("20200206", null, 0.0118177)]
[TestCase("20200207", null, 0.0094708)] // Dividend on this date
[TestCase("20200208", null, 0.0094708)]
[TestCase("20210203", null, 0.0067840)]
[TestCase("20210204", null, 0.0067840)]
[TestCase("20210205", null, 0.0059684)] // Dividend on this date
[TestCase("20210208", null, 0.0059684)]
[TestCase("20210209", null, 0.0059684)]
[TestCase("20491231", null, 0.0059684)] // Date in far future, assuming same rate
// With price:
[TestCase("19700306", 1.0, 0.0)] // Date in before the first date in file
[TestCase("20191107", 257.24, 0.0118177)] // Dividend on this date
[TestCase("20191108", 259.43, 0.0117179)]
[TestCase("20200205", 318.85, 0.0095342)]
[TestCase("20200206", 321.45, 0.0094571)]
[TestCase("20200207", 325.21, 0.0094708)] // Dividend on this date
[TestCase("20200210", 320.03, 0.0096240)]
[TestCase("20210203", 134.99, 0.0059819)]
[TestCase("20210204", 133.94, 0.0060288)]
[TestCase("20210205", 137.39, 0.0059684)] // Dividend on this date
[TestCase("20210208", 136.76, 0.0059959)]
[TestCase("20210209", 136.91, 0.0059893)] // Date in far future, assuming same rate
public void GetDividendYieldRate(string dateString, double? price, double expected)
{
var symbol = Symbols.AAPL;
var provider = new DividendYieldProvider(symbol);
var dateTime = Parse.DateTimeExact(dateString, "yyyyMMdd");
var result = price.HasValue
? provider.GetDividendYield(dateTime, Convert.ToDecimal(price.Value))
: provider.GetDividendYield(dateTime);
Assert.AreEqual(expected, (double)result, 1e-7);
}
[TestCase("19700101", 0.0)] // Date before Time.Start
[TestCase("20200101", 0.0)]
[TestCase("20500101", 0.0)] // Date in far future
public void GetDividendYieldWithoutFactorFile(string dateString, decimal expected)
{
var symbol = Symbols.EURUSD;
var provider = new DividendYieldProvider(symbol);
var dateTime = Parse.DateTimeExact(dateString, "yyyyMMdd");
var result = provider.GetDividendYield(dateTime);
Assert.AreEqual(expected, result);
}
[Test]
public void CacheIsCleared()
{
var symbol = Symbols.AAPL;
using var fileProviderTest = new DividendYieldProviderTest(symbol);
fileProviderTest.GetDividendYield(new DateTime(2020, 1, 1));
var fetchCount = fileProviderTest.FetchCount;
Thread.Sleep(1);
fileProviderTest.GetDividendYield(new DateTime(2020, 1, 1));
Assert.AreEqual(fetchCount, fileProviderTest.FetchCount);
var counter = 0;
while (counter++ < 10)
{
fileProviderTest.GetDividendYield(new DateTime(2020, 1, 1));
if (fileProviderTest.FetchCount <= fetchCount)
{
Thread.Sleep(250);
}
else
{
break;
}
}
Assert.Less(counter, 10);
}
[Test]
public void AnotherSymbolCall()
{
using var fileProviderTest = new DividendYieldProviderTest(Symbol.Create("TEST_A", SecurityType.Equity, QuantConnect.Market.USA));
var applYield = fileProviderTest.GetDividendYield(new DateTime(2020, 1, 1));
Assert.AreEqual(1, fileProviderTest.FetchCount);
using var fileProviderTest2 = new DividendYieldProviderTest(Symbol.Create("TEST_B", SecurityType.Equity, QuantConnect.Market.USA));
var spyYield = fileProviderTest2.GetDividendYield(new DateTime(2020, 1, 1));
Assert.AreEqual(1, fileProviderTest2.FetchCount);
}
private class DividendYieldProviderTest : DividendYieldProvider, IDisposable
{
public int FetchCount { get; set; }
protected override TimeSpan CacheRefreshPeriod => TimeSpan.FromSeconds(1);
public DividendYieldProviderTest(Symbol symbol)
: base(symbol)
{
}
protected override List<BaseData> LoadCorporateEvents(Symbol symbol)
{
FetchCount++;
return base.LoadCorporateEvents(symbol);
}
public void Reset()
{
try
{
// stop the refresh task
var task = DividendYieldProvider._cacheClearTask;
DividendYieldProvider._cacheClearTask = null;
task.Dispose();
}
catch
{
}
}
public void Dispose()
{
Reset();
}
}
}
}
@@ -0,0 +1,182 @@
/*
* 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 Common.Data.Consolidators;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class DollarVolumeRenkoConsolidatorTests : BaseConsolidatorTests
{
protected override IDataConsolidator CreateConsolidator()
{
return new DollarVolumeRenkoConsolidator(10m);
}
[Test]
public void OutputTypeIsVolumeRenkoBar()
{
using var consolidator = new DollarVolumeRenkoConsolidator(10);
Assert.AreEqual(typeof(VolumeRenkoBar), consolidator.OutputType);
}
[Test]
public void ConsolidatesOnTickDollarVolumeReached()
{
VolumeRenkoBar bar = null;
using var consolidator = new DollarVolumeRenkoConsolidator(100m); // $100 bar size
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var startTime = new DateTime(2023, 1, 1);
// Price: $10, Quantity: 7 -> $70 dollar volume
consolidator.Update(new Tick(startTime, Symbols.AAPL, "", "", 7m, 10m));
Assert.IsNull(bar);
// Price: $3, Quantity: 20 -> $60 dollar volume (total $110)
consolidator.Update(new Tick(startTime.AddHours(1), Symbols.AAPL, "", "", 20m, 3m));
Assert.IsNotNull(bar);
// Verify bar properties
Assert.AreEqual(10m, bar.Open);
Assert.AreEqual(10m, bar.High);
Assert.AreEqual(3m, bar.Low);
Assert.AreEqual(3m, bar.Close);
Assert.AreEqual(100m, bar.Volume);
Assert.AreEqual(100m, bar.BrickSize);
Assert.AreEqual(Symbols.AAPL, bar.Symbol);
Assert.AreEqual(startTime, bar.Start);
Assert.AreEqual(startTime.AddHours(1), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void ConsolidatesOnTradeBarDollarVolumeReached()
{
VolumeRenkoBar bar = null;
using var consolidator = new DollarVolumeRenkoConsolidator(200m); // $200 bar size
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var startTime = new DateTime(2023, 1, 1);
// Close: $11 Volume: 10 -> Dollar volume: $100
consolidator.Update(new TradeBar(startTime, Symbols.AAPL, 10m, 12m, 9m, 11m, 10m, TimeSpan.FromHours(1)));
Assert.IsNull(bar);
// Close: $21 Volume: 6 -> Dollar volume: $126 (total $226)
consolidator.Update(new TradeBar(startTime.AddHours(1), Symbols.AAPL, 20m, 22m, 18m, 21m, 6m, TimeSpan.FromHours(1)));
Assert.IsNotNull(bar);
// Verify bar properties
Assert.AreEqual(10m, bar.Open);
Assert.AreEqual(22m, bar.High);
Assert.AreEqual(9m, bar.Low);
Assert.AreEqual(21m, bar.Close);
Assert.AreEqual(200m, bar.Volume);
Assert.AreEqual(200m, bar.BrickSize);
Assert.AreEqual(Symbols.AAPL, bar.Symbol);
Assert.AreEqual(startTime, bar.Start);
Assert.AreEqual(startTime.AddHours(2), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void HandlesMultipleConsolidations()
{
var consolidatedBars = new List<VolumeRenkoBar>();
using var consolidator = new DollarVolumeRenkoConsolidator(100m);
consolidator.DataConsolidated += (sender, consolidated) =>
{
consolidatedBars.Add(consolidated);
};
var startTime = new DateTime(2023, 1, 1);
// First bar: $50 + $60 = $110
consolidator.Update(new Tick(startTime, Symbols.AAPL, "", "", 10m, 5m));
consolidator.Update(new Tick(startTime.AddHours(1), Symbols.AAPL, "", "", 20m, 3m));
// Second bar: $80 + $30 = $110 (total $220)
consolidator.Update(new Tick(startTime.AddHours(2), Symbols.AAPL, "", "", 20m, 4m));
consolidator.Update(new Tick(startTime.AddHours(3), Symbols.AAPL, "", "", 20m, 1.5m));
Assert.AreEqual(2, consolidatedBars.Count);
// Verify first bar
Assert.AreEqual(5m, consolidatedBars[0].Open);
Assert.AreEqual(3m, consolidatedBars[0].Close);
Assert.AreEqual(100m, consolidatedBars[0].Volume);
Assert.IsTrue(consolidatedBars[0].IsClosed);
// Verify second bar
Assert.AreEqual(3m, consolidatedBars[1].Open);
Assert.AreEqual(1.5m, consolidatedBars[1].Close);
Assert.AreEqual(100m, consolidatedBars[1].Volume);
Assert.IsTrue(consolidatedBars[1].IsClosed);
}
[Test]
public void ThrowsOnNonTradeData()
{
using var consolidator = new DollarVolumeRenkoConsolidator(100m);
var startTime = new DateTime(2023, 1, 1);
Assert.Throws<ArgumentException>(() =>
consolidator.Update(new QuoteBar(
startTime,
Symbols.AAPL,
new Bar(1m, 1m, 1m, 1m),
1m,
new Bar(1m, 1m, 1m, 1m),
1m,
TimeSpan.FromHours(1)
))
);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2023, 1, 1);
return new List<Tick>()
{
new Tick(time, Symbols.AAPL, "", "", 10m, 5m), // $50
new Tick(time.AddSeconds(1), Symbols.AAPL, "", "", 12m, 4m), // $48
new Tick(time.AddSeconds(2), Symbols.AAPL, "", "", 15m, 2m), // $30
new Tick(time.AddSeconds(3), Symbols.AAPL, "", "", 14m, 3m), // $42
new Tick(time.AddSeconds(4), Symbols.AAPL, "", "", 16m, 5m), // $80
new Tick(time.AddSeconds(5), Symbols.AAPL, "", "", 18m, 3m), // $54
new Tick(time.AddSeconds(6), Symbols.AAPL, "", "", 17m, 4m), // $68
new Tick(time.AddSeconds(7), Symbols.AAPL, "", "", 19m, 2m), // $38
new Tick(time.AddSeconds(8), Symbols.AAPL, "", "", 20m, 6m), // $120
new Tick(time.AddSeconds(9), Symbols.AAPL, "", "", 22m, 3m), // $66
new Tick(time.AddSeconds(10), Symbols.AAPL, "", "", 21m, 4m), // $84
new Tick(time.AddSeconds(11), Symbols.AAPL, "", "", 23m, 5m), // $115
new Tick(time.AddSeconds(12), Symbols.AAPL, "", "", 25m, 2m) // $50
};
}
}
}
@@ -0,0 +1,312 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class DynamicDataConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void AggregatesTimeValuePairsWithOutVolumeProperly()
{
TradeBar newTradeBar = null;
using var consolidator = new DynamicDataConsolidator(4);
consolidator.DataConsolidated += (sender, tradeBar) =>
{
newTradeBar = tradeBar;
};
var reference = DateTime.Today;
var bar1 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference,
Value = 5
};
consolidator.Update(bar1);
Assert.IsNull(newTradeBar);
var bar2 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
Value = 10
};
consolidator.Update(bar2);
Assert.IsNull(newTradeBar);
var bar3 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(2),
Value = 1
};
consolidator.Update(bar3);
Assert.IsNull(newTradeBar);
var bar4 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(3),
Value = 9
};
consolidator.Update(bar4);
Assert.IsNotNull(newTradeBar);
Assert.AreEqual(Symbols.SPY, newTradeBar.Symbol);
Assert.AreEqual(bar1.Time, newTradeBar.Time);
Assert.AreEqual(bar1.Value, newTradeBar.Open);
Assert.AreEqual(bar2.Value, newTradeBar.High);
Assert.AreEqual(bar3.Value, newTradeBar.Low);
Assert.AreEqual(bar4.Value, newTradeBar.Close);
Assert.AreEqual(0, newTradeBar.Volume);
Assert.AreEqual(bar4.EndTime, newTradeBar.EndTime);
}
[Test]
public void AggregatesTimeValuePairsWithVolumeProperly()
{
TradeBar newTradeBar = null;
using var consolidator = new DynamicDataConsolidator(4);
consolidator.DataConsolidated += (sender, tradeBar) =>
{
newTradeBar = tradeBar;
};
var reference = DateTime.Today;
dynamic bar1 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference,
Value = 5,
};
bar1.Volume = 75L;
consolidator.Update(bar1);
Assert.IsNull(newTradeBar);
dynamic bar2 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
Value = 10
};
bar2.Volume = 100L;
consolidator.Update(bar2);
Assert.IsNull(newTradeBar);
dynamic bar3 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(2),
Value = 1
};
bar3.Volume = 115L;
consolidator.Update(bar3);
Assert.IsNull(newTradeBar);
dynamic bar4 = new CustomData
{
Symbol = Symbols.SPY,
Time = reference.AddHours(3),
Value = 9
};
bar4.Volume = 85L;
consolidator.Update(bar4);
Assert.IsNotNull(newTradeBar);
Assert.AreEqual(Symbols.SPY, newTradeBar.Symbol);
Assert.AreEqual(bar1.Time, newTradeBar.Time);
Assert.AreEqual(bar1.Value, newTradeBar.Open);
Assert.AreEqual(bar2.Value, newTradeBar.High);
Assert.AreEqual(bar3.Value, newTradeBar.Low);
Assert.AreEqual(bar4.Value, newTradeBar.Close);
Assert.AreEqual(bar1.Volume + bar2.Volume + bar3.Volume + bar4.Volume, newTradeBar.Volume);
}
[Test]
public void AggregatesTradeBarsWithVolumeProperly()
{
TradeBar consolidated = null;
using var consolidator = new DynamicDataConsolidator(3);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = DateTime.Today;
dynamic bar1 = new CustomData();
bar1.Symbol = Symbols.SPY;
bar1.Time = reference;
bar1.Open = 10;
bar1.High = 100m;
bar1.Low = 1m;
bar1.Close = 50m;
bar1.Volume = 75L;
dynamic bar2 = new CustomData();
bar2.Symbol = Symbols.SPY;
bar2.Time = reference.AddHours(1);
bar2.Open = 50m;
bar2.High = 123m;
bar2.Low = 35m;
bar2.Close = 75m;
bar2.Volume = 100L;
dynamic bar3 = new CustomData();
bar3.Symbol = Symbols.SPY;
bar3.Time = reference.AddHours(1);
bar3.Open = 75m;
bar3.High = 100m;
bar3.Low = 50m;
bar3.Close = 83m;
bar3.Volume = 125L;
consolidator.Update(bar1);
Assert.IsNull(consolidated);
consolidator.Update(bar2);
Assert.IsNull(consolidated);
consolidator.Update(bar3);
Assert.IsNotNull(consolidated);
Assert.AreEqual(Symbols.SPY, consolidated.Symbol);
Assert.AreEqual(bar1.Open, consolidated.Open);
Assert.AreEqual(Math.Max(bar1.High, Math.Max(bar2.High, bar3.High)), consolidated.High);
Assert.AreEqual(Math.Min(bar1.Low, Math.Min(bar2.Low, bar3.Low)), consolidated.Low);
Assert.AreEqual(bar3.Close, consolidated.Close);
Assert.AreEqual(bar1.Volume + bar2.Volume + bar3.Volume, consolidated.Volume);
}
[Test]
public void AggregatesTradeBarsWithOutVolumeProperly()
{
TradeBar consolidated = null;
using var consolidator = new DynamicDataConsolidator(3);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = DateTime.Today;
dynamic bar1 = new CustomData();
bar1.Symbol = Symbols.SPY;
bar1.Time = reference;
bar1.Open = 10;
bar1.High = 100m;
bar1.Low = 1m;
bar1.Close = 50m;
dynamic bar2 = new CustomData();
bar2.Symbol = Symbols.SPY;
bar2.Time = reference.AddHours(1);
bar2.Open = 50m;
bar2.High = 123m;
bar2.Low = 35m;
bar2.Close = 75m;
dynamic bar3 = new CustomData();
bar3.Symbol = Symbols.SPY;
bar3.Time = reference.AddHours(1);
bar3.Open = 75m;
bar3.High = 100m;
bar3.Low = 50m;
bar3.Close = 83m;
consolidator.Update(bar1);
Assert.IsNull(consolidated);
consolidator.Update(bar2);
Assert.IsNull(consolidated);
consolidator.Update(bar3);
Assert.IsNotNull(consolidated);
Assert.AreEqual(Symbols.SPY, consolidated.Symbol);
Assert.AreEqual(bar1.Open, consolidated.Open);
Assert.AreEqual(Math.Max(bar1.High, Math.Max(bar2.High, bar3.High)), consolidated.High);
Assert.AreEqual(Math.Min(bar1.Low, Math.Min(bar2.Low, bar3.Low)), consolidated.Low);
Assert.AreEqual(bar3.Close, consolidated.Close);
Assert.AreEqual(0, consolidated.Volume);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var reference = DateTime.Today;
dynamic bar1 = new CustomData();
bar1.Symbol = Symbols.SPY;
bar1.Time = reference;
bar1.Open = 10;
bar1.High = 100m;
bar1.Low = 1m;
bar1.Close = 50m;
dynamic bar2 = new CustomData();
bar2.Symbol = Symbols.SPY;
bar2.Time = reference.AddHours(1);
bar2.Open = 50m;
bar2.High = 123m;
bar2.Low = 35m;
bar2.Close = 75m;
dynamic bar3 = new CustomData();
bar3.Symbol = Symbols.SPY;
bar3.Time = reference.AddHours(2);
bar3.Open = 75m;
bar3.High = 100m;
bar3.Low = 50m;
bar3.Close = 83m;
return new List<CustomData>()
{
bar1,
bar2,
bar3,
bar1,
bar3,
bar2,
bar1,
bar1,
bar3,
bar1
};
}
protected override IDataConsolidator CreateConsolidator()
{
return new DynamicDataConsolidator(3);
}
private class CustomData : DynamicData
{
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
throw new NotImplementedException();
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
throw new NotImplementedException();
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class DynamicDataTests
{
[Test]
public void SupportsSnakeNameRetrival()
{
dynamic data = new DataType();
data.PropertyA = 1;
Assert.AreEqual(1, data.property_a);
Assert.AreEqual(data.PropertyA, data.property_a);
}
[Test]
public void StoresValues_Using_LowerCaseKeys()
{
dynamic data = new DataType();
data.Property = 1;
Assert.AreEqual(1, data.Property);
Assert.AreEqual(data.Property, data.property);
}
[Test]
public void StoresBaseDataValues_Using_BaseDataProperties()
{
var value = 1234.567890m;
var time = new DateTime(2000, 1, 2, 3, 4, 5, 6);
var symbol = Symbol.Create("ticker", SecurityType.Base, QuantConnect.Market.USA, baseDataType: typeof(DataType));
dynamic data = new DataType();
data.Time = time;
data.Value = value;
data.Symbol = symbol;
BaseData baseData = data;
Assert.AreEqual(time, baseData.Time);
Assert.AreEqual(time, baseData.EndTime);
Assert.AreEqual(value, baseData.Value);
Assert.AreEqual(value, baseData.Price);
Assert.AreEqual(symbol, baseData.Symbol);
// let's access the properties through the dynamic handling
Assert.AreEqual(time, data.Time);
Assert.AreEqual(time, data.EndTime);
Assert.AreEqual(value, data.Value);
Assert.AreEqual(value, data.Price);
Assert.AreEqual(symbol, data.Symbol);
}
[Test]
public void AccessingPropertyThatDoesNotExist_ThrowsKeyNotFoundException()
{
dynamic data = new DataType();
Assert.Throws<KeyNotFoundException>(() =>
{
var _ = data.UndefinedPropertyName;
});
}
private class DataType : DynamicData
{
}
}
}
@@ -0,0 +1,37 @@
/*
* 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 QuantConnect.Data;
using System;
using System.Collections.Generic;
namespace QuantConnect.Tests.Common.Data
{
public class FakeDataQueuehandlerSubscriptionManager : DataQueueHandlerSubscriptionManager
{
private Func<TickType, string> _getChannelName;
public FakeDataQueuehandlerSubscriptionManager(Func<TickType, string> getChannelName)
{
_getChannelName = getChannelName;
}
protected override bool Subscribe(IEnumerable<Symbol> symbols, TickType tickType) => true;
protected override bool Unsubscribe(IEnumerable<Symbol> symbols, TickType tickType) => true;
protected override string ChannelNameFromTickType(TickType tickType) => _getChannelName(tickType);
}
}
@@ -0,0 +1,60 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
[TestFixture]
public class BaseFundamentalDataProviderTests
{
[Test]
public void NoValueNull()
{
Assert.IsTrue(BaseFundamentalDataProvider.IsNone(null));
Assert.IsTrue(BaseFundamentalDataProvider.IsNone(null, null));
}
[Test]
public void NoValueDouble()
{
var noValue = BaseFundamentalDataProvider.GetDefault<double>();
Assert.AreEqual(double.NaN, noValue);
Assert.IsTrue(BaseFundamentalDataProvider.IsNone(noValue));
}
[Test]
public void NoValueDecimal()
{
var noValue = BaseFundamentalDataProvider.GetDefault<decimal>();
Assert.AreEqual(0, noValue);
Assert.IsTrue(BaseFundamentalDataProvider.IsNone(noValue));
}
[Test]
public void DatetimeNoTz()
{
var noValue = BaseFundamentalDataProvider.GetDefault<DateTime>();
Assert.AreEqual(DateTime.MinValue, noValue);
Assert.AreEqual(DateTimeKind.Unspecified, noValue.Kind);
Assert.IsTrue(BaseFundamentalDataProvider.IsNone(noValue));
}
}
}
@@ -0,0 +1,51 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
[TestFixture]
public class FundamentalTests
{
[SetUp]
public void Setup()
{
FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProvider(), false);
}
[Test]
public void ComputesMarketCapCorrectly()
{
var fine = new QuantConnect.Data.Fundamental.Fundamental(new DateTime(2014, 04, 01), Symbols.AAPL);
Assert.AreEqual(541.74m, fine.Price);
Assert.AreEqual(469400291359, fine.MarketCap);
}
[Test]
public void ZeroMarketCapForDefaultObject()
{
var fine = new QuantConnect.Data.Fundamental.Fundamental();
Assert.AreEqual(0, fine.Price);
Assert.AreEqual(0, fine.MarketCap);
}
}
}
@@ -0,0 +1,64 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Statistics;
using System.Collections.Generic;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
[TestFixture]
public class FundamentalUniverseSelectionModelTests
{
[Test]
public void PythonAlgorithmUsingCSharpSelection()
{
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters("FundamentalUniverseSelectionAlgorithm",
new Dictionary<string, string> {
{PerformanceMetrics.TotalOrders, "3"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "-3.123%"},
{"Drawdown", "0.100%"},
{"Expectancy", "0"},
{"Net Profit", "-0.122%"},
{"Sharpe Ratio", "-5.568"},
{"Probabilistic Sharpe Ratio", "6.292%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.022"},
{"Beta", "0.048"},
{"Annual Standard Deviation", "0.006"},
{"Annual Variance", "0"},
{"Information Ratio", "1.712"},
{"Tracking Error", "0.093"},
{"Treynor Ratio", "-0.636"},
{"Total Fees", "$3.00"},
{"Estimated Strategy Capacity", "$2300000000.00"},
{"Lowest Capacity Asset", "IBM R735QTJ8XC9X"},
{"Portfolio Turnover", "0.42%"},
{"OrderListHash", "9bd2017fdcf8f503e86dfa3bf6e33520"}
},
Language.Python,
AlgorithmStatus.Completed);
AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
parameter.Statistics,
parameter.Language,
parameter.ExpectedFinalStatus);
}
}
}
@@ -0,0 +1,292 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using NUnit.Framework;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
[TestFixture]
public class MultiPeriodFieldTests
{
private TestMultiPeriodField _field;
[SetUp]
public void SetUp()
{
_field = new TestMultiPeriodField();
_field.ThreeMonths = 1;
_field.OneYear = 5;
_field.FiveYears = 2;
}
[Test]
public void ReturnsDefaultPeriod()
{
Assert.IsTrue(_field.HasValue);
Assert.AreEqual(5, (decimal)_field);
Assert.AreEqual(5, _field.Value);
}
[Test]
public void ReturnsRequestedPeriodWithDataAvailable()
{
Assert.IsTrue(_field.HasPeriodValue("3M"));
Assert.AreEqual(1, _field.GetPeriodValue("3M"));
Assert.AreEqual(1, _field.ThreeMonths);
}
[Test]
public void ReturnsRequestedPeriodWithNoData()
{
Assert.IsFalse(_field.HasPeriodValue("3Y"));
Assert.AreEqual(MultiPeriodField.NoValue, _field.GetPeriodValue("3Y"));
Assert.AreEqual(MultiPeriodField.NoValue, _field.ThreeYears);
}
[Test]
public void ReturnsCorrectPeriodNamesAndValues()
{
Assert.AreEqual(new[] { "1Y", "3M", "5Y" }, _field.GetPeriodNames());
Assert.AreEqual(new[] { "1Y", "3M", "5Y" }, _field.GetPeriodValues().Keys);
Assert.AreEqual(new[] { 5, 1, 2 }, _field.GetPeriodValues().Values);
}
[Test]
public void ReturnsFirstPeriodIfNoDefaultAvailable()
{
var field = new TestMultiPeriodField();
field.ThreeMonths = 1;
field.FiveYears = 2;
Assert.IsFalse(field.HasValue);
Assert.AreEqual(1, (decimal)field);
Assert.AreEqual(1, field.Value);
}
[Test]
public void EmptyStore()
{
using var field = new TestMultiPeriodField();
Assert.IsFalse(field.HasValue);
Assert.AreEqual(MultiPeriodField.NoValue, field.Value);
Assert.AreEqual(MultiPeriodField.NoValue, field.FiveYears);
Assert.AreEqual(MultiPeriodField.NoValue, field.OneYear);
Assert.AreEqual(Enumerable.Empty<string>(), field.GetPeriodNames());
Assert.AreEqual(MultiPeriodField.NoValue, field.GetPeriodValue(QuantConnect.Data.Fundamental.Period.OneYear));
Assert.AreEqual(MultiPeriodField.NoValue, field.GetPeriodValue(QuantConnect.Data.Fundamental.Period.TenYears));
Assert.AreEqual(0, field.GetPeriodValues().Count);
Assert.IsFalse(field.HasPeriodValue(QuantConnect.Data.Fundamental.Period.OneYear));
Assert.IsFalse(field.HasPeriodValue(QuantConnect.Data.Fundamental.Period.TenYears));
}
[Test]
public void EmptyStoreToString()
{
using var field = new TestMultiPeriodField();
Assert.AreEqual("", field.ToString());
}
[Test]
public void ArithmeticOperatorsBetweenDoubleFields()
{
var left = new TestMultiPeriodField();
left.OneYear = 10;
var right = new TestMultiPeriodField();
right.OneYear = 4;
Assert.AreEqual(14m, left + right);
Assert.AreEqual(6m, left - right);
Assert.AreEqual(40m, left * right);
Assert.AreEqual(2.5m, left / right);
Assert.AreEqual(2m, left % right);
}
[Test]
public void ArithmeticOperatorsBetweenLongFields()
{
var left = new TestMultiPeriodFieldLong();
left.OneYear = 10;
var right = new TestMultiPeriodFieldLong();
right.OneYear = 4;
Assert.AreEqual(14m, left + right);
Assert.AreEqual(6m, left - right);
Assert.AreEqual(40m, left * right);
Assert.AreEqual(2.5m, left / right);
Assert.AreEqual(2m, left % right);
}
[Test]
public void ArithmeticOperatorsBetweenDoubleAndLongFields()
{
var doubleField = new TestMultiPeriodField();
doubleField.OneYear = 10;
var longField = new TestMultiPeriodFieldLong();
longField.OneYear = 4;
// double on the left, long on the right
Assert.AreEqual(14m, doubleField + longField);
Assert.AreEqual(6m, doubleField - longField);
Assert.AreEqual(40m, doubleField * longField);
Assert.AreEqual(2.5m, doubleField / longField);
Assert.AreEqual(2m, doubleField % longField);
// long on the left, double on the right
Assert.AreEqual(14m, longField + doubleField);
Assert.AreEqual(-6m, longField - doubleField);
Assert.AreEqual(40m, longField * doubleField);
Assert.AreEqual(0.4m, longField / doubleField);
Assert.AreEqual(4m, longField % doubleField);
}
[Test]
public void ArithmeticOperatorsWithScalar()
{
var field = new TestMultiPeriodField();
field.OneYear = 10;
// field implicitly converts to decimal, the built-in decimal operators apply
Assert.AreEqual(13m, field + 3m);
Assert.AreEqual(7m, field - 3m);
Assert.AreEqual(30m, field * 3m);
Assert.AreEqual(5m, field / 2m);
Assert.AreEqual(1m, field % 3m);
}
[Test]
public void ArithmeticOperatorsUseDefaultPeriodValue()
{
// No default period value available, falls back to first available period (3M = 1)
var left = new TestMultiPeriodField();
left.ThreeMonths = 1;
var right = new TestMultiPeriodField();
right.ThreeMonths = 1;
Assert.IsFalse(left.HasValue);
Assert.AreEqual(2m, left + right);
}
private class TestMultiPeriodField : MultiPeriodField
{
protected override string DefaultPeriod => "OneYear";
public double ThreeMonths { get; set; } = NoValue;
public double OneYear { get; set; } = NoValue;
public double ThreeYears { get; set; } = NoValue;
public double FiveYears { get; set; } = NoValue;
public override bool HasValue => !BaseFundamentalDataProvider.IsNone(typeof(double), OneYear);
public override double Value
{
get
{
var defaultValue = OneYear;
if (!BaseFundamentalDataProvider.IsNone(typeof(double), defaultValue))
{
return defaultValue;
}
return base.Value;
}
}
public override double GetPeriodValue(string period)
{
switch(period)
{
case QuantConnect.Data.Fundamental.Period.ThreeMonths:
return ThreeMonths;
case QuantConnect.Data.Fundamental.Period.OneYear:
return OneYear;
case QuantConnect.Data.Fundamental.Period.ThreeYears:
return ThreeYears;
case QuantConnect.Data.Fundamental.Period.FiveYears:
return FiveYears;
default:
return NoValue;
}
}
public override IReadOnlyDictionary<string, double> GetPeriodValues()
{
var result = new Dictionary<string, double>();
foreach (var kvp in new[] { new Tuple<string, double>("1Y", OneYear), new Tuple<string, double>("3M", ThreeMonths), new Tuple<string, double>("3Y", ThreeYears), new Tuple<string, double>("5Y", FiveYears) })
{
if (!BaseFundamentalDataProvider.IsNone(typeof(double), kvp.Item2))
{
result[kvp.Item1] = kvp.Item2;
}
}
return result;
}
}
private class TestMultiPeriodFieldLong : MultiPeriodFieldLong
{
protected override string DefaultPeriod => "OneYear";
public long ThreeMonths { get; set; } = NoValue;
public long OneYear { get; set; } = NoValue;
public override bool HasValue => !BaseFundamentalDataProvider.IsNone(typeof(long), OneYear);
public override long Value
{
get
{
var defaultValue = OneYear;
if (!BaseFundamentalDataProvider.IsNone(typeof(long), defaultValue))
{
return defaultValue;
}
return base.Value;
}
}
public override long GetPeriodValue(string period)
{
switch (period)
{
case QuantConnect.Data.Fundamental.Period.ThreeMonths:
return ThreeMonths;
case QuantConnect.Data.Fundamental.Period.OneYear:
return OneYear;
default:
return NoValue;
}
}
public override IReadOnlyDictionary<string, long> GetPeriodValues()
{
var result = new Dictionary<string, long>();
foreach (var kvp in new[] { new Tuple<string, long>("1Y", OneYear), new Tuple<string, long>("3M", ThreeMonths) })
{
if (!BaseFundamentalDataProvider.IsNone(typeof(long), kvp.Item2))
{
result[kvp.Item1] = kvp.Item2;
}
}
return result;
}
}
}
}
@@ -0,0 +1,30 @@
/*
* 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 QuantConnect.Interfaces;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
public class NullFundamentalDataProvider : IFundamentalDataProvider
{
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name) => BaseFundamentalDataProvider.GetDefault<T>();
public void Initialize(IDataProvider dataProvider, bool liveMode)
{
}
}
}
@@ -0,0 +1,142 @@
/*
* 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 QuantConnect.Interfaces;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Fundamental;
namespace QuantConnect.Tests.Common.Data.Fundamental
{
public class TestFundamentalDataProvider : IFundamentalDataProvider
{
private readonly CoarseFundamentalDataProvider _coarseFundamentalData = new();
private readonly Dictionary<string, double> _pERatio = new()
{
{ "AAPL R735QTJ8XC9X", 13.012856d },
{ "IBM R735QTJ8XC9X", 12.394244d },
{ "AIG R735QTJ8XC9X", 8.185855d },
};
private readonly Dictionary<string, string> _industryTemplateCode = new()
{
{ "AAPL R735QTJ8XC9X", "N" },
{ "IBM R735QTJ8XC9X", "N" },
{ "GOOG T1AZ164W5VTX", "N" },
{ "GOOCV VP83T1ZUHROL", "N" },
{ "NB R735QTJ8XC9X", "B" },
{ "AIG R735QTJ8XC9X", "I" },
};
private readonly Dictionary<string, double> _equityPerShareGrowthOneYear = new()
{
{ "AAPL R735QTJ8XC9X", 0.091652d },
{ "IBM R735QTJ8XC9X", 0.280664d },
{ "GOOCV VP83T1ZUHROL", 0.196226d },
{ "NB R735QTJ8XC9X", 0.022944d },
};
private readonly Dictionary<string, long> _marketCap = new()
{
{ "AIG R735QTJ8XC9X", 72866646492 },
{ "AAPL R735QTJ8XC9X", 469400291359 },
{ "IBM R735QTJ8XC9X", 192825068158 },
{ "GOOCV VP83T1ZUHROL", 375779584963 },
{ "NB R735QTJ8XC9X", 181116782342 },
};
private readonly Dictionary<string, long> _sharesOutstanding = new()
{
{ "SPY R735QTJ8XC9X", 1331000000 },
{ "AAPL R735QTJ8XC9X", 22337000000000 },
};
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
if (securityIdentifier == SecurityIdentifier.Empty)
{
return default;
}
var enumName = Enum.GetName(name);
switch (enumName)
{
case nameof(CoarseFundamental.Price):
case nameof(CoarseFundamental.Value):
case nameof(CoarseFundamental.Market):
case nameof(CoarseFundamental.Volume):
case nameof(CoarseFundamental.PriceFactor):
case nameof(CoarseFundamental.SplitFactor):
case nameof(CoarseFundamental.DollarVolume):
return _coarseFundamentalData.Get<T>(time, securityIdentifier, name);
default:
return Get(time, securityIdentifier, enumName);
}
}
private dynamic Get(DateTime time, SecurityIdentifier securityIdentifier, string name)
{
switch (name)
{
case nameof(CoarseFundamental.HasFundamentalData):
return true;
case "CompanyProfile_MarketCap":
if(_marketCap.TryGetValue(securityIdentifier.ToString(), out var marketCap))
{
return marketCap;
}
return 0L;
case "CompanyProfile_HeadquarterCity":
if (securityIdentifier.Symbol == "AAPL")
{
return "Cupertino";
}
return string.Empty;
case "CompanyProfile_SharesOutstanding":
if (_sharesOutstanding.TryGetValue(securityIdentifier.ToString(), out var sharesOutstanding))
{
return sharesOutstanding;
}
return 0L;
case "CompanyReference_IndustryTemplateCode":
if(_industryTemplateCode.TryGetValue(securityIdentifier.ToString(), out var industryTemplateCode))
{
return industryTemplateCode;
}
return string.Empty;
case "EarningRatios_EquityPerShareGrowth_OneYear":
if(_equityPerShareGrowthOneYear.TryGetValue(securityIdentifier.ToString(), out var ePSG))
{
return ePSG;
}
return 0d;
case "ValuationRatios_PERatio":
if (_pERatio.TryGetValue(securityIdentifier.ToString(), out var peRatio))
{
return peRatio;
}
return 0d;
}
return null;
}
public void Initialize(IDataProvider dataProvider, bool liveMode)
{
_coarseFundamentalData.Initialize(dataProvider, liveMode);
}
}
}
@@ -0,0 +1,102 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class IdentityDataConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void ThrowsOnDataOfWrongType()
{
Assert.Throws<ArgumentNullException>(() =>
{
var identity = new IdentityDataConsolidator<Tick>();
identity.Update(new TradeBar());
});
}
[Test]
public void ReturnsTheSameObjectReference()
{
using var identity = new IdentityDataConsolidator<Tick>();
var tick = new Tick();
int count = 0;
identity.DataConsolidated += (sender, data) =>
{
Assert.IsTrue(ReferenceEquals(tick, data));
count++;
};
identity.Update(tick);
Assert.AreEqual(1, count);
}
[Test]
public void IgnoresNonTickDataWithSameTimestamps()
{
var reference = new DateTime(2015, 09, 23);
using var identity = new IdentityDataConsolidator<TradeBar>();
int count = 0;
identity.DataConsolidated += (sender, data) =>
{
count++;
};
var tradeBar = new TradeBar{EndTime = reference};
identity.Update(tradeBar);
tradeBar = (TradeBar) tradeBar.Clone();
identity.Update(tradeBar);
Assert.AreEqual(1, count);
}
[Test]
public void AcceptsTickDataWithSameTimestamps()
{
var reference = new DateTime(2015, 09, 23);
using var identity = new IdentityDataConsolidator<Tick>();
int count = 0;
identity.DataConsolidated += (sender, data) =>
{
count++;
};
var tradeBar = new Tick { EndTime = reference };
identity.Update(tradeBar);
tradeBar = (Tick)tradeBar.Clone();
identity.Update(tradeBar);
Assert.AreEqual(2, count);
}
protected override IDataConsolidator CreateConsolidator()
{
return new IdentityDataConsolidator<IndicatorDataPoint>();
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.IO;
using NUnit.Framework;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class InterestRateProviderTests
{
[Test]
public void Create()
{
const string csvLine = "2020-01-01,2.5";
const decimal expectedInterestRateValue = 0.025m;
var expectedInterestRateDate = new DateTime(2020, 1, 1);
if (!InterestRateProvider.TryParse(csvLine, out var date, out var interestRate))
{
Assert.Fail("Could not convert the line into interest rate data");
}
Assert.AreEqual(expectedInterestRateDate, date);
Assert.AreEqual(expectedInterestRateValue, interestRate);
}
[TestCase("alternative/interest-rate/usa/interest-rate.csv", true)]
[TestCase("non-existing.csv", false)]
public void FromCsvFile(string dir, bool getResults)
{
var filePath = Path.Combine(Globals.DataFolder, dir);
var result = InterestRateProvider.FromCsvFile(filePath, out _);
if (getResults)
{
Assert.GreaterOrEqual(result.Count, 30);
}
else
{
Assert.IsEmpty(result);
}
}
[TestCase("19700306", 0.0225)] // Date in before the first date in file
[TestCase("20200306", 0.0175)]
[TestCase("20200307", 0.0175)]
[TestCase("20200308", 0.0175)]
[TestCase("20200310", 0.0175)]
[TestCase("20501231", 0.055)] // Date in far future
public void GetInterestRate(string dateString, decimal expected)
{
var provider = new InterestRateProvider();
var dateTime = Parse.DateTimeExact(dateString, "yyyyMMdd");
var result = provider.GetInterestRate(dateTime);
Assert.AreEqual(expected, result);
}
}
}
+89
View File
@@ -0,0 +1,89 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class BarTests
{
[Test]
public void UpdatesProperly()
{
var bar = new Bar();
bar.Update(10);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
bar.Update(20);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(20, bar.Close);
bar.Update(5);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(5, bar.Low);
Assert.AreEqual(5, bar.Close);
bar.Update(11);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(5, bar.Low);
Assert.AreEqual(11, bar.Close);
}
[Test]
public void DoesNotHandleAssetsWithZeroPrice()
{
var bar = new Bar();
bar.Update(10);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
// no update performed
bar.Update(0);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
bar.Update(-5);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(-5, bar.Close);
bar.Update(5);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(5, bar.Close);
bar.Update(50);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(50, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(50, bar.Close);
}
}
}
@@ -0,0 +1,129 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class FuturesContractTests
{
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void QuoteBarNullBidAsk(bool hasBid, bool hasAsk)
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
Bar bid = hasBid ? new Bar(1, 1, 1, 1) : null;
Bar ask = hasAsk ? new Bar(2, 2, 2, 2) : null;
var quoteBar = new QuoteBar(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, bid, 10, ask, 20);
futureContract.Update(quoteBar);
Assert.AreEqual(hasBid ? bid.Close : 0, futureContract.BidPrice);
Assert.AreEqual(hasAsk ? ask.Close : 0, futureContract.AskPrice);
Assert.AreEqual(hasAsk ? 20 : 0, futureContract.AskSize);
Assert.AreEqual(hasBid ? 10 : 0, futureContract.BidSize);
Assert.AreEqual(0, futureContract.Volume);
Assert.AreEqual(0, futureContract.LastPrice);
Assert.AreEqual(0, futureContract.OpenInterest);
}
[Test]
public void QuoteTickUpdate()
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
var tick = new Tick(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, 1, 2, 3, 4);
futureContract.Update(tick);
Assert.AreEqual(1, futureContract.BidSize);
Assert.AreEqual(2, futureContract.BidPrice);
Assert.AreEqual(3, futureContract.AskSize);
Assert.AreEqual(4, futureContract.AskPrice);
Assert.AreEqual(0, futureContract.Volume);
Assert.AreEqual(0, futureContract.LastPrice);
Assert.AreEqual(0, futureContract.OpenInterest);
}
[Test]
public void TradeTickUpdate()
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
var tick = new Tick(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, string.Empty, Exchange.UNKNOWN, 1, 2);
futureContract.Update(tick);
Assert.AreEqual(1, futureContract.Volume);
Assert.AreEqual(2, futureContract.LastPrice);
Assert.AreEqual(0, futureContract.BidSize);
Assert.AreEqual(0, futureContract.BidPrice);
Assert.AreEqual(0, futureContract.AskSize);
Assert.AreEqual(0, futureContract.AskPrice);
Assert.AreEqual(0, futureContract.OpenInterest);
}
[Test]
public void TradeBarUpdate()
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
var tick = new TradeBar(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, 1, 2, 3, 4, 5);
futureContract.Update(tick);
Assert.AreEqual(5, futureContract.Volume);
Assert.AreEqual(4, futureContract.LastPrice);
Assert.AreEqual(0, futureContract.BidSize);
Assert.AreEqual(0, futureContract.BidPrice);
Assert.AreEqual(0, futureContract.AskSize);
Assert.AreEqual(0, futureContract.AskPrice);
Assert.AreEqual(0, futureContract.OpenInterest);
}
[Test]
public void PriceValueAndCloseAliasLastPrice()
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
// No data yet, all aliases default to zero
Assert.AreEqual(0, futureContract.LastPrice);
Assert.AreEqual(futureContract.LastPrice, futureContract.Price);
Assert.AreEqual(futureContract.LastPrice, futureContract.Value);
Assert.AreEqual(futureContract.LastPrice, futureContract.Close);
var tradeBar = new TradeBar(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, 1, 2, 3, 4, 5);
futureContract.Update(tradeBar);
Assert.AreEqual(4, futureContract.LastPrice);
Assert.AreEqual(futureContract.LastPrice, futureContract.Price);
Assert.AreEqual(futureContract.LastPrice, futureContract.Value);
Assert.AreEqual(futureContract.LastPrice, futureContract.Close);
}
[Test]
public void OpenInterest()
{
var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019);
var tick = new OpenInterest(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, 10);
futureContract.Update(tick);
Assert.AreEqual(10, futureContract.OpenInterest);
Assert.AreEqual(0, futureContract.Volume);
Assert.AreEqual(0, futureContract.LastPrice);
Assert.AreEqual(0, futureContract.BidSize);
Assert.AreEqual(0, futureContract.BidPrice);
Assert.AreEqual(0, futureContract.AskSize);
Assert.AreEqual(0, futureContract.AskPrice);
}
}
}
@@ -0,0 +1,73 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class OptionContractTests
{
private static Option CreateOption(Symbol symbol)
{
return new Option(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork, true, true, true),
new Cash(Currencies.USD, 0, 1m),
new OptionSymbolProperties(SymbolProperties.GetDefault(Currencies.USD)),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null
);
}
[SetUp]
public void ResetSharedOptionData()
{
// Other tests can leave the shared OptionPriceModelResultData.Null singleton holding a
// trade bar, which then leaks into any contract that hasn't set its own price model.
// Reset it by updating a throwaway (singleton-backed) contract with a zero-priced trade bar.
var symbol = Symbols.SPY_C_192_Feb19_2016;
new OptionContract(CreateOption(symbol))
.Update(new TradeBar(new DateTime(2016, 02, 16), symbol, 0, 0, 0, 0, 0));
}
[Test]
public void PriceValueAndCloseAliasLastPrice()
{
var symbol = Symbols.SPY_C_192_Feb19_2016;
var contract = new OptionContract(CreateOption(symbol)) { Time = new DateTime(2016, 02, 16) };
contract.SetOptionPriceModel(() => OptionPriceModelResult.None);
// No data yet, all aliases default to zero
Assert.AreEqual(0, contract.LastPrice);
Assert.AreEqual(contract.LastPrice, contract.Price);
Assert.AreEqual(contract.LastPrice, contract.Value);
Assert.AreEqual(contract.LastPrice, contract.Close);
var tradeBar = new TradeBar(new DateTime(2016, 02, 16), symbol, 1, 2, 3, 4, 5);
contract.Update(tradeBar);
Assert.AreEqual(4, contract.LastPrice);
Assert.AreEqual(contract.LastPrice, contract.Price);
Assert.AreEqual(contract.LastPrice, contract.Value);
Assert.AreEqual(contract.LastPrice, contract.Close);
}
}
}
+334
View File
@@ -0,0 +1,334 @@
/*
* 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.IO;
using System.Text;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class QuoteBarTests
{
private QuoteBar _quoteBar;
[OneTimeSetUp]
public void Setup()
{
_quoteBar = new QuoteBar();
}
[Test]
public void DoesntGenerateCorruptedPricesIfBidOrAskAreMissing()
{
var bar = new QuoteBar();
bar.UpdateAsk(10, 15);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
bar = new QuoteBar();
bar.Ask = new Bar(11,11,11,11);
Assert.AreEqual(11, bar.Open);
Assert.AreEqual(11, bar.High);
Assert.AreEqual(11, bar.Low);
Assert.AreEqual(11, bar.Close);
}
[Test]
public void QuoteBarReader_CanParseMalformattedData_Successfully()
{
var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
// Neither a quote or a trade
var line = "14340000,1.10907,1.109075,1.108985,1.1090214400000,1.109005,1.109005,1.10884,1.10887";
var date = DateTime.MaxValue;
var isLiveMode = false;
var quoteBar = new QuoteBar();
var parsedQuoteBar = (QuoteBar)quoteBar.Reader(config, line, date, isLiveMode);
Assert.AreEqual(parsedQuoteBar.Symbol, Symbols.SPY);
Assert.AreEqual(parsedQuoteBar.Ask.Open, 0);
Assert.AreEqual(parsedQuoteBar.Ask.High, 0);
Assert.AreEqual(parsedQuoteBar.Ask.Low, 0);
Assert.AreEqual(parsedQuoteBar.Ask.Close, 0);
Assert.AreEqual(parsedQuoteBar.Bid.Open, 0);
Assert.AreEqual(parsedQuoteBar.Bid.High, 0);
Assert.AreEqual(parsedQuoteBar.Bid.Low, 0);
Assert.AreEqual(parsedQuoteBar.Bid.Close, 0);
}
[Test]
public void QuoteBarReader_CanParseQuoteBar_Successfully()
{
var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
// Neither a quote or a trade
var line = "14340000,11090,11090,11089,11090,100,11090,11088,11088,11090,10000";
var date = DateTime.MaxValue;
var isLiveMode = false;
var quoteBar = new QuoteBar();
var parsedQuoteBar = (QuoteBar)quoteBar.Reader(config, line, date, isLiveMode);
Assert.AreEqual(parsedQuoteBar.Symbol, Symbols.SPY);
Assert.AreEqual(parsedQuoteBar.Bid.Open, 1.1090);
Assert.AreEqual(parsedQuoteBar.Bid.High, 1.1090);
Assert.AreEqual(parsedQuoteBar.Bid.Low, 1.1089);
Assert.AreEqual(parsedQuoteBar.Bid.Close, 1.1090);
Assert.AreEqual(parsedQuoteBar.Ask.Open, 1.10900);
Assert.AreEqual(parsedQuoteBar.Ask.High, 1.1088);
Assert.AreEqual(parsedQuoteBar.Ask.Low, 1.1088);
Assert.AreEqual(parsedQuoteBar.Ask.Close, 1.1090);
}
[Test]
public void QuoteBar_CanParseEquity_Successfully()
{
var config = new SubscriptionDataConfig(typeof(QuoteBar), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var line = "14340000,10000,20000,30000,40000,0,50000,60000,70000,80000,1";
var quoteBar = _quoteBar.ParseEquity(config, line, DateTime.MinValue);
Assert.AreEqual(quoteBar.Bid.Open, 1m);
Assert.AreEqual(quoteBar.Bid.High, 2m);
Assert.AreEqual(quoteBar.Bid.Low, 3m);
Assert.AreEqual(quoteBar.Bid.Close, 4m);
Assert.AreEqual(quoteBar.LastBidSize, 0m);
Assert.AreEqual(quoteBar.Ask.Open, 5m);
Assert.AreEqual(quoteBar.Ask.High, 6m);
Assert.AreEqual(quoteBar.Ask.Low, 7m);
Assert.AreEqual(quoteBar.Ask.Close, 8m);
Assert.AreEqual(quoteBar.LastAskSize, 1m);
}
[Test]
public void QuoteBar_CanParseForex_Successfully()
{
var config = new SubscriptionDataConfig(typeof(QuoteBar), Symbols.EURUSD, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var line = "14340000,1,2,3,4,0,5,6,7,8,1";
var quoteBar = _quoteBar.ParseForex(config, line, DateTime.MinValue);
Assert.AreEqual(quoteBar.Bid.Open, 1m);
Assert.AreEqual(quoteBar.Bid.High, 2m);
Assert.AreEqual(quoteBar.Bid.Low, 3m);
Assert.AreEqual(quoteBar.Bid.Close, 4m);
Assert.AreEqual(quoteBar.LastBidSize, 0m);
Assert.AreEqual(quoteBar.Ask.Open, 5m);
Assert.AreEqual(quoteBar.Ask.High, 6m);
Assert.AreEqual(quoteBar.Ask.Low, 7m);
Assert.AreEqual(quoteBar.Ask.Close, 8m);
Assert.AreEqual(quoteBar.LastAskSize, 1m);
}
[Test]
public void QuoteBar_CanParseCfd_Successfully()
{
var config = new SubscriptionDataConfig(typeof(QuoteBar), Symbols.DE10YBEUR, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var line = "14340000,1,2,3,4,0,5,6,7,8,1";
var quoteBar = _quoteBar.ParseCfd(config, line, DateTime.MinValue);
Assert.AreEqual(quoteBar.Bid.Open, 1m);
Assert.AreEqual(quoteBar.Bid.High, 2m);
Assert.AreEqual(quoteBar.Bid.Low, 3m);
Assert.AreEqual(quoteBar.Bid.Close, 4m);
Assert.AreEqual(quoteBar.LastBidSize, 0m);
Assert.AreEqual(quoteBar.Ask.Open, 5m);
Assert.AreEqual(quoteBar.Ask.High, 6m);
Assert.AreEqual(quoteBar.Ask.Low, 7m);
Assert.AreEqual(quoteBar.Ask.Close, 8m);
Assert.AreEqual(quoteBar.LastAskSize, 1m);
}
[Test]
public void QuoteBar_CanParseOption_Successfully()
{
var config = new SubscriptionDataConfig(typeof(QuoteBar), Symbols.SPY_C_192_Feb19_2016, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var line = "14340000,10000,20000,30000,40000,0,50000,60000,70000,80000,1";
var quoteBar = _quoteBar.ParseOption(config, line, DateTime.MinValue);
Assert.AreEqual(quoteBar.Bid.Open, 1m);
Assert.AreEqual(quoteBar.Bid.High, 2m);
Assert.AreEqual(quoteBar.Bid.Low, 3m);
Assert.AreEqual(quoteBar.Bid.Close, 4m);
Assert.AreEqual(quoteBar.LastBidSize, 0m);
Assert.AreEqual(quoteBar.Ask.Open, 5m);
Assert.AreEqual(quoteBar.Ask.High, 6m);
Assert.AreEqual(quoteBar.Ask.Low, 7m);
Assert.AreEqual(quoteBar.Ask.Close, 8m);
Assert.AreEqual(quoteBar.LastAskSize, 1m);
}
[Test]
public void QuoteBar_CanParseFuture_Successfully()
{
var config = new SubscriptionDataConfig(typeof(QuoteBar), Symbols.Fut_SPY_Feb19_2016, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var line = "14340000,1,2,3,4,0,5,6,7,8,1";
var quoteBar = _quoteBar.ParseFuture(config, line, DateTime.MinValue);
Assert.AreEqual(quoteBar.Bid.Open, 1m);
Assert.AreEqual(quoteBar.Bid.High, 2m);
Assert.AreEqual(quoteBar.Bid.Low, 3m);
Assert.AreEqual(quoteBar.Bid.Close, 4m);
Assert.AreEqual(quoteBar.LastBidSize, 0m);
Assert.AreEqual(quoteBar.Ask.Open, 5m);
Assert.AreEqual(quoteBar.Ask.High, 6m);
Assert.AreEqual(quoteBar.Ask.Low, 7m);
Assert.AreEqual(quoteBar.Ask.Close, 8m);
Assert.AreEqual(quoteBar.LastAskSize, 1m);
}
[Test]
public void QuoteBarParseScalesOptionsWithEquityUnderlying()
{
var factory = new QuoteBar();
var underlying = Symbol.Create("SPY", SecurityType.Equity, QuantConnect.Market.USA);
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.CME,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(QuoteBar),
optionSymbol,
Resolution.Minute,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Quote,
true,
DataNormalizationMode.Raw);
var quoteLine = "40560000,10000,15000,10000,15000,90,10000,15000,10000,15000,100";
using var stream = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(quoteLine)));
var quoteBarFromLine = (QuoteBar)factory.Reader(config, quoteLine, new DateTime(2020, 9, 22), false);
var quoteBarFromStream = (QuoteBar)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), quoteBarFromLine.EndTime);
Assert.AreEqual(optionSymbol, quoteBarFromLine.Symbol);
Assert.AreEqual(1m, quoteBarFromLine.Bid.Open);
Assert.AreEqual(1.5m, quoteBarFromLine.Bid.High);
Assert.AreEqual(1m, quoteBarFromLine.Bid.Low);
Assert.AreEqual(1.5m, quoteBarFromLine.Bid.Close);
Assert.AreEqual(90m, quoteBarFromLine.LastBidSize);
Assert.AreEqual(1m, quoteBarFromLine.Ask.Open);
Assert.AreEqual(1.5m, quoteBarFromLine.Ask.High);
Assert.AreEqual(1m, quoteBarFromLine.Ask.Low);
Assert.AreEqual(1.5m, quoteBarFromLine.Ask.Close);
Assert.AreEqual(100m, quoteBarFromLine.LastAskSize);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), quoteBarFromStream.EndTime);
Assert.AreEqual(optionSymbol, quoteBarFromStream.Symbol);
Assert.AreEqual(1m, quoteBarFromStream.Bid.Open);
Assert.AreEqual(1.5m, quoteBarFromStream.Bid.High);
Assert.AreEqual(1m, quoteBarFromStream.Bid.Low);
Assert.AreEqual(1.5m, quoteBarFromStream.Bid.Close);
Assert.AreEqual(90m, quoteBarFromStream.LastBidSize);
Assert.AreEqual(1m, quoteBarFromStream.Ask.Open);
Assert.AreEqual(1.5m, quoteBarFromStream.Ask.High);
Assert.AreEqual(1m, quoteBarFromStream.Ask.Low);
Assert.AreEqual(1.5m, quoteBarFromStream.Ask.Close);
Assert.AreEqual(100m, quoteBarFromStream.LastAskSize);
}
[Test]
public void QuoteBarParseDoesNotScaleOptionsWithNonEquityUnderlying()
{
var factory = new QuoteBar();
var underlying = Symbol.CreateFuture("ES", QuantConnect.Market.CME, new DateTime(2021, 3, 19));
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.CME,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(QuoteBar),
optionSymbol,
Resolution.Minute,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Quote,
true,
DataNormalizationMode.Raw);
var quoteLine = "40560000,1.0,1.5,1.0,1.5,90.0,1.0,1.5,1.0,1.5,100.0";
using var stream = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(quoteLine)));
var unscaledQuoteBarFromLine = (QuoteBar)factory.Reader(config, quoteLine, new DateTime(2020, 9, 22), false);
var unscaledQuoteBarFromStream = (QuoteBar)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), unscaledQuoteBarFromLine.EndTime);
Assert.AreEqual(optionSymbol, unscaledQuoteBarFromLine.Symbol);
Assert.AreEqual(1m, unscaledQuoteBarFromLine.Bid.Open);
Assert.AreEqual(1.5m, unscaledQuoteBarFromLine.Bid.High);
Assert.AreEqual(1m, unscaledQuoteBarFromLine.Bid.Low);
Assert.AreEqual(1.5m, unscaledQuoteBarFromLine.Bid.Close);
Assert.AreEqual(90m, unscaledQuoteBarFromLine.LastBidSize);
Assert.AreEqual(1m, unscaledQuoteBarFromLine.Ask.Open);
Assert.AreEqual(1.5m, unscaledQuoteBarFromLine.Ask.High);
Assert.AreEqual(1m, unscaledQuoteBarFromLine.Ask.Low);
Assert.AreEqual(1.5m, unscaledQuoteBarFromLine.Ask.Close);
Assert.AreEqual(100m, unscaledQuoteBarFromLine.LastAskSize);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), unscaledQuoteBarFromStream.EndTime);
Assert.AreEqual(optionSymbol, unscaledQuoteBarFromStream.Symbol);
Assert.AreEqual(1m, unscaledQuoteBarFromStream.Bid.Open);
Assert.AreEqual(1.5m, unscaledQuoteBarFromStream.Bid.High);
Assert.AreEqual(1m, unscaledQuoteBarFromStream.Bid.Low);
Assert.AreEqual(1.5m, unscaledQuoteBarFromStream.Bid.Close);
Assert.AreEqual(90m, unscaledQuoteBarFromStream.LastBidSize);
Assert.AreEqual(1m, unscaledQuoteBarFromStream.Ask.Open);
Assert.AreEqual(1.5m, unscaledQuoteBarFromStream.Ask.High);
Assert.AreEqual(1m, unscaledQuoteBarFromStream.Ask.Low);
Assert.AreEqual(1.5m, unscaledQuoteBarFromStream.Ask.Close);
Assert.AreEqual(100m, unscaledQuoteBarFromStream.LastAskSize);
}
}
}
+293
View File
@@ -0,0 +1,293 @@
/*
* 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.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuantConnect.Data;
using System.Globalization;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class TickTests
{
[Test]
public void ConstructsFromLine()
{
const string line = "15093000,1456300,100,P,T,0";
var baseDate = new DateTime(2013, 10, 08);
var tick = new Tick(Symbols.SPY, line, baseDate);
var ms = (tick.Time - baseDate).TotalMilliseconds;
Assert.AreEqual(15093000, ms);
Assert.AreEqual(1456300, tick.LastPrice * 10000m);
Assert.AreEqual(100, tick.Quantity);
Assert.AreEqual("P", tick.ExchangeCode);
Assert.AreEqual("ARCA", tick.Exchange);
Assert.AreEqual("T", tick.SaleCondition);
Assert.AreEqual(false, tick.Suspicious);
}
[TestCase("18000677.3,3669.12,0.0040077,3669.13,3.40618718", "18000677.3", "3669.12", "0.0040077", "3669.13", "3.40618718")]
[TestCase("18000677.3111,3669.12,0.0040077,3669.13,3.40618718", "18000677.3111", "3669.12", "0.0040077", "3669.13", "3.40618718")]
public void ConstructsFromLineWithDecimalTimestamp(string line, string milliseconds, string bidPrice,
string bidSize, string askPrice, string askSize)
{
var config = new SubscriptionDataConfig(
typeof(Tick), Symbols.BTCUSD, Resolution.Tick, TimeZones.Utc, TimeZones.Utc,
false, false, false, false, TickType.Quote);
var baseDate = new DateTime(2019, 1, 15);
var tick = new Tick(config, line, baseDate);
var ms = (tick.Time - baseDate).TotalMilliseconds;
Assert.AreEqual( decimal.Parse(milliseconds, CultureInfo.InvariantCulture), ms);
Assert.AreEqual(decimal.Parse(bidPrice, CultureInfo.InvariantCulture), tick.BidPrice);
Assert.AreEqual(decimal.Parse(bidSize, CultureInfo.InvariantCulture), tick.BidSize);
Assert.AreEqual(decimal.Parse(askPrice, CultureInfo.InvariantCulture), tick.AskPrice);
Assert.AreEqual(decimal.Parse(askSize, CultureInfo.InvariantCulture), tick.AskSize);
}
[Test]
public void ReadsFuturesTickFromLine()
{
const string line = "86399572,52.62,5,usa,,0,False";
var baseDate = new DateTime(2013, 10, 08);
var symbol = Symbol.CreateFuture(Futures.Energy.CrudeOilWTI, QuantConnect.Market.NYMEX, new DateTime(2017, 2, 28));
var config = new SubscriptionDataConfig(typeof(Tick), symbol, Resolution.Tick, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var tick = new Tick(config, line, baseDate);
var ms = (tick.Time - baseDate).TotalMilliseconds;
Assert.AreEqual(86399572, ms);
Assert.AreEqual(52.62, tick.LastPrice);
Assert.AreEqual(5, tick.Quantity);
Assert.AreEqual("", tick.Exchange);
Assert.AreEqual("", tick.SaleCondition);
Assert.AreEqual(false, tick.Suspicious);
}
[TestCase(SecurityType.Crypto, TickType.Trade, "1234567,18000,0.0001,0")]
[TestCase(SecurityType.Crypto, TickType.Quote, "1234567,18000,10,18100,15,0")]
[TestCase(SecurityType.CryptoFuture, TickType.Trade, "1234567,18000,0.0001,0")]
[TestCase(SecurityType.CryptoFuture, TickType.Quote, "1234567,18000,10,18100,15,0")]
public void ReadsCryptoAndCryptoFuturesTickFromLine(SecurityType securityType, TickType tickType, string line)
{
var baseDate = new DateTime(2013, 10, 08);
var symbol = Symbol.Create("BTCUSDT", securityType, QuantConnect.Market.Binance);
var config = new SubscriptionDataConfig(typeof(Tick), symbol, Resolution.Tick, TimeZones.NewYork, TimeZones.NewYork, false, false, false,
tickType: tickType);
var tick = new Tick(config, line, baseDate);
var ms = (tick.Time - baseDate).TotalMilliseconds;
Assert.AreEqual(1234567d, ms);
Assert.AreEqual("", tick.Exchange);
Assert.AreEqual("", tick.SaleCondition);
Assert.AreEqual(false, tick.Suspicious);
if (tickType == TickType.Trade)
{
Assert.AreEqual(18000m, tick.Value);
Assert.AreEqual(0.0001m, tick.Quantity);
Assert.AreEqual("", tick.Exchange);
Assert.AreEqual("", tick.SaleCondition);
Assert.AreEqual(false, tick.Suspicious);
}
else
{
Assert.AreEqual((18000m + 18100m) / 2m, tick.Value);
Assert.AreEqual(18000m, tick.BidPrice);
Assert.AreEqual(10m, tick.BidSize);
Assert.AreEqual(18100m, tick.AskPrice);
Assert.AreEqual(15m, tick.AskSize);
}
}
[TestCase("14400135,0,0,1680000,400,NASDAQ,00000001,0", 0, 0, 168, 400)]
[TestCase("14400135,10000,10,0,0,NASDAQ,00000001,0", 1, 10, 0, 0)]
[TestCase("14400135,10000,10,20000,20,NASDAQ,00000001,0", 1, 10, 2, 20)]
public void EquityQuoteTick(string line, decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize)
{
var baseDate = new DateTime(2013, 10, 08);
var config = new SubscriptionDataConfig(typeof(Tick),
Symbols.SPY,
Resolution.Tick,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false,
false,
TickType.Quote);
var tick = new Tick(config, line, baseDate);
var expectedValue = (askPrice + bidPrice) / 2;
if (askPrice == 0 || bidPrice == 0)
{
expectedValue = askPrice + bidPrice;
}
var ms = (tick.Time - baseDate).TotalMilliseconds;
Assert.AreEqual(14400135, ms);
Assert.AreEqual(expectedValue, tick.Value);
Assert.AreEqual(expectedValue, tick.LastPrice);
Assert.AreEqual(0, tick.Quantity);
Assert.AreEqual(askPrice, tick.AskPrice);
Assert.AreEqual(askSize, tick.AskSize);
Assert.AreEqual(bidPrice, tick.BidPrice);
Assert.AreEqual(bidSize, tick.BidSize);
Assert.AreEqual("NASDAQ", tick.Exchange);
Assert.AreEqual("00000001", tick.SaleCondition);
Assert.IsFalse(tick.Suspicious);
}
[Test]
public void OptionWithUnderlyingEquityScaled()
{
var factory = new Tick();
var tickLine = "40560000,10000,10,NYSE,00000001,0";
var underlying = Symbol.Create("SPY", SecurityType.Equity, QuantConnect.Market.USA);
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.USA,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(Tick),
optionSymbol,
Resolution.Tick,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Trade,
true,
DataNormalizationMode.Raw);
using var stream = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(tickLine)));
var tickFromLine = (Tick)factory.Reader(config, tickLine, new DateTime(2020, 9, 22), false);
var tickFromStream = (Tick)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 16, 0), tickFromLine.Time);
Assert.AreEqual(1m, tickFromLine.Price);
Assert.AreEqual(10, tickFromLine.Quantity);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 16, 0), tickFromStream.Time);
Assert.AreEqual(1m, tickFromStream.Price);
Assert.AreEqual(10, tickFromStream.Quantity);
}
[Test]
public void OptionWithUnderlyingFutureNotScaled()
{
var factory = new Tick();
var tickLine = "40560000,10000,10,CME,00000001,0";
var underlying = Symbol.CreateFuture("ES", QuantConnect.Market.CME, new DateTime(2021, 3, 19));
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.CME,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(Tick),
optionSymbol,
Resolution.Tick,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Trade,
true,
DataNormalizationMode.Raw);
using var stream = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(tickLine)));
var tickFromLine = (Tick)factory.Reader(config, tickLine, new DateTime(2020, 9, 22), false);
var tickFromStream = (Tick)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 16, 0), tickFromLine.Time);
Assert.AreEqual(10000m, tickFromLine.Price);
Assert.AreEqual(10, tickFromLine.Quantity);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 16, 0), tickFromStream.Time);
Assert.AreEqual(10000m, tickFromStream.Price);
Assert.AreEqual(10, tickFromStream.Quantity);
}
[Test]
public void ExchangeSetterHandlesNonExpectedEncoding()
{
const string line = "15093000,1456300,100,P,T,0";
var baseDate = new DateTime(2013, 10, 08);
var tick = new Tick(Symbols.SPY, line, baseDate);
Assert.DoesNotThrow(()=> tick.ExchangeCode = "LL");
Assert.AreEqual(Exchange.UNKNOWN, tick.Exchange.GetPrimaryExchange(), "Failed at Exchange Property");
Assert.AreEqual((string)Exchange.UNKNOWN, tick.ExchangeCode, "Failed at ExchangeCode Property");
}
[Test]
public void ExchangeSetterHandlesDefinedExchanges()
{
var baseDate = new DateTime(2013, 10, 08);
const string line = "15093000,1456300,100,P,T,0";
var exchanges = typeof(Exchange)
.GetProperties(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
.Where(p => p.PropertyType == typeof(Exchange))
.Select(propa => propa.GetValue(null))
.OfType<Exchange>()
.Where(exchange => exchange.Market == QuantConnect.Market.USA && exchange.SecurityTypes.Contains(SecurityType.Equity))
.ToList();
Assert.GreaterOrEqual(exchanges.Count, 20);
foreach (var exchange in exchanges)
{
{
var tick = new Tick(Symbols.SPY, line, baseDate);
Assert.DoesNotThrow(() => tick.ExchangeCode = exchange.Code);
Assert.AreEqual(exchange.Name, tick.Exchange, $"ExchangeCode: Failed at Exchange Property: {exchange}");
Assert.AreEqual(exchange.Code, tick.ExchangeCode, $"ExchangeCode: Failed at ExchangeCode Property: {exchange}");
}
{
var tick = new Tick(Symbols.SPY, line, baseDate);
Assert.DoesNotThrow(() => tick.Exchange = exchange);
Assert.AreEqual(exchange.Name, tick.Exchange, $"Exchange: Failed at Exchange Property: {exchange}");
Assert.AreEqual(exchange.Code, tick.ExchangeCode, $"Exchange: Failed at ExchangeCode Property: {exchange}");
}
}
}
}
}
+255
View File
@@ -0,0 +1,255 @@
/*
* 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.IO;
using System.Text;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data.Market
{
[TestFixture]
public class TradeBarTests
{
[Test]
public void UpdatesProperly()
{
var bar = new TradeBar();
bar.UpdateTrade(10, 10);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
Assert.AreEqual(10, bar.Volume);
bar.UpdateTrade(20, 5);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(20, bar.Close);
Assert.AreEqual(15, bar.Volume);
bar.UpdateTrade(5, 50);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(5, bar.Low);
Assert.AreEqual(5, bar.Close);
Assert.AreEqual(65, bar.Volume);
bar.UpdateTrade(11, 100);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(20, bar.High);
Assert.AreEqual(5, bar.Low);
Assert.AreEqual(11, bar.Close);
Assert.AreEqual(165, bar.Volume);
}
[Test]
public void HandlesAssetWithValidZeroPrice()
{
var bar = new TradeBar();
bar.UpdateTrade(10, 10);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(10, bar.Low);
Assert.AreEqual(10, bar.Close);
Assert.AreEqual(10, bar.Volume);
bar.UpdateTrade(0, 100);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(0, bar.Low);
Assert.AreEqual(0, bar.Close);
Assert.AreEqual(110, bar.Volume);
bar.UpdateTrade(-5, 100);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(-5, bar.Close);
Assert.AreEqual(210, bar.Volume);
bar.UpdateTrade(5, 100);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(10, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(5, bar.Close);
Assert.AreEqual(310, bar.Volume);
bar.UpdateTrade(50, 100);
Assert.AreEqual(10, bar.Open);
Assert.AreEqual(50, bar.High);
Assert.AreEqual(-5, bar.Low);
Assert.AreEqual(50, bar.Close);
Assert.AreEqual(410, bar.Volume);
}
[Test]
public void TradeBarParseScalesOptionsWithEquityUnderlying()
{
var factory = new TradeBar();
var underlying = Symbol.Create("SPY", SecurityType.Equity, QuantConnect.Market.USA);
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.CME,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(TradeBar),
optionSymbol,
Resolution.Minute,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Trade,
true,
DataNormalizationMode.Raw);
var tradeLine = "40560000,10000,15000,10000,15000,90";
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(tradeLine));
using var stream = new StreamReader(memoryStream);
var tradeBarFromLine = (TradeBar)factory.Reader(config, tradeLine, new DateTime(2020, 9, 22), false);
var tradeBarFromStream = (TradeBar)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), tradeBarFromLine.EndTime);
Assert.AreEqual(optionSymbol, tradeBarFromLine.Symbol);
Assert.AreEqual(1m, tradeBarFromLine.Open);
Assert.AreEqual(1.5m, tradeBarFromLine.High);
Assert.AreEqual(1m, tradeBarFromLine.Low);
Assert.AreEqual(1.5m, tradeBarFromLine.Close);
Assert.AreEqual(90m, tradeBarFromLine.Volume);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), tradeBarFromStream.EndTime);
Assert.AreEqual(optionSymbol, tradeBarFromStream.Symbol);
Assert.AreEqual(1m, tradeBarFromStream.Open);
Assert.AreEqual(1.5m, tradeBarFromStream.High);
Assert.AreEqual(1m, tradeBarFromStream.Low);
Assert.AreEqual(1.5m, tradeBarFromStream.Close);
Assert.AreEqual(90m, tradeBarFromStream.Volume);
}
[Test]
public void TradeBarParseDoesNotScaleOptionsWithNonEquityUnderlying()
{
var factory = new TradeBar();
var underlying = Symbol.CreateFuture("ES", QuantConnect.Market.CME, new DateTime(2021, 3, 19));
var optionSymbol = Symbol.CreateOption(
underlying,
QuantConnect.Market.CME,
OptionStyle.American,
OptionRight.Put,
4200m,
SecurityIdentifier.DefaultDate);
var config = new SubscriptionDataConfig(
typeof(TradeBar),
optionSymbol,
Resolution.Minute,
TimeZones.Chicago,
TimeZones.Chicago,
true,
false,
false,
false,
TickType.Trade,
true,
DataNormalizationMode.Raw);
var tradeLine = "40560000,1.0,1.5,1.0,1.5,90.0";
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(tradeLine));
using var stream = new StreamReader(memoryStream);
var unscaledTradeBarFromLine = (TradeBar)factory.Reader(config, tradeLine, new DateTime(2020, 9, 22), false);
var unscaledTradeBarFromStream = (TradeBar)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), unscaledTradeBarFromLine.EndTime);
Assert.AreEqual(optionSymbol, unscaledTradeBarFromLine.Symbol);
Assert.AreEqual(1m, unscaledTradeBarFromLine.Open);
Assert.AreEqual(1.5m, unscaledTradeBarFromLine.High);
Assert.AreEqual(1m, unscaledTradeBarFromLine.Low);
Assert.AreEqual(1.5m, unscaledTradeBarFromLine.Close);
Assert.AreEqual(90m, unscaledTradeBarFromLine.Volume);
Assert.AreEqual(new DateTime(2020, 9, 22, 11, 17, 0), unscaledTradeBarFromStream.EndTime);
Assert.AreEqual(optionSymbol, unscaledTradeBarFromStream.Symbol);
Assert.AreEqual(1m, unscaledTradeBarFromStream.Open);
Assert.AreEqual(1.5m, unscaledTradeBarFromStream.High);
Assert.AreEqual(1m, unscaledTradeBarFromStream.Low);
Assert.AreEqual(1.5m, unscaledTradeBarFromStream.Close);
Assert.AreEqual(90m, unscaledTradeBarFromStream.Volume);
}
[TestCase(Resolution.Minute, "43140000,21.04,21.44,20.4,21.24,0")]
[TestCase(Resolution.Hour, "20200922 11:00,21.04,21.44,20.4,21.24,0")]
[TestCase(Resolution.Daily, "20200921 00:00,21.04,21.44,20.4,21.24,0")]
public void TradeBarIndexLowResolutionParsing(Resolution resolution, string tradeLine)
{
var factory = new TradeBar();
var symbol = Symbols.CreateIndexSymbol("VIX");
var entry = MarketHoursDatabase.FromDataFolder()
.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType);
var config = new SubscriptionDataConfig(
typeof(TradeBar),
symbol,
resolution,
entry.DataTimeZone,
entry.ExchangeHours.TimeZone,
true,
false,
false,
false,
TickType.Trade,
true,
DataNormalizationMode.Raw);
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(tradeLine));
using var stream = new StreamReader(memoryStream);
var fromLine = (TradeBar)factory.Reader(config, tradeLine, new DateTime(2020, 9, 22), false);
var fromStream = (TradeBar)factory.Reader(config, stream, new DateTime(2020, 9, 22), false);
var expectedEndTime = new DateTime(2020, 9, 22, 12, 0, 0);
if (resolution == Resolution.Daily)
{
expectedEndTime = new DateTime(2020, 9, 22, 0, 0, 0);
}
Assert.AreEqual(expectedEndTime, fromLine.EndTime);
Assert.AreEqual(symbol, fromLine.Symbol);
Assert.AreEqual(21.04m, fromLine.Open);
Assert.AreEqual(21.44m, fromLine.High);
Assert.AreEqual(20.4m, fromLine.Low);
Assert.AreEqual(21.24m, fromLine.Close);
Assert.AreEqual(0m, fromLine.Volume);
Assert.AreEqual(expectedEndTime, fromStream.EndTime);
Assert.AreEqual(symbol, fromLine.Symbol);
Assert.AreEqual(21.04m, fromLine.Open);
Assert.AreEqual(21.44m, fromLine.High);
Assert.AreEqual(20.4m, fromLine.Low);
Assert.AreEqual(21.24m, fromLine.Close);
Assert.AreEqual(0m, fromLine.Volume);
}
}
}
@@ -0,0 +1,467 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2024 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Data.Consolidators;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class MarketHourAwareConsolidatorTests : BaseConsolidatorTests
{
[Test]
public void MarketAlwaysOpen()
{
var symbol = Symbols.BTCUSD;
using var consolidator = new MarketHourAwareConsolidator(true, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2015, 04, 13, 5, 0, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 100 });
time = new DateTime(2015, 04, 13, 10, 0, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 1 });
Assert.IsNull(latestBar);
time = time.AddHours(2);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 2 });
Assert.IsNull(latestBar);
time = new DateTime(2015, 04, 13, 15, 15, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 3 });
Assert.IsNull(latestBar);
time = new DateTime(2015, 04, 14, 0, 0, 0);
consolidator.Scan(time);
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.AreEqual(time, latestBar.EndTime);
Assert.AreEqual(time.AddDays(-1), latestBar.Time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.AreEqual(100, latestBar.High);
Assert.AreEqual(1, latestBar.Low);
}
[Test]
public void HandlerSeesPreviousConsolidatedBarWhileReceivingTheNewOne()
{
var symbol = Symbols.BTCUSD;
using var consolidator = new MarketHourAwareConsolidator(true, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
IBaseData eventArgument = null;
IBaseData consolidatedInsideHandler = null;
consolidator.DataConsolidated += (_, bar) =>
{
eventArgument = bar;
consolidatedInsideHandler = ((ConsolidatorBase)consolidator).Consolidated;
};
var time = new DateTime(2015, 04, 13, 10, 0, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 100 });
time = new DateTime(2015, 04, 14, 0, 0, 0);
consolidator.Scan(time);
// The handler receives the new bar as argument while Consolidated still holds the previous
// state, which is null here since this is the first consolidation
Assert.IsNotNull(eventArgument);
Assert.IsNull(consolidatedInsideHandler);
// Once the handler returned, the window reflects the just-consolidated bar
Assert.AreEqual(eventArgument, ((ConsolidatorBase)consolidator).Consolidated);
}
[TestCase(true)]
[TestCase(false)]
public void Daily(bool strictEndTime)
{
var symbol = strictEndTime ? Symbols.SPX : Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidator(strictEndTime, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2015, 04, 13, 5, 0, 0);
// this bar will be ignored because it's during market closed hours
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 100 });
time = new DateTime(2015, 04, 13, 10, 0, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 1 });
Assert.IsNull(latestBar);
time = time.AddHours(2);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 2 });
Assert.IsNull(latestBar);
time = new DateTime(2015, 04, 13, 15, 15, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 3 });
Assert.IsNull(latestBar);
time = strictEndTime ? time : new DateTime(2015, 04, 14, 0, 0, 0);
consolidator.Scan(time);
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.AreEqual(strictEndTime ? new DateTime(2015, 04, 13, 15, 15, 0) : time, latestBar.EndTime);
Assert.AreEqual(strictEndTime ? new DateTime(2015, 04, 13, 8, 30, 0) : time.AddDays(-1), latestBar.Time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.AreEqual(3, latestBar.High);
Assert.AreEqual(1, latestBar.Low);
}
[Test]
public void BarIsSkippedWhenDataResolutionIsNotHourAndMarketIsClose()
{
var symbol = Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidator(true, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2020, 05, 01, 09, 30, 0);
// this bar will be ignored because it's during market closed hours and the bar resolution is not Hour
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, Open = 1 });
Assert.IsNull(latestBar);
Assert.AreEqual(0, consolidatedBarsCount);
}
[Test]
public void DailyBarCanBeConsolidatedFromHourData()
{
var symbol = Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidator(true, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2020, 05, 01, 09, 0, 0);
var hourBars = new List<TradeBar>()
{
new TradeBar() { Time = time, Period = Time.OneHour, Symbol = symbol, Open = 2 },
new TradeBar() { Time = time.AddHours(1), Period = Time.OneHour, Symbol = symbol, High = 200 },
new TradeBar() { Time = time.AddHours(2), Period = Time.OneHour, Symbol = symbol, Low = 0.02m },
new TradeBar() { Time = time.AddHours(3), Period = Time.OneHour, Symbol = symbol, Close = 20 },
new TradeBar() { Time = time.AddHours(4), Period = Time.OneHour, Symbol = symbol, Open = 3 },
new TradeBar() { Time = time.AddHours(5), Period = Time.OneHour, Symbol = symbol, High = 300 },
new TradeBar() { Time = time.AddHours(6), Period = Time.OneHour, Symbol = symbol, Low = 0.03m, Close = 30 },
};
foreach (var bar in hourBars)
{
consolidator.Update(bar);
}
consolidator.Scan(time.AddHours(7));
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.AreEqual(time.AddHours(7), latestBar.EndTime);
Assert.AreEqual(time.AddMinutes(30), latestBar.Time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.AreEqual(2, latestBar.Open);
Assert.AreEqual(300, latestBar.High);
Assert.AreEqual(0.02, latestBar.Low);
Assert.AreEqual(30, latestBar.Close);
}
[TestCase(true)]
[TestCase(false)]
public void DailyExtendedMarketHours(bool strictEndTime)
{
var symbol = strictEndTime ? Symbols.SPX : Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidatorTest(Resolution.Daily, typeof(TradeBar), TickType.Trade, true);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2015, 04, 13, 8, 31, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 10 });
time = new DateTime(2015, 04, 13, 10, 0, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 15 });
Assert.IsNull(latestBar);
if (!strictEndTime)
{
time = new DateTime(2015, 04, 13, 18, 15, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = symbol, High = 20 });
Assert.IsNull(latestBar);
}
time = new DateTime(2015, 04, 13, 20, 0, 0);
consolidator.Scan(time);
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.AreEqual(strictEndTime ? new DateTime(2015, 04, 13, 15, 15, 0) : time, latestBar.EndTime);
Assert.AreEqual(strictEndTime ? new DateTime(2015, 04, 13, 8, 30, 0) : new DateTime(2015, 04, 13, 4, 0, 0), latestBar.Time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.AreEqual(strictEndTime ? 15 : 20, latestBar.High);
Assert.AreEqual(10, latestBar.Low);
}
[Test]
public void MarketHoursRespected()
{
using var consolidator = new MarketHourAwareConsolidator(true, Resolution.Hour, typeof(TradeBar), TickType.Trade, false);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = (TradeBar)bar;
consolidatedBarsCount++;
};
var time = new DateTime(2015, 04, 13, 9, 0, 0);
// this bar will be ignored because it's during market closed hours
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 100 });
time = new DateTime(2015, 04, 13, 9, 31, 0);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 1 });
Assert.IsNull(latestBar);
time = time.AddMinutes(2);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 2 });
Assert.IsNull(latestBar);
time = time.AddMinutes(2);
consolidator.Update(new TradeBar() { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 3 });
Assert.IsNull(latestBar);
time = new DateTime(2015, 04, 13, 10, 0, 0);
consolidator.Scan(time);
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.AreEqual(time, latestBar.EndTime);
Assert.AreEqual(new DateTime(2015, 04, 13, 9, 0, 0), latestBar.Time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.AreEqual(3, latestBar.High);
Assert.AreEqual(1, latestBar.Low);
}
[Test]
public void WorksWithDailyResolutionAndPreciseEndTimeFalse()
{
using var consolidator = new MarketHourAwareConsolidator(false, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
var time = new DateTime(2015, 04, 13, 0, 0, 0);
consolidator.Update(new TradeBar() { Time = time, Period = Time.OneDay, Symbol = Symbols.SPY, Open = 100, High = 100, Low = 100, Close = 100 });
Assert.IsNotNull(consolidator.WorkingData);
var workingData = (TradeBar)consolidator.WorkingData;
Assert.AreEqual(100, workingData.Open);
Assert.AreEqual(100, workingData.Low);
Assert.AreEqual(100, workingData.Close);
Assert.AreEqual(100, workingData.High);
// Trigger the consolidation
consolidator.Scan(time.AddDays(1));
Assert.IsNotNull(consolidator.Consolidated);
var consolidatedData = (TradeBar)consolidator.Consolidated;
Assert.AreEqual(100, consolidatedData.Open);
Assert.AreEqual(100, consolidatedData.Low);
Assert.AreEqual(100, consolidatedData.Close);
Assert.AreEqual(100, consolidatedData.High);
}
[Test]
public void IntradayConsolidatorIsAnchoredToMarketOpen()
{
var symbol = Symbols.Future_ESZ18_Dec2018;
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
var marketOpen = exchangeHours.GetNextMarketOpen(new DateTime(2024, 11, 30, 12, 0, 0), extendedMarketHours: true);
using var consolidator = new MarketHourAwareConsolidator(false, TimeSpan.FromMinutes(7), typeof(TradeBar), TickType.Trade, extendedMarketHours: true);
var bars = new List<TradeBar>();
consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b);
// feed the first 30 minutes after the open, one bar per minute
Feed(consolidator, symbol, marketOpen, 30);
Assert.GreaterOrEqual(bars.Count, 3);
Assert.AreEqual(marketOpen, bars[0].Time);
Assert.AreEqual(marketOpen.AddMinutes(7), bars[0].EndTime);
Assert.AreEqual(marketOpen.AddMinutes(14), bars[1].EndTime);
Assert.AreEqual(marketOpen.AddMinutes(21), bars[2].EndTime);
}
[Test]
public void IntradayConsolidatorLastBarEndsAtMarketClose()
{
var symbol = Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidator(false, TimeSpan.FromMinutes(7), typeof(TradeBar), TickType.Trade, extendedMarketHours: false);
var bars = new List<TradeBar>();
consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b);
// feed the last 10 minutes of day 1 (up to the 16:00 close) and the first 10 of day 2
Feed(consolidator, symbol, new DateTime(2015, 04, 13, 15, 50, 0), 10);
Feed(consolidator, symbol, new DateTime(2015, 04, 14, 9, 30, 0), 10);
// day 1 produces two 7 minute bars anchored to the market open at 9:30
var day1Bars = bars.FindAll(b => b.Time.Date == new DateTime(2015, 04, 13));
Assert.AreEqual(2, day1Bars.Count);
Assert.AreEqual(new DateTime(2015, 04, 13, 15, 48, 0), day1Bars[0].Time);
Assert.AreEqual(new DateTime(2015, 04, 13, 15, 55, 0), day1Bars[0].EndTime);
Assert.AreEqual(new DateTime(2015, 04, 13, 15, 55, 0), day1Bars[1].Time);
Assert.AreEqual(new DateTime(2015, 04, 13, 16, 0, 0), day1Bars[1].EndTime);
// next day starts over at the market open at 9:30
var day2Open = new DateTime(2015, 04, 14, 9, 30, 0);
var firstDay2 = bars.Find(b => b.Time == day2Open);
Assert.IsNotNull(firstDay2);
Assert.AreEqual(day2Open.AddMinutes(7), firstDay2.EndTime);
}
[TestCase(true)]
[TestCase(false)]
public void ConsolidatesPeriodGreaterThanOneDay(bool dailyStrictEndTimeEnabled)
{
var symbol = Symbols.SPX;
using var consolidator = new MarketHourAwareConsolidator(dailyStrictEndTimeEnabled, TimeSpan.FromDays(2), typeof(TradeBar), TickType.Trade, extendedMarketHours: true);
var bars = new List<TradeBar>();
consolidator.DataConsolidated += (_, b) => bars.Add((TradeBar)b);
// feed 4 daily bars
var start = new DateTime(2015, 04, 13, 10, 0, 0);
for (var i = 0; i < 4; i++)
{
consolidator.Update(new TradeBar { Time = start.AddDays(i), Period = Time.OneDay, Symbol = symbol, Open = 1, High = 1, Low = 1, Close = 1, Volume = 1 });
}
consolidator.Scan(start.AddDays(4));
Assert.AreEqual(2, bars.Count);
// first bar
Assert.AreEqual(TimeSpan.FromDays(2), bars[0].Period);
Assert.AreEqual(start, bars[0].Time);
Assert.AreEqual(start.AddDays(2), bars[0].EndTime);
// second bar
Assert.AreEqual(TimeSpan.FromDays(2), bars[1].Period);
Assert.AreEqual(start.AddDays(2), bars[1].Time);
Assert.AreEqual(start.AddDays(4), bars[1].EndTime);
}
private static void Feed(IDataConsolidator consolidator, Symbol symbol, DateTime from, int minutes)
{
for (var i = 0; i < minutes; i++)
{
var t = from.AddMinutes(i);
consolidator.Update(new TradeBar { Time = t, Period = Time.OneMinute, Symbol = symbol, Open = 1, High = 1, Low = 1, Close = 1, Volume = 1 });
}
}
[Test]
public void WindowIsPopulatedOnConsolidation()
{
var symbol = Symbols.SPY;
using var consolidator = new MarketHourAwareConsolidator(false, Resolution.Daily, typeof(TradeBar), TickType.Trade, false);
consolidator.Update(new TradeBar() { Time = new DateTime(2015, 04, 13, 12, 0, 0), Period = Time.OneMinute, Symbol = symbol, Close = 100 });
consolidator.Scan(new DateTime(2015, 04, 14, 0, 0, 0));
Assert.AreEqual(1, consolidator.Window.Count);
consolidator.Update(new TradeBar() { Time = new DateTime(2015, 04, 14, 12, 0, 0), Period = Time.OneMinute, Symbol = symbol, Close = 200 });
consolidator.Scan(new DateTime(2015, 04, 15, 0, 0, 0));
Assert.AreEqual(2, consolidator.Window.Count);
Assert.AreEqual(200, ((TradeBar)consolidator.Window[0]).Close);
Assert.AreEqual(100, ((TradeBar)consolidator.Window[1]).Close);
}
protected override IDataConsolidator CreateConsolidator()
{
return new MarketHourAwareConsolidator(true, Resolution.Hour, typeof(TradeBar), TickType.Trade, false);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2015, 04, 13, 8, 31, 0);
return new List<TradeBar>()
{
new TradeBar(){ Time = time, Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10 },
new TradeBar(){ Time = time.AddMinutes(1), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12 },
new TradeBar(){ Time = time.AddMinutes(2), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10 },
new TradeBar(){ Time = time.AddMinutes(3), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 5 },
new TradeBar(){ Time = time.AddMinutes(4), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 15 },
new TradeBar(){ Time = time.AddMinutes(5), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 20 },
new TradeBar(){ Time = time.AddMinutes(6), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 18 },
new TradeBar(){ Time = time.AddMinutes(7), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12 },
new TradeBar(){ Time = time.AddMinutes(8), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 25 },
new TradeBar(){ Time = time.AddMinutes(9), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 30 },
new TradeBar(){ Time = time.AddMinutes(10), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 26 },
};
}
private class MarketHourAwareConsolidatorTest : MarketHourAwareConsolidator
{
public MarketHourAwareConsolidatorTest(Resolution resolution, Type dataType, TickType tickType, bool extendedMarketHours)
: base(true, resolution, dataType, tickType, extendedMarketHours)
{
}
protected override bool UseStrictEndTime(Symbol symbol)
{
return true;
}
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.Linq;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using System.Collections.Generic;
namespace QuantConnect.Tests.Common.Data
{
internal class MockSubscriptionDataConfigProvider : ISubscriptionDataConfigProvider
{
public List<SubscriptionDataConfig> SubscriptionDataConfigs
= new List<SubscriptionDataConfig>();
public MockSubscriptionDataConfigProvider(SubscriptionDataConfig config = null)
{
if (config != null)
{
SubscriptionDataConfigs.Add(config);
}
}
public List<SubscriptionDataConfig> GetSubscriptionDataConfigs(Symbol symbol, bool includeInternalConfigs = false)
{
return SubscriptionDataConfigs.Where(config => !config.IsInternalFeed || includeInternalConfigs).ToList();
}
}
}
@@ -0,0 +1,170 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class OpenInterestConsolidatorTests : BaseConsolidatorTests
{
[TestCaseSource(nameof(HourAndDailyTestValues))]
public void HourAndDailyConsolidationKeepsTimeOfDay(TimeSpan period, List<(OpenInterest, bool)> data)
{
using var consolidator = new OpenInterestConsolidator(period);
var consolidatedOpenInterest = (OpenInterest)null;
consolidator.DataConsolidated += (sender, consolidated) =>
{
Log.Debug($"{consolidated.EndTime} - {consolidated}");
consolidatedOpenInterest = consolidated;
};
var prevData = (OpenInterest)null;
foreach (var (openInterest, shouldConsolidate) in data)
{
consolidator.Update(openInterest);
if (shouldConsolidate)
{
Assert.IsNotNull(consolidatedOpenInterest);
Assert.AreEqual(prevData.Symbol, consolidatedOpenInterest.Symbol);
Assert.AreEqual(prevData.Value, consolidatedOpenInterest.Value);
Assert.AreEqual(prevData.EndTime, consolidatedOpenInterest.EndTime);
consolidatedOpenInterest = null;
}
else
{
Assert.IsNull(consolidatedOpenInterest);
}
prevData = openInterest;
}
}
protected override IDataConsolidator CreateConsolidator()
{
return new OpenInterestConsolidator(TimeSpan.FromDays(1));
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2015, 04, 13, 8, 31, 0);
return new List<OpenInterest>()
{
new OpenInterest(){ Time = time, Symbol = Symbols.SPY, Value = 10 },
new OpenInterest(){ Time = time.AddMinutes(1), Symbol = Symbols.SPY, Value = 12 },
new OpenInterest(){ Time = time.AddMinutes(2), Symbol = Symbols.SPY, Value = 10 },
new OpenInterest(){ Time = time.AddMinutes(3), Symbol = Symbols.SPY, Value = 5 },
new OpenInterest(){ Time = time.AddMinutes(4), Symbol = Symbols.SPY, Value = 15 },
new OpenInterest(){ Time = time.AddMinutes(5), Symbol = Symbols.SPY, Value = 20 },
new OpenInterest(){ Time = time.AddMinutes(6), Symbol = Symbols.SPY, Value = 18 },
new OpenInterest(){ Time = time.AddMinutes(7), Symbol = Symbols.SPY, Value = 12 },
new OpenInterest(){ Time = time.AddMinutes(8), Symbol = Symbols.SPY, Value = 25 },
new OpenInterest(){ Time = time.AddMinutes(9), Symbol = Symbols.SPY, Value = 30 },
new OpenInterest(){ Time = time.AddMinutes(10), Symbol = Symbols.SPY, Value = 26 },
};
}
private static IEnumerable<TestCaseData> HourAndDailyTestValues()
{
var symbol = Symbols.SPY_C_192_Feb19_2016;
var time = new DateTime(2015, 04, 13, 6, 30, 0);
var period = Time.OneDay;
yield return new TestCaseData(
period,
new List<(OpenInterest, bool)>()
{
(new OpenInterest(time, symbol, 10), false),
(new OpenInterest(time.AddDays(1), symbol, 11), true),
(new OpenInterest(time.AddDays(2), symbol, 12), true),
(new OpenInterest(time.AddDays(3), symbol, 13), true),
(new OpenInterest(time.AddDays(4), symbol, 14), true),
(new OpenInterest(time.AddDays(5), symbol, 15), true),
});
yield return new TestCaseData(
period,
new List<(OpenInterest, bool)>()
{
(new OpenInterest(time, symbol, 10), false),
(new OpenInterest(time.AddDays(1), symbol, 11), true),
// Same date, should not consolidate
(new OpenInterest(time.AddDays(1).AddMinutes(1), symbol, 12), false),
// Same date, should not consolidate
(new OpenInterest(time.AddDays(1).AddMinutes(2), symbol, 13), false),
// Same date, should not consolidate
(new OpenInterest(time.AddDays(1).AddMinutes(3), symbol, 14), false),
// Not the full period passed but different date, should consolidate
(new OpenInterest(time.AddDays(2).AddHours(-1), symbol, 15), true),
(new OpenInterest(time.AddDays(3).AddHours(-2), symbol, 16), true),
(new OpenInterest(time.AddDays(4).AddHours(-3), symbol, 17), true),
(new OpenInterest(time.AddDays(5).AddHours(-4), symbol, 18), true),
});
period = Time.OneHour;
yield return new TestCaseData(
period,
new List<(OpenInterest, bool)>()
{
(new OpenInterest(time, symbol, 10), false),
(new OpenInterest(time.AddHours(1), symbol, 11), true),
(new OpenInterest(time.AddHours(2), symbol, 12), true),
(new OpenInterest(time.AddHours(3), symbol, 13), true),
(new OpenInterest(time.AddHours(4), symbol, 14), true),
(new OpenInterest(time.AddHours(5), symbol, 15), true),
});
yield return new TestCaseData(
period,
new List<(OpenInterest, bool)>()
{
(new OpenInterest(time.AddHours(0.5).AddMinutes(10), symbol, 10), false),
(new OpenInterest(time.AddHours(2.5).AddMinutes(20), symbol, 11), true),
(new OpenInterest(time.AddHours(4.5).AddMinutes(30), symbol, 12), true),
(new OpenInterest(time.AddHours(6.5).AddMinutes(40), symbol, 13), true),
(new OpenInterest(time.AddHours(8.5), symbol, 14), true),
(new OpenInterest(time.AddHours(10.5).AddMinutes(50), symbol, 15), true),
});
yield return new TestCaseData(
period,
new List<(OpenInterest, bool)>()
{
(new OpenInterest(time, symbol, 10), false),
(new OpenInterest(time.AddHours(1), symbol, 11), true),
// Same date, should not consolidate
(new OpenInterest(time.AddHours(1).AddMinutes(5), symbol, 12), false),
// Same date, should not consolidate
(new OpenInterest(time.AddHours(1).AddMinutes(10), symbol, 13), false),
// Same date, should not consolidate
(new OpenInterest(time.AddHours(1).AddMinutes(15), symbol, 14), false),
// Not the full period passed but different date, should consolidate
(new OpenInterest(time.AddHours(2).AddMinutes(-5), symbol, 15), true),
(new OpenInterest(time.AddHours(3).AddMinutes(-10), symbol, 16), true),
(new OpenInterest(time.AddHours(4).AddMinutes(-15), symbol, 17), true),
(new OpenInterest(time.AddHours(5).AddMinutes(-20), symbol, 18), true),
});
}
}
}
@@ -0,0 +1,409 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class PeriodCountConsolidatorTests
{
private static readonly object[] PeriodCases =
{
new [] { TimeSpan.FromDays(100), TimeSpan.FromDays(10) },
new [] { TimeSpan.FromDays(30), TimeSpan.FromDays(1) }, //GH Issue #4915
new [] { TimeSpan.FromDays(10), TimeSpan.FromDays(1) },
new [] { TimeSpan.FromDays(1), TimeSpan.FromHours(1) },
new [] { TimeSpan.FromHours(10), TimeSpan.FromHours(1) },
new [] { TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(1) },
new [] { TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(10) },
new [] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(0.1) }
};
[TestCaseSource(nameof(PeriodCases))]
public void ExpectedConsolidatedTradeBarsInPeriodMode(TimeSpan barSpan, TimeSpan updateSpan)
{
TradeBar consolidated = null;
using var consolidator = new BaseDataConsolidator(barSpan);
consolidator.DataConsolidated += (sender, bar) =>
{
Assert.AreEqual(barSpan, bar.Period); // The period matches our span
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
var dataTime = reference;
var nextBarTime = reference + barSpan;
var lastBarTime = reference;
// First data point
consolidator.Update(new Tick { Time = dataTime });
Assert.IsNull(consolidated);
for (var i = 0; i < 10; i++)
{
// Add data on the given interval until we expect a new bar
while (dataTime < nextBarTime)
{
dataTime = dataTime.Add(updateSpan);
consolidator.Update(new Tick { Time = dataTime });
}
// Our asserts
Assert.IsNotNull(consolidated); // We have a bar
Assert.AreEqual(dataTime, consolidated.EndTime); // New bar time should be dataTime
Assert.AreEqual(barSpan, consolidated.EndTime - lastBarTime); // The difference between the bars is the span
nextBarTime = dataTime + barSpan;
lastBarTime = consolidated.EndTime;
}
}
[TestCaseSource(nameof(PeriodCases))]
public void ExpectedConsolidatedQuoteBarsInPeriodMode(TimeSpan barSpan, TimeSpan updateSpan)
{
QuoteBar consolidated = null;
using var consolidator = new QuoteBarConsolidator(barSpan);
consolidator.DataConsolidated += (sender, bar) =>
{
Assert.AreEqual(barSpan, bar.Period); // The period matches our span
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
var dataTime = reference;
var nextBarTime = reference + barSpan;
var lastBarTime = reference;
// First data point
consolidator.Update(new QuoteBar { Time = dataTime, Period = updateSpan });
Assert.IsNull(consolidated);
for (var i = 0; i < 10; i++)
{
// Add data on the given interval until we expect a new bar
while (dataTime < nextBarTime)
{
dataTime = dataTime.Add(updateSpan);
consolidator.Update(new QuoteBar { Time = dataTime, Period = updateSpan });
}
// Our asserts
Assert.IsNotNull(consolidated); // We have a bar
Assert.AreEqual(dataTime, consolidated.EndTime); // New bar time should be dataTime
Assert.AreEqual(barSpan, consolidated.EndTime - lastBarTime); // The difference between the bars is the span
nextBarTime = dataTime + barSpan;
lastBarTime = consolidated.EndTime;
}
}
[Test]
public void ConsolidatorEmitsOffsetBarsCorrectly()
{
// This test is to cover an issue seen with the live data stack
// The consolidator would fail to emit every other bar because of a
// ms delay in data from a live stream
var period = TimeSpan.FromHours(2);
using var consolidator = new TradeBarConsolidator(period);
var consolidatedBarsCount = 0;
consolidator.DataConsolidated += (sender, bar) =>
{
consolidatedBarsCount++;
};
var random = new Random();
var time = new DateTime(2015, 04, 13);
// The bars time is accurate, covering the hour perfectly
// But the emit time is slightly offset (the timeslice that contains the bar)
// So add a random ms offset to the scan time
consolidator.Update(new TradeBar { Time = time, Period = Time.OneHour });
time = time.Add(period);
consolidator.Scan(time.AddMilliseconds(random.Next(800)));
consolidator.Update(new TradeBar { Time = time, Period = Time.OneHour });
time = time.Add(period);
consolidator.Scan(time.AddMilliseconds(random.Next(800)));
consolidator.Update(new TradeBar { Time = time, Period = Time.OneHour });
time = time.Add(period);
consolidator.Scan(time.AddMilliseconds(random.Next(800)));
consolidator.Update(new TradeBar { Time = time, Period = Time.OneHour });
time = time.Add(period);
consolidator.Scan(time.AddMilliseconds(random.Next(800)));
// We should expect to see 4 bars emitted from the consolidator
Assert.AreEqual(4, consolidatedBarsCount);
}
[Test]
public void ConsolidatorEmitsOldBarsUsingUpdate()
{
// This test is to ensure that no bars get swallowed by the consolidator
// even if it doesn't get the data on regular intervals.
// We will use the PushThrough method which calls update
var period = TimeSpan.FromHours(1);
using var consolidator = new TradeBarConsolidator(period);
TradeBar latestConsolidated = null;
var consolidatedBarsCount = 0;
consolidator.DataConsolidated += (sender, bar) =>
{
latestConsolidated = bar;
consolidatedBarsCount++;
};
// Set our starting time 04/13/2015 at 12:00AM
var time = new DateTime(2015, 04, 13);
// Update this consolidator with minute tradebars but one less than 60, which would trigger emit
PushBarsThrough(59, Time.OneMinute, consolidator, ref time);
// No bars should be emitted, lets assert the current time and count
Assert.IsTrue(time == new DateTime(2015, 04, 13, 0, 59, 0));
Assert.AreEqual(0, consolidatedBarsCount);
// Advance time way past (3 hours) the bar end time of 1AM
time += TimeSpan.FromHours(3); // Time = 3:59AM now
// Push one bar through at 3:59AM and check that we still get the 12AM - 1AM Bar emitted
PushBarsThrough(1, Time.OneMinute, consolidator, ref time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.IsTrue(latestConsolidated != null && latestConsolidated.Time == new DateTime(2015, 04, 13));
// Check the new working bar is 3AM to 4AM, This is because we pushed a bar in at 3:59AM
Assert.IsTrue(consolidator.WorkingBar.Time == new DateTime(2015, 04, 13, 3, 0, 0));
}
[Test]
public void ConsolidatorEmitsOldBarsUsingScan()
{
// This test is to ensure that no bars get swallowed by the consolidator
// even if it doesn't get the data on regular intervals.
// We will use Consolidators Scan method to emit bars
var period = TimeSpan.FromHours(1);
using var consolidator = new TradeBarConsolidator(period);
TradeBar latestConsolidated = null;
var consolidatedBarsCount = 0;
consolidator.DataConsolidated += (sender, bar) =>
{
latestConsolidated = bar;
consolidatedBarsCount++;
};
var time = new DateTime(2015, 04, 13);
// Push through one bar at 12:00AM to create the consolidators working bar
PushBarsThrough(1, Time.OneMinute, consolidator, ref time);
// There should be no emit, lets assert the current time and count
Assert.IsTrue(time == new DateTime(2015, 04, 13, 0, 1, 0));
Assert.AreEqual(0, consolidatedBarsCount);
// Now advance time way past (3 Hours) the bar end time of 1AM
time += TimeSpan.FromHours(3); // Time = 3:59AM now
// Call scan with current time, it should emit the 12AM - 1AM Bar without any update
consolidator.Scan(time);
Assert.AreEqual(1, consolidatedBarsCount);
Assert.IsTrue(latestConsolidated != null && latestConsolidated.Time == new DateTime(2015, 04, 13, 0, 0, 0));
// WorkingBar should be null, ready for whatever data comes through next
Assert.IsTrue(consolidator.WorkingBar == null);
}
[Test]
public void ConsolidatorEmitsRegularly()
{
// This test just pushes through 1000 bars
// and ensures that the emit time and count are correct
var period = TimeSpan.FromHours(2);
using var consolidator = new TradeBarConsolidator(period);
var consolidatedBarsCount = 0;
var time = new DateTime(2015, 04, 13);
consolidator.DataConsolidated += (sender, bar) =>
{
Assert.IsTrue(bar.EndTime == time);
consolidatedBarsCount++;
};
PushBarsThrough(1000, Time.OneHour, consolidator, ref time);
// Scan one last time so we can emit the 1000th bar
consolidator.Scan(time);
Assert.AreEqual(500, consolidatedBarsCount);
}
[TestCase(14)] // 2PM
[TestCase(15)] // 3PM
[TestCase(16)] // 4PM
public void BarsEmitOnTime(int hour)
{
// This test just pushes one full hourly bar into a consolidator
// and scans to see if it will emit immediately as expected
using var consolidator = new TradeBarConsolidator(Time.OneHour);
var consolidatedBarsCount = 0;
TradeBar latestBar = null;
var time = new DateTime(2015, 04, 13, hour, 0, 0);
consolidator.DataConsolidated += (sender, bar) =>
{
latestBar = bar;
consolidatedBarsCount++;
};
// Update with one tradebar that ends at this time
// This is to simulate getting a data bar for the last period
consolidator.Update(new TradeBar { Time = time.Subtract(Time.OneMinute), Period = Time.OneMinute });
// Assert that the bar hasn't emitted
Assert.IsNull(latestBar);
Assert.AreEqual(0, consolidatedBarsCount);
// Scan afterwards (Like algorithmManager does)
consolidator.Scan(time);
// Assert that the bar emitted
Assert.IsNotNull(latestBar);
Assert.IsTrue(latestBar.EndTime == time);
Assert.AreEqual(1, consolidatedBarsCount);
}
[TestCase(typeof(BaseDataConsolidator))]
[TestCase(typeof(TradeBarConsolidator))]
[TestCase(typeof(QuoteBarConsolidator))]
[TestCase(typeof(TickConsolidator))]
[TestCase(typeof(TickQuoteBarConsolidator))]
[TestCase(typeof(OpenInterestConsolidator))]
[TestCase(typeof(DynamicDataConsolidator))]
public void ConsolidatorShouldConsolidateOnMaxCountAndUseLastEndTime(Type consolidatorType)
{
// Create a consolidator with maxCount = 2
var consolidator = (IDataConsolidator)Activator.CreateInstance(consolidatorType, 2);
IBaseData consolidated = null;
consolidator.DataConsolidated += (sender, bar) =>
{
// Store the consolidated bar when the DataConsolidated event fires
consolidated = bar;
};
var startDate = new DateTime(2015, 04, 13, 10, 20, 0);
var expectedEndTime = startDate.AddMinutes(61);
var tickType =
consolidatorType == typeof(TickQuoteBarConsolidator) ? TickType.Quote :
consolidatorType == typeof(TickConsolidator) ? TickType.Trade :
TickType.OpenInterest;
var tradeBars = new List<TradeBar>
{
new TradeBar { Symbol = Symbols.SPY, DataType = MarketDataType.TradeBar, Time = startDate, EndTime = startDate.AddMinutes(1) },
new TradeBar { Symbol = Symbols.SPY, DataType = MarketDataType.TradeBar, Time = startDate.AddMinutes(1), EndTime = startDate.AddMinutes(2) },
new TradeBar { Symbol = Symbols.SPY, DataType = MarketDataType.TradeBar, Time = startDate.AddMinutes(2), EndTime = startDate.AddMinutes(3) },
new TradeBar { Symbol = Symbols.SPY, DataType = MarketDataType.TradeBar, Time = startDate.AddHours(1), EndTime = startDate.AddMinutes(61) },
};
var quoteBars = new List<QuoteBar>
{
new QuoteBar { Symbol = Symbols.SPY, DataType = MarketDataType.QuoteBar, Time = startDate, EndTime = startDate.AddMinutes(1) },
new QuoteBar { Symbol = Symbols.SPY, DataType = MarketDataType.QuoteBar, Time = startDate.AddMinutes(1), EndTime = startDate.AddMinutes(2) },
new QuoteBar { Symbol = Symbols.SPY, DataType = MarketDataType.QuoteBar, Time = startDate.AddMinutes(2), EndTime = startDate.AddMinutes(3) },
new QuoteBar { Symbol = Symbols.SPY, DataType = MarketDataType.QuoteBar, Time = startDate.AddHours(1), EndTime = startDate.AddMinutes(61) },
};
var ticks = new List<Tick>
{
new Tick { Symbol = Symbols.SPY, DataType = MarketDataType.Tick, TickType = tickType, Time = startDate, EndTime = startDate.AddMinutes(1) },
new Tick { Symbol = Symbols.SPY, DataType = MarketDataType.Tick, TickType = tickType, Time = startDate.AddMinutes(1), EndTime = startDate.AddMinutes(2) },
new Tick { Symbol = Symbols.SPY, DataType = MarketDataType.Tick, TickType = tickType, Time = startDate.AddMinutes(2), EndTime = startDate.AddMinutes(3) },
new Tick { Symbol = Symbols.SPY, DataType = MarketDataType.Tick, TickType = tickType, Time = startDate.AddHours(1), EndTime = startDate.AddMinutes(61) },
};
var customData = new List<CustomData>
{
new CustomData { Symbol = Symbols.SPY, Time = startDate, EndTime = startDate.AddMinutes(1) },
new CustomData { Symbol = Symbols.SPY, Time = startDate.AddMinutes(1), EndTime = startDate.AddMinutes(2) },
new CustomData { Symbol = Symbols.SPY, Time = startDate.AddMinutes(2), EndTime = startDate.AddMinutes(3) },
new CustomData { Symbol = Symbols.SPY, Time = startDate.AddHours(1), EndTime = startDate.AddMinutes(61) },
};
var dataMap = new Dictionary<Type, IEnumerable<BaseData>>
{
{ typeof(TradeBarConsolidator), tradeBars },
{ typeof(BaseDataConsolidator), tradeBars },
{ typeof(QuoteBarConsolidator), quoteBars },
{ typeof(TickQuoteBarConsolidator), ticks },
{ typeof(OpenInterestConsolidator), ticks },
{ typeof(TickConsolidator), ticks },
{ typeof(DynamicDataConsolidator), customData }
};
if (dataMap.TryGetValue(consolidatorType, out var dataList))
{
// Feed the consolidator with the appropriate data
foreach (var data in dataList)
{
consolidator.Update(data);
}
}
// Assert the consolidated bar is not null and its EndTime matches the last received bar's EndTime
Assert.IsNotNull(consolidated);
Assert.AreEqual(Symbols.SPY, consolidated.Symbol);
Assert.AreEqual(expectedEndTime, consolidated.EndTime);
}
private static void PushBarsThrough(int barCount, TimeSpan period, TradeBarConsolidator consolidator, ref DateTime time)
{
TradeBar bar;
for (int i = 0; i < barCount; i++)
{
bar = new TradeBar { Time = time, Period = period };
consolidator.Update(bar);
// Advance time
time += period;
}
}
private class CustomData : DynamicData
{
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
throw new NotImplementedException();
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,478 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class QuoteBarConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void ThrowsWhenPeriodIsSmallerThanDataPeriod()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(Time.OneHour);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = new DateTime(2022, 6, 6, 13, 30, 1);
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = TimeSpan.FromDays(1)
};
Assert.Throws<ArgumentException>(() => creator.Update(bar1));
}
[Test]
public void MultipleResolutionConsolidation()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(Time.OneDay);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = new DateTime(2022, 6, 6);
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = TimeSpan.FromDays(1)
};
creator.Update(bar1);
Assert.IsNull(quoteBar);
creator.Scan(bar1.EndTime);
Assert.IsNotNull(quoteBar);
quoteBar = null;
// now let's send in other resolution data
var previousBar = bar1;
for (int i = 0; i <= 24; i++)
{
previousBar = new QuoteBar
{
Time = previousBar.EndTime,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = TimeSpan.FromHours(1)
};
creator.Update(previousBar);
if (i < 24)
{
Assert.IsNull(quoteBar, $"{i} {previousBar.EndTime}");
}
else
{
Assert.IsNotNull(quoteBar, $"{i} {previousBar.EndTime}");
}
}
}
[Test]
public void GentlyHandlesPeriodAndDataAreSameResolution()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(Time.OneDay);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = new DateTime(2022, 6, 6, 13, 30, 1);
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = TimeSpan.FromDays(1)
};
creator.Update(bar1);
Assert.IsNull(quoteBar);
creator.Scan(bar1.EndTime);
Assert.IsNotNull(quoteBar);
Assert.AreEqual(bar1.Symbol, quoteBar.Symbol);
Assert.AreEqual(bar1.Ask, quoteBar.Ask);
Assert.AreEqual(bar1.Bid.Open, quoteBar.Bid.Open);
Assert.AreEqual(bar1.Bid.High, quoteBar.Bid.High);
Assert.AreEqual(bar1.Bid.Low, quoteBar.Bid.Low);
Assert.AreEqual(bar1.Bid.Close, quoteBar.Bid.Close);
Assert.AreEqual(bar1.LastBidSize, quoteBar.LastBidSize);
Assert.AreEqual(bar1.LastAskSize, quoteBar.LastAskSize);
Assert.AreEqual(bar1.Value, quoteBar.Value);
Assert.AreEqual(bar1.EndTime, quoteBar.EndTime);
Assert.AreEqual(bar1.Time, quoteBar.Time);
Assert.AreEqual(bar1.Period, quoteBar.Period);
}
[Test]
public void AggregatesNewCountQuoteBarProperlyDaily()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(1);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = new DateTime(2022, 6, 6, 13, 30, 1);
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = TimeSpan.FromDays(1)
};
creator.Update(bar1);
Assert.IsNotNull(quoteBar);
Assert.AreEqual(bar1.Symbol, quoteBar.Symbol);
Assert.AreEqual(bar1.Ask, quoteBar.Ask);
Assert.AreEqual(bar1.Bid.Open, quoteBar.Bid.Open);
Assert.AreEqual(bar1.Bid.High, quoteBar.Bid.High);
Assert.AreEqual(bar1.Bid.Low, quoteBar.Bid.Low);
Assert.AreEqual(bar1.Bid.Close, quoteBar.Bid.Close);
Assert.AreEqual(bar1.LastBidSize, quoteBar.LastBidSize);
Assert.AreEqual(bar1.LastAskSize, quoteBar.LastAskSize);
Assert.AreEqual(bar1.Value, quoteBar.Value);
Assert.AreEqual(bar1.EndTime, quoteBar.EndTime);
Assert.AreEqual(bar1.Time, quoteBar.Time);
Assert.AreEqual(bar1.Period, quoteBar.Period);
}
[Test]
public void AggregatesNewCountQuoteBarProperly()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(4);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = DateTime.Today;
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar1);
Assert.IsNull(quoteBar);
var bar2 = new QuoteBar
{
Time = bar1.EndTime,
Symbol = Symbols.SPY,
Bid = new Bar(1.1m, 2.2m, 0.9m, 2.1m),
LastBidSize = 3,
Ask = new Bar(2.2m, 4.4m, 3.3m, 3.3m),
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar2);
Assert.IsNull(quoteBar);
var bar3 = new QuoteBar
{
Time = bar2.EndTime,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.5m, 1.75m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar3);
Assert.IsNull(quoteBar);
var bar4 = new QuoteBar
{
Time = bar3.EndTime,
Symbol = Symbols.SPY,
Bid = null,
LastBidSize = 0,
Ask = new Bar(1, 7, 0.5m, 4.4m),
LastAskSize = 4,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar4);
Assert.IsNotNull(quoteBar);
Assert.AreEqual(bar1.Symbol, quoteBar.Symbol);
Assert.AreEqual(bar1.Bid.Open, quoteBar.Bid.Open);
Assert.AreEqual(bar2.Ask.Open, quoteBar.Ask.Open);
Assert.AreEqual(bar2.Bid.High, quoteBar.Bid.High);
Assert.AreEqual(bar4.Ask.High, quoteBar.Ask.High);
Assert.AreEqual(bar3.Bid.Low, quoteBar.Bid.Low);
Assert.AreEqual(bar4.Ask.Low, quoteBar.Ask.Low);
Assert.AreEqual(bar3.Bid.Close, quoteBar.Bid.Close);
Assert.AreEqual(bar4.Ask.Close, quoteBar.Ask.Close);
Assert.AreEqual(bar3.LastBidSize, quoteBar.LastBidSize);
Assert.AreEqual(bar4.LastAskSize, quoteBar.LastAskSize);
Assert.AreEqual(bar1.Value, quoteBar.Value);
Assert.AreEqual(bar1.Time, quoteBar.Time);
Assert.AreEqual(bar4.EndTime, quoteBar.EndTime);
Assert.AreEqual(TimeSpan.FromMinutes(4), quoteBar.Period);
}
[Test]
public void AggregatesNewTimeSpanQuoteBarProperly()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(TimeSpan.FromMinutes(2));
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = DateTime.Today;
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar1);
Assert.IsNull(quoteBar);
var bar2 = new QuoteBar
{
Time = bar1.EndTime,
Symbol = Symbols.SPY,
Bid = new Bar(1.1m, 2.2m, 0.9m, 2.1m),
LastBidSize = 3,
Ask = new Bar(2.2m, 4.4m, 3.3m, 3.3m),
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar2);
Assert.IsNull(quoteBar);
// pushing another bar to force the fire
var bar3 = new QuoteBar
{
Time = bar2.EndTime,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.5m, 1.75m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = Time.OneMinute
};
creator.Update(bar3);
Assert.IsNotNull(quoteBar);
Assert.AreEqual(bar1.Symbol, quoteBar.Symbol);
Assert.AreEqual(bar1.Time, quoteBar.Time);
Assert.AreEqual(bar2.EndTime, quoteBar.EndTime);
Assert.AreEqual(TimeSpan.FromMinutes(2), quoteBar.Period);
// Bid
Assert.AreEqual(bar1.Bid.Open, quoteBar.Bid.Open);
Assert.AreEqual(bar2.Bid.Close, quoteBar.Bid.Close);
Assert.AreEqual(Math.Max(bar2.Bid.High, bar1.Bid.High), quoteBar.Bid.High);
Assert.AreEqual(Math.Min(bar2.Bid.Low, bar1.Bid.Low), quoteBar.Bid.Low);
// Ask
Assert.AreEqual(bar2.Ask.Open, quoteBar.Ask.Open);
Assert.AreEqual(bar2.Ask.Close, quoteBar.Ask.Close);
Assert.AreEqual(bar2.Ask.High, quoteBar.Ask.High);
Assert.AreEqual(bar2.Ask.Low, quoteBar.Ask.Low);
Assert.AreEqual(bar1.LastAskSize, quoteBar.LastAskSize);
Assert.AreEqual(1, quoteBar.Value);
}
[Test]
public void DoesNotConsolidateDifferentSymbols()
{
using var consolidator = new QuoteBarConsolidator(2);
var time = DateTime.Today;
var period = TimeSpan.FromMinutes(1);
var bar1 = new QuoteBar
{
Symbol = Symbols.AAPL,
Time = time,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = period
};
var bar2 = new QuoteBar
{
Symbol = Symbols.ZNGA,
Time = time,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = period
};
consolidator.Update(bar1);
Exception ex = Assert.Throws<InvalidOperationException>(() => consolidator.Update(bar2));
Assert.IsTrue(ex.Message.Contains("is not the same", StringComparison.InvariantCultureIgnoreCase));
}
[Test]
public void LastCloseAndCurrentOpenPriceShouldBeSameConsolidatedOnTimeSpan()
{
QuoteBar quoteBar = null;
using var creator = new QuoteBarConsolidator(TimeSpan.FromMinutes(2));
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var time = DateTime.Today;
var period = TimeSpan.FromMinutes(1);
var bar1 = new QuoteBar
{
Time = time,
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.75m, 1.25m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = period
};
creator.Update(bar1);
Assert.IsNull(quoteBar);
var bar2 = new QuoteBar
{
Time = time + TimeSpan.FromMinutes(1),
Symbol = Symbols.SPY,
Bid = null,
LastBidSize = 0,
Ask = new Bar(2.2m, 4.4m, 3.3m, 3.3m),
LastAskSize = 10,
Value = 1,
Period = period
};
creator.Update(bar2);
Assert.IsNull(quoteBar);
// pushing another bar to force the fire
var bar3 = new QuoteBar
{
Time = time + TimeSpan.FromMinutes(2),
Symbol = Symbols.SPY,
Bid = new Bar(1, 2, 0.5m, 1.75m),
LastBidSize = 3,
Ask = null,
LastAskSize = 0,
Value = 1,
Period = period
};
creator.Update(bar3);
Assert.IsNotNull(quoteBar);
//force the consolidator to emit DataConsolidated
creator.Scan(time.AddMinutes(4));
Assert.AreEqual(bar1.Symbol, quoteBar.Symbol);
Assert.AreEqual(time + TimeSpan.FromMinutes(4), quoteBar.EndTime);
Assert.AreEqual(TimeSpan.FromMinutes(2), quoteBar.Period);
// Bid
Assert.AreEqual(quoteBar.Bid.Open, bar1.Bid.Close);
// Ask
Assert.AreEqual(quoteBar.Ask.Open, bar2.Ask.Close);
}
protected override IDataConsolidator CreateConsolidator()
{
return new QuoteBarConsolidator(2);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = DateTime.Today;
return new List<QuoteBar>()
{
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(1, 2, 0.5m, 1.75m), Ask = new Bar(2.2m, 4.4m, 3.3m, 3.3m), LastBidSize = 10, LastAskSize = 0 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(0, 4, 0.4m, 3.75m), Ask = new Bar(2.3m, 9.4m, 2.3m, 4.5m), LastBidSize = 5, LastAskSize = 4 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(2, 2, 0.9m, 1.45m), Ask = new Bar(2.7m, 8.4m, 3.6m, 3.6m), LastBidSize = 8, LastAskSize = 4 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(2, 6, 2.5m, 5.55m), Ask = new Bar(3.2m, 6.4m, 2.3m, 5.3m), LastBidSize = 9, LastAskSize = 4 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(1, 2, 1.5m, 0.34m), Ask = new Bar(3.6m, 9.4m, 3.7m, 3.8m), LastBidSize = 5, LastAskSize = 8 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(1, 2, 1.1m, 0.75m), Ask = new Bar(3.8m, 8.4m, 7.3m, 5.3m), LastBidSize = 9, LastAskSize = 5 },
new QuoteBar(){Time = time, Symbol = Symbols.SPY, Bid = new Bar(3, 3, 2.2m, 1.12m), Ask = new Bar(4.5m, 7.2m, 7.1m, 6.1m), LastBidSize = 6, LastAskSize = 3 },
};
}
}
}
+251
View File
@@ -0,0 +1,251 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class RangeConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void RangeConsolidatorReturnsExpectedValues()
{
using var consolidator = CreateRangeConsolidator(100);
var testValues = new List<decimal>() { 90m, 94.5m, 94m, 89.5m, 89m, 90.5m, 90m, 91.5m, 90m, 90.5m, 92.5m };
#pragma warning disable CS0618
var returnedBars = UpdateConsolidator(consolidator, testValues, "IBM");
#pragma warning restore CS0618
var expectedValues = GetRangeConsolidatorExpectedValues();
RangeBar lastRangeBar = null;
for (int index = 0; index < returnedBars.Count; index++)
{
var open = expectedValues[index][0];
var low = expectedValues[index][1];
var high = expectedValues[index][2];
var close = expectedValues[index][3];
var volume = expectedValues[index][4];
// Check RangeBar's values
Assert.AreEqual(open, returnedBars[index].Open);
Assert.AreEqual(low, returnedBars[index].Low);
Assert.AreEqual(high, returnedBars[index].High);
Assert.AreEqual(close, returnedBars[index].Close);
Assert.AreEqual(volume, returnedBars[index].Volume);
// Check the size of each RangeBar
Assert.AreEqual(1, Math.Round(returnedBars[index].High - returnedBars[index].Low, 2));
// Check the Open value of the current bar is outside last bar Low-High interval
if (lastRangeBar != null)
{
Assert.IsTrue(returnedBars[index].Open < lastRangeBar.Low || returnedBars[index].Open > lastRangeBar.High);
}
lastRangeBar = returnedBars[index];
}
}
[TestCaseSource(nameof(PriceGapBehaviorIsTheExpectedOneTestCases))]
public virtual void PriceGapBehaviorIsTheExpectedOne(Symbol symbol, double minimumPriceVariation, double range)
{
using var consolidator = CreateRangeConsolidator((int)range);
var testValues = new List<decimal>() { 90m, 94.5m, 94m, 89.5m, 89m, 90.5m, 90m, 91.5m, 90m, 90.5m, 92.5m };
var returnedBars = UpdateConsolidator(consolidator, testValues, symbol);
RangeBar lastRangeBar = null;
for (int index = 0; index < returnedBars.Count; index++)
{
// Check the gap between each bar is of the size of the minimum price variation
if (lastRangeBar != null)
{
Assert.IsTrue(returnedBars[index].Open == (lastRangeBar.High + (decimal)minimumPriceVariation) || returnedBars[index].Open == (lastRangeBar.Low - (decimal)minimumPriceVariation));
}
lastRangeBar = returnedBars[index];
}
}
[TestCaseSource(nameof(ConsolidatorCreatesExpectedBarsTestCases))]
public virtual void ConsolidatorCreatesExpectedBarsInDifferentScenarios(List<decimal> testValues, RangeBar[] expectedBars)
{
using var consolidator = CreateRangeConsolidator(100);
var returnedBars = UpdateConsolidator(consolidator, testValues, Symbols.IBM);
Assert.IsNotEmpty(returnedBars);
for (int index = 0; index < returnedBars.Count; index++)
{
Assert.AreEqual(expectedBars[index].Open, returnedBars[index].Open);
Assert.AreEqual(expectedBars[index].Low, returnedBars[index].Low);
Assert.AreEqual(expectedBars[index].High, returnedBars[index].High);
Assert.AreEqual(expectedBars[index].Close, returnedBars[index].Close);
Assert.AreEqual(expectedBars[index].Volume, returnedBars[index].Volume);
Assert.AreEqual(expectedBars[index].EndTime, returnedBars[index].EndTime);
}
}
[TestCase(new double[] { 94, 94.1, 94.2, 94.3, 94.4, 94.5, 94.6, 94.7, 94.8, 94.9, 95, 95.1 }, new double[] { 94, 95, 94, 95, 110 })]
[TestCase(new double[] { 94, 93.9, 93.8, 93.7, 93.6, 93.5, 93.4, 93.3, 93.2, 93.1, 93, 92.9 }, new double[] { 94, 94, 93, 93, 110 })]
[TestCase(new double[] { 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95.1 }, new double[] { 94, 95, 94, 95, 160 })]
[TestCase(new double[] { 94, 93.9, 94.1, 93.8, 94.2, 93.7, 94.3, 93.6, 94.4, 93.5, 94.5, 93.4 }, new double[] { 94, 94.5, 93.5, 93.5, 110 })]
public void ConsolidatorUpdatesTheVolumeOfTheBarsAsExpected(double[] testValues, double[] expectedBar)
{
using var consolidator = CreateRangeConsolidator(100);
var returnedBars = UpdateConsolidator(consolidator, new List<decimal>(testValues.Select(x => (decimal)x)), Symbols.IBM);
Assert.AreEqual(1, returnedBars.Count);
Assert.AreEqual(expectedBar[0], returnedBars[0].Open);
Assert.AreEqual(expectedBar[1], returnedBars[0].High);
Assert.AreEqual(expectedBar[2], returnedBars[0].Low);
Assert.AreEqual(expectedBar[3], returnedBars[0].Close);
Assert.AreEqual(expectedBar[4], returnedBars[0].Volume);
}
protected virtual RangeConsolidator CreateRangeConsolidator(int range)
{
return new RangeConsolidator(range, x => x.Value, x => 10m);
}
private List<RangeBar> UpdateConsolidator(RangeConsolidator rangeConsolidator, List<decimal> testValues, Symbol symbol)
{
var time = new DateTime(2016, 1, 1);
using var consolidator = rangeConsolidator;
var returnedBars = new List<RangeBar>();
consolidator.DataConsolidated += (sender, rangeBar) =>
{
returnedBars.Add(rangeBar);
};
for (int i = 0; i < testValues.Count; i++)
{
var data = new IndicatorDataPoint(symbol, time.AddDays(i), testValues[i]);
consolidator.Update(data);
}
return returnedBars;
}
private static object[] ConsolidatorCreatesExpectedBarsTestCases = new object[]
{
new object[] { new List<decimal>(){ 90m, 94.5m }, new RangeBar[] {
new RangeBar{ Open = 90m, Low = 90m, High = 91m, Close = 91m, Volume = 10m, EndTime = new DateTime(2016, 1, 2)},
new RangeBar{ Open = 91.01m, Low = 91.01m, High = 92.01m, Close = 92.01m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar{ Open = 92.02m, Low = 92.02m, High = 93.02m, Close = 93.02m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar{ Open = 93.03m, Low = 93.03m, High = 94.03m, Close = 94.03m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
}},
new object[] { new List<decimal>(){ 94m, 89.5m }, new RangeBar[] {
new RangeBar { Open = 94m, Low = 93m, High = 94m, Close = 93m, Volume = 10m, EndTime = new DateTime(2016, 1, 2)},
new RangeBar { Open = 92.99m, Low = 91.99m, High = 92.99m, Close = 91.99m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 91.98m, Low = 90.98m, High = 91.98m, Close = 90.98m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 90.97m, Low = 89.97m, High = 90.97m, Close = 89.97m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) }
}},
new object[] { new List<decimal>{ 90m, 94.5m, 89.5m }, new RangeBar[] {
new RangeBar { Open = 90m, Low = 90m, High = 91m, Close = 91m, Volume = 10m , EndTime = new DateTime(2016, 1, 2)},
new RangeBar { Open = 91.01m, Low = 91.01m, High = 92.01m, Close = 92.01m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 92.02m, Low = 92.02m, High = 93.02m, Close = 93.02m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 93.03m, Low = 93.03m, High = 94.03m, Close = 94.03m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 94.04m, Low = 93.50m, High = 94.50m, Close = 93.50m, Volume = 10m, EndTime = new DateTime(2016, 1, 3)},
new RangeBar { Open = 93.49m, Low = 92.49m, High = 93.49m, Close = 92.49m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) },
new RangeBar { Open = 92.48m, Low = 91.48m, High = 92.48m, Close = 91.48m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) },
new RangeBar { Open = 91.47m, Low = 90.47m, High = 91.47m, Close = 90.47m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) }
}},
new object[] { new List<decimal>{ 94.5m, 89.5m, 94.5m }, new RangeBar[] {
new RangeBar { Open = 95m, Low = 94m, High = 95m, Close = 94m, Volume = 10m, EndTime = new DateTime(2016, 1, 2)},
new RangeBar { Open = 93.99m, Low = 92.99m, High = 93.99m, Close = 92.99m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 92.98m, Low = 91.98m, High = 92.98m, Close = 91.98m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 91.97m, Low = 90.97m, High = 91.97m, Close = 90.97m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 90.96m, Low = 89.96m, High = 90.96m, Close = 89.96m, Volume = 0m, EndTime = new DateTime(2016, 1, 2) },
new RangeBar { Open = 89.95m, Low = 89.50m, High = 90.50m, Close = 90.50m, Volume = 10m, EndTime = new DateTime(2016, 1, 3)},
new RangeBar { Open = 90.51m, Low = 90.51m, High = 91.51m, Close = 91.51m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) },
new RangeBar { Open = 91.52m, Low = 91.52m, High = 92.52m, Close = 92.52m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) },
new RangeBar { Open = 92.53m, Low = 92.53m, High = 93.53m, Close = 93.53m, Volume = 0m, EndTime = new DateTime(2016, 1, 3) },
}},
new object[] {new List<decimal> { 94m, 93.9m, 94.1m, 93.8m, 94.2m, 93.7m, 94.3m, 93.6m, 94.4m, 93.5m, 94.5m, 93.4m },
new RangeBar[]{ new RangeBar { Open = 94m, High = 94.5m, Low = 93.5m, Close = 93.5m, Volume = 110, EndTime = new DateTime(2016, 1, 12) } }},
new object[] {new List<decimal> { 94m, 94m, 94m, 94m, 94m, 95.1m },
new RangeBar[]{ new RangeBar { Open = 94m, High = 95m, Low = 94m, Close = 95m, Volume = 50, EndTime = new DateTime(2016, 1, 6) } }}
};
protected static object[] PriceGapBehaviorIsTheExpectedOneTestCases = new object[]
{
new object[] { Symbols.XAUUSD, 0.001, 1000},
new object[] { Symbols.XAGUSD, 0.00001, 100000},
new object[] { Symbols.DE30EUR, 0.1, 10},
new object[] { Symbols.XAUJPY, 1, 1}
};
protected virtual decimal[][] GetRangeConsolidatorExpectedValues()
{
return new decimal[][] {
new decimal[]{ 90m, 90m, 91m, 91m, 10m },
new decimal[]{ 91.01m, 91.01m, 92.01m, 92.01m, 0m },
new decimal[]{ 92.02m, 92.02m, 93.02m, 93.02m, 0m },
new decimal[]{ 93.03m, 93.03m, 94.03m, 94.03m, 0m },
new decimal[]{ 94.04m, 93.5m, 94.5m, 93.5m, 20m},
new decimal[]{ 93.49m, 92.49m, 93.49m, 92.49m, 0m},
new decimal[]{ 92.48m, 91.48m, 92.48m, 91.48m, 0m},
new decimal[]{ 91.47m, 90.47m, 91.47m, 90.47m, 0m},
new decimal[]{ 90.46m, 89.46m, 90.46m, 89.46m, 10m},
new decimal[]{ 89.45m, 89m, 90m, 90m, 10m},
new decimal[]{ 90.01m, 90m, 91m, 91m, 20m},
new decimal[]{ 91.01m, 90.5m, 91.5m, 90.5m, 10m},
new decimal[]{ 90.49m, 90m, 91m, 91m, 20m},
new decimal[]{ 91.01m, 91.01m, 92.01m, 92.01m, 0m }
};
}
protected override IDataConsolidator CreateConsolidator()
{
return new RangeConsolidator(100);
}
protected override void AssertConsolidator(IDataConsolidator consolidator)
{
base.AssertConsolidator(consolidator);
Assert.AreEqual(0, ((RangeConsolidator)consolidator).RangeSize);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var testValues = new List<decimal>() { 90m, 94.5m, 94m, 89.5m, 89m, 90.5m, 90m, 91.5m, 90m, 90.5m, 92.5m };
var time = new DateTime(2016, 1, 1);
return new List<IndicatorDataPoint>()
{
new IndicatorDataPoint(time, 90m),
new IndicatorDataPoint(time.AddSeconds(1), 94.5m),
new IndicatorDataPoint(time.AddSeconds(2), 94m),
new IndicatorDataPoint(time.AddSeconds(3), 89.5m),
new IndicatorDataPoint(time.AddSeconds(4), 89m),
new IndicatorDataPoint(time.AddSeconds(5), 90.5m),
new IndicatorDataPoint(time.AddSeconds(6), 90m),
new IndicatorDataPoint(time.AddSeconds(7), 91.5m),
new IndicatorDataPoint(time.AddSeconds(8), 90m),
new IndicatorDataPoint(time.AddSeconds(9), 90.5m),
new IndicatorDataPoint(time.AddSeconds(10), 92.5m),
new IndicatorDataPoint(time.AddSeconds(11), 94.5m),
new IndicatorDataPoint(time.AddSeconds(12), 94m),
new IndicatorDataPoint(time.AddSeconds(13), 89.5m),
new IndicatorDataPoint(time.AddSeconds(14), 89m),
};
}
}
}
+770
View File
@@ -0,0 +1,770 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class RenkoConsolidatorTests: BaseConsolidatorTests
{
[TestCase(0)]
[TestCase(-1)]
public void WickedRenkoConsolidatorFailsWhenBarSizeIsZero(double barSize)
{
var message = Assert.Throws<ArgumentException>( () => new RenkoConsolidator((decimal)barSize));
Assert.AreEqual("Renko consolidator BarSize must be strictly greater than zero", message.Message);
}
[Test]
public void WickedOutputTypeIsRenkoBar()
{
using var consolidator = new RenkoConsolidator(10.0m);
Assert.AreEqual(typeof(RenkoBar), consolidator.OutputType);
}
[Test]
public void WickedNoFallingRenko()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 9.1m));
Assert.AreEqual(renkos.Count, 0);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.0m);
Assert.AreEqual(openRenko.Low, 9.1m);
Assert.AreEqual(openRenko.Close, 9.1m);
}
[Test]
public void WickedNoRisingRenko()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 10.9m));
Assert.AreEqual(renkos.Count, 0);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.9m);
Assert.AreEqual(openRenko.Low, 10.0m);
Assert.AreEqual(openRenko.Close, 10.9m);
}
[Test]
public void WickedNoFallingRenkoKissLimit()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 9.0m));
Assert.AreEqual(renkos.Count, 0);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.0m);
Assert.AreEqual(openRenko.Low, 9.0m);
Assert.AreEqual(openRenko.Close, 9.0m);
}
[Test]
public void WickedNoRisingRenkoKissLimit()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 11.0m));
Assert.AreEqual(renkos.Count, 0);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 11.0m);
Assert.AreEqual(openRenko.Low, 10.0m);
Assert.AreEqual(openRenko.Close, 11.0m);
}
[Test]
public void WickedOneFallingRenko()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 8.9m));
Assert.AreEqual(renkos.Count, 1);
Assert.AreEqual(renkos[0].Open, 10.0m);
Assert.AreEqual(renkos[0].High, 10.0m);
Assert.AreEqual(renkos[0].Low, 9.0m);
Assert.AreEqual(renkos[0].Close, 9.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[0].Start, tickOn1);
Assert.AreEqual(renkos[0].EndTime, tickOn2);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Start, tickOn2);
Assert.AreEqual(openRenko.EndTime, tickOn2);
Assert.AreEqual(openRenko.Open, 9.0m);
Assert.AreEqual(openRenko.High, 9.0m);
Assert.AreEqual(openRenko.Low, 8.9m);
Assert.AreEqual(openRenko.Close, 8.9m);
}
[Test]
public void WickedOneRisingRenko()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 10.1m));
Assert.AreEqual(renkos.Count, 1);
Assert.AreEqual(renkos[0].Open, 9.0m);
Assert.AreEqual(renkos[0].High, 10.0m);
Assert.AreEqual(renkos[0].Low, 9.0m);
Assert.AreEqual(renkos[0].Close, 10.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[0].Start, tickOn1);
Assert.AreEqual(renkos[0].EndTime, tickOn2);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Start, tickOn2);
Assert.AreEqual(openRenko.EndTime, tickOn2);
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.1m);
Assert.AreEqual(openRenko.Low, 10.0m);
Assert.AreEqual(openRenko.Close, 10.1m);
}
[Test]
public void WickedTwoFallingThenOneRisingRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.9m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.1m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 7.8m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 7.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.1m));
Assert.AreEqual(renkos.Count, 3);
Assert.AreEqual(renkos[0].Open, 10.0m);
Assert.AreEqual(renkos[0].High, 10.5m);
Assert.AreEqual(renkos[0].Low, 9.0m);
Assert.AreEqual(renkos[0].Close, 9.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[1].Open, 9.0m);
Assert.AreEqual(renkos[1].High, 9.2m);
Assert.AreEqual(renkos[1].Low, 8.0m);
Assert.AreEqual(renkos[1].Close, 8.0m);
Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[1].Spread, 1.0m);
Assert.AreEqual(renkos[2].Open, 9.0m);
Assert.AreEqual(renkos[2].High, 10.0m);
Assert.AreEqual(renkos[2].Low, 7.6m);
Assert.AreEqual(renkos[2].Close, 10.0m);
Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[2].Spread, 1.0m);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.1m);
Assert.AreEqual(openRenko.Low, 10.0m);
Assert.AreEqual(openRenko.Close, 10.1m);
}
[Test]
public void WickedTwoRisingThenOneFallingRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.1m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.7m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.4m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.9m));
Assert.AreEqual(renkos.Count, 3);
Assert.AreEqual(renkos[0].Open, 10.0m);
Assert.AreEqual(renkos[0].High, 11.0m);
Assert.AreEqual(renkos[0].Low, 9.6m);
Assert.AreEqual(renkos[0].Close, 11.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[1].Open, 11.0m);
Assert.AreEqual(renkos[1].High, 12.0m);
Assert.AreEqual(renkos[1].Low, 10.7m);
Assert.AreEqual(renkos[1].Close, 12.0m);
Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[1].Spread, 1.0m);
Assert.AreEqual(renkos[2].Open, 11.0m);
Assert.AreEqual(renkos[2].High, 12.4m);
Assert.AreEqual(renkos[2].Low, 10.0m);
Assert.AreEqual(renkos[2].Close, 10.0m);
Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[2].Spread, 1.0m);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 10.0m);
Assert.AreEqual(openRenko.High, 10.0m);
Assert.AreEqual(openRenko.Low, 9.9m);
Assert.AreEqual(openRenko.Close, 9.9m);
}
[Test]
public void WickedThreeRisingGapRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 14.0m));
Assert.AreEqual(renkos.Count, 3);
Assert.AreEqual(renkos[0].Start, tickOn1);
Assert.AreEqual(renkos[0].EndTime, tickOn2);
Assert.AreEqual(renkos[0].Open, 10.0m);
Assert.AreEqual(renkos[0].High, 11.0m);
Assert.AreEqual(renkos[0].Low, 10.0m);
Assert.AreEqual(renkos[0].Close, 11.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[1].Start, tickOn2);
Assert.AreEqual(renkos[1].EndTime, tickOn2);
Assert.AreEqual(renkos[1].Open, 11.0m);
Assert.AreEqual(renkos[1].High, 12.0m);
Assert.AreEqual(renkos[1].Low, 11.0m);
Assert.AreEqual(renkos[1].Close, 12.0m);
Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[1].Spread, 1.0m);
Assert.AreEqual(renkos[2].Start, tickOn2);
Assert.AreEqual(renkos[2].EndTime, tickOn2);
Assert.AreEqual(renkos[2].Open, 12.0m);
Assert.AreEqual(renkos[2].High, 13.0m);
Assert.AreEqual(renkos[2].Low, 12.0m);
Assert.AreEqual(renkos[2].Close, 13.0m);
Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[2].Spread, 1.0m);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Start, tickOn2);
Assert.AreEqual(openRenko.EndTime, tickOn2);
Assert.AreEqual(openRenko.Open, 13.0m);
Assert.AreEqual(openRenko.High, 14.0m);
Assert.AreEqual(openRenko.Low, 13.0m);
Assert.AreEqual(openRenko.Close, 14.0m);
}
[Test]
public void WickedThreeFallingGapRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 14.0m));
consolidator.Update(new IndicatorDataPoint(tickOn2, 10.0m));
Assert.AreEqual(renkos.Count, 3);
Assert.AreEqual(renkos[0].Start, tickOn1);
Assert.AreEqual(renkos[0].EndTime, tickOn2);
Assert.AreEqual(renkos[0].Open, 14.0m);
Assert.AreEqual(renkos[0].High, 14.0m);
Assert.AreEqual(renkos[0].Low, 13.0m);
Assert.AreEqual(renkos[0].Close, 13.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[1].Start, tickOn2);
Assert.AreEqual(renkos[1].EndTime, tickOn2);
Assert.AreEqual(renkos[1].Open, 13.0m);
Assert.AreEqual(renkos[1].High, 13.0m);
Assert.AreEqual(renkos[1].Low, 12.0m);
Assert.AreEqual(renkos[1].Close, 12.0m);
Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[1].Spread, 1.0);
Assert.AreEqual(renkos[2].Start, tickOn2);
Assert.AreEqual(renkos[2].EndTime, tickOn2);
Assert.AreEqual(renkos[2].Open, 12.0m);
Assert.AreEqual(renkos[2].High, 12.0m);
Assert.AreEqual(renkos[2].Low, 11.0m);
Assert.AreEqual(renkos[2].Close, 11.0m);
Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[2].Spread, 1.0m);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 11.0m);
Assert.AreEqual(openRenko.High, 11.0m);
Assert.AreEqual(openRenko.Low, 10.0m);
Assert.AreEqual(openRenko.Close, 10.0m);
}
[Test]
public void WickedTwoFallingThenThreeRisingGapRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.9m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.1m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 7.8m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 7.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.1m));
Assert.AreEqual(renkos.Count, 5);
Assert.AreEqual(renkos[0].Open, 10.0m);
Assert.AreEqual(renkos[0].High, 10.5m);
Assert.AreEqual(renkos[0].Low, 9.0m);
Assert.AreEqual(renkos[0].Close, 9.0m);
Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[0].Spread, 1.0m);
Assert.AreEqual(renkos[1].Open, 9.0m);
Assert.AreEqual(renkos[1].High, 9.2m);
Assert.AreEqual(renkos[1].Low, 8.0m);
Assert.AreEqual(renkos[1].Close, 8.0m);
Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[1].Spread, 1.0m);
Assert.AreEqual(renkos[2].Open, 9.0m);
Assert.AreEqual(renkos[2].High, 10.0m);
Assert.AreEqual(renkos[2].Low, 7.6m);
Assert.AreEqual(renkos[2].Close, 10.0m);
Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[2].Spread, 1.0m);
Assert.AreEqual(renkos[3].Open, 10.0m);
Assert.AreEqual(renkos[3].High, 11.0m);
Assert.AreEqual(renkos[3].Low, 10.0m);
Assert.AreEqual(renkos[3].Close, 11.0m);
Assert.AreEqual(renkos[3].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[3].Spread, 1.0m);
Assert.AreEqual(renkos[4].Open, 11.0m);
Assert.AreEqual(renkos[4].High, 12.0m);
Assert.AreEqual(renkos[4].Low, 11.0m);
Assert.AreEqual(renkos[4].Close, 12.0m);
Assert.AreEqual(renkos[4].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[4].Spread, 1.0m);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 12.0m);
Assert.AreEqual(openRenko.High, 12.1m);
Assert.AreEqual(openRenko.Low, 12.0m);
Assert.AreEqual(openRenko.Close, 12.1m);
}
[Test]
public void WickedTwoRisingThenThreeFallingGapRenkos()
{
using var consolidator = new TestRenkoConsolidator(1.0m);
var renkos = new List<RenkoBar>();
consolidator.DataConsolidated += (sender, renko) =>
renkos.Add(renko);
var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 9.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.1m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.0m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 10.7m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.6m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 12.4m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 11.5m));
consolidator.Update(new IndicatorDataPoint(tickOn1, 7.9m));
Assert.AreEqual(renkos.Count, 5);
Assert.AreEqual(renkos[0].Open, 10.0);
Assert.AreEqual(renkos[0].High, 11.0);
Assert.AreEqual(renkos[0].Low, 9.6);
Assert.AreEqual(renkos[0].Close, 11.0);
Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[0].Spread, 1.0);
Assert.AreEqual(renkos[1].Open, 11.0);
Assert.AreEqual(renkos[1].High, 12.0);
Assert.AreEqual(renkos[1].Low, 10.7);
Assert.AreEqual(renkos[1].Close, 12.0);
Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
Assert.AreEqual(renkos[1].Spread, 1.0);
Assert.AreEqual(renkos[2].Open, 11.0);
Assert.AreEqual(renkos[2].High, 12.4);
Assert.AreEqual(renkos[2].Low, 10.0);
Assert.AreEqual(renkos[2].Close, 10.0);
Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[2].Spread, 1.0);
Assert.AreEqual(renkos[3].Open, 10.0);
Assert.AreEqual(renkos[3].High, 10.0);
Assert.AreEqual(renkos[3].Low, 9.0);
Assert.AreEqual(renkos[3].Close, 9.0);
Assert.AreEqual(renkos[3].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[3].Spread, 1.0);
Assert.AreEqual(renkos[4].Open, 9.0);
Assert.AreEqual(renkos[4].High, 9.0);
Assert.AreEqual(renkos[4].Low, 8.0);
Assert.AreEqual(renkos[4].Close, 8.0);
Assert.AreEqual(renkos[4].Direction, BarDirection.Falling);
Assert.AreEqual(renkos[4].Spread, 1.0);
var openRenko = consolidator.OpenRenko();
Assert.AreEqual(openRenko.Open, 8.0);
Assert.AreEqual(openRenko.High, 8.0);
Assert.AreEqual(openRenko.Low, 7.9);
Assert.AreEqual(openRenko.Close, 7.9);
}
[TestCase(new double[] {1.38687, 1.38688, 1.38687, 1.38686, 1.38685, 1.38683,
1.38682, 1.38682, 1.38684, 1.38682, 1.38682, 1.38680,
1.38681, 1.38686, 1.38688, 1.38688, 1.38690, 1.38690,
1.38691, 1.38692, 1.38694, 1.38695, 1.38697, 1.38697,
1.38700, 1.38699, 1.38699, 1.38699, 1.38698, 1.38699,
1.38697, 1.38698, 1.38698, 1.38697, 1.38698, 1.38698,
1.38697, 1.38697, 1.38700, 1.38702, 1.38701, 1.38699,
1.38697, 1.38698, 1.38696, 1.38698, 1.38697, 1.38695,
1.38695, 1.38696, 1.38693, 1.38692, 1.38693, 1.38693,
1.38692, 1.38693, 1.38692, 1.38690, 1.38686, 1.38685,
1.38687, 1.38686, 1.38686, 1.38686, 1.38686, 1.38685,
1.38684, 1.38678, 1.38679, 1.38680, 1.38680, 1.38681,
1.38685, 1.38685, 1.38683, 1.38682, 1.38682, 1.38683,
1.38682, 1.38683, 1.38682, 1.38681, 1.38680, 1.38681,
1.38681, 1.38681, 1.38682, 1.38680, 1.38679, 1.38678,
1.38675, 1.38678, 1.38678, 1.38678, 1.38682, 1.38681,
1.38682, 1.38680, 1.38682, 1.38683, 1.38685, 1.38683,
1.38683, 1.38684, 1.38683, 1.38683, 1.38684, 1.38685,
1.38684, 1.38683, 1.38686, 1.38685, 1.38685, 1.38684,
1.38685, 1.38682, 1.38684, 1.38683, 1.38682, 1.38683,
1.38685, 1.38685, 1.38685, 1.38683, 1.38685, 1.38684,
1.38686, 1.38693, 1.38695, 1.38693, 1.38694, 1.38693,
1.38692, 1.38693, 1.38695, 1.38697, 1.38698, 1.38695,
1.38696}, 0.0001)]
[TestCase(new double[] {90.38687, 12.38688, 33.38687, 69.38686, 22.38685, 19.38683,
19.38682, 51.38682, 12.38684, 41.38682, 47.38682, 30.38680,
81.38681, 16.38686, 21.38688, 14.38688, 89.38690, 72.38690,
71.38691, 71.38692, 3.38694, 71.38695, 50.38697, 97.38697,
16.38700, 18.38699, 14.38699, 91.38699, 60.38698, 35.38699,
51.38697, 91.38698, 41.38698, 21.38697, 44.38698, 35.38698,
14.38697, 10.38697, 5.38700, 1.38702, 1.38701, 15.38699,
31.38697, 11.38698, 16.38696, 21.38698, 16.38697, 19.38695,
12.38695, 21.38696, 61.38693, 32.38692, 20.38693, 23.38693,
11.38692, 13.38693, 7.38692, 16.38690, 30.38686, 34.38685,
91.38687, 41.38686, 18.38686, 12.38686, 40.38686, 44.38685,
18.38684, 15.38678, 81.38679, 19.38680, 32.38680, 37.38681,
71.38685, 61.38685, 9.38683, 21.38682, 27.38682, 28.38683,
16.38682, 17.38683, 0.38682, 81.38681, 60.38680, 65.38681,
51.38681, 81.38681, 11.38682, 81.38680, 60.38679, 65.38678,
41.38675, 19.38678, 11.38678, 51.38678, 25.38682, 30.38681,
13.38682, 1.38680, 2.38682, 51.38683, 47.38685, 55.38683,
21.38683, 11.38684, 13.38683, 81.38683, 70.38684, 75.38685,
11.38684, 21.38683, 31.38686, 91.38685, 87.38685, 92.38684,
10.38685, 13.38682, 4.38684, 21.38683, 29.38682, 34.38683,
19.38685, 41.38685, 51.38685, 12.38683, 28.38685, 34.38684,
81.38686, 15.38693, 15.38695, 1.38693, 8.38694, 13.38693,
17.38692, 61.38693, 6.38695, 13.38697, 4.38698, 9.38695,
61.38696}, 5)]
public void ConsistentRenkos(double[] values, double barSize)
{
// Reproduce issue #5479
// Test Renko bar consistency amongst three consolidators starting at different times
var time = new DateTime(2016, 1, 1);
var testValues = new List<decimal> (values.Select(x => (decimal)x));
var consolidator1 = new RenkoConsolidator((decimal)barSize);
var consolidator2 = new RenkoConsolidator((decimal)barSize);
var consolidator3 = new RenkoConsolidator((decimal)barSize);
// Update each of our consolidators starting at different indexes of test values
for (int i = 0; i < testValues.Count; i++)
{
var data = new IndicatorDataPoint(time.AddSeconds(i), testValues[i]);
consolidator1.Update(data);
if (i > 10)
{
consolidator2.Update(data);
}
if (i > 20)
{
consolidator3.Update(data);
}
}
// Assert that consolidator 2 and 3 price is the same as 1. Even though they started at different
// indexes they should be the same
var bar1 = consolidator1.Consolidated as RenkoBar;
var bar2 = consolidator2.Consolidated as RenkoBar;
var bar3 = consolidator3.Consolidated as RenkoBar;
Assert.AreEqual(bar1.Close, bar2.Close);
Assert.AreEqual(bar1.Close, bar3.Close);
consolidator1.Dispose();
consolidator2.Dispose();
consolidator3.Dispose();
}
[TestCase(12.38684, 0.0001, 12.3868)]
[TestCase(12.38686, 0.0001, 12.3869)]
[TestCase(3.38694, 0.001, 3.387)]
[TestCase(3.38644, 0.001, 3.386)]
[TestCase(41.38698, 0.01, 41.39)]
[TestCase(41.38498, 0.01, 41.38)]
[TestCase(16.38696, 0.1, 16.4)]
[TestCase(16.32696, 0.1, 16.3)]
[TestCase(7.38692, 1, 7)]
[TestCase(7.78692, 1, 8)]
[TestCase(81.38679, 10, 80)]
[TestCase(88.38679, 10, 90)]
[TestCase(1247.38682, 100, 1200)]
[TestCase(1257.38682, 100, 1300)]
[TestCase(44500.2349, 1000, 45000)]
[TestCase(44300.2349, 1000, 44000)]
public void GetClosestMultipleWorksAsExpected(double price, double barSize, double expectedClosestMultiple)
{
Assert.AreEqual((decimal)expectedClosestMultiple, RenkoConsolidator.GetClosestMultiple((decimal)price, (decimal)barSize));
}
[TestCase(0)]
[TestCase(-1)]
public void GetClosestMultipleFailsWhenBarSizeIsLessThanZero(double barSize)
{
var message = Assert.Throws<ArgumentException>(() => RenkoConsolidator.GetClosestMultiple((decimal)34.78989, (decimal)barSize));
Assert.AreEqual("BarSize must be strictly greater than zero", message.Message);
}
protected override IDataConsolidator CreateConsolidator()
{
return new TestRenkoConsolidator(1m);
}
protected override void AssertConsolidator(IDataConsolidator consolidator)
{
base.AssertConsolidator(consolidator);
var renkoConsolidator = consolidator as TestRenkoConsolidator;
var renkoBar = renkoConsolidator.OpenRenko();
Assert.AreEqual(0, renkoBar.Open);
Assert.AreEqual(0, renkoBar.Close);
Assert.AreEqual(0, renkoBar.High);
Assert.AreEqual(0, renkoBar.Low);
Assert.AreEqual(default(DateTime), renkoBar.Start);
Assert.AreEqual(default(DateTime), renkoBar.End);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2016, 3, 1);
return new List<IndicatorDataPoint>()
{
new IndicatorDataPoint(time, 10.0m),
new IndicatorDataPoint(time.AddSeconds(1), 9.6m),
new IndicatorDataPoint(time.AddSeconds(2), 10.5m),
new IndicatorDataPoint(time.AddSeconds(3), 11.1m),
new IndicatorDataPoint(time.AddSeconds(4), 11.0m),
new IndicatorDataPoint(time.AddSeconds(5), 10.7m),
new IndicatorDataPoint(time.AddSeconds(6), 11.6m),
new IndicatorDataPoint(time.AddSeconds(7), 12.3m),
new IndicatorDataPoint(time.AddSeconds(8), 12.3m),
new IndicatorDataPoint(time.AddSeconds(9), 12.4m),
new IndicatorDataPoint(time.AddSeconds(10), 11.5m),
new IndicatorDataPoint(time.AddSeconds(11), 7.9m),
new IndicatorDataPoint(time.AddSeconds(12), 7.9m)
};
}
private class TestRenkoConsolidator : RenkoConsolidator
{
public TestRenkoConsolidator(decimal barSize)
: base(barSize)
{
}
public RenkoBar OpenRenko()
{
return new RenkoBar(null, OpenOn, CloseOn, BarSize, OpenRate, HighRate, LowRate, CloseRate);
}
}
}
}
@@ -0,0 +1,109 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class SequentialConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void SequentialConsolidatorsFiresAllEvents()
{
using var first = new IdentityDataConsolidator<IBaseData>();
using var second = new IdentityDataConsolidator<IBaseData>();
using var sequential = new SequentialConsolidator(first, second);
bool firstFired = false;
bool secondFired = false;
bool sequentialFired = false;
first.DataConsolidated += (sender, consolidated) =>
{
firstFired = true;
};
second.DataConsolidated += (sender, consolidated) =>
{
secondFired = true;
};
sequential.DataConsolidated += (sender, consolidated) =>
{
sequentialFired = true;
};
sequential.Update(new TradeBar());
Assert.IsTrue(firstFired);
Assert.IsTrue(secondFired);
Assert.IsTrue(sequentialFired);
}
[Test]
public void SequentialConsolidatorAcceptsSubTypesForSecondInputType()
{
using var first = new IdentityDataConsolidator<TradeBar>();
using var second = new IdentityDataConsolidator<IBaseData>();
using var sequential = new SequentialConsolidator(first, second);
bool firstFired = false;
bool secondFired = false;
bool sequentialFired = false;
first.DataConsolidated += (sender, consolidated) =>
{
firstFired = true;
};
second.DataConsolidated += (sender, consolidated) =>
{
secondFired = true;
};
sequential.DataConsolidated += (sender, consolidated) =>
{
sequentialFired = true;
};
sequential.Update(new TradeBar());
Assert.IsTrue(firstFired);
Assert.IsTrue(secondFired);
Assert.IsTrue(sequentialFired);
}
protected override IDataConsolidator CreateConsolidator()
{
var first = new IdentityDataConsolidator<IndicatorDataPoint>();
var second = new IdentityDataConsolidator<IndicatorDataPoint>();
return new SequentialConsolidator(first, second);
}
protected override void AssertConsolidator(IDataConsolidator consolidator)
{
base.AssertConsolidator(consolidator);
var sequentialConsolidator = consolidator as SequentialConsolidator;
Assert.IsNull(sequentialConsolidator.First.Consolidated);
Assert.IsNull(sequentialConsolidator.Second.Consolidated);
}
}
}
@@ -0,0 +1,306 @@
/*
* 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 Common.Data.Consolidators;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class SessionConsolidatorTests
{
[Test]
public void CalculatesOHLCVRespectingMarketHours()
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(TickType.Trade);
var date = new DateTime(2025, 8, 25);
var tradeBar1 = new TradeBar(date.AddHours(12), symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1));
var tradeBar2 = new TradeBar(date.AddHours(13), symbol, 101, 102, 100, 101.5m, 1100, TimeSpan.FromHours(1));
var tradeBar3 = new TradeBar(date.AddHours(14), symbol, 102, 103, 101, 102.5m, 1200, TimeSpan.FromHours(1));
consolidator.Update(tradeBar1);
consolidator.Update(tradeBar2);
consolidator.Update(tradeBar3);
var eventTime = new DateTime(2025, 8, 26, 0, 0, 0);
// This should fire the scan, because is the end of the day
consolidator.ValidateAndScan(eventTime);
Assert.IsNotNull(consolidator.Consolidated);
var consolidated = (SessionBar)consolidator.Consolidated;
Assert.AreEqual(100, consolidated.Open);
Assert.AreEqual(103, consolidated.High);
Assert.AreEqual(99, consolidated.Low);
Assert.AreEqual(102.5, consolidated.Close);
Assert.AreEqual(3300, consolidated.Volume);
}
[Test]
public void TracksOpenInterestFromOpenInterestTicks()
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(TickType.Quote);
var date = new DateTime(2025, 8, 25);
var openInterest = new Tick(date.AddHours(12), symbol, 5);
var tick1 = new Tick(date.AddHours(12), symbol, 100, 101);
var tick2 = new Tick(date.AddHours(13), symbol, 101, 102);
var tick3 = new Tick(date.AddHours(14), symbol, 102, 103);
consolidator.Update(openInterest);
consolidator.Update(tick1);
consolidator.Update(tick2);
consolidator.Update(tick3);
var workingData = (SessionBar)consolidator.WorkingData;
Assert.AreEqual(5, workingData.OpenInterest);
Assert.AreEqual(0, workingData.Volume);
Assert.AreEqual(100.5, workingData.Open);
Assert.AreEqual(102.5, workingData.High);
Assert.AreEqual(100.5, workingData.Low);
Assert.AreEqual(102.5, workingData.Close);
}
[Test]
public void AccumulatesVolumeFromTradeBarsAndTradeTicksCorrectly()
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(TickType.Quote);
var date = new DateTime(2025, 8, 25);
// QuoteBars will be processed normally
var quoteBar1 = new QuoteBar(date.AddHours(11), symbol, new Bar(100, 101, 100, 101), 0, new Bar(101, 102, 100, 101), 0);
var quoteBar2 = new QuoteBar(date.AddHours(12), symbol, new Bar(100, 101, 100, 101), 0, new Bar(101, 102, 100, 101), 0);
consolidator.Update(quoteBar1);
consolidator.Update(quoteBar2);
// We will handle the volume manually for trade bars and ticks(trade)
// We will take the volume (1000) from the trade bar
var tradeBar = new TradeBar(date.AddHours(13), symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1));
consolidator.Update(tradeBar);
// We will take the quantity (500) from the tick
var tick1 = new Tick(date.AddHours(14), symbol, "", "", 500, 5);
consolidator.Update(tick1);
var workingData = (SessionBar)consolidator.WorkingData;
Assert.AreEqual(1500, workingData.Volume);
Assert.AreEqual(100.5, workingData.Open);
Assert.AreEqual(101.5, workingData.High);
Assert.AreEqual(100, workingData.Low);
Assert.AreEqual(101, workingData.Close);
}
[Test]
public void AccumulatesVolumeCorrectlyAfterReset()
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(TickType.Quote);
var date = new DateTime(2025, 8, 25, 0, 0, 0);
// Resolution = Hour, accumulates normally
var tradeBar1 = new TradeBar(date.AddHours(12), symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1));
var tradeBar2 = new TradeBar(date.AddHours(13), symbol, 101, 102, 100, 101.5m, 1100, TimeSpan.FromHours(1));
consolidator.Update(tradeBar1);
consolidator.Update(tradeBar2);
Assert.AreEqual(2100, ((SessionBar)consolidator.WorkingData).Volume);
consolidator.Reset();
tradeBar1 = new TradeBar(date.AddHours(12), symbol, 100, 101, 99, 100.5m, 2000, TimeSpan.FromMinutes(1));
tradeBar2 = new TradeBar(date.AddHours(12).AddMinutes(1), symbol, 101, 102, 100, 101.5m, 3000, TimeSpan.FromMinutes(1));
consolidator.Update(tradeBar1);
consolidator.Update(tradeBar2);
Assert.AreEqual(5000, ((SessionBar)consolidator.WorkingData).Volume);
}
[Test]
public void PreservesSymbolAfterConsolidation()
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(TickType.Trade);
var date = new DateTime(2025, 8, 25);
var tradeBar = new TradeBar(date.AddHours(12), symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1));
consolidator.Update(tradeBar);
Assert.AreEqual(symbol, consolidator.WorkingData.Symbol);
var eventTime = new DateTime(2025, 8, 26, 0, 0, 0);
// This should fire the scan, because is the end of the day
consolidator.ValidateAndScan(eventTime);
Assert.AreEqual(symbol, consolidator.Consolidated.Symbol);
}
[TestCase(TickType.Trade, Resolution.Tick, Resolution.Second)]
[TestCase(TickType.Trade, Resolution.Tick, Resolution.Minute)]
[TestCase(TickType.Trade, Resolution.Tick, Resolution.Hour)]
[TestCase(TickType.Trade, Resolution.Second, Resolution.Minute)]
[TestCase(TickType.Trade, Resolution.Second, Resolution.Hour)]
[TestCase(TickType.Trade, Resolution.Minute, Resolution.Hour)]
[TestCase(TickType.Quote, Resolution.Tick, Resolution.Second)]
[TestCase(TickType.Quote, Resolution.Tick, Resolution.Minute)]
[TestCase(TickType.Quote, Resolution.Tick, Resolution.Hour)]
[TestCase(TickType.Quote, Resolution.Second, Resolution.Minute)]
[TestCase(TickType.Quote, Resolution.Second, Resolution.Hour)]
[TestCase(TickType.Quote, Resolution.Minute, Resolution.Hour)]
public void IgnoresOverlappingHigherResolutionData(TickType tickType, Resolution firstResolution, Resolution secondResolution)
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(tickType);
var currentTime = new DateTime(2025, 8, 25, 11, 0, 0);
var dataDictionary = new Dictionary<(TickType, Resolution), BaseData>
{
{ (TickType.Trade, Resolution.Tick), new Tick(currentTime, symbol, "", "", 600, 15) },
{ (TickType.Quote, Resolution.Tick), new Tick(currentTime, symbol, 100, 101) },
{ (TickType.Trade, Resolution.Second), new TradeBar(currentTime, symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromSeconds(1)) },
{ (TickType.Quote, Resolution.Second), new QuoteBar(currentTime, symbol, new Bar(300, 301, 300, 301), 0, new Bar(300, 301, 300, 301), 0, TimeSpan.FromSeconds(1)) },
{ (TickType.Trade, Resolution.Minute), new TradeBar(currentTime, symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromMinutes(1)) },
{ (TickType.Quote, Resolution.Minute), new QuoteBar(currentTime, symbol, new Bar(300, 301, 300, 301), 0, new Bar(300, 301, 300, 301), 0, TimeSpan.FromMinutes(1)) },
{ (TickType.Trade, Resolution.Hour), new TradeBar(currentTime, symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1)) },
{ (TickType.Quote, Resolution.Hour), new QuoteBar(currentTime, symbol, new Bar(300, 301, 300, 301), 0, new Bar(300, 301, 300, 301), 0, TimeSpan.FromHours(1)) }
};
// First update with lower-resolution data (should be accepted)
var firstData = dataDictionary[(tickType, firstResolution)];
firstData.Time = currentTime.AddTicks(1);
consolidator.Update(firstData);
var workingData = (SessionBar)consolidator.WorkingData;
var currentTimeAfterFirstUpdate = workingData.Time;
// Second update with higher-resolution overlapping data (should be ignored)
var secondData = dataDictionary[(tickType, secondResolution)];
consolidator.Update(secondData);
workingData = (SessionBar)consolidator.WorkingData;
var currentTimeAfterSecondUpdate = workingData.Time;
// Verify that the higher-resolution update did not overwrite the current session state
Assert.AreEqual(currentTimeAfterFirstUpdate, currentTimeAfterSecondUpdate);
}
[TestCase(TickType.Trade, true)]
[TestCase(TickType.Trade, false)]
[TestCase(TickType.Quote, true)]
[TestCase(TickType.Quote, false)]
public void ConsolidateUsingBars(TickType tickType, bool isTick)
{
var symbol = Symbols.SPY;
using var consolidator = GetConsolidator(tickType);
var date = new DateTime(2025, 8, 25, 9, 0, 0);
var tradeBars = new List<TradeBar>
{
new TradeBar(date, symbol, 100, 101, 99, 100.5m, 1000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(1), symbol, 200, 201, 199, 200.5m, 2000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(2), symbol, 300, 301, 299, 300.5m, 3000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(3), symbol, 400, 401, 399, 400.5m, 4000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(4), symbol, 500, 501, 499, 500.5m, 5000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(5), symbol, 600, 601, 599, 600.5m, 6000, TimeSpan.FromHours(1)),
new TradeBar(date.AddHours(6), symbol, 700, 701, 699, 700.5m, 7000, TimeSpan.FromHours(1))
};
var tradeTicks = new List<Tick>
{
new Tick(date.AddHours(1), symbol, "", "", 600, 15),
new Tick(date.AddHours(2), symbol, "", "", 700, 25),
new Tick(date.AddHours(3), symbol, "", "", 800, 35),
new Tick(date.AddHours(4), symbol, "", "", 900, 45),
new Tick(date.AddHours(5), symbol, "", "", 1000, 55),
new Tick(date.AddHours(6), symbol, "", "", 1100, 65),
new Tick(date.AddHours(7), symbol, "", "", 1200, 75)
};
var quoteBars = new List<QuoteBar>
{
new QuoteBar(date, symbol, new Bar(100, 101, 100, 101), 0, new Bar(100, 101, 100, 101), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(1), symbol, new Bar(200, 201, 200, 201), 0, new Bar(200, 201, 200, 201), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(2), symbol, new Bar(300, 301, 300, 301), 0, new Bar(300, 301, 300, 301), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(3), symbol, new Bar(400, 401, 400, 401), 0, new Bar(400, 401, 400, 401), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(4), symbol, new Bar(500, 501, 500, 501), 0, new Bar(500, 501, 500, 501), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(5), symbol, new Bar(600, 601, 600, 601), 0, new Bar(600, 601, 600, 601), 0, TimeSpan.FromHours(1)),
new QuoteBar(date.AddHours(6), symbol, new Bar(700, 701, 700, 701), 0, new Bar(700, 701, 700, 701), 0, TimeSpan.FromHours(1))
};
var quoteTicks = new List<Tick>
{
new Tick(date.AddHours(1), symbol, 100, 101),
new Tick(date.AddHours(2), symbol, 200, 201),
new Tick(date.AddHours(3), symbol, 300, 301),
new Tick(date.AddHours(4), symbol, 400, 401),
new Tick(date.AddHours(5), symbol, 500, 501),
new Tick(date.AddHours(6), symbol, 600, 601),
new Tick(date.AddHours(7), symbol, 700, 701)
};
var dataToUpdate = tickType == TickType.Trade
? (isTick ? tradeTicks.Cast<BaseData>() : tradeBars.Cast<BaseData>())
: (isTick ? quoteTicks.Cast<BaseData>() : quoteBars.Cast<BaseData>());
foreach (var data in dataToUpdate)
{
consolidator.Update(data);
}
var eventTime = new DateTime(2025, 8, 26, 0, 0, 0);
// This should fire the scan, because is the end of the day
consolidator.ValidateAndScan(eventTime);
Assert.IsNotNull(consolidator.Consolidated);
var consolidated = (SessionBar)consolidator.Consolidated;
var (expectedOpen, expectedHigh, expectedLow, expectedClose, expectedVolume) =
(tickType, isTick) switch
{
(TickType.Trade, true) => (15m, 65m, 15m, 65m, 5100L),
(TickType.Trade, false) => (100m, 701m, 99m, 700.5m, 28000L),
(TickType.Quote, true) => (100.5m, 600.5m, 100.5m, 600.5m, 0L),
(TickType.Quote, false) => (100m, 701m, 100m, 701m, 0L),
_ => throw new NotImplementedException()
};
Assert.AreEqual(expectedOpen, consolidated.Open);
Assert.AreEqual(expectedHigh, consolidated.High);
Assert.AreEqual(expectedLow, consolidated.Low);
Assert.AreEqual(expectedClose, consolidated.Close);
Assert.AreEqual(expectedVolume, consolidated.Volume);
}
private static SessionConsolidator GetConsolidator(TickType tickType)
{
var symbol = Symbols.SPY;
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
return new SessionConsolidator(exchangeHours, tickType, symbol);
}
}
}
@@ -0,0 +1,125 @@
/*
* 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.Configuration;
using QuantConnect.Data.Shortable;
namespace QuantConnect.Tests.Common.Data.Shortable
{
[TestFixture]
public class ShortableProviderTests
{
private readonly Dictionary<string, Dictionary<Symbol, ShortableData>[]> _resultsByBrokerage = new();
private Symbol[] _symbols;
[SetUp]
public void SetupConfig()
{
Config.Set("data-folder", "TestData");
Globals.Reset();
_symbols = new[] { "AAPL", "GOOG", "BAC" }
.Select(x => new Symbol(SecurityIdentifier.GenerateEquity(x, QuantConnect.Market.USA, mappingResolveDate: new DateTime(2021, 1, 4)), x))
.ToArray();
_resultsByBrokerage["testinteractivebrokers"] = new[]
{
new Dictionary<Symbol, ShortableData>
{
{ _symbols[0], new(2000, 0.0507m, 0.0025m) },
{ _symbols[1], new(5000, 0.0517m, 0.0035m) },
{ _symbols[2], new(null, 0, 0) } // we have no data for this symbol
},
new Dictionary<Symbol, ShortableData>
{
{ _symbols[0], new(4000, 0.0509m, 0.003m) },
{ _symbols[1], new(10000, 0.0519m, 0.004m) },
{ _symbols[2], new(null, 0, 0) } // we have no data for this symbol
}
};
_resultsByBrokerage["testbrokerage"] = new[]
{
new Dictionary<Symbol, ShortableData>
{
{ _symbols[0], new(2000, 0, 0) },
{ _symbols[1], new(5000, 0, 0) },
{ _symbols[2], new(null, 0, 0) } // we have no data for this symbol
},
new Dictionary<Symbol, ShortableData>
{
{ _symbols[0], new(4000, 0, 0) },
{ _symbols[1], new(10000, 0, 0) },
{ _symbols[2], new(null, 0, 0) } // we have no data for this symbol
}
};
}
[TearDown]
public void ResetConfig()
{
Config.Reset();
Globals.Reset();
}
[TestCase("testbrokerage")]
[TestCase("testinteractivebrokers")]
public void LocalDiskShortableProviderGetsDataBySymbol(string brokerage)
{
var shortableProvider = new LocalDiskShortableProvider(brokerage);
var results = _resultsByBrokerage[brokerage];
var dates = new[]
{
new DateTime(2020, 12, 21),
new DateTime(2020, 12, 22)
};
foreach (var symbol in _symbols)
{
for (var i = 0; i < dates.Length; i++)
{
var date = dates[i];
var shortableQuantity = shortableProvider.ShortableQuantity(symbol, date);
var rebateRate = shortableProvider.RebateRate(symbol, date);
var feeRate = shortableProvider.FeeRate(symbol, date);
Assert.AreEqual(results[i][symbol].ShortableQuantity, shortableQuantity);
Assert.AreEqual(results[i][symbol].RebateRate, rebateRate);
Assert.AreEqual(results[i][symbol].FeeRate, feeRate);
}
}
}
[TestCase("AAPL", "nobrokerage")]
[TestCase("SPY", "testbrokerage")]
public void LocalDiskShortableProviderDefaultsToNullForMissingData(string ticker, string brokerage)
{
var provider = new LocalDiskShortableProvider(brokerage);
var date = new DateTime(2020, 12, 21);
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(ticker, QuantConnect.Market.USA, mappingResolveDate: date), ticker);
Assert.IsFalse(provider.ShortableQuantity(symbol, date).HasValue);
Assert.AreEqual(0, provider.RebateRate(symbol, date));
Assert.AreEqual(0, provider.FeeRate(symbol, date));
}
private record ShortableData(long? ShortableQuantity, decimal RebateRate, decimal FeeRate);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,93 @@
/*
* 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 NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data;
using System;
using System.Collections.Generic;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class SubscriptionDataSourceTests
{
[Test]
public void ComparesEqualWithIdenticalSourceAndTransportMedium()
{
var one = new SubscriptionDataSource("source", SubscriptionTransportMedium.LocalFile);
var two = new SubscriptionDataSource("source", SubscriptionTransportMedium.LocalFile);
Assert.IsTrue(one == two);
Assert.IsTrue(one.Equals(two));
}
[Test]
public void ComparesNotEqualWithDifferentSource()
{
var one = new SubscriptionDataSource("source1", SubscriptionTransportMedium.LocalFile);
var two = new SubscriptionDataSource("source2", SubscriptionTransportMedium.LocalFile);
Assert.IsTrue(one != two);
Assert.IsTrue(!one.Equals(two));
}
[Test]
public void ComparesNotEqualWithDifferentTransportMedium()
{
var one = new SubscriptionDataSource("source", SubscriptionTransportMedium.LocalFile);
var two = new SubscriptionDataSource("source", SubscriptionTransportMedium.RemoteFile);
Assert.IsTrue(one != two);
Assert.IsTrue(!one.Equals(two));
}
[Test]
public void SupportsPythonDictionaryHeaders()
{
using (Py.GIL())
{
using var headers = new PyDict();
headers.SetItem("Authorization".ToPython(), "Basic test-token".ToPython());
headers.SetItem("X-Api-Key".ToPython(), "abc123".ToPython());
var dataSource = new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, headers);
CollectionAssert.AreEquivalent(new[]
{
new KeyValuePair<string, string>("Authorization", "Basic test-token"),
new KeyValuePair<string, string>("X-Api-Key", "abc123")
}, dataSource.Headers);
}
}
[Test]
public void SupportsNullPythonDictionaryHeaders()
{
var dataSource = new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, (PyObject)null);
Assert.IsEmpty(dataSource.Headers);
}
[Test]
public void ThrowsForInvalidPythonHeadersType()
{
using (Py.GIL())
{
using var invalidHeaders = "invalid-headers".ToPython();
var exception = Assert.Throws<ArgumentException>(() =>
new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, invalidHeaders));
StringAssert.Contains("ConvertToDictionary cannot be used", exception.Message);
}
}
}
}
@@ -0,0 +1,765 @@
/*
* 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 System.Threading;
using System.Threading.Tasks;
using NodaTime;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Logging;
using QuantConnect.Statistics;
using QuantConnect.Tests.Engine.DataFeeds;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class SubscriptionManagerTests
{
[TestCase(SecurityType.Forex, Resolution.Daily, 1, TickType.Quote)]
[TestCase(SecurityType.Forex, Resolution.Hour, 1, TickType.Quote)]
[TestCase(SecurityType.Cfd, Resolution.Daily, 1, TickType.Quote)]
[TestCase(SecurityType.Cfd, Resolution.Hour, 1, TickType.Quote)]
[TestCase(SecurityType.Crypto, Resolution.Daily, 2, TickType.Trade, TickType.Quote)]
[TestCase(SecurityType.Crypto, Resolution.Hour, 2, TickType.Trade, TickType.Quote)]
[TestCase(SecurityType.Equity, Resolution.Daily, 1, TickType.Trade)]
[TestCase(SecurityType.Equity, Resolution.Hour, 1, TickType.Trade)]
public void GetsSubscriptionDataTypesLowResolution(SecurityType securityType, Resolution resolution, int count, params TickType[] expectedTickTypes)
{
var types = GetSubscriptionDataTypes(securityType, resolution);
Assert.AreEqual(count, types.Count);
for (var i = 0; i < expectedTickTypes.Length; i++)
{
Assert.IsTrue(types[i].Item2 == expectedTickTypes[i]);
}
}
[Test]
[TestCase(SecurityType.Base, Resolution.Minute, typeof(TradeBar), TickType.Trade)]
[TestCase(SecurityType.Base, Resolution.Tick, typeof(Tick), TickType.Trade)]
[TestCase(SecurityType.Forex, Resolution.Minute, typeof(QuoteBar), TickType.Quote)]
[TestCase(SecurityType.Forex, Resolution.Tick, typeof(Tick), TickType.Quote)]
[TestCase(SecurityType.Cfd, Resolution.Minute, typeof(QuoteBar), TickType.Quote)]
[TestCase(SecurityType.Cfd, Resolution.Tick, typeof(Tick), TickType.Quote)]
public void GetsSubscriptionDataTypesSingle(SecurityType securityType, Resolution resolution, Type expectedDataType, TickType expectedTickType)
{
var types = GetSubscriptionDataTypes(securityType, resolution);
Assert.AreEqual(1, types.Count);
Assert.AreEqual(expectedDataType, types[0].Item1);
Assert.AreEqual(expectedTickType, types[0].Item2);
}
[Test]
[TestCase(SecurityType.Future, Resolution.Minute, typeof(FutureUniverse), TickType.Quote)]
[TestCase(SecurityType.Future, Resolution.Tick, typeof(FutureUniverse), TickType.Quote)]
[TestCase(SecurityType.FutureOption, Resolution.Minute, typeof(OptionUniverse), TickType.Quote)]
[TestCase(SecurityType.FutureOption, Resolution.Tick, typeof(OptionUniverse), TickType.Quote)]
[TestCase(SecurityType.Option, Resolution.Minute, typeof(OptionUniverse), TickType.Quote)]
[TestCase(SecurityType.Option, Resolution.Tick, typeof(OptionUniverse), TickType.Quote)]
[TestCase(SecurityType.IndexOption, Resolution.Minute, typeof(OptionUniverse), TickType.Quote)]
[TestCase(SecurityType.IndexOption, Resolution.Tick, typeof(OptionUniverse), TickType.Quote)]
public void GetsSubscriptionDataTypesCanonical(SecurityType securityType, Resolution resolution, Type expectedDataType, TickType expectedTickType)
{
var types = GetSubscriptionDataTypes(securityType, resolution, true);
Assert.AreEqual(1, types.Count);
Assert.AreEqual(expectedDataType, types[0].Item1);
Assert.AreEqual(expectedTickType, types[0].Item2);
}
[Test]
[TestCase(SecurityType.Future, Resolution.Minute)]
[TestCase(SecurityType.Option, Resolution.Minute)]
public void GetsSubscriptionDataTypesFuturesOptionsMinute(SecurityType securityType, Resolution resolution)
{
var types = GetSubscriptionDataTypes(securityType, resolution);
Assert.AreEqual(3, types.Count);
Assert.AreEqual(typeof(QuoteBar), types[0].Item1);
Assert.AreEqual(TickType.Quote, types[0].Item2);
Assert.AreEqual(typeof(TradeBar), types[1].Item1);
Assert.AreEqual(TickType.Trade, types[1].Item2);
Assert.AreEqual(typeof(OpenInterest), types[2].Item1);
Assert.AreEqual(TickType.OpenInterest, types[2].Item2);
}
[Test]
[TestCase(SecurityType.Future, Resolution.Tick)]
[TestCase(SecurityType.Option, Resolution.Tick)]
public void GetsSubscriptionDataTypesFuturesOptionsTick(SecurityType securityType, Resolution resolution)
{
var types = GetSubscriptionDataTypes(securityType, resolution);
Assert.AreEqual(3, types.Count);
Assert.AreEqual(typeof(Tick), types[0].Item1);
Assert.AreEqual(TickType.Quote, types[0].Item2);
Assert.AreEqual(typeof(Tick), types[1].Item1);
Assert.AreEqual(TickType.Trade, types[1].Item2);
Assert.AreEqual(typeof(Tick), types[2].Item1);
Assert.AreEqual(TickType.OpenInterest, types[2].Item2);
}
[Test]
[TestCase(SecurityType.Equity, Resolution.Minute)]
[TestCase(SecurityType.Equity, Resolution.Second)]
[TestCase(SecurityType.Equity, Resolution.Tick)]
[TestCase(SecurityType.Crypto, Resolution.Minute)]
[TestCase(SecurityType.Crypto, Resolution.Second)]
[TestCase(SecurityType.Crypto, Resolution.Tick)]
public void GetsSubscriptionDataTypes(SecurityType securityType, Resolution resolution)
{
var types = GetSubscriptionDataTypes(securityType, resolution);
Assert.AreEqual(2, types.Count);
if (resolution == Resolution.Tick)
{
Assert.AreEqual(typeof(Tick), types[0].Item1);
Assert.AreEqual(typeof(Tick), types[1].Item1);
}
else
{
Assert.AreEqual(typeof(TradeBar), types[0].Item1);
Assert.AreEqual(typeof(QuoteBar), types[1].Item1);
}
Assert.AreEqual(TickType.Trade, types[0].Item2);
Assert.AreEqual(TickType.Quote, types[1].Item2);
}
[Test]
public void SubscriptionsMemberIsThreadSafe()
{
var subscriptionManager = new SubscriptionManager(NullTimeKeeper.Instance);
subscriptionManager.SetDataManager(new DataManagerStub());
var start = DateTime.UtcNow;
var end = start.AddSeconds(5);
var tickers = QuantConnect.Algorithm.CSharp.StressSymbols.StockSymbols.ToList();
var symbols = tickers.Select(ticker => Symbol.Create(ticker, SecurityType.Equity, QuantConnect.Market.USA)).ToList();
var readTask = new TaskFactory().StartNew(() =>
{
Log.Trace("Read task started");
while (DateTime.UtcNow < end)
{
subscriptionManager.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(Resolution.Minute).Min();
Thread.Sleep(1);
}
Log.Trace("Read task ended");
});
while (readTask.Status != TaskStatus.Running) Thread.Sleep(1);
var addTask = new TaskFactory().StartNew(() =>
{
Log.Trace("Add task started");
foreach (var symbol in symbols)
{
subscriptionManager.Add(symbol, Resolution.Minute, DateTimeZone.Utc, DateTimeZone.Utc, true, false);
}
Log.Trace("Add task ended");
});
Task.WaitAll(addTask, readTask);
}
[Test]
public void ScanPastConsolidatorsIsThreadSafe()
{
var subscriptionManager = new SubscriptionManager(new TimeKeeper(DateTime.UtcNow));
var algorithm = new AlgorithmStub();
subscriptionManager.SetDataManager(new DataManagerStub());
var start = DateTime.UtcNow;
var end = start.AddSeconds(5);
var tickers = QuantConnect.Algorithm.CSharp.StressSymbols.StockSymbols.Take(100).ToList();
var symbols = tickers.Select(ticker => Symbol.Create(ticker, SecurityType.Equity, QuantConnect.Market.USA)).ToList();
var consolidators = new Queue<Tuple<Symbol, IDataConsolidator>>();
foreach (var symbol in symbols)
{
subscriptionManager.Add(symbol, Resolution.Minute, DateTimeZone.Utc, DateTimeZone.Utc, true, false);
}
var scanTask = Task.Factory.StartNew(() =>
{
Log.Debug("ScanPastConsolidators started");
while (DateTime.UtcNow < end)
{
subscriptionManager.ScanPastConsolidators(end.AddDays(1), algorithm);
}
Log.Debug("ScanPastConsolidators finished");
});
var addTask = Task.Factory.StartNew(() =>
{
while (scanTask.Status == TaskStatus.Running)
{
Log.Debug("AddConsolidators started");
foreach (var symbol in symbols)
{
var consolidator = new IdentityDataConsolidator<BaseData>();
subscriptionManager.AddConsolidator(symbol, consolidator);
consolidators.Enqueue(new Tuple<Symbol, IDataConsolidator>(symbol, consolidator));
}
Log.Debug("AddConsolidators finished");
Assert.AreEqual(100, consolidators.Count);
Log.Debug("RemoveConsolidators started");
while (consolidators.TryDequeue(out var pair))
{
subscriptionManager.RemoveConsolidator(pair.Item1, pair.Item2);
}
Log.Debug("RemoveConsolidators finished");
}
});
Task.WaitAll(scanTask, addTask);
Assert.AreEqual(100, subscriptionManager.Count);
Assert.AreEqual(0, consolidators.Count);
}
[Test]
public void GetsCustomSubscriptionDataTypes()
{
var subscriptionManager = new SubscriptionManager(NullTimeKeeper.Instance);
subscriptionManager.SetDataManager(new DataManagerStub());
subscriptionManager.AvailableDataTypes[SecurityType.Commodity] = new List<TickType> { TickType.OpenInterest, TickType.Quote, TickType.Trade };
var types = subscriptionManager.LookupSubscriptionConfigDataTypes(SecurityType.Commodity, Resolution.Daily, false);
Assert.AreEqual(3, types.Count);
Assert.AreEqual(typeof(OpenInterest), types[0].Item1);
Assert.AreEqual(typeof(QuoteBar), types[1].Item1);
Assert.AreEqual(typeof(TradeBar), types[2].Item1);
Assert.AreEqual(TickType.OpenInterest, types[0].Item2);
Assert.AreEqual(TickType.Quote, types[1].Item2);
Assert.AreEqual(TickType.Trade, types[2].Item2);
}
[TestCase(SecurityType.Future, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, TickType.Trade, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, TickType.Trade, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Future, TickType.Trade, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Future, TickType.Trade, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Future, TickType.Quote, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Future, TickType.OpenInterest, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Equity, TickType.Trade, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Equity, TickType.Quote, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Equity, TickType.OpenInterest, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Cfd, TickType.Trade, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Cfd, TickType.Quote, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, TickType.OpenInterest, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Forex, TickType.Trade, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Forex, TickType.Quote, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Forex, TickType.OpenInterest, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Crypto, TickType.Trade, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Crypto, TickType.Quote, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, TickType.OpenInterest, typeof(RenkoBar), typeof(Tick), false)]
public void ValidatesSubscriptionTickTypesForConsolidators(
SecurityType securityType,
TickType? subscriptionTickType,
Type consolidatorInputType,
Type consolidatorOutputType,
bool expected)
{
var subscription = new SubscriptionDataConfig(
typeof(Tick),
Symbol.Create("XYZ", securityType, QuantConnect.Market.USA),
Resolution.Tick,
DateTimeZone.Utc,
DateTimeZone.Utc,
true,
false,
false,
false,
subscriptionTickType);
using var consolidator = new TestConsolidator(consolidatorInputType, consolidatorOutputType);
Assert.AreEqual(expected, SubscriptionManager.IsSubscriptionValidForConsolidator(subscription, consolidator));
}
[TestCase(TickType.Trade, TickType.Trade, true)]
[TestCase(TickType.Trade, TickType.Quote, false)]
[TestCase(TickType.Trade, TickType.OpenInterest, false)]
[TestCase(TickType.Quote, TickType.Quote, true)]
[TestCase(TickType.Quote, TickType.Trade, false)]
[TestCase(TickType.Quote, TickType.OpenInterest, false)]
[TestCase(TickType.OpenInterest, TickType.OpenInterest, true)]
[TestCase(TickType.OpenInterest, TickType.Quote, false)]
[TestCase(TickType.OpenInterest, TickType.Trade, false)]
public void ValidatesSubscriptionTickTypesForClassicRenkoConsolidators(TickType subscriptionTickType, TickType desiredTickType, bool expected)
{
var subscription = new SubscriptionDataConfig(
typeof(Tick),
Symbol.Create("XYZ", SecurityType.Future, QuantConnect.Market.USA),
Resolution.Tick,
DateTimeZone.Utc,
DateTimeZone.Utc,
true,
false,
false,
false,
subscriptionTickType);
Func<IBaseData, decimal> selector = data =>
{
var tick = data as Tick;
return tick.Quantity * tick.Price;
};
using var consolidator = new ClassicRenkoConsolidator(2000m, selector);
Assert.AreEqual(expected, SubscriptionManager.IsSubscriptionValidForConsolidator(subscription, consolidator, desiredTickType));
}
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(IBaseData), TickType.Trade, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Future, typeof(OpenInterest), TickType.Trade, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(OpenInterest), TickType.Trade, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Trade, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(IBaseData), TickType.Quote, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Future, typeof(OpenInterest), TickType.Quote, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(OpenInterest), TickType.Quote, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, TickType.Trade, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.Quote, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.OpenInterest, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Future, typeof(IBaseData), TickType.OpenInterest, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Future, typeof(Tick), TickType.OpenInterest, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(IBaseData), TickType.Trade, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Equity, typeof(OpenInterest), TickType.Trade, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(OpenInterest), TickType.Trade, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Trade, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(IBaseData), TickType.Quote, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Equity, typeof(OpenInterest), TickType.Quote, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(OpenInterest), TickType.Quote, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, TickType.Trade, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.Quote, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.OpenInterest, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Equity, typeof(IBaseData), TickType.OpenInterest, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Equity, typeof(Tick), TickType.OpenInterest, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(IBaseData), TickType.Trade, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.Trade, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.Trade, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Trade, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(IBaseData), TickType.Quote, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.Quote, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.Quote, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, TickType.Trade, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.Quote, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(IBaseData), TickType.OpenInterest, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.OpenInterest, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(OpenInterest), TickType.OpenInterest, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Cfd, typeof(Tick), TickType.OpenInterest, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(IBaseData), TickType.Trade, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.Trade, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.Trade, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Trade, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(IBaseData), TickType.Quote, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.Quote, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.Quote, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, TickType.Trade, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.Quote, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(IBaseData), TickType.OpenInterest, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.OpenInterest, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(OpenInterest), TickType.OpenInterest, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Forex, typeof(Tick), TickType.OpenInterest, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(IBaseData), TickType.Trade, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.Trade, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.Trade, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Trade, TickType.Trade, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(QuoteBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(IBaseData), TickType.Quote, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(IBaseData), typeof(Tick), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.Quote, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.Quote, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, TickType.Trade, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.Quote, TickType.Quote, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(Tick), typeof(QuoteBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(IBaseData), TickType.OpenInterest, null, typeof(IBaseData), typeof(RenkoBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(IBaseData), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, null, typeof(RenkoBar), typeof(Tick), false)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.OpenInterest, null, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(OpenInterest), TickType.OpenInterest, TickType.Trade, typeof(OpenInterest), typeof(TradeBar), true)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, TickType.Trade, typeof(TradeBar), typeof(TradeBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, TickType.Quote, typeof(Tick), typeof(RenkoBar), false)]
[TestCase(SecurityType.Crypto, typeof(Tick), TickType.OpenInterest, TickType.OpenInterest, typeof(Tick), typeof(RenkoBar), true)]
public void GetsExpectedSubscriptionsGivenATickType(SecurityType securityType,
Type subscriptionType,
TickType? subscriptionTickType,
TickType? desiredTickType,
Type consolidatorInputType,
Type consolidatorOutputType,
bool expected)
{
var subscription = new SubscriptionDataConfig(
subscriptionType,
Symbol.Create("XYZ", securityType, QuantConnect.Market.USA),
Resolution.Tick,
DateTimeZone.Utc,
DateTimeZone.Utc,
true,
false,
false,
false,
subscriptionTickType);
using var consolidator = new TestConsolidator(consolidatorInputType, consolidatorOutputType);
Assert.AreEqual(expected, SubscriptionManager.IsSubscriptionValidForConsolidator(subscription, consolidator, desiredTickType));
}
[Test]
public void CanAddAndRemoveCSharpConsolidatorFromPython()
{
// NOTE: we use the IdentityDataConsolidator here because it's a generic class, which reproduces the bug.
// pyConsolidator.TryConvert(out IDataConsolidator consolidator) will return false, because the python type name
// and the C# type name don't match for generic types (e.g. IdentityDataConsolidator[TradeBar] != IdentityDataConsolidator`1)
using var _ = Py.GIL();
var module = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def get_consolidator():
return IdentityDataConsolidator[TradeBar]()
");
var algorithm = new AlgorithmStub();
var symbol = algorithm.AddEquity("SPY").Symbol;
var consolidator = module.GetAttr("get_consolidator").Invoke();
algorithm.SubscriptionManager.AddConsolidator(symbol, consolidator);
Assert.AreEqual(1, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
algorithm.SubscriptionManager.RemoveConsolidator(symbol, consolidator);
Assert.AreEqual(0, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
}
[Test]
public void CanAddAndRemoveCSharpConsolidatorFromPythonWithWrapper()
{
using var _ = Py.GIL();
var module = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def get_consolidator():
return IdentityDataConsolidator[TradeBar]()
");
var algorithm = new AlgorithmStub();
var symbol = algorithm.AddEquity("SPY").Symbol;
var pyConsolidator = module.GetAttr("get_consolidator").Invoke();
algorithm.SubscriptionManager.AddConsolidator(Symbols.SPY, pyConsolidator);
Assert.AreEqual(1, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
algorithm.SubscriptionManager.RemoveConsolidator(Symbols.SPY, pyConsolidator);
Assert.AreEqual(0, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
}
[Test]
public void CanAddAndRemovePythonConsolidator()
{
using var _ = Py.GIL();
var module = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class CustomQuoteBarConsolidator(PythonConsolidator):
def __init__(self):
#IDataConsolidator required vars for all consolidators
self.consolidated = None
self.working_data = None
self.input_type = QuoteBar
self.output_type = QuoteBar
def update(self, data):
pass
def scan(self, time):
pass
def get_consolidator():
return CustomQuoteBarConsolidator()
");
var algorithm = new AlgorithmStub();
var symbol = algorithm.AddEquity("SPY").Symbol;
var pyConsolidator = module.GetAttr("get_consolidator").Invoke();
algorithm.SubscriptionManager.AddConsolidator(Symbols.SPY, pyConsolidator);
Assert.AreEqual(1, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
algorithm.SubscriptionManager.RemoveConsolidator(Symbols.SPY, pyConsolidator);
Assert.AreEqual(0, algorithm.SubscriptionManager.Subscriptions.Sum(x => x.Consolidators.Count));
}
[Test, Parallelizable(ParallelScope.None)]
public void RunRemoveConsolidatorsRegressionAlgorithm()
{
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters("ManuallyRemovedConsolidatorsAlgorithm",
new Dictionary<string, string> {
{PerformanceMetrics.TotalOrders, "0"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "0%"},
{"Drawdown", "0%"},
{"Expectancy", "0"},
{"Net Profit", "0%"},
{"Sharpe Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0"},
{"Beta", "0"},
{"Annual Standard Deviation", "0"},
{"Annual Variance", "0"},
{"Information Ratio", "-8.91"},
{"Tracking Error", "0.223"},
{"Treynor Ratio", "0"},
{"Total Fees", "$0.00"}
},
Language.Python,
AlgorithmStatus.Completed);
AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
parameter.Statistics,
parameter.Language,
parameter.ExpectedFinalStatus);
}
private class TestConsolidator : IDataConsolidator
{
#pragma warning disable 0067 // TestConsolidator never uses this event; just ignore the warning
public event DataConsolidatedHandler DataConsolidated;
#pragma warning restore 0067
public IBaseData Consolidated { get; }
public IBaseData WorkingData { get; }
public Type InputType { get; }
public Type OutputType { get; }
public void Update(IBaseData data) { }
public void Scan(DateTime currentLocalTime) { }
public void Dispose() { }
public TestConsolidator(Type inputType, Type outputType)
{
InputType = inputType;
OutputType = outputType;
}
public TestConsolidator(Type inputType)
{
InputType = inputType;
}
public void Reset()
{
}
}
private static List<Tuple<Type, TickType>> GetSubscriptionDataTypes(SecurityType securityType, Resolution resolution, bool isCanonical = false)
{
var subscriptionManager = new SubscriptionManager(NullTimeKeeper.Instance);
subscriptionManager.SetDataManager(new DataManagerStub());
return subscriptionManager.LookupSubscriptionConfigDataTypes(securityType, resolution, isCanonical);
}
}
}
+363
View File
@@ -0,0 +1,363 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class TickConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void AggregatesNewTradeBarsProperly()
{
TradeBar newTradeBar = null;
using var consolidator = new TickConsolidator(4);
consolidator.DataConsolidated += (sender, tradeBar) =>
{
newTradeBar = tradeBar;
};
var reference = DateTime.Today;
var bar1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
Value = 5,
Quantity = 10
};
consolidator.Update(bar1);
Assert.IsNull(newTradeBar);
var bar2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
Value = 10,
Quantity = 20
};
consolidator.Update(bar2);
Assert.IsNull(newTradeBar);
var bar3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(2),
Value = 1,
Quantity = 10
};
consolidator.Update(bar3);
Assert.IsNull(newTradeBar);
var bar4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(3),
Value = 9,
Quantity = 20
};
consolidator.Update(bar4);
Assert.IsNotNull(newTradeBar);
Assert.AreEqual(Symbols.SPY, newTradeBar.Symbol);
Assert.AreEqual(bar1.Time, newTradeBar.Time);
Assert.AreEqual(bar1.Value, newTradeBar.Open);
Assert.AreEqual(bar2.Value, newTradeBar.High);
Assert.AreEqual(bar3.Value, newTradeBar.Low);
Assert.AreEqual(bar4.Value, newTradeBar.Close);
Assert.AreEqual(bar4.EndTime, newTradeBar.EndTime);
Assert.AreEqual(bar1.Quantity + bar2.Quantity + bar3.Quantity + bar4.Quantity, newTradeBar.Volume);
}
[Test]
public void DoesNotConsolidateDifferentSymbols()
{
using var consolidator = new TickConsolidator(2);
var reference = DateTime.Today;
var tick1 = new Tick
{
Symbol = Symbols.AAPL,
Time = reference,
BidPrice = 1000,
BidSize = 20,
TickType = TickType.Quote,
};
var tick2 = new Tick
{
Symbol = Symbols.ZNGA,
Time = reference,
BidPrice = 20,
BidSize = 30,
TickType = TickType.Quote,
};
consolidator.Update(tick1);
Exception ex = Assert.Throws<InvalidOperationException>(() => consolidator.Update(tick2));
Assert.IsTrue(ex.Message.Contains("is not the same"));
}
[Test]
public void AggregatesPeriodInCountModeWithDailyData()
{
TradeBar consolidated = null;
using var consolidator = new TickConsolidator(2);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference});
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddMilliseconds(1)});
Assert.IsNotNull(consolidated);
// The EndTime of the consolidated bar should match the EndTime of the last data point
Assert.AreEqual(reference.AddMilliseconds(1), consolidated.EndTime);
Assert.AreEqual(TimeSpan.FromMilliseconds(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddMilliseconds(2)});
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddMilliseconds(3)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddMilliseconds(3), consolidated.EndTime);
Assert.AreEqual(TimeSpan.FromMilliseconds(1), consolidated.Period);
}
[Test]
public void AggregatesPeriodInPeriodModeWithDailyData()
{
TradeBar consolidated = null;
using var consolidator = new TickConsolidator(TimeSpan.FromDays(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference});
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddDays(1)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(2)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(3)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
}
[Test]
public void AggregatesPeriodInPeriodModeWithDailyDataAndRoundedTime()
{
TradeBar consolidated = null;
using var consolidator = new TickConsolidator(TimeSpan.FromDays(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new Tick { Time = reference.AddSeconds(5) });
Assert.IsNull(consolidated);
consolidator.Update(new Tick { Time = reference.AddDays(1).AddSeconds(15) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference, consolidated.Time);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(2).AddMinutes(1) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference.AddDays(1), consolidated.Time);
consolidated = null;
consolidator.Update(new Tick { Time = reference.AddDays(3).AddMinutes(5) });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(1), consolidated.Period);
Assert.AreEqual(reference.AddDays(2), consolidated.Time);
}
[Test]
public void AggregatesNewTicksInPeriodWithRoundedTime()
{
TradeBar consolidated = null;
using var consolidator = new TickConsolidator(TimeSpan.FromMinutes(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 06, 02);
var tick1 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(3),
Value = 1.1000m
};
consolidator.Update(tick1);
Assert.IsNull(consolidated);
var tick2 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(10),
Value = 1.1005m
};
consolidator.Update(tick2);
Assert.IsNull(consolidated);
var tick3 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(61),
Value = 1.1010m
};
consolidator.Update(tick3);
Assert.IsNotNull(consolidated);
Assert.AreEqual(consolidated.Time, reference);
Assert.AreEqual(consolidated.Open, tick1.Value);
Assert.AreEqual(consolidated.Close, tick2.Value);
var tick4 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(70),
Value = 1.1015m
};
consolidator.Update(tick4);
Assert.IsNotNull(consolidated);
var tick5 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(118),
Value = 1.1020m
};
consolidator.Update(tick5);
Assert.IsNotNull(consolidated);
var tick6 = new Tick
{
Symbol = Symbols.EURUSD,
Time = reference.AddSeconds(140),
Value = 1.1025m
};
consolidator.Update(tick6);
Assert.IsNotNull(consolidated);
Assert.AreEqual(consolidated.Time, reference.AddSeconds(60));
Assert.AreEqual(consolidated.Open, tick3.Value);
Assert.AreEqual(consolidated.Close, tick5.Value);
}
[Test]
public void ProcessesTradeTicksOnly()
{
TradeBar consolidated = null;
using var consolidator = new TickConsolidator(TimeSpan.FromMinutes(1));
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 06, 02);
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(3),
Value = 200m
};
consolidator.Update(tick1);
Assert.IsNull(consolidated);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(10),
Value = 20000m,
TickType = TickType.OpenInterest
};
consolidator.Update(tick2);
Assert.IsNull(consolidated);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(10),
Value = 10000m,
TickType = TickType.Quote
};
consolidator.Update(tick3);
Assert.IsNull(consolidated);
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(61),
Value = 250m
};
consolidator.Update(tick4);
Assert.IsNotNull(consolidated);
Assert.AreEqual(consolidated.Time, reference);
Assert.AreEqual(consolidated.Open, tick1.Value);
Assert.AreEqual(consolidated.Close, tick1.Value);
}
protected override IDataConsolidator CreateConsolidator()
{
return new TickConsolidator(2);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = DateTime.Today;
return new List<Tick>()
{
new Tick(){Symbol = Symbols.SPY, Time = time, Value = 10 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(1), Value = 2 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(2), Value = 8 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(3), Value = 5 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(4), Value = 13 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(5), Value = 15 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(6), Value = 10 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(7), Value = 11 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(8), Value = 11 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(9), Value = 4 },
new Tick(){Symbol = Symbols.SPY, Time = time.AddSeconds(10), Value = 7 },
};
}
}
}
@@ -0,0 +1,291 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class TickQuoteBarConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void AggregatesNewQuoteBarProperly()
{
QuoteBar quoteBar = null;
using var creator = new TickQuoteBarConsolidator(4);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var reference = DateTime.Today;
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
BidPrice = 10,
BidSize = 20,
TickType = TickType.Quote
};
creator.Update(tick1);
Assert.IsNull(quoteBar);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
AskPrice = 20,
AskSize = 10,
TickType = TickType.Quote
};
var badTick = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(1),
AskPrice = 25,
AskSize = 100,
BidPrice = -100,
BidSize = 2,
Value = 50,
Quantity = 1234,
TickType = TickType.Trade
};
creator.Update(badTick);
Assert.IsNull(quoteBar);
creator.Update(tick2);
Assert.IsNull(quoteBar);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(2),
BidPrice = 12,
BidSize = 50,
TickType = TickType.Quote
};
creator.Update(tick3);
Assert.IsNull(quoteBar);
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddHours(3),
AskPrice = 17,
AskSize = 15,
TickType = TickType.Quote
};
creator.Update(tick4);
Assert.IsNotNull(quoteBar);
Assert.AreEqual(Symbols.SPY, quoteBar.Symbol);
Assert.AreEqual(tick1.Time, quoteBar.Time);
Assert.AreEqual(tick4.EndTime, quoteBar.EndTime);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Low);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.High);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Close);
Assert.AreEqual(tick3.BidSize, quoteBar.LastBidSize);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Low);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.High);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Close);
Assert.AreEqual(tick4.AskSize, quoteBar.LastAskSize);
}
[Test]
public void DoesNotConsolidateDifferentSymbols()
{
using var consolidator = new TickQuoteBarConsolidator(2);
var reference = DateTime.Today;
var tick1 = new Tick
{
Symbol = Symbols.AAPL,
Time = reference,
BidPrice = 1000,
BidSize = 20,
TickType = TickType.Quote,
};
var tick2 = new Tick
{
Symbol = Symbols.ZNGA,
Time = reference,
BidPrice = 20,
BidSize = 30,
TickType = TickType.Quote,
};
consolidator.Update(tick1);
Exception ex = Assert.Throws<InvalidOperationException>(() => consolidator.Update(tick2));
Assert.IsTrue(ex.Message.Contains("is not the same"));
}
[Test]
public void LastCloseAndCurrentOpenPriceShouldBeSameConsolidatedOnCount()
{
QuoteBar quoteBar = null;
using var creator = new TickQuoteBarConsolidator(2);
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var reference = DateTime.Today;
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
TickType = TickType.Quote,
AskPrice = 0,
BidPrice = 24,
};
creator.Update(tick1);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
TickType = TickType.Quote,
AskPrice = 25,
BidPrice = 0,
};
creator.Update(tick2);
// bar 1 emitted
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Close);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Close);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(1),
TickType = TickType.Quote,
AskPrice = 36,
BidPrice = 35,
};
creator.Update(tick3);
creator.Update(tick3);
// bar 2 emitted
// ask is from tick 2
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open, "Ask Open not equal to Previous Close");
// bid is from tick 1
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open, "Bid Open not equal to Previous Close");
Assert.AreEqual(tick3.AskPrice, quoteBar.Ask.Close, "Ask Close incorrect");
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Close, "Bid Close incorrect");
}
[Test]
public void LastCloseAndCurrentOpenPriceShouldBeSameConsolidatedOnTimeSpan()
{
QuoteBar quoteBar = null;
using var creator = new TickQuoteBarConsolidator(TimeSpan.FromMinutes(1));
creator.DataConsolidated += (sender, args) =>
{
quoteBar = args;
};
var reference = DateTime.Today;
// timeframe 1
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
TickType = TickType.Quote,
AskPrice = 25,
BidPrice = 24,
};
creator.Update(tick1);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(1),
TickType = TickType.Quote,
AskPrice = 26,
BidPrice = 0,
};
creator.Update(tick2);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(1),
TickType = TickType.Quote,
AskPrice = 0,
BidPrice = 25,
};
creator.Update(tick3);
// timeframe 2
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddMinutes(1),
TickType = TickType.Quote,
AskPrice = 36,
BidPrice = 35,
};
creator.Update(tick4);
//force the consolidator to emit DataConsolidated
creator.Scan(reference.AddMinutes(2));
// bid is from tick 2
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open, "Ask Open not equal to Previous Close");
// bid is from tick 3
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Open, "Bid Open not equal to Previous Close");
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Close, "Ask Close incorrect");
Assert.AreEqual(tick4.BidPrice, quoteBar.Bid.Close, "Bid Close incorrect");
}
protected override IDataConsolidator CreateConsolidator()
{
return new TickQuoteBarConsolidator(2);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = DateTime.Today;
return new List<Tick>()
{
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time, Value = 10, AskPrice = 10, BidPrice = 5 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(1), Value = 2, AskPrice = 10, BidPrice = 7 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(2), Value = 8, AskPrice = 11, BidPrice = 9 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(3), Value = 5, AskPrice = 15, BidPrice = 6 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(4), Value = 13, AskPrice = 15, BidPrice = 7 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(5), Value = 15 , AskPrice = 13, BidPrice = 8 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(6), Value = 10 , AskPrice = 14, BidPrice = 7 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(7), Value = 11 , AskPrice = 13, BidPrice = 8 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(8), Value = 11 , AskPrice = 14, BidPrice = 6 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(9), Value = 4 , AskPrice = 14, BidPrice = 9 },
new Tick(){Symbol = Symbols.SPY, TickType = TickType.Quote, Time = time.AddSeconds(10), Value = 7 , AskPrice = 13, BidPrice = 5 },
};
}
}
}
@@ -0,0 +1,591 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class TradeBarConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void ZeroCountAlwaysFires()
{
// defining a TradeBarConsolidator with a zero max count should cause it to always fire identity
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(0);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
consolidator.Update(new TradeBar());
Assert.IsNotNull(consolidated);
}
[Test]
public void OneCountAlwaysFires()
{
// defining a TradeBarConsolidator with a one max count should cause it to always fire identity
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(1);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
consolidator.Update(new TradeBar());
Assert.IsNotNull(consolidated);
}
[Test]
public void TwoCountFiresEveryOther()
{
// defining a TradeBarConsolidator with a two max count should cause it to fire every other TradeBar
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(2);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
consolidator.Update(new TradeBar());
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar());
Assert.IsNotNull(consolidated);
consolidated = null;
consolidator.Update(new TradeBar());
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar());
Assert.IsNotNull(consolidated);
}
[Test]
public void ZeroSpanAlwaysThrows()
{
// defining a TradeBarConsolidator with a zero period should cause it to always throw an exception
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(TimeSpan.Zero);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2014, 12, 01, 01, 01, 00);
Assert.Throws<ArgumentException>(() => consolidator.Update(new TradeBar { Time = reference, Period = Time.OneDay }));
}
[Test]
public void ConsolidatesOHLCV()
{
// verifies that the TradeBarConsolidator correctly consolidates OHLCV data into a new TradeBar instance
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(3);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var tb1 = new TradeBar
{
Symbol = Symbols.SPY,
Open = 10,
High = 100,
Low = 1,
Close = 50,
Volume = 75,
DataType = MarketDataType.TradeBar
};
var tb2 = new TradeBar
{
Symbol = Symbols.SPY,
Open = 50,
High = 123,
Low = 35,
Close = 75,
Volume = 100,
DataType = MarketDataType.TradeBar
};
var tb3 = new TradeBar
{
Symbol = Symbols.SPY,
Open = 75,
High = 100,
Low = 50,
Close = 83,
Volume = 125,
DataType = MarketDataType.TradeBar
};
consolidator.Update(tb1);
consolidator.Update(tb2);
consolidator.Update(tb3);
Assert.IsNotNull(consolidated);
Assert.AreEqual(Symbols.SPY, consolidated.Symbol);
Assert.AreEqual(10m, consolidated.Open);
Assert.AreEqual(123m, consolidated.High);
Assert.AreEqual(1m, consolidated.Low);
Assert.AreEqual(83m, consolidated.Close);
Assert.AreEqual(300L, consolidated.Volume);
}
[Test]
public void DoesNotConsolidateDifferentSymbols()
{
// verifies that the TradeBarConsolidator does not consolidate data with different symbols
using var consolidator = new TradeBarConsolidator(2);
var tb1 = new TradeBar
{
Symbol = Symbols.AAPL,
Open = 10,
High = 100,
Low = 1,
Close = 50,
Volume = 75,
DataType = MarketDataType.TradeBar
};
var tb2 = new TradeBar
{
Symbol = Symbols.ZNGA,
Open = 50,
High = 123,
Low = 35,
Close = 75,
Volume = 100,
DataType = MarketDataType.TradeBar
};
consolidator.Update(tb1);
Exception ex = Assert.Throws<InvalidOperationException>(() => consolidator.Update(tb2));
Assert.IsTrue(ex.Message.Contains("is not the same", StringComparison.InvariantCultureIgnoreCase));
}
[Test]
public void ConsolidatedTimeIsFromBeginningOfBar()
{
// verifies that the consolidated bar uses the time from the beginning of the first bar
// in the period that covers the current bar
using var consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(2));
TradeBar consolidated = null;
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2014, 12, 1, 10, 00, 0);
//10:00 - start new
consolidator.Update(new TradeBar {Time = reference});
Assert.IsNull(consolidated);
//10:01 - aggregate
consolidator.Update(new TradeBar {Time = reference.AddMinutes(1)});
Assert.IsNull(consolidated);
//10:02 - fire & start new
consolidator.Update(new TradeBar {Time = reference.AddMinutes(2)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
consolidated = null;
//10:03 - aggregate
consolidator.Update(new TradeBar {Time = reference.AddMinutes(3)});
Assert.IsNull(consolidated);
//10:05 - fire & start new
consolidator.Update(new TradeBar {Time = reference.AddMinutes(5)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddMinutes(2), consolidated.Time);
consolidated = null;
//10:08 - fire & start new
consolidator.Update(new TradeBar {Time = reference.AddMinutes(8)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddMinutes(4), consolidated.Time);
consolidated = null;
//10:08:01 - aggregate
consolidator.Update(new TradeBar {Time = reference.AddMinutes(8).AddSeconds(1)});
Assert.IsNull(consolidated);
//10:09 - aggregate
consolidator.Update(new TradeBar {Time = reference.AddMinutes(9)});
Assert.IsNull(consolidated);
}
[Test]
public void HandlesDataGapsInMixedMode()
{
// define a three minute consolidator on a one minute stream of data
using var consolidator = new TradeBarConsolidator(3, TimeSpan.FromMinutes(3));
TradeBar consolidated = null;
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2014, 12, 1, 10, 00, 0);
//10:00 - new
consolidator.Update(new TradeBar {Time = reference});
Assert.IsNull(consolidated);
//10:01 - aggregate
consolidator.Update(new TradeBar {Time = reference.AddMinutes(1)});
Assert.IsNull(consolidated);
//10:02 - fire
consolidator.Update(new TradeBar {Time = reference.AddMinutes(2)});
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
//10:03 - new
consolidator.Update(new TradeBar {Time = reference.AddMinutes(3)});
Assert.AreEqual(reference, consolidated.Time);
//10:06 - aggregate/fire
consolidator.Update(new TradeBar {Time = reference.AddMinutes(6)});
Assert.AreEqual(reference.AddMinutes(3), consolidated.Time);
//10:08 - new/fire -- will have timestamp from 10:08, instead of 10:06
consolidator.Update(new TradeBar {Time = reference.AddMinutes(8)});
Assert.AreEqual(reference.AddMinutes(8), consolidated.Time);
}
[Test]
public void HandlesGappingAcrossDays()
{
// this test requires inspection to verify we're getting clean bars on the correct times
using var consolidator = new TradeBarConsolidator(TimeSpan.FromHours(1));
TradeBar consolidated = null;
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
// from 1/1 9:30 to 1/2 12:00 by minute
var start = new DateTime(2014, 01, 01, 09, 30, 00, 00);
var end = new DateTime(2014, 01, 02, 12, 00, 00, 00);
foreach (var bar in StreamTradeBars(start, end, TimeSpan.FromMinutes(1)))
{
consolidator.Update(bar);
}
}
/// <summary>
/// Testing the behaviors where, the bar range is closed on the left and open on
/// the right in time span mode: [T, T+TimeSpan).
/// For example, if time span is 1 minute, we have [10:00, 10:01): so data at
/// 10:01 is not included in the bar starting at 10:00.
/// </summary>
[Test]
public void ClosedLeftOpenRightInTimeSpanModeTest()
{
// define a three minute consolidator
int timeSpanUnits = 3;
using var consolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(timeSpanUnits));
TradeBar consolidated = null;
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var refDateTime = new DateTime(2014, 12, 1, 10, 00, 0);
// loop for 3 times the timeSpanUnits + 1, so it would consolidate the bars 3 times
for (int i=0; i < 3*timeSpanUnits + 1 ; ++i)
{
consolidator.Update(new TradeBar { Time = refDateTime });
if (i < timeSpanUnits) // before initial consolidation happens
{
Assert.IsNull(consolidated);
}
else
{
Assert.IsNotNull(consolidated);
if (i % timeSpanUnits == 0) // i = 3, 6, 9
{
Assert.AreEqual(refDateTime.AddMinutes(-timeSpanUnits), consolidated.Time);
}
}
refDateTime = refDateTime.AddMinutes(1);
}
}
[Test]
public void AggregatesPeriodInCountModeWithDailyData()
{
TradeBar consolidated = null;
var period = TimeSpan.FromDays(1);
using var consolidator = new TradeBarConsolidator(2);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new TradeBar { Time = reference, Period = period});
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = reference.AddDays(1), Period = period });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(2), consolidated.Period);
consolidated = null;
consolidator.Update(new TradeBar { Time = reference.AddDays(2), Period = period });
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = reference.AddDays(3), Period = period });
Assert.IsNotNull(consolidated);
Assert.AreEqual(TimeSpan.FromDays(2), consolidated.Period);
}
[Test]
public void AggregatesPeriodInPeriodModeWithDailyData()
{
TradeBar consolidated = null;
var period = TimeSpan.FromDays(2);
using var consolidator = new TradeBarConsolidator(period);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new TradeBar { Time = reference, Period = Time.OneDay});
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = reference.AddDays(1), Period = Time.OneDay });
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = reference.AddDays(2), Period = Time.OneDay });
Assert.IsNotNull(consolidated);
Assert.AreEqual(period, consolidated.Period);
consolidated = null;
consolidator.Update(new TradeBar { Time = reference.AddDays(3), Period = Time.OneDay });
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = reference.AddDays(4), Period = Time.OneDay });
Assert.IsNotNull(consolidated);
Assert.AreEqual(period, consolidated.Period);
}
[Test]
public void ThrowsWhenPeriodIsSmallerThanDataPeriod()
{
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(Time.OneHour);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
Assert.Throws<ArgumentException>(() => consolidator.Update(new TradeBar { Time = reference, Period = Time.OneDay }));
}
[Test]
public void GentlyHandlesPeriodAndDataAreSameResolution()
{
TradeBar consolidated = null;
using var consolidator = new TradeBarConsolidator(Time.OneDay);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
var bar = new TradeBar { Time = reference, Period = Time.OneDay };
consolidator.Update(bar);
Assert.IsNull(consolidated);
consolidator.Scan(bar.EndTime);
Assert.IsNotNull(consolidated);
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
Assert.AreEqual(Time.OneDay, consolidated.Period);
}
[Test]
public void FiresEventAfterTimePassesViaScan()
{
TradeBar consolidated = null;
var period = TimeSpan.FromDays(2);
using var consolidator = new TradeBarConsolidator(period);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13);
consolidator.Update(new TradeBar { Time = reference, Period = Time.OneDay });
Assert.IsNull(consolidated);
consolidator.Scan(reference + period);
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
}
[Test]
public void ConsolidatedPeriodEqualsTimeBasedConsolidatorPeriod()
{
TradeBar consolidated = null;
var period = TimeSpan.FromMinutes(2);
using var consolidator = new TradeBarConsolidator(period);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13, 10, 20, 0);
var time = reference;
consolidator.Update(new TradeBar { Time = time, Period = Time.OneMinute });
time = reference.Add(period);
consolidator.Scan(time);
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
Assert.AreEqual(period, consolidated.Period);
}
[Test]
public void FiresEventAfterTimePassesViaScanWithMultipleResolutions()
{
TradeBar consolidated = null;
var period = TimeSpan.FromMinutes(2);
using var consolidator = new TradeBarConsolidator(period);
consolidator.DataConsolidated += (sender, bar) =>
{
consolidated = bar;
};
var reference = new DateTime(2015, 04, 13, 10, 20, 0);
var time = reference;
for (int i = 0; i < 10; i++)
{
consolidator.Update(new TradeBar {Time = time, Period = Time.OneSecond});
time = time.AddSeconds(1);
consolidator.Scan(time);
Assert.IsNull(consolidated);
}
consolidator.Update(new TradeBar { Time = time, Period = Time.OneMinute });
time = reference.Add(period);
consolidator.Scan(time);
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference, consolidated.Time);
Assert.AreEqual(period, consolidated.Period);
consolidated = null;
consolidator.Update(new TradeBar { Time = time, Period = Time.OneSecond });
time = time.AddSeconds(1);
consolidator.Scan(time);
Assert.IsNull(consolidated);
time = time.AddSeconds(-1);
consolidator.Update(new TradeBar { Time = time, Period = Time.OneMinute });
time = time.AddMinutes(1);
consolidator.Scan(time);
Assert.IsNull(consolidated);
consolidator.Update(new TradeBar { Time = time, Period = Time.OneMinute });
time = time.AddMinutes(1);
consolidator.Scan(time);
Assert.IsNotNull(consolidated);
Assert.AreEqual(reference.AddMinutes(2), consolidated.Time);
Assert.AreEqual(period, consolidated.Period);
}
private readonly TimeSpan marketStop = new DateTime(2000, 1, 1, 12 + 4, 0, 0).TimeOfDay;
private readonly TimeSpan marketStart = new DateTime(2000, 1, 1, 9, 30, 0).TimeOfDay;
private IEnumerable<TradeBar> StreamTradeBars(DateTime start, DateTime end, TimeSpan resolution, bool skipAferMarketHours = true)
{
DateTime current = start;
while (current < end)
{
var timeOfDay = current.TimeOfDay;
if (skipAferMarketHours && (marketStart > timeOfDay || marketStop < timeOfDay))
{
// set current to the next days market start
current = current.Date.AddDays(1).Add(marketStart);
continue;
}
// either we don't care about after market hours or it's within regular market hours
yield return new TradeBar {Time = current};
current = current + resolution;
}
}
protected override IDataConsolidator CreateConsolidator()
{
return new TradeBarConsolidator(2);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2015, 04, 13, 8, 31, 0);
return new List<TradeBar>()
{
new TradeBar(){ Time = time, Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10 },
new TradeBar(){ Time = time.AddMinutes(1), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12 },
new TradeBar(){ Time = time.AddMinutes(2), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 10 },
new TradeBar(){ Time = time.AddMinutes(3), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 5 },
new TradeBar(){ Time = time.AddMinutes(4), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 15 },
new TradeBar(){ Time = time.AddMinutes(5), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 20 },
new TradeBar(){ Time = time.AddMinutes(6), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 18 },
new TradeBar(){ Time = time.AddMinutes(7), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 12 },
new TradeBar(){ Time = time.AddMinutes(8), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 25 },
new TradeBar(){ Time = time.AddMinutes(9), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 30 },
new TradeBar(){ Time = time.AddMinutes(10), Period = Time.OneMinute, Symbol = Symbols.SPY, High = 26 },
};
}
}
}
@@ -0,0 +1,45 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class CoarseFundamentalTests
{
[Test, TestCaseSource(nameof(TestParameters))]
public void ParsesCoarseCsvLine(string line, bool hasFundamentalData, decimal price, decimal priceFactor, decimal splitFactor, decimal adjustedPrice)
{
var cf = (CoarseFundamental)CoarseFundamentalDataProvider.Read(line, DateTime.MinValue);
Assert.AreEqual(hasFundamentalData, cf.HasFundamentalData);
Assert.AreEqual(price, cf.Price);
Assert.AreEqual(priceFactor, cf.PriceFactor);
Assert.AreEqual(splitFactor, cf.SplitFactor);
Assert.AreEqual(adjustedPrice, cf.AdjustedPrice);
}
public static object[] TestParameters =
{
new object[] { "AAPL R735QTJ8XC9X,AAPL,537.46,5483955,3490219402,True", true, 537.46m, 1m, 1m, 537.46m },
new object[] { "AAPL R735QTJ8XC9X,AAPL,645.57,7831583,5055835037,True,0.9304792,0.142857", true, 645.57m, 0.9304792m, 0.142857m, 85.812693779220408m },
new object[] { "AAPL R735QTJ8XC9X,AAPL,93.7,37807206,3542535202,True,0.9304792,1", true, 93.7m, 0.9304792m, 1m, 87.18590104m },
};
}
}
@@ -0,0 +1,118 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class ConstituentsUniverseDataTests
{
private SubscriptionDataConfig _config;
private SecurityExchangeHours _exchangeHours;
[SetUp]
public void SetUp()
{
_config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.AAPL,
Resolution.Second,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false,
false,
TickType.Trade,
false);
_exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(Symbols.AAPL.ID.Market, Symbols.AAPL, Symbols.AAPL.ID.SecurityType).ExchangeHours;
}
[Test]
public void BacktestSourceForEachTradableDate()
{
var reader = new ConstituentsUniverseData();
var tradableDays = Time.EachTradeableDayInTimeZone(_exchangeHours,
new DateTime(2019, 06, 9), // sunday
new DateTime(2019, 06, 16),
_config.DataTimeZone,
_config.ExtendedMarketHours);
foreach (var tradableDay in tradableDays)
{
if (tradableDay.DayOfWeek == DayOfWeek.Saturday
|| tradableDay.DayOfWeek == DayOfWeek.Sunday)
{
Assert.Fail($"Unexpected tradable DayOfWeek {tradableDay.DayOfWeek}");
}
var source = reader.GetSource(_config, tradableDay, false);
// Mon to Friday
Assert.IsTrue(source.Source.Contains($"{tradableDay:yyyyMMdd}"));
}
}
[Test]
public void BacktestDataTimeForEachTradableDate()
{
var reader = new ConstituentsUniverseData();
var tradableDays = Time.EachTradeableDayInTimeZone(_exchangeHours,
new DateTime(2019, 06, 9), // sunday
new DateTime(2019, 06, 16),
_config.DataTimeZone,
_config.ExtendedMarketHours);
foreach (var tradableDay in tradableDays)
{
var dataPoint = reader.Reader(_config, "NONE,NONE 0", tradableDay, false);
Assert.AreEqual(dataPoint.Time, tradableDay);
// emitted tomorrow
Assert.AreEqual(dataPoint.EndTime, tradableDay.AddDays(1));
}
}
[Test]
public void LiveSourceForCurrentDate()
{
var reader = new ConstituentsUniverseData();
var currentTime = DateTime.UtcNow;
var source = reader.GetSource(_config, currentTime, true);
// From Tue to Sat will find files from Mon to Friday
Assert.IsTrue(source.Source.Contains($"{currentTime.AddDays(-1):yyyyMMdd}"));
}
[Test]
public void LiveDataTimeForCurrentDate()
{
var reader = new ConstituentsUniverseData();
var currentTime = DateTime.UtcNow;
var dataPoint = reader.Reader(_config, "NONE,NONE 0", currentTime, true);
Assert.AreEqual(dataPoint.Time, currentTime.AddDays(-1));
// emitted right away
Assert.AreEqual(dataPoint.EndTime, currentTime);
}
}
}
@@ -0,0 +1,110 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace QuantConnect.Tests.Common.Securities.Options
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class OptionUniverseTests
{
private static string TestOptionUniverseFile = @"
#expiry,strike,right,open,high,low,close,volume,open_interest,implied_volatility,delta,gamma,vega,theta,rho
,,,5488.47998046875,5523.64013671875,5451.1201171875,5460.47998046875,7199220000,,,,,,,
20260618,5400,C,780.3000,853.9000,709.6000,767.7500,0,135,0.1637928,0.6382026,0.0002890,26.5721377,-0.5042690,55.5035521
20261218,5400,C,893.1400,907.7100,893.1400,907.5400,37,1039,0.1701839,0.6420671,0.0002447,28.9774913,-0.4608812,67.5259867
20271217,5400,C,1073.0000,1073.0000,1073.0000,1073.0000,0,889,0.1839256,0.6456981,0.0001858,32.6109403,-0.3963479,88.5870185
20281215,5400,C,1248.0000,1248.0000,1248.0000,1248.0000,0,301,0.1934730,0.6472619,0.0001512,35.1083627,-0.3434647,106.9858230
20291221,5400,C,1467.9000,1467.9000,1467.9000,1467.9000,0,9,0.2046702,0.6460372,0.0001254,36.9157598,-0.2993105,122.2236355
20240719,5405,C,95.4500,95.4500,95.4500,95.4500,1,311,0.1006795,0.6960459,0.0026897,4.4991247,-1.4284818,2.0701880
20240816,5405,C,161.4000,161.4000,161.4000,161.4000,0,380,0.1088739,0.6472976,0.0017128,7.3449930,-1.1139626,4.5112640
20240920,5405,C,213.7000,213.7000,211.0000,211.0000,0,33,0.1149306,0.6316343,0.0012532,9.7567496,-0.9462173,7.4872272
20241018,5405,C,254.0000,303.3500,218.2500,238.0500,0,0,0.1183992,0.6273390,0.0010556,11.2892617,-0.8673778,9.8420483
20240719,5410,C,143.5900,143.5900,119.7100,119.7100,11,355,0.0995106,0.6842402,0.0027673,4.5750811,-1.4291241,2.0364155
20240816,5410,C,151.2000,151.2000,151.2000,151.2000,0,68,0.1080883,0.6395066,0.0017388,7.4027436,-1.1113164,4.4598077
20240920,5410,C,202.5000,202.5000,201.9800,201.9800,0,211,0.1142983,0.6258911,0.0012667,9.8073284,-0.9438102,7.4239078
20241018,5410,C,256.4800,256.4800,255.9000,255.9000,0,91,0.1180060,0.6223570,0.0010637,11.3388534,-0.8661655,9.7694707
20241115,5410,C,279.7500,279.7500,279.2300,279.2300,0,65,0.1268034,0.6170056,0.0008881,12.7072390,-0.8357895,11.9829003
20240719,5415,C,123.1800,123.1800,98.0300,98.0300,5,307,0.0985516,0.6716430,0.0028403,4.6505424,-1.4312099,2.0001484
20240816,5415,C,146.6900,146.6900,146.6900,146.6900,3,901,0.1073207,0.6315307,0.0017645,7.4585091,-1.1084001,4.4069495
20240920,5415,C,194.1000,196.7000,194.1000,196.7000,0,63,0.1136398,0.6200837,0.0012804,9.8561442,-0.9410592,7.3597879
20241018,5415,C,246.5000,295.7500,210.7500,230.9500,0,0,0.1172852,0.6175838,0.0010746,11.3844988,-0.8632046,9.7014393
20240719,5420,C,119.7500,119.7500,94.0000,94.0000,31,453,0.0973479,0.6589639,0.0029188,4.7207612,-1.4288180,1.9636645
20240816,5420,C,181.5800,181.5800,154.8300,154.8300,4,110,0.1065704,0.6233721,0.0017897,7.5120648,-1.1051922,4.3527055
".TrimStart();
private List<OptionUniverse> _optionUniverseFile;
[OneTimeSetUp]
public void OneTimeSetUp()
{
var config = new SubscriptionDataConfig(typeof(OptionUniverse),
Symbol.CreateCanonicalOption(Symbols.SPX),
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var date = new DateTime(2024, 06, 28);
_optionUniverseFile = new List<OptionUniverse>();
var factory = new OptionUniverse();
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(TestOptionUniverseFile));
using var reader = new StreamReader(stream);
while (!reader.EndOfStream)
{
var data = (OptionUniverse)factory.Reader(config, reader, date, false);
if (data == null) continue;
_optionUniverseFile.Add(data);
}
}
[Test]
public void RoundTripCsvConversion()
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("#" + OptionUniverse.CsvHeader(SecurityType.Option));
foreach (var data in _optionUniverseFile)
{
string csv = null;
if (data.Symbol.SecurityType.IsOption())
{
csv = OptionUniverse.ToCsv(data.Symbol, data.Open, data.High, data.Low, data.Close, data.Volume, data.OpenInterest,
data.ImpliedVolatility, data.Greeks);
}
else
{
csv = OptionUniverse.ToCsv(data.Symbol, data.Open, data.High, data.Low, data.Close, data.Volume, null, null, null);
}
stringBuilder.AppendLine(csv);
}
var csvString = stringBuilder.ToString();
Assert.AreEqual(TestOptionUniverseFile, csvString);
}
}
}
@@ -0,0 +1,130 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class ScheduledUniverseTests
{
private DateTimeZone _timezone;
private TimeKeeper _timekeeper;
private SecurityManager _securities;
private DateRules _dateRules;
private TimeRules _timeRules;
[SetUp]
public void Setup()
{
_timezone = TimeZones.NewYork;
_timekeeper = new TimeKeeper(new DateTime(2000, 1, 1), _timezone);
_securities = new SecurityManager(_timekeeper);
var mhdb = MarketHoursDatabase.FromDataFolder();
_dateRules = new DateRules(null, _securities, _timezone, mhdb);
_timeRules = new TimeRules(null, _securities, _timezone, mhdb);
}
[Test]
public void TimeTriggeredDoesNotReturnPastTimes()
{
// Schedule our universe for 12PM each day
using var universe = new ScheduledUniverse(
_dateRules.EveryDay(), _timeRules.At(12, 0),
(time =>
{
return new List<Symbol>();
})
);
// For this test; start time will be 1/5/2000 wednesday at 3PM
// which is after 12PM, this case will ensure we don't have a 1/5 12pm event
var start = new DateTime(2000, 1, 5, 15, 0, 0);
var end = new DateTime(2000, 1, 10);
// Get our trigger times, these will be in UTC
var triggerTimesUtc = universe.GetTriggerTimes(start.ConvertToUtc(_timezone), end.ConvertToUtc(_timezone), MarketHoursDatabase.AlwaysOpen);
// Setup expectDate variables to assert behavior
// We expect the first day to be 1/6 12PM
var expectedDate = new DateTime(2000, 1, 6, 12, 0, 0);
foreach (var time in triggerTimesUtc)
{
// Convert our UTC time back to our timezone
var localTime = time.ConvertFromUtc(_timezone);
// Assert we aren't receiving dates prior to our start
Assert.IsTrue(localTime > start);
// Verify the date
Assert.AreEqual(expectedDate, localTime);
expectedDate = expectedDate.AddDays(1);
}
}
[Test]
public void TimeTriggeredDoesNotReturnTimesAfterEndTime()
{
// Schedule our universe for 12PM each day
using var universe = new ScheduledUniverse(
_dateRules.EveryDay(), _timeRules.At(12, 0),
time => new List<Symbol>()
);
var start = new DateTime(2000, 1, 5, 8, 0, 0).ConvertToUtc(_timezone);
var end = new DateTime(2000, 1, 5, 11, 0, 0).ConvertToUtc(_timezone);
// Get our trigger times
var triggerTimes = universe.GetTriggerTimes(start, end, MarketHoursDatabase.AlwaysOpen).ToList();
// Assert that there are no trigger times because 12PM is after the end time of 11AM
Assert.IsEmpty(triggerTimes);
}
[Test]
public void TriggerTimesNone()
{
// Test to see what happens when we expect no trigger times.
// To do this we will create an everyday at 12pm rule, but ask for triggers times
// on a single day from 3pm-4pm, meaning we should get none.
var timezone = TimeZones.NewYork;
var start = new DateTime(2000, 1, 5, 15, 0, 0);
var end = new DateTime(2000, 1, 5, 16, 0, 0);
var dateRule = _dateRules.EveryDay();
var timeRule = _timeRules.At(12, 0);
using var universe = new ScheduledUniverse(dateRule, timeRule, time =>
{
return new List<Symbol>();
});
var triggerTimesUtc = universe.GetTriggerTimes(start.ConvertToUtc(timezone), end.ConvertToUtc(timezone),
MarketHoursDatabase.AlwaysOpen);
// Assert that its empty
Assert.IsTrue(!triggerTimesUtc.Any());
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class SecurityChangesTests
{
[Test]
public void WillNotFilterCustomSecuritiesByDefault()
{
var security = new Security(Symbols.SPY,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
var customSecurity = new Security(Symbol.CreateBase(typeof(TradeBar), Symbols.SPY, QuantConnect.Market.USA),
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
var changes = CreateNonInternal(new List<Security> { security, customSecurity },
new List<Security> { security, customSecurity });
Assert.IsTrue(changes.AddedSecurities.Contains(customSecurity));
Assert.IsTrue(changes.AddedSecurities.Contains(security));
Assert.IsTrue(changes.RemovedSecurities.Contains(customSecurity));
Assert.IsTrue(changes.RemovedSecurities.Contains(security));
}
[Test]
public void FilterCustomSecuritiesIfDesired()
{
var security = new Security(Symbols.SPY,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
var customSecurity = new Security(Symbol.CreateBase(typeof(TradeBar), Symbols.SPY, QuantConnect.Market.USA),
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
var changes = CreateNonInternal(new List<Security> { security, customSecurity },
new List<Security> { security, customSecurity });
changes.FilterCustomSecurities = true;
foreach (var addedSecurity in changes.AddedSecurities)
{
Assert.AreNotEqual(SecurityType.Base, addedSecurity.Type);
}
foreach (var removedSecurity in changes.RemovedSecurities)
{
Assert.AreNotEqual(SecurityType.Base, removedSecurity.Type);
}
}
/// <summary>
/// Returns a new instance of <see cref="SecurityChanges"/> with the specified securities marked as added
/// </summary>
/// <param name="securities">The added securities</param>
/// <remarks>Useful for testing</remarks>
/// <returns>A new security changes instance with the specified securities marked as added</returns>
public static SecurityChanges AddedNonInternal(params Security[] securities)
{
if (securities == null || securities.Length == 0) return SecurityChanges.None;
return CreateNonInternal(securities, Enumerable.Empty<Security>());
}
/// <summary>
/// Returns a new instance of <see cref="SecurityChanges"/> with the specified securities marked as removed
/// </summary>
/// <param name="securities">The removed securities</param>
/// <remarks>Useful for testing</remarks>
/// <returns>A new security changes instance with the specified securities marked as removed</returns>
public static SecurityChanges RemovedNonInternal(params Security[] securities)
{
if (securities == null || securities.Length == 0) return SecurityChanges.None;
return CreateNonInternal(Enumerable.Empty<Security>(), securities);
}
/// <summary>
/// Initializes a new instance of the <see cref="SecurityChanges"/> class all none internal
/// </summary>
/// <param name="addedSecurities">Added symbols list</param>
/// <param name="removedSecurities">Removed symbols list</param>
/// <remarks>Useful for testing</remarks>
public static SecurityChanges CreateNonInternal(IEnumerable<Security> addedSecurities, IEnumerable<Security> removedSecurities)
{
return SecurityChanges.Create(addedSecurities.ToList(), removedSecurities.ToList(), new List<Security>(), new List<Security>());
}
}
}
@@ -0,0 +1,146 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class UniverseTests
{
private SubscriptionDataConfig _config;
private Security _security;
[SetUp]
public void SetUp()
{
_config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.AAPL,
Resolution.Second,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false,
false,
TickType.Trade,
false);
_security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
_config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
_security.SetMarketPrice(new TradeBar(new DateTime(2022, 10, 10), _security.Symbol, 1, 1, 1, 1, 1));
}
[Test]
public void RoundsTimeWhenCheckingMinimumTimeInUniverse_Seconds()
{
using var universe = new TestUniverse(_config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromSeconds(30)));
var addedTime = new DateTime(2018, 1, 1);
universe.AddMember(addedTime, _security, false);
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddSeconds(29), _security));
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddSeconds(29.4), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddSeconds(29.5), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddSeconds(31), _security));
}
[Test]
public void RoundsTimeWhenCheckingMinimumTimeInUniverse_Minutes()
{
using var universe = new TestUniverse(_config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromMinutes(30)));
var addedTime = new DateTime(2018, 1, 1);
universe.AddMember(addedTime, _security, false);
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddMinutes(29), _security));
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddMinutes(29.4), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddMinutes(29.5), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddMinutes(31), _security));
}
[Test]
public void RoundsTimeWhenCheckingMinimumTimeInUniverse_Hour()
{
using var universe = new TestUniverse(_config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromHours(6)));
var addedTime = new DateTime(2018, 1, 1);
universe.AddMember(addedTime, _security, false);
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddHours(5), _security));
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddHours(5.1), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddHours(5.5), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddHours(6), _security));
}
[Test]
public void RoundsTimeWhenCheckingMinimumTimeInUniverse_Daily()
{
using var universe = new TestUniverse(_config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromDays(1)));
var addedTime = new DateTime(2018, 1, 1);
universe.AddMember(addedTime, _security, false);
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddHours(5), _security));
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddHours(12), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddHours(12.1), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddHours(28), _security));
}
[Test]
public void RoundsTimeWhenCheckingMinimumTimeInUniverse_SevenDays()
{
using var universe = new TestUniverse(_config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromDays(7)));
var addedTime = new DateTime(2018, 1, 1);
universe.AddMember(addedTime, _security, false);
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddDays(4), _security));
Assert.IsFalse(universe.CanRemoveMember(addedTime.AddDays(6.5), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddDays(6.51), _security));
Assert.IsTrue(universe.CanRemoveMember(addedTime.AddDays(8), _security));
}
private class TestUniverse : Universe
{
public TestUniverse(SubscriptionDataConfig config, UniverseSettings universeSettings)
: base(config)
{
UniverseSettings = universeSettings;
}
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,111 @@
/*
* 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 NUnit.Framework;
using System.Threading;
using QuantConnect.Data;
using System.Collections.Generic;
using QuantConnect.Algorithm.CSharp;
using QuantConnect.Statistics;
namespace QuantConnect.Tests.Common.Data.UniverseSelection
{
[TestFixture]
public class UserDefinedUniverseTests
{
[Test]
public void ThreadSafety()
{
// allow the system to stabilize
Thread.Sleep(1000);
var results = AlgorithmRunner.RunLocalBacktest(nameof(TestUserDefinedUniverseAlgorithm),
new Dictionary<string, string> { { PerformanceMetrics.TotalOrders, "1" } },
Language.CSharp,
AlgorithmStatus.Completed,
algorithmLocation: "QuantConnect.Tests.dll");
Assert.GreaterOrEqual(TestUserDefinedUniverseAlgorithm.AdditionCount, 50, $"We added {TestUserDefinedUniverseAlgorithm.AdditionCount} times");
}
}
public class TestUserDefinedUniverseAlgorithm : BasicTemplateAlgorithm
{
public static long AdditionCount;
private Thread _thread;
private CancellationTokenSource _cancellationTokenSource = new();
private ManualResetEvent _threadStarted = new (false);
public override void Initialize()
{
SetStartDate(2013, 10, 07);
SetEndDate(2013, 10, 11);
Settings.SeedInitialPrices = false;
#pragma warning disable CS0618
var spy = AddEquity("SPY", Resolution.Minute, dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
_thread = new Thread(() =>
{
_threadStarted.Set();
try
{
while (!_cancellationTokenSource.IsCancellationRequested && AdditionCount < 250)
{
var currentCount = Interlocked.Increment(ref AdditionCount);
var contract = QuantConnect.Symbol.CreateOption(spy, QuantConnect.Market.USA, OptionStyle.American, OptionRight.Call, currentCount, new DateTime(2022, 10, 10));
AddOptionContract(contract);
if (currentCount % 2 == 0)
{
RemoveSecurity("AAPL");
}
else
{
AddEquity("AAPL");
#pragma warning restore CS0618
}
if (currentCount % 25 == 0)
{
Thread.Sleep(10);
}
}
}
catch (Exception ex)
{
Error(ex);
SetStatus(AlgorithmStatus.RuntimeError);
}
}) { IsBackground = true };
}
public override void OnData(Slice data)
{
if (!_threadStarted.WaitOne(0))
{
_thread.Start();
_threadStarted.WaitOne();
}
base.OnData(data);
}
public override void OnEndOfAlgorithm()
{
_thread.StopSafely(TimeSpan.FromSeconds(2), _cancellationTokenSource);
base.OnEndOfAlgorithm();
}
}
}
@@ -0,0 +1,257 @@
/*
* 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 NUnit.Framework;
using Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Common.Data
{
[TestFixture]
public class VolumeRenkoConsolidatorTests: BaseConsolidatorTests
{
[Test]
public void OutputTypeIsVolumeRenkoBar()
{
using var consolidator = new VolumeRenkoConsolidator(10);
Assert.AreEqual(typeof(VolumeRenkoBar), consolidator.OutputType);
}
[Test]
public void ConsolidatesOnTickVolumeReached()
{
VolumeRenkoBar bar = null;
using var consolidator = new VolumeRenkoConsolidator(10);
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var reference = new DateTime(2013, 10, 1);
consolidator.Update(new Tick(reference, Symbol.Empty, String.Empty, String.Empty, 2m, 1m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(1), Symbol.Empty, String.Empty, String.Empty, 3m, 2m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(2), Symbol.Empty, String.Empty, String.Empty, 3m, 3m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(3), Symbol.Empty, String.Empty, String.Empty, 2m, 2m));
Assert.IsNotNull(bar);
Assert.AreEqual(1m, bar.Open);
Assert.AreEqual(3m, bar.High);
Assert.AreEqual(1m, bar.Low);
Assert.AreEqual(2m, bar.Close);
Assert.AreEqual(10m, bar.Volume);
Assert.AreEqual(10m, bar.BrickSize);
Assert.AreEqual(Symbol.Empty, bar.Symbol);
Assert.AreEqual(reference, bar.Start);
Assert.AreEqual(reference.AddHours(3), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void ConsolidatesOnTraderBarVolumeReached()
{
VolumeRenkoBar bar = null;
using var consolidator = new VolumeRenkoConsolidator(10);
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var reference = new DateTime(2013, 10, 1);
consolidator.Update(new TradeBar(reference, Symbol.Empty, 1m, 2m, 0.5m, 1.5m, 2m, new TimeSpan(1, 0, 0)));
Assert.IsNull(bar);
consolidator.Update(new TradeBar(reference.AddHours(1), Symbol.Empty, 1.5m, 3m, 1m, 3m, 3m, new TimeSpan(1, 0, 0)));
Assert.IsNull(bar);
consolidator.Update(new TradeBar(reference.AddHours(2), Symbol.Empty, 3m, 3m, 1m, 2m, 3m, new TimeSpan(1, 0, 0)));
Assert.IsNull(bar);
consolidator.Update(new TradeBar(reference.AddHours(3), Symbol.Empty, 2m, 4m, 1.5m, 2.5m, 2m, new TimeSpan(1, 0, 0)));
Assert.IsNotNull(bar);
Assert.AreEqual(1m, bar.Open);
Assert.AreEqual(4m, bar.High);
Assert.AreEqual(0.5m, bar.Low);
Assert.AreEqual(2.5m, bar.Close);
Assert.AreEqual(10m, bar.Volume);
Assert.AreEqual(10m, bar.BrickSize);
Assert.AreEqual(Symbol.Empty, bar.Symbol);
Assert.AreEqual(reference, bar.Start);
Assert.AreEqual(reference.AddHours(4), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
}
[Test]
public void ConsolidatesOnQuoteBar()
{
using var consolidator = new VolumeRenkoConsolidator(10);
var reference = new DateTime(2013, 10, 1);
Assert.Throws<ArgumentException>(() =>
consolidator.Update(new QuoteBar(reference, Symbol.Empty, new Bar(1m, 1m, 1m, 1m), 1m, new Bar(1m, 1m, 1m, 1m), 1m, TimeSpan.MinValue)));
}
[Test]
public void ConsistentRenkos()
{
// Test Renko bar consistency amongst three consolidators starting at different times
var time = new DateTime(2016, 1, 1);
var testValues = new List<decimal[]>
{
new decimal[]{5m, 5m}, new decimal[]{5m, 3m}, new decimal[]{5m, 7m}, new decimal[]{5m, 6m},
new decimal[]{5m, 5m}, new decimal[]{5m, 3m}, new decimal[]{5m, 7m}, new decimal[]{5m, 6m},
new decimal[]{5m, 5m}, new decimal[]{5m, 3m}, new decimal[]{5m, 7m}, new decimal[]{5m, 6m},
new decimal[]{5m, 5m}, new decimal[]{5m, 3m}, new decimal[]{5m, 7m}, new decimal[]{5m, 6m}
};
var consolidator1 = new VolumeRenkoConsolidator(20m);
var consolidator2 = new VolumeRenkoConsolidator(20m);
var consolidator3 = new VolumeRenkoConsolidator(20m);
// Update each of our consolidators starting at different indexes of test values
for (int i = 0; i < testValues.Count; i++)
{
var data = new Tick(time.AddSeconds(i), Symbol.Empty, String.Empty, String.Empty, testValues[i][0], testValues[i][1]);
consolidator1.Update(data);
if (i > 3)
{
consolidator2.Update(data);
}
if (i > 7)
{
consolidator3.Update(data);
}
}
// Assert that consolidator 2 and 3 price is the same as 1. Even though they started at different
// indexes they should be the same
var bar1 = consolidator1.Consolidated as VolumeRenkoBar;
var bar2 = consolidator2.Consolidated as VolumeRenkoBar;
var bar3 = consolidator3.Consolidated as VolumeRenkoBar;
Assert.AreEqual(bar1.Close, bar2.Close);
Assert.AreEqual(bar1.Close, bar3.Close);
consolidator1.Dispose();
consolidator2.Dispose();
consolidator3.Dispose();
}
[Test]
public void MultipleConsoldation()
{
VolumeRenkoBar bar = null;
using var consolidator = new VolumeRenkoConsolidator(10m);
consolidator.DataConsolidated += (sender, consolidated) =>
{
bar = consolidated;
};
var reference = new DateTime(2013, 10, 1);
consolidator.Update(new Tick(reference, Symbol.Empty, String.Empty, String.Empty, 2m, 1m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(1), Symbol.Empty, String.Empty, String.Empty, 3m, 2m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(2), Symbol.Empty, String.Empty, String.Empty, 3m, 3m));
Assert.IsNull(bar);
consolidator.Update(new Tick(reference.AddHours(3), Symbol.Empty, String.Empty, String.Empty, 2m, 2m));
Assert.IsNotNull(bar);
Assert.AreEqual(1m, bar.Open);
Assert.AreEqual(3m, bar.High);
Assert.AreEqual(1m, bar.Low);
Assert.AreEqual(2m, bar.Close);
Assert.AreEqual(10m, bar.Volume);
Assert.AreEqual(10m, bar.BrickSize);
Assert.AreEqual(Symbol.Empty, bar.Symbol);
Assert.AreEqual(reference, bar.Start);
Assert.AreEqual(reference.AddHours(3), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
consolidator.Update(new Tick(reference.AddHours(4), Symbol.Empty, String.Empty, String.Empty, 4m, 1m));
consolidator.Update(new Tick(reference.AddHours(5), Symbol.Empty, String.Empty, String.Empty, 3m, 2m));
consolidator.Update(new Tick(reference.AddHours(6), Symbol.Empty, String.Empty, String.Empty, 4m, 3m));
Assert.AreEqual(2m, bar.Open);
Assert.AreEqual(3m, bar.High);
Assert.AreEqual(1m, bar.Low);
Assert.AreEqual(3m, bar.Close);
Assert.AreEqual(10m, bar.Volume);
Assert.AreEqual(10m, bar.BrickSize);
Assert.AreEqual(Symbol.Empty, bar.Symbol);
Assert.AreEqual(reference.AddHours(3), bar.Start);
Assert.AreEqual(reference.AddHours(6), bar.EndTime);
Assert.IsTrue(bar.IsClosed); // bar is always closed since it is the consolidated bar instance
consolidator.Update(new Tick(reference.AddHours(7), Symbol.Empty, String.Empty, String.Empty, 5m, 10m));
// Not yet consolidated, so bar is not updated yet
Assert.AreEqual(2m, bar.Open);
Assert.AreEqual(3m, bar.High);
Assert.AreEqual(1m, bar.Low);
Assert.AreEqual(3m, bar.Close);
Assert.AreEqual(10m, bar.Volume);
Assert.AreEqual(10m, bar.BrickSize);
Assert.AreEqual(Symbol.Empty, bar.Symbol);
Assert.AreEqual(reference.AddHours(3), bar.Start);
Assert.AreEqual(reference.AddHours(6), bar.EndTime);
Assert.IsTrue(bar.IsClosed);
}
protected override IEnumerable<IBaseData> GetTestValues()
{
var time = new DateTime(2016, 3, 1);
return new List<Tick>()
{
new Tick(time, Symbol.Empty, String.Empty, String.Empty, 5m, 5m),
new Tick(time.AddSeconds(1), Symbol.Empty, String.Empty, String.Empty, 5m, 3m),
new Tick(time.AddSeconds(2), Symbol.Empty, String.Empty, String.Empty, 5m, 7m),
new Tick(time.AddSeconds(3), Symbol.Empty, String.Empty, String.Empty, 5m, 6m),
new Tick(time.AddSeconds(4), Symbol.Empty, String.Empty, String.Empty, 5m, 5m),
new Tick(time.AddSeconds(5), Symbol.Empty, String.Empty, String.Empty, 5m, 3m),
new Tick(time.AddSeconds(6), Symbol.Empty, String.Empty, String.Empty, 5m, 7m),
new Tick(time.AddSeconds(7), Symbol.Empty, String.Empty, String.Empty, 5m, 6m),
new Tick(time.AddSeconds(8), Symbol.Empty, String.Empty, String.Empty, 5m, 5m),
new Tick(time.AddSeconds(9), Symbol.Empty, String.Empty, String.Empty, 5m, 6m),
new Tick(time.AddSeconds(10), Symbol.Empty, String.Empty, String.Empty, 5m, 7m),
new Tick(time.AddSeconds(11), Symbol.Empty, String.Empty, String.Empty, 5m, 8m),
new Tick(time.AddSeconds(12), Symbol.Empty, String.Empty, String.Empty, 5m, 9m)
};
}
protected override IDataConsolidator CreateConsolidator()
{
return new VolumeRenkoConsolidator(10m);
}
}
}