/// <summary> /// Creates a new factor file with the specified data applied. /// Only <see cref="Dividend"/> and <see cref="Split"/> data types /// will be used. /// </summary> /// <param name="data">The data to apply</param> /// <param name="exchangeHours">Exchange hours used for resolving the previous trading day</param> /// <returns>A new factor file that incorporates the specified dividend</returns> public CorporateFactorProvider Apply(List <BaseData> data, SecurityExchangeHours exchangeHours) { if (data.Count == 0) { return(this); } var factorFileRows = new List <CorporateFactorRow>(); var firstEntry = SortedFactorFileData.First().Value.First(); var lastEntry = SortedFactorFileData.Last().Value.First(); factorFileRows.Add(lastEntry); var splitsAndDividends = GetSplitsAndDividends(data[0].Symbol, exchangeHours); var combinedData = splitsAndDividends.Concat(data) .DistinctBy(e => $"{e.GetType().Name}{e.Time.ToStringInvariant(DateFormat.EightCharacter)}") .OrderByDescending(d => d.Time.Date); foreach (var datum in combinedData) { CorporateFactorRow 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); } } } var firstFactorFileRow = new CorporateFactorRow(firstEntry.Date, factorFileRows.Last().PriceFactor, factorFileRows.Last().SplitFactor, firstEntry.ReferencePrice == 0 ? 0 : firstEntry.ReferencePrice); var existing = factorFileRows.FindIndex(row => row.Date == firstFactorFileRow.Date); if (existing == -1) { // only add it if not present factorFileRows.Add(firstFactorFileRow); } return(new CorporateFactorProvider(Permtick, factorFileRows, FactorFileMinimumDate)); }
/// <summary> /// Gets all of the splits and dividends represented by this factor file /// </summary> /// <param name="symbol">The symbol to ues for the dividend and split objects</param> /// <param name="exchangeHours">Exchange hours used for resolving the previous trading day</param> /// <returns>All splits and diviends represented by this factor file in chronological order</returns> public List <BaseData> GetSplitsAndDividends(Symbol symbol, SecurityExchangeHours exchangeHours) { var dividendsAndSplits = new List <BaseData>(); 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()); }
/// <summary> /// Writes this factor file data to an enumerable of csv lines /// </summary> /// <returns>An enumerable of lines representing this factor file</returns> public IEnumerable <string> ToCsvLines() { if (FactorFileMinimumDate != null) { var min = SortedFactorFileData.First().Value; yield return($"{FactorFileMinimumDate:yyyyMMdd},{min.PriceFactor},{min.SplitFactor}"); } foreach (var kvp in SortedFactorFileData) { yield return($"{kvp.Key:yyyyMMdd},{kvp.Value.PriceFactor},{kvp.Value.SplitFactor}"); } }
/// <summary> /// Creates a new factor file with the specified data applied. /// Only <see cref="Dividend"/> and <see cref="Split"/> data types /// will be used. /// </summary> /// <param name="data">The data to apply</param> /// <param name="exchangeHours">Exchange hours used for resolving the previous trading day</param> /// <returns>A new factor file that incorporates the specified dividend</returns> public FactorFile Apply(List <BaseData> data, SecurityExchangeHours exchangeHours) { if (data.Count == 0) { return(this); } var factorFileRows = new List <FactorFileRow>(); 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)); }
/// <summary> /// Returns true if the specified date is the last trading day before a split event /// is to be fired /// </summary> /// <remarks> /// 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. /// </remarks> 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); }
/// <summary> /// Returns true if the specified date is the last trading day before a dividend event /// is to be fired /// </summary> /// <remarks> /// 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. /// </remarks> /// <param name="date">The date to check the factor file for a dividend event</param> /// <param name="priceFactorRatio">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)</param> 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); }
/// <summary> /// Writes this factor file data to an enumerable of csv lines /// </summary> /// <returns>An enumerable of lines representing this factor file</returns> public IEnumerable <string> GetFileFormat() { return(SortedFactorFileData.SelectMany(kvp => kvp.Value.Select(row => row.GetFileFormat()))); }