/* * 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().Single(d => d.Time == reference.AddDays(-363)); distribution = Dividend.ComputeDistribution(100m, .7m / .8m, 2); Assert.AreEqual(distribution, dividend.Distribution); split = splitsAndDividends.OfType().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().ToList(); var dividends = splitsAndDividends.OfType().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 {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 { 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 { 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 { 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 { 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); } } }