Lean  $LEAN_TAG$
DividendYieldProvider.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Linq;
18 using QuantConnect.Util;
19 using System.Threading.Tasks;
21 using System.Collections.Generic;
25 
26 namespace QuantConnect.Data
27 {
28  /// <summary>
29  /// Estimated annualized continuous dividend yield at given date
30  /// </summary>
32  {
33  private static MarketHoursDatabase _marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
34 
35  /// <summary>
36  /// The default symbol to use as a dividend yield provider
37  /// </summary>
38  /// <remarks>This is useful for index and future options which do not have an underlying that yields dividends.
39  /// Defaults to SPY</remarks>
40  public static Symbol DefaultSymbol { get; set; } = Symbol.Create("SPY", SecurityType.Equity, QuantConnect.Market.USA);
41 
42  /// <summary>
43  /// The dividends by symbol
44  /// </summary>
45  protected static Dictionary<Symbol, List<BaseData>> _corporateEventsCache;
46 
47  /// <summary>
48  /// Task to clear the cache
49  /// </summary>
50  protected static Task _cacheClearTask;
51  private static readonly object _lock = new();
52 
53  private readonly Symbol _symbol;
54  private readonly SecurityExchangeHours _exchangeHours;
55 
56  /// <summary>
57  /// Default no dividend payout
58  /// </summary>
59  public static readonly decimal DefaultDividendYieldRate = 0.0m;
60 
61  /// <summary>
62  /// The cached refresh period for the dividend yield rate
63  /// </summary>
64  /// <remarks>Exposed for testing</remarks>
65  protected virtual TimeSpan CacheRefreshPeriod
66  {
67  get
68  {
69  var dueTime = Time.GetNextLiveAuxiliaryDataDueTime();
70  if (dueTime > TimeSpan.FromMinutes(10))
71  {
72  // Clear the cache before the auxiliary due time to avoid race conditions with consumers
73  return dueTime - TimeSpan.FromMinutes(10);
74  }
75  return dueTime;
76  }
77  }
78 
79  /// <summary>
80  /// Creates a new instance using the default symbol
81  /// </summary>
83  {
84  }
85 
86  /// <summary>
87  /// Instantiates a <see cref="DividendYieldProvider"/> with the specified Symbol
88  /// </summary>
90  {
91  _symbol = symbol;
92  _exchangeHours = _marketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.ID.SecurityType);
93 
94  if (_cacheClearTask == null)
95  {
96  lock (_lock)
97  {
98  // only the first triggers the expiration task check
99  if (_cacheClearTask == null)
100  {
101  StartExpirationTask(CacheRefreshPeriod);
102  }
103  }
104  }
105  }
106 
107  /// <summary>
108  /// Creates a new instance for the given option symbol
109  /// </summary>
110  public static IDividendYieldModel CreateForOption(Symbol optionSymbol)
111  {
112  if (optionSymbol.SecurityType == SecurityType.Option)
113  {
114  return new DividendYieldProvider(optionSymbol.Underlying);
115  }
116 
117  if (optionSymbol.SecurityType == SecurityType.IndexOption)
118  {
119  return optionSymbol.Value switch
120  {
121  "SPX" => new DividendYieldProvider(Symbol.Create("SPY", SecurityType.Equity, QuantConnect.Market.USA)),
122  "NDX" => new DividendYieldProvider(Symbol.Create("QQQ", SecurityType.Equity, QuantConnect.Market.USA)),
123  "VIX" => new ConstantDividendYieldModel(0),
124  _ => new DividendYieldProvider()
125  };
126  }
127 
128  return new ConstantDividendYieldModel(0);
129  }
130 
131  /// <summary>
132  /// Helper method that will clear any cached dividend rate in a daily basis, this is useful for live trading
133  /// </summary>
134  private static void StartExpirationTask(TimeSpan cacheRefreshPeriod)
135  {
136  lock (_lock)
137  {
138  // we clear the dividend yield rate cache so they are reloaded
139  _corporateEventsCache = new();
140  }
141  _cacheClearTask = Task.Delay(cacheRefreshPeriod).ContinueWith(_ => StartExpirationTask(cacheRefreshPeriod));
142  }
143 
144  /// <summary>
145  /// Get dividend yield by a given date of a given symbol.
146  /// It will get the dividend yield at the time of the most recent dividend since no price is provided.
147  /// In order to get more accurate dividend yield, provide the security price at the given date to
148  /// the <see cref="GetDividendYield(DateTime, decimal)"/> or <see cref="GetDividendYield(IBaseData)"/> methods.
149  /// </summary>
150  /// <param name="date">The date</param>
151  /// <returns>Dividend yield on the given date of the given symbol</returns>
152  public decimal GetDividendYield(DateTime date)
153  {
154  return GetDividendYieldImpl(date, null);
155  }
156 
157  /// <summary>
158  /// Gets the dividend yield at the date of the specified data, using the data price as the security price
159  /// </summary>
160  /// <param name="priceData">Price data instance</param>
161  /// <returns>Dividend yield on the given date of the given symbol</returns>
162  /// <remarks>Price data must be raw (<see cref="DataNormalizationMode.Raw"/>)</remarks>
163  public decimal GetDividendYield(IBaseData priceData)
164  {
165  if (priceData.Symbol != _symbol)
166  {
167  throw new ArgumentException($"Trying to get {priceData.Symbol} dividend yield using the {_symbol} dividend yield provider.");
168  }
169 
170  return GetDividendYield(priceData.EndTime, priceData.Value);
171  }
172 
173  /// <summary>
174  /// Get dividend yield at given date and security price
175  /// </summary>
176  /// <param name="date">The date</param>
177  /// <param name="securityPrice">The security price at the given date</param>
178  /// <returns>Dividend yield on the given date of the given symbol</returns>
179  /// <remarks>Price data must be raw (<see cref="DataNormalizationMode.Raw"/>)</remarks>
180  public decimal GetDividendYield(DateTime date, decimal securityPrice)
181  {
182  return GetDividendYieldImpl(date, securityPrice);
183  }
184 
185  /// <summary>
186  /// Get dividend yield at given date and security price.
187  /// </summary>
188  /// <remarks>
189  /// <paramref name="securityPrice"/> is nullable for backwards compatibility, so <see cref="GetDividendYield(DateTime)"/> is usable.
190  /// If dividend yield is requested at a given date without a price, the dividend yield at the time of the most recent dividend is returned.
191  /// Price data must be raw (<see cref="DataNormalizationMode.Raw"/>).
192  /// </remarks>
193  private decimal GetDividendYieldImpl(DateTime date, decimal? securityPrice)
194  {
195  List<BaseData> symbolCorporateEvents;
196  lock (_lock)
197  {
198  if (!_corporateEventsCache.TryGetValue(_symbol, out symbolCorporateEvents))
199  {
200  // load the symbol factor if it is the first encounter
201  symbolCorporateEvents = _corporateEventsCache[_symbol] = LoadCorporateEvents(_symbol);
202  }
203  }
204 
205  if (symbolCorporateEvents == null)
206  {
208  }
209 
210  // We need both corporate event types, so we get the most recent one, either dividend or split
211  var mostRecentCorporateEventIndex = symbolCorporateEvents.FindLastIndex(x => x.EndTime <= date.Date);
212  if (mostRecentCorporateEventIndex == -1)
213  {
215  }
216 
217  // Now we get the most recent dividend in order to get the end of the trailing twelve months period for the dividend yield
218  var mostRecentCorporateEvent = symbolCorporateEvents[mostRecentCorporateEventIndex];
219  var mostRecentDividend = mostRecentCorporateEvent as Dividend;
220  if (mostRecentDividend == null)
221  {
222  for (var i = mostRecentCorporateEventIndex - 1; i >= 0; i--)
223  {
224  if (symbolCorporateEvents[i] is Dividend dividend)
225  {
226  mostRecentDividend = dividend;
227  break;
228  }
229  }
230  }
231 
232  // If there is no dividend in the past year, we return the default dividend yield rate
233  if (mostRecentDividend == null)
234  {
236  }
237 
238  securityPrice ??= mostRecentDividend.ReferencePrice;
239  if (securityPrice == 0)
240  {
241  throw new ArgumentException("Security price cannot be zero.");
242  }
243 
244  // The dividend yield is the sum of the dividends in the past year (ending in the most recent dividend date,
245  // not on the price quote date) divided by the last close price:
246 
247  // 15 days window from 1y to avoid overestimation from last year value
248  var trailingYearStartDate = mostRecentDividend.EndTime.AddDays(-350);
249 
250  var yearlyDividend = 0m;
251  var currentSplitFactor = 1m;
252  for (var i = mostRecentCorporateEventIndex; i >= 0; i--)
253  {
254  var corporateEvent = symbolCorporateEvents[i];
255  if (corporateEvent.EndTime < trailingYearStartDate)
256  {
257  break;
258  }
259 
260  if (corporateEvent is Dividend dividend)
261  {
262  yearlyDividend += dividend.Distribution * currentSplitFactor;
263  }
264  else
265  {
266  // Update the split factor to adjust the dividend value per share
267  currentSplitFactor *= ((Split)corporateEvent).SplitFactor;
268  }
269  }
270 
271  return yearlyDividend / securityPrice.Value;
272  }
273 
274  /// <summary>
275  /// Generate the corporate events from the corporate factor file for the specified symbol
276  /// </summary>
277  /// <remarks>Exposed for testing</remarks>
278  protected virtual List<BaseData> LoadCorporateEvents(Symbol symbol)
279  {
280  var factorFileProvider = Composer.Instance.GetPart<IFactorFileProvider>();
281  var corporateFactors = factorFileProvider
282  .Get(symbol)
283  .Select(factorRow => factorRow as CorporateFactorRow)
284  .Where(corporateFactor => corporateFactor != null);
285 
286  var symbolCorporateEvents = FromCorporateFactorRows(corporateFactors, symbol).ToList();
287  if (symbolCorporateEvents.Count == 0)
288  {
289  return null;
290  }
291 
292  return symbolCorporateEvents;
293  }
294 
295  /// <summary>
296  /// Generates the splits and dividends from the corporate factor rows
297  /// </summary>
298  private IEnumerable<BaseData> FromCorporateFactorRows(IEnumerable<CorporateFactorRow> corporateFactors, Symbol symbol)
299  {
300  var dividends = new List<Dividend>();
301 
302  // Get all dividends from the corporate actions
303  var rows = corporateFactors.OrderBy(corporateFactor => corporateFactor.Date).ToArray();
304  for (var i = 0; i < rows.Length - 1; i++)
305  {
306  var row = rows[i];
307  var nextRow = rows[i + 1];
308  if (row.PriceFactor != nextRow.PriceFactor)
309  {
310  yield return row.GetDividend(nextRow, symbol, _exchangeHours, decimalPlaces: 3);
311  }
312  else
313  {
314  yield return row.GetSplit(nextRow, symbol, _exchangeHours);
315  }
316 
317  }
318  }
319  }
320 }