Lean  $LEAN_TAG$
Insight.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.Collections.Generic;
18 using Newtonsoft.Json;
22 
24 {
25  /// <summary>
26  /// Defines a alpha prediction for a single symbol generated by the algorithm
27  /// </summary>
28  /// <remarks>
29  /// Serialization of this type is delegated to the <see cref="InsightJsonConverter"/> which uses the <see cref="SerializedInsight"/> as a model.
30  /// </remarks>
31  [JsonConverter(typeof(InsightJsonConverter))]
32  public class Insight
33  {
34  private readonly IPeriodSpecification _periodSpecification;
35 
36  /// <summary>
37  /// Gets the unique identifier for this insight
38  /// </summary>
39  public Guid Id { get; protected set; }
40 
41  /// <summary>
42  /// Gets the group id this insight belongs to, null if not in a group
43  /// </summary>
44  public Guid? GroupId { get; protected set; }
45 
46  /// <summary>
47  /// Gets an identifier for the source model that generated this insight.
48  /// </summary>
49  public string SourceModel { get; set; }
50 
51  /// <summary>
52  /// Gets the utc time this insight was generated
53  /// </summary>
54  /// <remarks>
55  /// The algorithm framework handles setting this value appropriately.
56  /// If providing custom <see cref="Insight"/> implementation, be sure
57  /// to set this value to algorithm.UtcTime when the insight is generated.
58  /// </remarks>
59  public DateTime GeneratedTimeUtc { get; set; }
60 
61  /// <summary>
62  /// Gets the insight's prediction end time. This is the time when this
63  /// insight prediction is expected to be fulfilled. This time takes into
64  /// account market hours, weekends, as well as the symbol's data resolution
65  /// </summary>
66  public DateTime CloseTimeUtc { get; set; }
67 
68  /// <summary>
69  /// Gets the symbol this insight is for
70  /// </summary>
71  public Symbol Symbol { get; private set; }
72 
73  /// <summary>
74  /// Gets the type of insight, for example, price insight or volatility insight
75  /// </summary>
76  public InsightType Type { get; private set; }
77 
78  /// <summary>
79  /// Gets the initial reference value this insight is predicting against. The value is dependent on the specified <see cref="InsightType"/>
80  /// </summary>
81  public decimal ReferenceValue { get; set; }
82 
83  /// <summary>
84  /// Gets the final reference value, used for scoring, this insight is predicting against. The value is dependent on the specified <see cref="InsightType"/>
85  /// </summary>
86  public decimal ReferenceValueFinal { get; set; }
87 
88  /// <summary>
89  /// Gets the predicted direction, down, flat or up
90  /// </summary>
91  public InsightDirection Direction { get; private set; }
92 
93  /// <summary>
94  /// Gets the period over which this insight is expected to come to fruition
95  /// </summary>
96  public TimeSpan Period { get; internal set; }
97 
98  /// <summary>
99  /// Gets the predicted percent change in the insight type (price/volatility)
100  /// </summary>
101  public double? Magnitude { get; private set; }
102 
103  /// <summary>
104  /// Gets the confidence in this insight
105  /// </summary>
106  public double? Confidence { get; private set; }
107 
108  /// <summary>
109  /// Gets the portfolio weight of this insight
110  /// </summary>
111  public double? Weight { get; private set; }
112 
113  /// <summary>
114  /// Gets the most recent scores for this insight
115  /// </summary>
116  public InsightScore Score { get; protected set; }
117 
118  /// <summary>
119  /// Gets the estimated value of this insight in the account currency
120  /// </summary>
121  public decimal EstimatedValue { get; set; }
122 
123  /// <summary>
124  /// The insight's tag containing additional information
125  /// </summary>
126  public string Tag { get; protected set; }
127 
128  /// <summary>
129  /// Determines whether or not this insight is considered expired at the specified <paramref name="utcTime"/>
130  /// </summary>
131  /// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
132  /// <returns>True if this insight is expired, false otherwise</returns>
133  public bool IsExpired(DateTime utcTime)
134  {
135  return CloseTimeUtc < utcTime;
136  }
137 
138  /// <summary>
139  /// Determines whether or not this insight is considered active at the specified <paramref name="utcTime"/>
140  /// </summary>
141  /// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
142  /// <returns>True if this insight is active, false otherwise</returns>
143  public bool IsActive(DateTime utcTime)
144  {
145  return !IsExpired(utcTime);
146  }
147 
148  /// <summary>
149  /// Expire this insight
150  /// </summary>
151  /// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
152  public void Expire(DateTime utcTime)
153  {
154  if (IsActive(utcTime))
155  {
156  CloseTimeUtc = utcTime.Add(-Time.OneSecond);
158  }
159  }
160 
161  /// <summary>
162  /// Cancel this insight
163  /// </summary>
164  /// <param name="utcTime">The algorithm's current time in UTC. See <see cref="IAlgorithm.UtcTime"/></param>
165  public void Cancel(DateTime utcTime)
166  {
167  Expire(utcTime);
168  }
169 
170  /// <summary>
171  /// Initializes a new instance of the <see cref="Insight"/> class
172  /// </summary>
173  /// <param name="symbol">The symbol this insight is for</param>
174  /// <param name="period">The period over which the prediction will come true</param>
175  /// <param name="type">The type of insight, price/volatility</param>
176  /// <param name="direction">The predicted direction</param>
177  /// <param name="tag">The insight's tag containing additional information</param>
178  public Insight(Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, string tag = "")
179  : this(symbol, period, type, direction, null, null, tag)
180  {
181  }
182 
183  /// <summary>
184  /// Initializes a new instance of the <see cref="Insight"/> class
185  /// </summary>
186  /// <param name="symbol">The symbol this insight is for</param>
187  /// <param name="period">The period over which the prediction will come true</param>
188  /// <param name="type">The type of insight, price/volatility</param>
189  /// <param name="direction">The predicted direction</param>
190  /// <param name="magnitude">The predicted magnitude as a percentage change</param>
191  /// <param name="confidence">The confidence in this insight</param>
192  /// <param name="sourceModel">An identifier defining the model that generated this insight</param>
193  /// <param name="weight">The portfolio weight of this insight</param>
194  /// <param name="tag">The insight's tag containing additional information</param>
195  public Insight(Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
196  {
197  Id = Guid.NewGuid();
198  Score = new InsightScore();
199  SourceModel = sourceModel;
200 
201  Symbol = symbol;
202  Type = type;
203  Direction = direction;
204  Period = period;
205 
206  // Optional
207  Magnitude = magnitude;
208  Confidence = confidence;
209  Weight = weight;
210  Tag = tag;
211 
212  _periodSpecification = new TimeSpanPeriodSpecification(period);
213  }
214 
215  /// <summary>
216  /// Initializes a new instance of the <see cref="Insight"/> class
217  /// </summary>
218  /// <param name="symbol">The symbol this insight is for</param>
219  /// <param name="expiryFunc">Func that defines the expiry time</param>
220  /// <param name="type">The type of insight, price/volatility</param>
221  /// <param name="direction">The predicted direction</param>
222  /// <param name="tag">The insight's tag containing additional information</param>
223  public Insight(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightType type, InsightDirection direction, string tag = "")
224  : this(symbol, expiryFunc, type, direction, null, null, tag)
225  {
226  }
227 
228  /// <summary>
229  /// Initializes a new instance of the <see cref="Insight"/> class
230  /// </summary>
231  /// <param name="symbol">The symbol this insight is for</param>
232  /// <param name="expiryFunc">Func that defines the expiry time</param>
233  /// <param name="type">The type of insight, price/volatility</param>
234  /// <param name="direction">The predicted direction</param>
235  /// <param name="magnitude">The predicted magnitude as a percentage change</param>
236  /// <param name="confidence">The confidence in this insight</param>
237  /// <param name="sourceModel">An identifier defining the model that generated this insight</param>
238  /// <param name="weight">The portfolio weight of this insight</param>
239  /// <param name="tag">The insight's tag containing additional information</param>
240  public Insight(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
241  : this(symbol, new FuncPeriodSpecification(expiryFunc), type, direction, magnitude, confidence, sourceModel, weight, tag)
242  {
243  }
244 
245  /// <summary>
246  /// Initializes a new instance of the <see cref="Insight"/> class.
247  /// This constructor is provided mostly for testing purposes. When running inside an algorithm,
248  /// the generated and close times are set based on the algorithm's time.
249  /// </summary>
250  /// <param name="generatedTimeUtc">The time this insight was generated in utc</param>
251  /// <param name="symbol">The symbol this insight is for</param>
252  /// <param name="period">The period over which the prediction will come true</param>
253  /// <param name="type">The type of insight, price/volatility</param>
254  /// <param name="direction">The predicted direction</param>
255  /// <param name="magnitude">The predicted magnitude as a percentage change</param>
256  /// <param name="confidence">The confidence in this insight</param>
257  /// <param name="sourceModel">An identifier defining the model that generated this insight</param>
258  /// <param name="weight">The portfolio weight of this insight</param>
259  /// <param name="tag">The insight's tag containing additional information</param>
260  public Insight(DateTime generatedTimeUtc, Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
261  : this(symbol, period, type, direction, magnitude, confidence, sourceModel, weight, tag)
262  {
263  GeneratedTimeUtc = generatedTimeUtc;
264  CloseTimeUtc = generatedTimeUtc + period;
265  }
266 
267  /// <summary>
268  /// Private constructor used to keep track of how a user defined the insight period.
269  /// </summary>
270  /// <param name="symbol">The symbol this insight is for</param>
271  /// <param name="periodSpec">A specification defining how the insight's period was defined, via time span, via resolution/barcount, via close time</param>
272  /// <param name="type">The type of insight, price/volatility</param>
273  /// <param name="direction">The predicted direction</param>
274  /// <param name="magnitude">The predicted magnitude as a percentage change</param>
275  /// <param name="confidence">The confidence in this insight</param>
276  /// <param name="sourceModel">An identifier defining the model that generated this insight</param>
277  /// <param name="weight">The portfolio weight of this insight</param>
278  /// <param name="tag">The insight's tag containing additional information</param>
279  private Insight(Symbol symbol, IPeriodSpecification periodSpec, InsightType type, InsightDirection direction, double? magnitude, double? confidence, string sourceModel = null, double? weight = null, string tag = "")
280  {
281  Id = Guid.NewGuid();
282  Score = new InsightScore();
283  SourceModel = sourceModel;
284 
285  Symbol = symbol;
286  Type = type;
287  Direction = direction;
288 
289  // Optional
290  Magnitude = magnitude;
291  Confidence = confidence;
292  Weight = weight;
293  Tag = tag;
294 
295  _periodSpecification = periodSpec;
296 
297  // keep existing behavior of Insight.Price such that we set the period immediately
298  var period = (periodSpec as TimeSpanPeriodSpecification)?.Period;
299  if (period != null)
300  {
301  Period = period.Value;
302  }
303  }
304 
305  /// <summary>
306  /// Sets the insight period and close times if they have not already been set.
307  /// </summary>
308  /// <param name="exchangeHours">The insight's security exchange hours</param>
309  public void SetPeriodAndCloseTime(SecurityExchangeHours exchangeHours)
310  {
311  if (GeneratedTimeUtc == default(DateTime))
312  {
313  throw new InvalidOperationException(Messages.Insight.GeneratedTimeUtcNotSet(this));
314  }
315 
316  _periodSpecification.SetPeriodAndCloseTime(this, exchangeHours);
317  }
318 
319  /// <summary>
320  /// Creates a deep clone of this insight instance
321  /// </summary>
322  /// <returns>A new insight with identical values, but new instances</returns>
323  public virtual Insight Clone()
324  {
325  return new Insight(Symbol, Period, Type, Direction, Magnitude, Confidence, weight: Weight, tag: Tag)
326  {
329  Score = Score,
330  Id = Id,
335  GroupId = GroupId
336  };
337  }
338 
339  /// <summary>
340  /// Creates a new insight for predicting the percent change in price over the specified period
341  /// </summary>
342  /// <param name="symbol">The symbol this insight is for</param>
343  /// <param name="resolution">The resolution used to define the insight's period and also used to determine the insight's close time</param>
344  /// <param name="barCount">The number of resolution time steps to make in market hours to compute the insight's closing time</param>
345  /// <param name="direction">The predicted direction</param>
346  /// <param name="magnitude">The predicted magnitude as a percent change</param>
347  /// <param name="confidence">The confidence in this insight</param>
348  /// <param name="sourceModel">The model generating this insight</param>
349  /// <param name="weight">The portfolio weight of this insight</param>
350  /// <param name="tag">The insight's tag containing additional information</param>
351  /// <returns>A new insight object for the specified parameters</returns>
352  public static Insight Price(Symbol symbol, Resolution resolution, int barCount, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
353  {
354  if (barCount < 1)
355  {
356  throw new ArgumentOutOfRangeException(nameof(barCount), Messages.Insight.InvalidBarCount);
357  }
358 
359  var spec = new ResolutionBarCountPeriodSpecification(resolution, barCount);
360  return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
361  }
362 
363  /// <summary>
364  /// Creates a new insight for predicting the percent change in price over the specified period
365  /// </summary>
366  /// <param name="symbol">The symbol this insight is for</param>
367  /// <param name="closeTimeLocal">The insight's closing time in the security's exchange time zone</param>
368  /// <param name="direction">The predicted direction</param>
369  /// <param name="magnitude">The predicted magnitude as a percent change</param>
370  /// <param name="confidence">The confidence in this insight</param>
371  /// <param name="sourceModel">The model generating this insight</param>
372  /// <param name="weight">The portfolio weight of this insight</param>
373  /// <param name="tag">The insight's tag containing additional information</param>
374  /// <returns>A new insight object for the specified parameters</returns>
375  public static Insight Price(Symbol symbol, DateTime closeTimeLocal, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
376  {
377  var spec = closeTimeLocal == Time.EndOfTime ? (IPeriodSpecification)
378  new EndOfTimeCloseTimePeriodSpecification() : new CloseTimePeriodSpecification(closeTimeLocal);
379  return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
380  }
381 
382  /// <summary>
383  /// Creates a new insight for predicting the percent change in price over the specified period
384  /// </summary>
385  /// <param name="symbol">The symbol this insight is for</param>
386  /// <param name="period">The period over which the prediction will come true</param>
387  /// <param name="direction">The predicted direction</param>
388  /// <param name="magnitude">The predicted magnitude as a percent change</param>
389  /// <param name="confidence">The confidence in this insight</param>
390  /// <param name="sourceModel">The model generating this insight</param>
391  /// <param name="weight">The portfolio weight of this insight</param>
392  /// <param name="tag">The insight's tag containing additional information</param>
393  /// <returns>A new insight object for the specified parameters</returns>
394  public static Insight Price(Symbol symbol, TimeSpan period, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
395  {
396  if (period < Time.OneSecond)
397  {
398  throw new ArgumentOutOfRangeException(nameof(period), Messages.Insight.InvalidPeriod);
399  }
400 
401  var spec = period == Time.EndOfTimeTimeSpan ? (IPeriodSpecification)
402  new EndOfTimeCloseTimePeriodSpecification() : new TimeSpanPeriodSpecification(period);
403  return new Insight(symbol, spec, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
404  }
405 
406  /// <summary>
407  /// Creates a new insight for predicting the percent change in price over the specified period
408  /// </summary>
409  /// <param name="symbol">The symbol this insight is for</param>
410  /// <param name="expiryFunc">Func that defines the expiry time</param>
411  /// <param name="direction">The predicted direction</param>
412  /// <param name="magnitude">The predicted magnitude as a percent change</param>
413  /// <param name="confidence">The confidence in this insight</param>
414  /// <param name="sourceModel">The model generating this insight</param>
415  /// <param name="weight">The portfolio weight of this insight</param>
416  /// <param name="tag">The insight's tag containing additional information</param>
417  /// <returns>A new insight object for the specified parameters</returns>
418  public static Insight Price(Symbol symbol, Func<DateTime, DateTime> expiryFunc, InsightDirection direction, double? magnitude = null, double? confidence = null, string sourceModel = null, double? weight = null, string tag = "")
419  {
420  return new Insight(symbol, expiryFunc, InsightType.Price, direction, magnitude, confidence, sourceModel, weight, tag);
421  }
422 
423  /// <summary>
424  /// Creates a new, unique group id and sets it on each insight
425  /// </summary>
426  /// <param name="insights">The insights to be grouped</param>
427  public static IEnumerable<Insight> Group(params Insight[] insights)
428  {
429  if (insights == null)
430  {
431  throw new ArgumentNullException(nameof(insights));
432  }
433 
434  var groupId = Guid.NewGuid();
435  foreach (var insight in insights)
436  {
437  if (insight.GroupId.HasValue)
438  {
439  throw new InvalidOperationException(Messages.Insight.InsightAlreadyAssignedToAGroup(insight));
440  }
441 
442  insight.GroupId = groupId;
443  }
444  return insights;
445  }
446 
447  /// <summary>
448  /// Creates a new, unique group id and sets it on each insight
449  /// </summary>
450  /// <param name="insight">The insight to be grouped</param>
451  public static IEnumerable<Insight> Group(Insight insight) => Group(new[] {insight});
452 
453  /// <summary>
454  /// Creates a new <see cref="Insight"/> object from the specified serialized form
455  /// </summary>
456  /// <param name="serializedInsight">The insight DTO</param>
457  /// <returns>A new insight containing the information specified</returns>
458  public static Insight FromSerializedInsight(SerializedInsight serializedInsight)
459  {
460  var insight = new Insight(
461  Time.UnixTimeStampToDateTime(serializedInsight.CreatedTime),
462  new Symbol(SecurityIdentifier.Parse(serializedInsight.Symbol), serializedInsight.Ticker),
463  TimeSpan.FromSeconds(serializedInsight.Period),
464  serializedInsight.Type,
465  serializedInsight.Direction,
466  serializedInsight.Magnitude,
467  serializedInsight.Confidence,
468  serializedInsight.SourceModel,
469  serializedInsight.Weight,
470  serializedInsight.Tag
471  )
472  {
473  Id = Guid.Parse(serializedInsight.Id),
474  CloseTimeUtc = Time.UnixTimeStampToDateTime(serializedInsight.CloseTime),
475  EstimatedValue = serializedInsight.EstimatedValue,
476  ReferenceValue = serializedInsight.ReferenceValue,
477  ReferenceValueFinal = serializedInsight.ReferenceValueFinal,
478  GroupId = string.IsNullOrEmpty(serializedInsight.GroupId) ? (Guid?) null : Guid.Parse(serializedInsight.GroupId)
479  };
480 
481  // only set score values if non-zero or if they're the final scores
482  if (serializedInsight.ScoreIsFinal)
483  {
484  insight.Score.SetScore(InsightScoreType.Magnitude, serializedInsight.ScoreMagnitude, insight.CloseTimeUtc);
485  insight.Score.SetScore(InsightScoreType.Direction, serializedInsight.ScoreDirection, insight.CloseTimeUtc);
486  insight.Score.Finalize(insight.CloseTimeUtc);
487  }
488  else
489  {
490  if (serializedInsight.ScoreMagnitude != 0)
491  {
492  insight.Score.SetScore(InsightScoreType.Magnitude, serializedInsight.ScoreMagnitude, insight.CloseTimeUtc);
493  }
494 
495  if (serializedInsight.ScoreDirection != 0)
496  {
497  insight.Score.SetScore(InsightScoreType.Direction, serializedInsight.ScoreDirection, insight.CloseTimeUtc);
498  }
499  }
500 
501  return insight;
502  }
503 
504  /// <summary>
505  /// Computes the insight closing time from the given generated time, resolution and bar count.
506  /// This will step through market hours using the given resolution, respecting holidays, early closes, weekends, etc..
507  /// </summary>
508  /// <param name="exchangeHours">The exchange hours of the insight's security</param>
509  /// <param name="generatedTimeUtc">The insight's generated time in utc</param>
510  /// <param name="resolution">The resolution used to 'step-through' market hours to compute a reasonable close time</param>
511  /// <param name="barCount">The number of resolution steps to take</param>
512  /// <returns>The insight's closing time in utc</returns>
513  public static DateTime ComputeCloseTime(SecurityExchangeHours exchangeHours, DateTime generatedTimeUtc, Resolution resolution, int barCount)
514  {
515  if (barCount < 1)
516  {
517  throw new ArgumentOutOfRangeException(nameof(barCount), Messages.Insight.InvalidBarCount);
518  }
519 
520  // remap ticks to seconds
521  resolution = resolution == Resolution.Tick ? Resolution.Second : resolution;
522  if (resolution == Resolution.Hour)
523  {
524  // remap hours to minutes to avoid complications w/ stepping through
525  // for example 9->10 is an hour step but market opens at 9:30
526  barCount *= 60;
527  resolution = Resolution.Minute;
528  }
529 
530  var barSize = resolution.ToTimeSpan();
531  var startTimeLocal = generatedTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
532  var closeTimeLocal = Time.GetEndTimeForTradeBars(exchangeHours, startTimeLocal, barSize, barCount, false);
533  return closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
534  }
535 
536  /// <summary>
537  /// computs the insight closing time from the given generated time and period
538  /// </summary>
539  /// <param name="exchangeHours">The exchange hours of the insight's security</param>
540  /// <param name="generatedTimeUtc">The insight's generated time in utc</param>
541  /// <param name="period">The insight's period</param>
542  /// <returns>The insight's closing time in utc</returns>
543  public static DateTime ComputeCloseTime(SecurityExchangeHours exchangeHours, DateTime generatedTimeUtc, TimeSpan period)
544  {
545  if (period < Time.OneSecond)
546  {
547  throw new ArgumentOutOfRangeException(nameof(period), Messages.Insight.InvalidPeriod);
548  }
549 
550  var barSize = period.ToHigherResolutionEquivalent(false);
551  // remap ticks to seconds
552  barSize = barSize == Resolution.Tick ? Resolution.Second : barSize;
553  // remap hours to minutes to avoid complications w/ stepping through, for example 9->10 is an hour step but market opens at 9:30
554  barSize = barSize == Resolution.Hour ? Resolution.Minute : barSize;
555  var barCount = (int)(period.Ticks / barSize.ToTimeSpan().Ticks);
556  var closeTimeUtc = ComputeCloseTime(exchangeHours, generatedTimeUtc, barSize, barCount);
557  if (closeTimeUtc == generatedTimeUtc)
558  {
559  return ComputeCloseTime(exchangeHours, generatedTimeUtc, Resolution.Second, 1);
560  }
561 
562  var totalPeriodUsed = barSize.ToTimeSpan().Multiply(barCount);
563  if (totalPeriodUsed != period)
564  {
565  var delta = period - totalPeriodUsed;
566 
567  // interpret the remainder as fractional trading days
568  if (barSize == Resolution.Daily)
569  {
570  var percentOfDay = delta.Ticks / (double) Time.OneDay.Ticks;
571  delta = exchangeHours.RegularMarketDuration.Multiply(percentOfDay);
572  }
573 
574  if (delta != TimeSpan.Zero)
575  {
576  // continue stepping forward using minute resolution for the remainder
577  barCount = (int) (delta.Ticks / Time.OneMinute.Ticks);
578  if (barCount > 0)
579  {
580  closeTimeUtc = ComputeCloseTime(exchangeHours, closeTimeUtc, Resolution.Minute, barCount);
581  }
582  }
583  }
584 
585  return closeTimeUtc;
586  }
587 
588  /// <summary>Returns a string that represents the current object.</summary>
589  /// <returns>A string that represents the current object.</returns>
590  /// <filterpriority>2</filterpriority>
591  public override string ToString()
592  {
593  return Messages.Insight.ToString(this);
594  }
595 
596  /// <summary>
597  /// Returns a short string that represents the current object.
598  /// </summary>
599  /// <returns>A string that represents the current object.</returns>
600  public string ShortToString()
601  {
602  return Messages.Insight.ShortToString(this);
603  }
604 
605 
606  /// <summary>
607  /// Distinguishes between the different ways an insight's period/close times can be specified
608  /// This was really only required since we can't properly acces certain data from within a static
609  /// context (such as Insight.Price) or from within a constructor w/out requiring the users to properly
610  /// fetch the required data and supply it as an argument.
611  /// </summary>
612  private interface IPeriodSpecification
613  {
614  void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours);
615  }
616 
617  /// <summary>
618  /// User defined the insight's period using a time span
619  /// </summary>
620  private class TimeSpanPeriodSpecification : IPeriodSpecification
621  {
622  public readonly TimeSpan Period;
623 
624  public TimeSpanPeriodSpecification(TimeSpan period)
625  {
626  if (period == TimeSpan.Zero)
627  {
628  period = Time.OneSecond;
629  }
630 
631  Period = period;
632  }
633 
634  public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
635  {
636  insight.Period = Period;
637  insight.CloseTimeUtc = ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, Period);
638  }
639  }
640 
641  /// <summary>
642  /// User defined insight's period using a resolution and bar count
643  /// </summary>
644  private class ResolutionBarCountPeriodSpecification : IPeriodSpecification
645  {
646  public readonly Resolution Resolution;
647  public readonly int BarCount;
648 
649  public ResolutionBarCountPeriodSpecification(Resolution resolution, int barCount)
650  {
651  if (resolution == Resolution.Tick)
652  {
653  resolution = Resolution.Second;
654  }
655 
656  if (resolution == Resolution.Hour)
657  {
658  // remap hours to minutes to avoid errors w/ half hours, for example, 9:30 open
659  barCount *= 60;
660  resolution = Resolution.Minute;
661  }
662 
663  Resolution = resolution;
664  BarCount = barCount;
665  }
666 
667  public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
668  {
669  insight.CloseTimeUtc = ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, Resolution, BarCount);
670  insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
671  }
672  }
673 
674  /// <summary>
675  /// User defined the insight's local closing time
676  /// </summary>
677  private class CloseTimePeriodSpecification : IPeriodSpecification
678  {
679  public readonly DateTime CloseTimeLocal;
680 
681  public CloseTimePeriodSpecification(DateTime closeTimeLocal)
682  {
683  CloseTimeLocal = closeTimeLocal;
684  }
685 
686  public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
687  {
688  // Prevent close time to be defined to a date/time in closed market
689  var closeTimeLocal = exchangeHours.IsOpen(CloseTimeLocal, false)
690  ? CloseTimeLocal
691  : exchangeHours.GetNextMarketOpen(CloseTimeLocal, false);
692 
693  insight.CloseTimeUtc = closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
694 
695  if (insight.GeneratedTimeUtc > insight.CloseTimeUtc)
696  {
697  throw new ArgumentOutOfRangeException(nameof(closeTimeLocal), $"Insight closeTimeLocal must not be in the past.");
698  }
699 
700  insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
701  }
702  }
703 
704  /// <summary>
705  /// Special case for insights which close time is defined by a function
706  /// and want insights to expiry with calendar rules
707  /// </summary>
708  private class FuncPeriodSpecification : IPeriodSpecification
709  {
710  public readonly Func<DateTime, DateTime> _expiryFunc;
711 
712  public FuncPeriodSpecification(Func<DateTime, DateTime> expiryFunc)
713  {
714  _expiryFunc = expiryFunc;
715  }
716 
717  public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
718  {
719  var closeTimeLocal = insight.GeneratedTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
720  closeTimeLocal = _expiryFunc(closeTimeLocal);
721 
722  // Prevent close time to be defined to a date/time in closed market
723  if (!exchangeHours.IsOpen(closeTimeLocal, false))
724  {
725  closeTimeLocal = exchangeHours.GetNextMarketOpen(closeTimeLocal, false);
726  }
727 
728  insight.CloseTimeUtc = closeTimeLocal.ConvertToUtc(exchangeHours.TimeZone);
729  insight.Period = insight.CloseTimeUtc - insight.GeneratedTimeUtc;
730  }
731  }
732 
733  /// <summary>
734  /// Special case for insights where we do not know whats the
735  /// <see cref="Period"/> or <see cref="CloseTimeUtc"/>.
736  /// </summary>
737  private class EndOfTimeCloseTimePeriodSpecification : IPeriodSpecification
738  {
739  public void SetPeriodAndCloseTime(Insight insight, SecurityExchangeHours exchangeHours)
740  {
741  insight.Period = Time.EndOfTimeTimeSpan;
742  insight.CloseTimeUtc = Time.EndOfTime;
743  }
744  }
745  }
746 }