chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user