示例#1
0
        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));
            }
        }
        public override void Initialize()
        {
            SetStartDate(2014, 3, 25);      //Set Start Date
            SetEndDate(2014, 4, 7);         //Set End Date
            SetCash(100000);                //Set Strategy Cash

            // Set our DataNormalizationMode to raw
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
            _googl = AddEquity(Ticker, Resolution.Daily).Symbol;

            // Get our factor file for this regression
            var dataProvider =
                Composer.Instance.GetExportedValueByTypeName <IDataProvider>(Config.Get("data-provider",
                                                                                        "DefaultDataProvider"));

            var mapFileProvider = new LocalDiskMapFileProvider();

            mapFileProvider.Initialize(dataProvider);
            var factorFileProvider = new LocalDiskFactorFileProvider();

            factorFileProvider.Initialize(mapFileProvider, dataProvider);
            _factorFile = factorFileProvider.Get(_googl) as CorporateFactorProvider;

            // Prime our expected values
            _expectedRawPrices.MoveNext();
        }
示例#3
0
 /// <summary>
 /// Initializes this instance
 /// </summary>
 /// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
 /// <param name="factorFileProvider">The factor file provider to use</param>
 /// <param name="mapFileProvider">The <see cref="Data.Auxiliary.MapFile"/> provider to use</param>
 /// <param name="startTime">Start date for the data request</param>
 public void Initialize(
     SubscriptionDataConfig config,
     IFactorFileProvider factorFileProvider,
     IMapFileProvider mapFileProvider,
     DateTime startTime)
 {
     _config     = config;
     _mapFile    = mapFileProvider.ResolveMapFile(_config);
     _factorFile = factorFileProvider.Get(_config.Symbol) as CorporateFactorProvider;
 }
示例#4
0
        public void PriceScaleDoesNotUpdateForFillForwardBar()
        {
            var referenceTime = new DateTime(2020, 08, 06);
            var point         = new Tick(referenceTime, Symbols.SPY, 1, 2);
            var point2        = point.Clone(true);

            point2.Time = referenceTime.AddDays(1);
            var point3 = point.Clone(false);

            point3.Time = referenceTime.AddDays(2);
            ;
            var enumerator = new List <BaseData> {
                point, point2, point3
            }.GetEnumerator();
            var factorFileProfider = new Mock <IFactorFileProvider>();

            var factorFile = new CorporateFactorProvider(_security.Symbol.Value, new[]
            {
                new CorporateFactorRow(referenceTime, 0.5m, 1),
                new CorporateFactorRow(referenceTime.AddDays(1), 1m, 1)
            }, referenceTime);

            factorFileProfider.Setup(s => s.Get(It.IsAny <Symbol>())).Returns(factorFile);

            var subscription = SubscriptionUtils.CreateAndScheduleWorker(
                new SubscriptionRequest(
                    false,
                    null,
                    _security,
                    _config,
                    referenceTime,
                    Time.EndOfTime
                    ),
                enumerator,
                factorFileProfider.Object,
                true);

            Assert.IsTrue(subscription.MoveNext());
            Assert.AreEqual(1, (subscription.Current.Data as Tick).AskPrice);
            Assert.IsFalse((subscription.Current.Data as Tick).IsFillForward);

            Assert.IsTrue(subscription.MoveNext());
            Assert.AreEqual(1, (subscription.Current.Data as Tick).AskPrice);
            Assert.IsTrue((subscription.Current.Data as Tick).IsFillForward);

            Assert.IsTrue(subscription.MoveNext());
            Assert.AreEqual(2, (subscription.Current.Data as Tick).AskPrice);
            Assert.IsFalse((subscription.Current.Data as Tick).IsFillForward);

            subscription.DisposeSafely();
        }
示例#5
0
        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);
        }
示例#6
0
        public override void Initialize()
        {
            SetStartDate(2014, 6, 5);      //Set Start Date
            SetEndDate(2014, 6, 5);        //Set End Date

            UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted;
            _aapl = AddEquity(Ticker, Resolution.Minute).Symbol;

            var dataProvider =
                Composer.Instance.GetExportedValueByTypeName <IDataProvider>(Config.Get("data-provider",
                                                                                        "DefaultDataProvider"));

            var mapFileProvider = new LocalDiskMapFileProvider();

            mapFileProvider.Initialize(dataProvider);
            var factorFileProvider = new LocalDiskFactorFileProvider();

            factorFileProvider.Initialize(mapFileProvider, dataProvider);


            _factorFile = factorFileProvider.Get(_aapl) as CorporateFactorProvider;
        }
示例#7
0
        public static void GenerateRandomData(RandomDataGeneratorSettings settings, ConsoleLeveledOutput output)
        {
            // can specify a seed value in this ctor if determinism is desired
            var random = new Random();
            var randomValueGenerator = new RandomValueGenerator();

            if (settings.RandomSeedSet)
            {
                random = new Random(settings.RandomSeed);
                randomValueGenerator = new RandomValueGenerator(settings.RandomSeed);
            }

            var maxSymbolCount = randomValueGenerator.GetAvailableSymbolCount(settings.SecurityType, settings.Market);

            if (settings.SymbolCount > maxSymbolCount)
            {
                output.Warn.WriteLine($"Limiting symbol count to {maxSymbolCount}, we don't have more {settings.SecurityType} tickers for {settings.Market}");
                settings.SymbolCount = maxSymbolCount;
            }

            var symbolGenerator = new SymbolGenerator(settings, randomValueGenerator);
            var tickGenerator   = new TickGenerator(settings, randomValueGenerator);

            output.Warn.WriteLine($"Begin data generation of {settings.SymbolCount} randomly generated {settings.SecurityType} assets...");

            // iterate over our randomly generated symbols
            var count         = 0;
            var progress      = 0d;
            var previousMonth = -1;

            Func <Tick, DateTime>          tickDay = (tick => new DateTime(tick.Time.Year, tick.Time.Month, tick.Time.Day));
            Func <Data.BaseData, DateTime> dataDay = (data => new DateTime(data.Time.Year, data.Time.Month, data.Time.Day));

            foreach (var currentSymbol in symbolGenerator.GenerateRandomSymbols())
            {
                // This is done so that we can update the symbol in the case of a rename event
                var delistDate     = GetDelistingDate(settings.Start, settings.End, randomValueGenerator);
                var symbol         = currentSymbol;
                var willBeDelisted = randomValueGenerator.NextBool(1.0);
                var monthsTrading  = 0;

                // Keep track of renamed symbols and the time they were renamed.
                var renamedSymbols = new Dictionary <Symbol, DateTime>();

                output.Warn.WriteLine($"\tSymbol[{++count}]: {symbol} Progress: {progress:0.0}% - Generating data...");

                // define aggregators via settings
                var aggregators = settings.CreateAggregators().ToList();
                var tickHistory = tickGenerator.GenerateTicks(symbol).ToList();

                // Companies rarely IPO then disappear within 6 months
                if (willBeDelisted && tickHistory.Select(tick => tick.Time.Month).Distinct().Count() <= 6)
                {
                    willBeDelisted = false;
                }

                var dividendsSplitsMaps = new DividendSplitMapGenerator(
                    symbol,
                    settings,
                    randomValueGenerator,
                    random,
                    delistDate,
                    willBeDelisted);

                if (settings.SecurityType == SecurityType.Equity)
                {
                    dividendsSplitsMaps.GenerateSplitsDividends(tickHistory);

                    if (!willBeDelisted)
                    {
                        dividendsSplitsMaps.DividendsSplits.Add(new CorporateFactorRow(new DateTime(2050, 12, 31), 1m, 1m));

                        if (dividendsSplitsMaps.MapRows.Count > 1)
                        {
                            // Remove the last element if we're going to have a 20501231 entry
                            dividendsSplitsMaps.MapRows.RemoveAt(dividendsSplitsMaps.MapRows.Count - 1);
                        }
                        dividendsSplitsMaps.MapRows.Add(new MapFileRow(new DateTime(2050, 12, 31), dividendsSplitsMaps.CurrentSymbol.Value));
                    }

                    // If the symbol value has changed, update the current symbol
                    if (symbol != dividendsSplitsMaps.CurrentSymbol)
                    {
                        // Add all symbol rename events to dictionary
                        // We skip the first row as it contains the listing event instead of a rename event
                        foreach (var renameEvent in dividendsSplitsMaps.MapRows.Skip(1))
                        {
                            // Symbol.UpdateMappedSymbol does not update the underlying security ID symbol, which
                            // is used to create the hash code. Create a new equity symbol from scratch instead.
                            symbol = Symbol.Create(renameEvent.MappedSymbol, SecurityType.Equity, settings.Market);
                            renamedSymbols.Add(symbol, renameEvent.Date);

                            output.Warn.WriteLine($"\tSymbol[{count}]: {symbol} will be renamed on {renameEvent.Date}");
                        }
                    }
                    else
                    {
                        // This ensures that ticks will be written for the current symbol up until 9999-12-31
                        renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
                    }

                    symbol = dividendsSplitsMaps.CurrentSymbol;

                    // Write Splits and Dividend events to directory factor_files
                    var factorFile = new CorporateFactorProvider(symbol.Value, dividendsSplitsMaps.DividendsSplits, settings.Start);
                    var mapFile    = new MapFile(symbol.Value, dividendsSplitsMaps.MapRows);

                    factorFile.WriteToFile(symbol);
                    mapFile.WriteToCsv(settings.Market, symbol.SecurityType);

                    output.Warn.WriteLine($"\tSymbol[{count}]: {symbol} Dividends, splits, and map files have been written to disk.");
                }
                else
                {
                    // This ensures that ticks will be written for the current symbol up until 9999-12-31
                    renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
                }

                Symbol previousSymbol = null;
                var    currentCount   = 0;

                foreach (var renamed in renamedSymbols)
                {
                    var previousRenameDate    = previousSymbol == null ? new DateTime(1, 1, 1) : renamedSymbols[previousSymbol];
                    var previousRenameDateDay = new DateTime(previousRenameDate.Year, previousRenameDate.Month, previousRenameDate.Day);
                    var renameDate            = renamed.Value;
                    var renameDateDay         = new DateTime(renameDate.Year, renameDate.Month, renameDate.Day);

                    foreach (var tick in tickHistory.Where(tick => tick.Time >= previousRenameDate && previousRenameDateDay != tickDay(tick)))
                    {
                        // Prevents the aggregator from being updated with ticks after the rename event
                        if (tickDay(tick) > renameDateDay)
                        {
                            break;
                        }

                        if (tick.Time.Month != previousMonth)
                        {
                            output.Info.WriteLine($"\tSymbol[{count}]: Month: {tick.Time:MMMM}");
                            previousMonth = tick.Time.Month;
                            monthsTrading++;
                        }

                        foreach (var item in aggregators)
                        {
                            tick.Value = tick.Value / dividendsSplitsMaps.FinalSplitFactor;
                            item.Consolidator.Update(tick);
                        }

                        if (monthsTrading >= 6 && willBeDelisted && tick.Time > delistDate)
                        {
                            output.Warn.WriteLine($"\tSymbol[{count}]: {renamed.Key} delisted at {tick.Time:MMMM yyyy}");
                            break;
                        }
                    }

                    // count each stage as a point, so total points is 2*symbol-count
                    // and the current progress is twice the current, but less one because we haven't finished writing data yet
                    progress = 100 * (2 * count - 1) / (2.0 * settings.SymbolCount);

                    output.Warn.WriteLine($"\tSymbol[{count}]: {renamed.Key} Progress: {progress:0.0}% - Saving data in LEAN format");

                    // persist consolidated data to disk
                    foreach (var item in aggregators)
                    {
                        var writer = new LeanDataWriter(item.Resolution, renamed.Key, Globals.DataFolder, item.TickType);

                        // send the flushed data into the writer. pulling the flushed list is very important,
                        // lest we likely wouldn't get the last piece of data stuck in the consolidator
                        // Filter out the data we're going to write here because filtering them in the consolidator update phase
                        // makes it write all dates for some unknown reason
                        writer.Write(item.Flush().Where(data => data.Time > previousRenameDate && previousRenameDateDay != dataDay(data)));
                    }

                    // update progress
                    progress = 100 * (2 * count) / (2.0 * settings.SymbolCount);
                    output.Warn.WriteLine($"\tSymbol[{count}]: {symbol} Progress: {progress:0.0}% - Symbol data generation and output completed");

                    previousSymbol = renamed.Key;
                    currentCount++;
                }
            }

            output.Info.WriteLine("Random data generation has completed.");
        }
示例#8
0
        /// <summary>
        /// Starts data generation
        /// </summary>
        public void Run()
        {
            var tickTypesPerSecurityType = SubscriptionManager.DefaultDataTypes();
            // can specify a seed value in this ctor if determinism is desired
            var random = new Random();
            var randomValueGenerator = new RandomValueGenerator();

            if (_settings.RandomSeedSet)
            {
                random = new Random(_settings.RandomSeed);
                randomValueGenerator = new RandomValueGenerator(_settings.RandomSeed);
            }

            var symbolGenerator = BaseSymbolGenerator.Create(_settings, randomValueGenerator);

            var maxSymbolCount = symbolGenerator.GetAvailableSymbolCount();

            if (_settings.SymbolCount > maxSymbolCount)
            {
                Log.Error($"RandomDataGenerator.Run(): Limiting Symbol count to {maxSymbolCount}, we don't have more {_settings.SecurityType} tickers for {_settings.Market}");
                _settings.SymbolCount = maxSymbolCount;
            }

            Log.Trace($"RandomDataGenerator.Run(): Begin data generation of {_settings.SymbolCount} randomly generated {_settings.SecurityType} assets...");

            // iterate over our randomly generated symbols
            var count         = 0;
            var progress      = 0d;
            var previousMonth = -1;

            foreach (var(symbolRef, currentSymbolGroup) in symbolGenerator.GenerateRandomSymbols()
                     .GroupBy(s => s.HasUnderlying ? s.Underlying : s)
                     .Select(g => (g.Key, g.OrderBy(s => s.HasUnderlying).ToList())))
            {
                Log.Trace($"RandomDataGenerator.Run(): Symbol[{++count}]: {symbolRef} Progress: {progress:0.0}% - Generating data...");

                var      tickGenerators     = new List <IEnumerator <Tick> >();
                var      tickHistories      = new Dictionary <Symbol, List <Tick> >();
                Security underlyingSecurity = null;
                foreach (var currentSymbol in currentSymbolGroup)
                {
                    if (!_securityManager.TryGetValue(currentSymbol, out var security))
                    {
                        security = _securityManager.CreateSecurity(
                            currentSymbol,
                            new List <SubscriptionDataConfig>(),
                            underlying: underlyingSecurity);
                        _securityManager.Add(security);
                    }

                    underlyingSecurity ??= security;

                    tickGenerators.Add(
                        new TickGenerator(_settings, tickTypesPerSecurityType[currentSymbol.SecurityType].ToArray(), security, randomValueGenerator)
                        .GenerateTicks()
                        .GetEnumerator());

                    tickHistories.Add(
                        currentSymbol,
                        new List <Tick>());
                }

                using var sync = new SynchronizingBaseDataEnumerator(tickGenerators);
                while (sync.MoveNext())
                {
                    var dataPoint = sync.Current;
                    if (!_securityManager.TryGetValue(dataPoint.Symbol, out var security))
                    {
                        Log.Error($"RandomDataGenerator.Run(): Could not find security for symbol {sync.Current.Symbol}");
                        continue;
                    }

                    tickHistories[security.Symbol].Add(dataPoint as Tick);
                    security.Update(new List <BaseData> {
                        dataPoint
                    }, dataPoint.GetType(), false);
                }

                foreach (var(currentSymbol, tickHistory) in tickHistories)
                {
                    var symbol = currentSymbol;

                    // This is done so that we can update the Symbol in the case of a rename event
                    var delistDate     = GetDelistingDate(_settings.Start, _settings.End, randomValueGenerator);
                    var willBeDelisted = randomValueGenerator.NextBool(1.0);

                    // Companies rarely IPO then disappear within 6 months
                    if (willBeDelisted && tickHistory.Select(tick => tick.Time.Month).Distinct().Count() <= 6)
                    {
                        willBeDelisted = false;
                    }

                    var dividendsSplitsMaps = new DividendSplitMapGenerator(
                        symbol,
                        _settings,
                        randomValueGenerator,
                        symbolGenerator,
                        random,
                        delistDate,
                        willBeDelisted);

                    // Keep track of renamed symbols and the time they were renamed.
                    var renamedSymbols = new Dictionary <Symbol, DateTime>();

                    if (_settings.SecurityType == SecurityType.Equity)
                    {
                        dividendsSplitsMaps.GenerateSplitsDividends(tickHistory);

                        if (!willBeDelisted)
                        {
                            dividendsSplitsMaps.DividendsSplits.Add(new CorporateFactorRow(new DateTime(2050, 12, 31), 1m, 1m));

                            if (dividendsSplitsMaps.MapRows.Count > 1)
                            {
                                // Remove the last element if we're going to have a 20501231 entry
                                dividendsSplitsMaps.MapRows.RemoveAt(dividendsSplitsMaps.MapRows.Count - 1);
                            }
                            dividendsSplitsMaps.MapRows.Add(new MapFileRow(new DateTime(2050, 12, 31), dividendsSplitsMaps.CurrentSymbol.Value));
                        }

                        // If the Symbol value has changed, update the current Symbol
                        if (symbol != dividendsSplitsMaps.CurrentSymbol)
                        {
                            // Add all Symbol rename events to dictionary
                            // We skip the first row as it contains the listing event instead of a rename event
                            foreach (var renameEvent in dividendsSplitsMaps.MapRows.Skip(1))
                            {
                                // Symbol.UpdateMappedSymbol does not update the underlying security ID Symbol, which
                                // is used to create the hash code. Create a new equity Symbol from scratch instead.
                                symbol = Symbol.Create(renameEvent.MappedSymbol, SecurityType.Equity, _settings.Market);
                                renamedSymbols.Add(symbol, renameEvent.Date);

                                Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} will be renamed on {renameEvent.Date}");
                            }
                        }
                        else
                        {
                            // This ensures that ticks will be written for the current Symbol up until 9999-12-31
                            renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
                        }

                        symbol = dividendsSplitsMaps.CurrentSymbol;

                        // Write Splits and Dividend events to directory factor_files
                        var factorFile = new CorporateFactorProvider(symbol.Value, dividendsSplitsMaps.DividendsSplits, _settings.Start);
                        var mapFile    = new MapFile(symbol.Value, dividendsSplitsMaps.MapRows);

                        factorFile.WriteToFile(symbol);
                        mapFile.WriteToCsv(_settings.Market, symbol.SecurityType);

                        Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} Dividends, splits, and map files have been written to disk.");
                    }
                    else
                    {
                        // This ensures that ticks will be written for the current Symbol up until 9999-12-31
                        renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
                    }

                    // define aggregators via settings
                    var    aggregators    = CreateAggregators(_settings, tickTypesPerSecurityType[currentSymbol.SecurityType].ToArray()).ToList();
                    Symbol previousSymbol = null;
                    var    currentCount   = 0;
                    var    monthsTrading  = 0;

                    foreach (var renamed in renamedSymbols)
                    {
                        var previousRenameDate    = previousSymbol == null ? new DateTime(1, 1, 1) : renamedSymbols[previousSymbol];
                        var previousRenameDateDay = new DateTime(previousRenameDate.Year, previousRenameDate.Month, previousRenameDate.Day);
                        var renameDate            = renamed.Value;
                        var renameDateDay         = new DateTime(renameDate.Year, renameDate.Month, renameDate.Day);

                        foreach (var tick in tickHistory.Where(tick => tick.Time >= previousRenameDate && previousRenameDateDay != TickDay(tick)))
                        {
                            // Prevents the aggregator from being updated with ticks after the rename event
                            if (TickDay(tick) > renameDateDay)
                            {
                                break;
                            }

                            if (tick.Time.Month != previousMonth)
                            {
                                Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: Month: {tick.Time:MMMM}");
                                previousMonth = tick.Time.Month;
                                monthsTrading++;
                            }

                            foreach (var item in aggregators)
                            {
                                tick.Value = tick.Value / dividendsSplitsMaps.FinalSplitFactor;
                                item.Consolidator.Update(tick);
                            }

                            if (monthsTrading >= 6 && willBeDelisted && tick.Time > delistDate)
                            {
                                Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {renamed.Key} delisted at {tick.Time:MMMM yyyy}");
                                break;
                            }
                        }

                        // count each stage as a point, so total points is 2*Symbol-count
                        // and the current progress is twice the current, but less one because we haven't finished writing data yet
                        progress = 100 * (2 * count - 1) / (2.0 * _settings.SymbolCount);

                        Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {renamed.Key} Progress: {progress:0.0}% - Saving data in LEAN format");

                        // persist consolidated data to disk
                        foreach (var item in aggregators)
                        {
                            var writer = new LeanDataWriter(item.Resolution, renamed.Key, Globals.DataFolder, item.TickType);

                            // send the flushed data into the writer. pulling the flushed list is very important,
                            // lest we likely wouldn't get the last piece of data stuck in the consolidator
                            // Filter out the data we're going to write here because filtering them in the consolidator update phase
                            // makes it write all dates for some unknown reason
                            writer.Write(item.Flush().Where(data => data.Time > previousRenameDate && previousRenameDateDay != DataDay(data)));
                        }

                        // update progress
                        progress = 100 * (2 * count) / (2.0 * _settings.SymbolCount);
                        Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} Progress: {progress:0.0}% - Symbol data generation and output completed");

                        previousSymbol = renamed.Key;
                        currentCount++;
                    }
                }
            }

            Log.Trace("RandomDataGenerator.Run(): Random data generation has completed.");

            DateTime TickDay(Tick tick) => new(tick.Time.Year, tick.Time.Month, tick.Time.Day);
            DateTime DataDay(BaseData data) => new(data.Time.Year, data.Time.Month, data.Time.Day);
        }
        /// <summary>
        /// Generates the factor file row.
        /// </summary>
        /// <param name="ticker">The ticker.</param>
        /// <param name="sidContext">The sid context.</param>
        /// <param name="factorFile">The factor file.</param>
        /// <param name="tradeBar">The trade bar.</param>
        /// <param name="fineAvailableDates">The fine available dates.</param>
        /// <param name="fineFundamentalFolder">The fine fundamental folder.</param>
        /// <returns></returns>
        private static string GenerateFactorFileRow(string ticker, SecurityIdentifierContext sidContext, CorporateFactorProvider factorFile, TradeBar tradeBar, IEnumerable <DateTime> fineAvailableDates, DirectoryInfo fineFundamentalFolder)
        {
            var  date               = tradeBar.Time;
            var  factorFileRow      = factorFile?.GetScalingFactors(date);
            var  dollarVolume       = Math.Truncate(tradeBar.Close * tradeBar.Volume);
            var  priceFactor        = factorFileRow?.PriceFactor.Normalize() ?? 1m;
            var  splitFactor        = factorFileRow?.SplitFactor.Normalize() ?? 1m;
            bool hasFundamentalData = CheckFundamentalData(date, sidContext.MapFile, fineAvailableDates, fineFundamentalFolder);

            // sid,symbol,close,volume,dollar volume,has fundamental data,price factor,split factor
            var coarseFileLine = $"{sidContext.SID},{ticker.ToUpperInvariant()},{tradeBar.Close.Normalize()},{tradeBar.Volume.Normalize()},{Math.Truncate(dollarVolume)},{hasFundamentalData},{priceFactor},{splitFactor}";

            return(coarseFileLine);
        }