Lean  $LEAN_TAG$
RelativeDailyVolume.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 using System;
16 using System.Linq;
17 using System.Collections.Generic;
19 
21 {
22  /// <summary>
23  /// The Relative Daily Volume indicator is an indicator that compares current
24  /// cumulative volume to the cumulative volume for a given
25  /// time of day, measured as a ratio.
26  ///
27  /// Current volume from open to current time of day / Average over the past x days from open to current time of day
28  /// </summary>
30  {
31  private readonly SortedDictionary<TimeSpan, SimpleMovingAverage> _relativeData;
32  private readonly Dictionary<DateTime, decimal> _currentData;
33  private int _previousDay;
34  private int _days;
35  private int _period;
36 
37  /// <summary>
38  /// Gets a flag indicating when the indicator is ready and fully initialized
39  /// </summary>
40  public override bool IsReady => _days >= _period;
41 
42  /// <summary>
43  /// Initializes a new instance of the RelativeDailyVolume class using the specified period
44  /// </summary>
45  /// <param name="period">The period over which to perform the computation</param>
46  public RelativeDailyVolume(int period = 2)
47  : this($"RDV({period})", period)
48  {
49  }
50 
51  /// <summary>
52  /// Creates a new RelativeDailyVolume indicator with the specified period
53  /// </summary>
54  /// <param name="name">The name of this indicator</param>
55  /// <param name="period">The period of this indicator</param>
56  public RelativeDailyVolume(string name, int period)
57  : base(name)
58  {
59  _relativeData = new SortedDictionary<TimeSpan, SimpleMovingAverage>();
60  _currentData = new Dictionary<DateTime, decimal>();
61  _period = period;
62  _previousDay = -1; // No calendar day can be -1, thus default is not a calendar day
63  _days = -1; // Will increment by one after first TradeBar, then will increment by one every new day
64  }
65 
66  /// <summary>
67  /// Computes the next value for this indicator from the given state.
68  /// </summary>
69  /// <param name="input">The input value to this indicator on this time step</param>
70  /// <returns>A a value for this indicator</returns>
71  protected override decimal ComputeNextValue(TradeBar input)
72  {
73  if (input.Time.Day != _previousDay)
74  {
75  var cumulativeVolume = 0.0m;
76  foreach (var pair in _currentData)
77  {
78  var timeBar = pair.Key.TimeOfDay;
79  SimpleMovingAverage daysAverage;
80  cumulativeVolume += pair.Value;
81  if (!_relativeData.TryGetValue(timeBar, out daysAverage))
82  {
83  daysAverage = _relativeData[timeBar] = new SimpleMovingAverage(_period);
84  }
85  daysAverage.Update(pair.Key, cumulativeVolume);
86  }
87  _currentData.Clear();
88  _previousDay = input.Time.Day;
89  _days += 1; // _days is starting from -1, to reach IsReady => _days == WarmUpPeriod; also means WarmUpPeriod+1
90  }
91 
92  _currentData[input.Time] = input.Volume;
93 
94  if (!IsReady)
95  {
96  return 0;
97  }
98 
99  var currentTimeBar = input.Time.TimeOfDay;
100  var denominator = 0.0m;
101 
102  SimpleMovingAverage currentAverage;
103  if (_relativeData.TryGetValue(currentTimeBar, out currentAverage))
104  {
105  denominator = currentAverage.Current.Value;
106  }
107  else
108  {
109  // If there is no historical data for the current time, get most recent historical data
110  // This may come into play for crypto assets or a circuit breaker event
111  var relativeDataKeys = _relativeData.Keys.ToList();
112  for (int i = 1; i < relativeDataKeys.Count; i++)
113  {
114  if (relativeDataKeys[i] > currentTimeBar)
115  {
116  denominator = _relativeData[relativeDataKeys[i - 1]].Current.Value;
117  }
118  }
119  }
120  if (denominator == 0)
121  {
122  return 0;
123  }
124  var relativeDailyVolume = _currentData.Values.Sum() / denominator;
125  return relativeDailyVolume;
126  }
127 
128  /// <summary>
129  /// Resets this indicator to its initial state
130  /// </summary>
131  public override void Reset()
132  {
133  _relativeData.Clear();
134  _currentData.Clear();
135  _previousDay = -1;
136  _days = -1;
137  base.Reset();
138  }
139  }
140 }