Lean  $LEAN_TAG$
DualSymbolIndicator.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;
19 using NodaTime;
20 using QuantConnect.Data;
21 
23 {
24  /// <summary>
25  /// Base class for indicators that work with two different symbols and calculate an indicator based on them.
26  /// </summary>
27  /// <typeparam name="T">Type of the data points stored in the rolling windows for each symbol (e.g., double, decimal, etc.)</typeparam>
29  {
30  /// <summary>
31  /// Time zone of the target symbol.
32  /// </summary>
33  private readonly DateTimeZone _targetTimeZone;
34 
35  /// <summary>
36  /// Time zone of the reference symbol.
37  /// </summary>
38  private readonly DateTimeZone _referenceTimeZone;
39 
40  /// <summary>
41  /// Stores the previous input data point.
42  /// </summary>
43  private IBaseDataBar _previousInput;
44 
45  /// <summary>
46  /// The resolution of the data (e.g., daily, hourly, etc.).
47  /// </summary>
48  private Resolution _resolution;
49 
50  /// <summary>
51  /// RollingWindow to store the data points of the target symbol
52  /// </summary>
54 
55  /// <summary>
56  /// RollingWindow to store the data points of the reference symbol
57  /// </summary>
59 
60  /// <summary>
61  /// Symbol of the reference used
62  /// </summary>
63  protected Symbol ReferenceSymbol { get; }
64 
65  /// <summary>
66  /// Symbol of the target used
67  /// </summary>
68  protected Symbol TargetSymbol { get; }
69 
70  /// <summary>
71  /// Indicates if the time zone for the target and reference are different.
72  /// </summary>
73  protected bool IsTimezoneDifferent { get; }
74 
75  /// <summary>
76  /// The most recently computed value of the indicator.
77  /// </summary>
78  protected decimal IndicatorValue { get; set; }
79 
80  /// <summary>
81  /// Required period, in data points, for the indicator to be ready and fully initialized.
82  /// </summary>
83  public int WarmUpPeriod { get; set; }
84 
85  /// <summary>
86  /// Initializes the dual symbol indicator.
87  /// <para>
88  /// The constructor accepts a target symbol and a reference symbol. It also initializes
89  /// the time zones for both symbols and checks if they are different.
90  /// </para>
91  /// </summary>
92  /// <param name="name">The name of the indicator.</param>
93  /// <param name="targetSymbol">The symbol of the target asset.</param>
94  /// <param name="referenceSymbol">The symbol of the reference asset.</param>
95  /// <param name="period">The period (number of data points) over which to calculate the indicator.</param>
96  protected DualSymbolIndicator(string name, Symbol targetSymbol, Symbol referenceSymbol, int period) : base(name)
97  {
98  TargetDataPoints = new RollingWindow<T>(period);
100  TargetSymbol = targetSymbol;
101  ReferenceSymbol = referenceSymbol;
102 
103  var dataFolder = MarketHoursDatabase.FromDataFolder();
105  _referenceTimeZone = dataFolder.GetExchangeHours(ReferenceSymbol.ID.Market, ReferenceSymbol, ReferenceSymbol.ID.SecurityType).TimeZone;
106  IsTimezoneDifferent = _targetTimeZone != _referenceTimeZone;
107  }
108 
109  /// <summary>
110  /// Checks and computes the indicator if the input data matches.
111  /// This method ensures the input data points are from matching time periods and different symbols.
112  /// </summary>
113  /// <param name="input">The input data point (e.g., TradeBar for a symbol).</param>
114  /// <returns>The most recently computed value of the indicator.</returns>
115  protected override decimal ComputeNextValue(IBaseDataBar input)
116  {
117  if (_previousInput == null)
118  {
119  _previousInput = input;
120  _resolution = GetResolution(input);
121  return decimal.Zero;
122  }
123 
124  var isMatchingTime = CompareEndTimes(input.EndTime, _previousInput.EndTime);
125 
126  if (input.Symbol != _previousInput.Symbol && isMatchingTime)
127  {
128  AddDataPoint(input);
129  AddDataPoint(_previousInput);
131  }
132  _previousInput = input;
133  return IndicatorValue;
134  }
135 
136  /// <summary>
137  /// Performs the specific computation for the indicator.
138  /// </summary>
139  protected abstract void ComputeIndicator();
140 
141  /// <summary>
142  /// Determines the resolution of the input data based on the time difference between its start and end times.
143  /// Returns <see cref="Resolution.Daily"/> if the difference exceeds 1 hour; otherwise, calculates a higher equivalent resolution.
144  /// </summary>
145  private Resolution GetResolution(IBaseData input)
146  {
147  var timeDifference = input.EndTime - input.Time;
148  return timeDifference.TotalHours > 1 ? Resolution.Daily : timeDifference.ToHigherResolutionEquivalent(false);
149  }
150 
151  /// <summary>
152  /// Truncates the given DateTime based on the specified resolution (Daily, Hourly, Minute, or Second).
153  /// </summary>
154  /// <param name="date">The DateTime to truncate.</param>
155  /// <returns>A DateTime truncated to the specified resolution.</returns>
156  private DateTime AdjustDateToResolution(DateTime date)
157  {
158  switch (_resolution)
159  {
160  case Resolution.Daily:
161  return date.Date;
162  case Resolution.Hour:
163  return date.Date.AddHours(date.Hour);
164  case Resolution.Minute:
165  return date.Date.AddHours(date.Hour).AddMinutes(date.Minute);
166  case Resolution.Second:
167  return date;
168  default:
169  return date;
170  }
171  }
172 
173  /// <summary>
174  /// Compares the end times of two data points to check if they are in the same time period.
175  /// Adjusts for time zone differences if necessary.
176  /// </summary>
177  /// <param name="currentEndTime">The end time of the current data point.</param>
178  /// <param name="previousEndTime">The end time of the previous data point.</param>
179  /// <returns>True if the end times match after considering time zones and resolution.</returns>
180  private bool CompareEndTimes(DateTime currentEndTime, DateTime previousEndTime)
181  {
182  var previousSymbolIsTarget = _previousInput.Symbol == TargetSymbol;
184  {
185  currentEndTime = currentEndTime.ConvertToUtc(previousSymbolIsTarget ? _referenceTimeZone : _targetTimeZone);
186  previousEndTime = previousEndTime.ConvertToUtc(previousSymbolIsTarget ? _targetTimeZone : _referenceTimeZone);
187  }
188  return AdjustDateToResolution(currentEndTime) == AdjustDateToResolution(previousEndTime);
189  }
190 
191  /// <summary>
192  /// Adds the closing price to the corresponding symbol's data set (target or reference).
193  /// This method stores the data points for each symbol and performs specific calculations
194  /// based on the symbol. For instance, it computes returns in the case of the Beta indicator.
195  /// </summary>
196  /// <param name="input">The input value for this symbol</param>
197  /// <exception cref="ArgumentException">Thrown if the input symbol does not match either the target or reference symbol.</exception>
198  protected abstract void AddDataPoint(IBaseDataBar input);
199 
200  /// <summary>
201  /// Resets this indicator to its initial state
202  /// </summary>
203  public override void Reset()
204  {
205  _previousInput = null;
206  IndicatorValue = 0;
207  TargetDataPoints.Reset();
208  ReferenceDataPoints.Reset();
209  base.Reset();
210  }
211  }
212 }