Lean  $LEAN_TAG$
Beta.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;
18 using MathNet.Numerics.Statistics;
20 using NodaTime;
21 
23 {
24  /// <summary>
25  /// In technical analysis Beta indicator is used to measure volatility or risk of a target (ETF) relative to the overall
26  /// risk (volatility) of the reference (market indexes). The Beta indicators compares target's price movement to the
27  /// movements of the indexes over the same period of time.
28  ///
29  /// It is common practice to use the SPX index as a benchmark of the overall reference market when it comes to Beta
30  /// calculations.
31  /// </summary>
33  {
34  /// <summary>
35  /// RollingWindow to store the data points of the target symbol
36  /// </summary>
37  private readonly RollingWindow<decimal> _targetDataPoints;
38 
39  /// <summary>
40  /// RollingWindow to store the data points of the reference symbol
41  /// </summary>
42  private readonly RollingWindow<decimal> _referenceDataPoints;
43 
44  /// <summary>
45  /// Symbol of the reference used
46  /// </summary>
47  private readonly Symbol _referenceSymbol;
48 
49  /// <summary>
50  /// Symbol of the target used
51  /// </summary>
52  private readonly Symbol _targetSymbol;
53 
54  /// <summary>
55  /// Stores the previous input data point.
56  /// </summary>
57  private IBaseDataBar _previousInput;
58 
59  /// <summary>
60  /// Indicates whether the previous symbol is the target symbol.
61  /// </summary>
62  private bool _previousSymbolIsTarget;
63 
64  /// <summary>
65  /// Indicates if the time zone for the target and reference are different.
66  /// </summary>
67  private bool _isTimezoneDifferent;
68 
69  /// <summary>
70  /// Time zone of the target symbol.
71  /// </summary>
72  private DateTimeZone _targetTimeZone;
73 
74  /// <summary>
75  /// Time zone of the reference symbol.
76  /// </summary>
77  private DateTimeZone _referenceTimeZone;
78 
79  /// <summary>
80  /// The resolution of the data (e.g., daily, hourly, etc.).
81  /// </summary>
82  private Resolution _resolution;
83 
84  /// <summary>
85  /// RollingWindow of returns of the target symbol in the given period
86  /// </summary>
87  private readonly RollingWindow<double> _targetReturns;
88 
89  /// <summary>
90  /// RollingWindow of returns of the reference symbol in the given period
91  /// </summary>
92  private readonly RollingWindow<double> _referenceReturns;
93 
94  /// <summary>
95  /// Beta of the target used in relation with the reference
96  /// </summary>
97  private decimal _beta;
98 
99  /// <summary>
100  /// Required period, in data points, for the indicator to be ready and fully initialized.
101  /// </summary>
102  public int WarmUpPeriod { get; private set; }
103 
104  /// <summary>
105  /// Gets a flag indicating when the indicator is ready and fully initialized
106  /// </summary>
107  public override bool IsReady => _targetReturns.IsReady && _referenceReturns.IsReady;
108 
109  /// <summary>
110  /// Creates a new Beta indicator with the specified name, target, reference,
111  /// and period values
112  /// </summary>
113  /// <param name="name">The name of this indicator</param>
114  /// <param name="targetSymbol">The target symbol of this indicator</param>
115  /// <param name="period">The period of this indicator</param>
116  /// <param name="referenceSymbol">The reference symbol of this indicator</param>
117  public Beta(string name, Symbol targetSymbol, Symbol referenceSymbol, int period)
118  : base(name)
119  {
120  // Assert the period is greater than two, otherwise the beta can not be computed
121  if (period < 2)
122  {
123  throw new ArgumentException($"Period parameter for Beta indicator must be greater than 2 but was {period}.");
124  }
125  _referenceSymbol = referenceSymbol;
126  _targetSymbol = targetSymbol;
127 
128  _targetDataPoints = new RollingWindow<decimal>(2);
129  _referenceDataPoints = new RollingWindow<decimal>(2);
130 
131  _targetReturns = new RollingWindow<double>(period);
132  _referenceReturns = new RollingWindow<double>(period);
133  _beta = 0;
134  var dataFolder = MarketHoursDatabase.FromDataFolder();
135  _targetTimeZone = dataFolder.GetExchangeHours(_targetSymbol.ID.Market, _targetSymbol, _targetSymbol.ID.SecurityType).TimeZone;
136  _referenceTimeZone = dataFolder.GetExchangeHours(_referenceSymbol.ID.Market, _referenceSymbol, _referenceSymbol.ID.SecurityType).TimeZone;
137  _isTimezoneDifferent = _targetTimeZone != _referenceTimeZone;
138  WarmUpPeriod = period + 1 + (_isTimezoneDifferent ? 1 : 0);
139  }
140 
141  /// <summary>
142  /// Creates a new Beta indicator with the specified target, reference,
143  /// and period values
144  /// </summary>
145  /// <param name="targetSymbol">The target symbol of this indicator</param>
146  /// <param name="period">The period of this indicator</param>
147  /// <param name="referenceSymbol">The reference symbol of this indicator</param>
148  public Beta(Symbol targetSymbol, Symbol referenceSymbol, int period)
149  : this($"B({period})", targetSymbol, referenceSymbol, period)
150  {
151  }
152 
153  /// <summary>
154  /// Creates a new Beta indicator with the specified name, period, target and
155  /// reference values
156  /// </summary>
157  /// <param name="name">The name of this indicator</param>
158  /// <param name="period">The period of this indicator</param>
159  /// <param name="targetSymbol">The target symbol of this indicator</param>
160  /// <param name="referenceSymbol">The reference symbol of this indicator</param>
161  /// <remarks>Constructor overload for backward compatibility.</remarks>
162  public Beta(string name, int period, Symbol targetSymbol, Symbol referenceSymbol)
163  : this(name, targetSymbol, referenceSymbol, period)
164  {
165  }
166 
167  /// <summary>
168  /// Computes the next value for this indicator from the given state.
169  ///
170  /// As this indicator is receiving data points from two different symbols,
171  /// it's going to compute the next value when the amount of data points
172  /// of each of them is the same. Otherwise, it will return the last beta
173  /// value computed
174  /// </summary>
175  /// <param name="input">The input value of this indicator on this time step.
176  /// It can be either from the target or the reference symbol</param>
177  /// <returns>The beta value of the target used in relation with the reference</returns>
178  protected override decimal ComputeNextValue(IBaseDataBar input)
179  {
180  if (_previousInput == null)
181  {
182  _previousInput = input;
183  _previousSymbolIsTarget = input.Symbol == _targetSymbol;
184  var timeDifference = input.EndTime - input.Time;
185  _resolution = timeDifference.TotalHours > 1 ? Resolution.Daily : timeDifference.ToHigherResolutionEquivalent(false);
186  return decimal.Zero;
187  }
188 
189  var inputEndTime = input.EndTime;
190  var previousInputEndTime = _previousInput.EndTime;
191 
192  if (_isTimezoneDifferent)
193  {
194  inputEndTime = inputEndTime.ConvertToUtc(_previousSymbolIsTarget ? _referenceTimeZone : _targetTimeZone);
195  previousInputEndTime = previousInputEndTime.ConvertToUtc(_previousSymbolIsTarget ? _targetTimeZone : _referenceTimeZone);
196  }
197 
198  // Process data if symbol has changed and timestamps match
199  if (input.Symbol != _previousInput.Symbol && TruncateToResolution(inputEndTime) == TruncateToResolution(previousInputEndTime))
200  {
201  AddDataPoint(input);
202  AddDataPoint(_previousInput);
203  ComputeBeta();
204  }
205  _previousInput = input;
206  _previousSymbolIsTarget = input.Symbol == _targetSymbol;
207  return _beta;
208  }
209 
210  /// <summary>
211  /// Truncates the given DateTime based on the specified resolution (Daily, Hourly, Minute, or Second).
212  /// </summary>
213  /// <param name="date">The DateTime to truncate.</param>
214  /// <returns>A DateTime truncated to the specified resolution.</returns>
215  private DateTime TruncateToResolution(DateTime date)
216  {
217  switch (_resolution)
218  {
219  case Resolution.Daily:
220  return date.Date;
221  case Resolution.Hour:
222  return date.Date.AddHours(date.Hour);
223  case Resolution.Minute:
224  return date.Date.AddHours(date.Hour).AddMinutes(date.Minute);
225  case Resolution.Second:
226  return date;
227  default:
228  return date;
229  }
230  }
231 
232  /// <summary>
233  /// Adds the closing price to the corresponding symbol's data set (target or reference).
234  /// Computes returns when there are enough data points for each symbol.
235  /// </summary>
236  /// <param name="input">The input value for this symbol</param>
237  private void AddDataPoint(IBaseDataBar input)
238  {
239  if (input.Symbol == _targetSymbol)
240  {
241  _targetDataPoints.Add(input.Close);
242  if (_targetDataPoints.Count > 1)
243  {
244  _targetReturns.Add(GetNewReturn(_targetDataPoints));
245  }
246  }
247  else if (input.Symbol == _referenceSymbol)
248  {
249  _referenceDataPoints.Add(input.Close);
250  if (_referenceDataPoints.Count > 1)
251  {
252  _referenceReturns.Add(GetNewReturn(_referenceDataPoints));
253  }
254  }
255  else
256  {
257  throw new ArgumentException($"The given symbol {input.Symbol} was not {_targetSymbol} or {_referenceSymbol} symbol");
258  }
259  }
260 
261  /// <summary>
262  /// Computes the returns with the new given data point and the last given data point
263  /// </summary>
264  /// <param name="rollingWindow">The collection of data points from which we want
265  /// to compute the return</param>
266  /// <returns>The returns with the new given data point</returns>
267  private static double GetNewReturn(RollingWindow<decimal> rollingWindow)
268  {
269  return (double)((rollingWindow[0].SafeDivision(rollingWindow[1]) - 1));
270  }
271 
272  /// <summary>
273  /// Computes the beta value of the target in relation with the reference
274  /// using the target and reference returns
275  /// </summary>
276  private void ComputeBeta()
277  {
278  var varianceComputed = _referenceReturns.Variance();
279  var covarianceComputed = _targetReturns.Covariance(_referenceReturns);
280 
281  // Avoid division with NaN or by zero
282  var variance = !varianceComputed.IsNaNOrZero() ? varianceComputed : 1;
283  var covariance = !covarianceComputed.IsNaNOrZero() ? covarianceComputed : 0;
284  _beta = (decimal)(covariance / variance);
285  }
286 
287  /// <summary>
288  /// Resets this indicator to its initial state
289  /// </summary>
290  public override void Reset()
291  {
292  _previousInput = null;
293  _targetDataPoints.Reset();
294  _referenceDataPoints.Reset();
295  _targetReturns.Reset();
296  _referenceReturns.Reset();
297  _beta = 0;
298  base.Reset();
299  }
300  }
301 }