/* * 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.IO; using System.Linq; using QuantConnect.Data.Market; using QuantConnect.Logging; using QuantConnect.Securities; using QuantConnect.Util; namespace QuantConnect.Data.Auxiliary { /// /// Represents an entire factor file for a specified symbol /// public class FactorFile : IEnumerable { /// /// Keeping a reversed version is more performant that reversing it each time we need it /// private readonly List _reversedFactorFileDates; /// /// The factor file data rows sorted by date /// public SortedList SortedFactorFileData { get; set; } /// /// The minimum tradeable date for the symbol /// /// /// Some factor files have INF split values, indicating that the stock has so many splits /// that prices can't be calculated with correct numerical precision. /// To allow backtesting these symbols, we need to move the starting date /// forward when reading the data. /// Known symbols: GBSN, JUNI, NEWL /// public DateTime? FactorFileMinimumDate { get; set; } /// /// Gets the most recent factor change in the factor file /// public DateTime MostRecentFactorChange => _reversedFactorFileDates .FirstOrDefault(time => time != Time.EndOfTime); /// /// Gets the symbol this factor file represents /// public string Permtick { get; } /// /// Initializes a new instance of the class. /// public FactorFile(string permtick, IEnumerable data, DateTime? factorFileMinimumDate = null) { Permtick = permtick.LazyToUpper(); var dictionary = new Dictionary(); foreach (var row in data) { if (dictionary.ContainsKey(row.Date)) { Log.Trace($"Skipping duplicate factor file row for symbol: {permtick}, date: {row.Date:yyyyMMdd}"); continue; } dictionary.Add(row.Date, row); } SortedFactorFileData = new SortedList(dictionary); _reversedFactorFileDates = new List(); foreach (var time in SortedFactorFileData.Keys.Reverse()) { _reversedFactorFileDates.Add(time); } FactorFileMinimumDate = factorFileMinimumDate; } /// /// Reads a FactorFile in from the . /// public static FactorFile Read(string permtick, string market) { DateTime? factorFileMinimumDate; return new FactorFile(permtick, FactorFileRow.Read(permtick, market, out factorFileMinimumDate), factorFileMinimumDate); } /// /// Parses the specified lines as a factor file /// public static FactorFile Parse(string permtick, IEnumerable lines) { DateTime? factorFileMinimumDate; return new FactorFile(permtick, FactorFileRow.Parse(lines, out factorFileMinimumDate), factorFileMinimumDate); } /// /// Gets the price scale factor that includes dividend and split adjustments for the specified search date /// public decimal GetPriceScaleFactor(DateTime searchDate) { decimal factor = 1; //Iterate backwards to find the most recent factor: foreach (var splitDate in _reversedFactorFileDates) { if (splitDate.Date < searchDate.Date) break; factor = SortedFactorFileData[splitDate].PriceScaleFactor; } return factor; } /// /// Gets the split factor to be applied at the specified date /// public decimal GetSplitFactor(DateTime searchDate) { decimal factor = 1; //Iterate backwards to find the most recent factor: foreach (var splitDate in _reversedFactorFileDates) { if (splitDate.Date < searchDate.Date) break; factor = SortedFactorFileData[splitDate].SplitFactor; } return factor; } /// /// Gets price and split factors to be applied at the specified date /// public FactorFileRow GetScalingFactors(DateTime searchDate) { var factors = new FactorFileRow(searchDate, 1m, 1m, 0m); // Iterate backwards to find the most recent factors foreach (var splitDate in _reversedFactorFileDates) { if (splitDate.Date < searchDate.Date) break; factors = SortedFactorFileData[splitDate]; } return factors; } /// /// Checks whether or not a symbol has scaling factors /// public static bool HasScalingFactors(string permtick, string market) { // check for factor files var path = Path.Combine(Globals.DataFolder, "equity", market, "factor_files", permtick.ToLower() + ".csv"); if (File.Exists(path)) { return true; } Log.Trace("FactorFile.HasScalingFactors(): Factor file not found: " + permtick); return false; } /// /// Returns true if the specified date is the last trading day before a dividend event /// is to be fired /// /// /// NOTE: The dividend event in the algorithm should be fired at the end or AFTER /// this date. This is the date in the file that a factor is applied, so for example, /// MSFT has a 31 cent dividend on 2015.02.17, but in the factor file the factor is applied /// to 2015.02.13, which is the first trading day BEFORE the actual effective date. /// /// The date to check the factor file for a dividend event /// When this function returns true, this value will be populated /// with the price factor ratio required to scale the closing value (pf_i/pf_i+1) public bool HasDividendEventOnNextTradingDay(DateTime date, out decimal priceFactorRatio) { priceFactorRatio = 0; var index = SortedFactorFileData.IndexOfKey(date); if (index > -1 && index < SortedFactorFileData.Count - 1) { // grab the next key to ensure it's a dividend event var thisRow = SortedFactorFileData.Values[index]; var nextRow = SortedFactorFileData.Values[index + 1]; // if the price factors have changed then it's a dividend event if (thisRow.PriceFactor != nextRow.PriceFactor) { priceFactorRatio = thisRow.PriceFactor/nextRow.PriceFactor; return true; } } return false; } /// /// Returns true if the specified date is the last trading day before a split event /// is to be fired /// /// /// NOTE: The split event in the algorithm should be fired at the end or AFTER this /// date. This is the date in the file that a factor is applied, so for example MSFT /// has a split on 1999.03.29, but in the factor file the split factor is applied on /// 1999.03.26, which is the first trading day BEFORE the actual split date. /// public bool HasSplitEventOnNextTradingDay(DateTime date, out decimal splitFactor) { splitFactor = 1; var index = SortedFactorFileData.IndexOfKey(date); if (index > -1 && index < SortedFactorFileData.Count - 1) { // grab the next key to ensure it's a split event var thisRow = SortedFactorFileData.Values[index]; var nextRow = SortedFactorFileData.Values[index + 1]; // if the split factors have changed then it's a split event if (thisRow.SplitFactor != nextRow.SplitFactor) { splitFactor = thisRow.SplitFactor/nextRow.SplitFactor; return true; } } return false; } /// /// Writes this factor file data to an enumerable of csv lines /// /// An enumerable of lines representing this factor file public IEnumerable ToCsvLines() { foreach (var kvp in SortedFactorFileData) { yield return kvp.Value.ToCsv(); } } /// /// Write the factor file to the correct place in the default Data folder /// /// The symbol this factor file represents public void WriteToCsv(Symbol symbol) { var filePath = LeanData.GenerateRelativeFactorFilePath(symbol); File.WriteAllLines(filePath, ToCsvLines()); } /// /// Gets all of the splits and dividends represented by this factor file /// /// The symbol to ues for the dividend and split objects /// Exchange hours used for resolving the previous trading day /// All splits and diviends represented by this factor file in chronological order public List GetSplitsAndDividends(Symbol symbol, SecurityExchangeHours exchangeHours) { var dividendsAndSplits = new List(); if (SortedFactorFileData.Count == 0) { Log.Trace($"{symbol} has no factors!"); return dividendsAndSplits; } var futureFactorFileRow = SortedFactorFileData.Last().Value; for (var i = SortedFactorFileData.Count - 2; i >= 0 ; i--) { var row = SortedFactorFileData.Values[i]; var dividend = row.GetDividend(futureFactorFileRow, symbol, exchangeHours); if (dividend.Distribution != 0m) { dividendsAndSplits.Add(dividend); } var split = row.GetSplit(futureFactorFileRow, symbol, exchangeHours); if (split.SplitFactor != 1m) { dividendsAndSplits.Add(split); } futureFactorFileRow = row; } return dividendsAndSplits.OrderBy(d => d.Time.Date).ToList(); } /// /// Creates a new factor file with the specified data applied. /// Only and data types /// will be used. /// /// The data to apply /// Exchange hours used for resolving the previous trading day /// A new factor file that incorporates the specified dividend public FactorFile Apply(List data, SecurityExchangeHours exchangeHours) { if (data.Count == 0) { return this; } var factorFileRows = new List(); var lastEntry = SortedFactorFileData.Last().Value; factorFileRows.Add(lastEntry); var combinedData = GetSplitsAndDividends(data[0].Symbol, exchangeHours).Concat(data) .OrderByDescending(d => d.Time.Date); foreach (var datum in combinedData) { FactorFileRow nextEntry = null; var split = datum as Split; var dividend = datum as Dividend; if (dividend != null) { nextEntry = lastEntry.Apply(dividend, exchangeHours); lastEntry = nextEntry; } else if (split != null) { nextEntry = lastEntry.Apply(split, exchangeHours); lastEntry = nextEntry; } if (nextEntry != null) { // overwrite the latest entry -- this handles splits/dividends on the same date if (nextEntry.Date == factorFileRows.Last().Date) { factorFileRows[factorFileRows.Count - 1] = nextEntry; } else { factorFileRows.Add(nextEntry); } } } return new FactorFile(Permtick, factorFileRows, FactorFileMinimumDate); } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. /// 1 public IEnumerator GetEnumerator() { foreach (var kvp in SortedFactorFileData) { yield return kvp.Value; } } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }