/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Linq; using System.Collections.Generic; using QuantConnect.Data.Market; namespace QuantConnect.Indicators { /// /// The Relative Daily Volume indicator is an indicator that compares current /// cumulative volume to the cumulative volume for a given /// time of day, measured as a ratio. /// /// Current volume from open to current time of day / Average over the past x days from open to current time of day /// public class RelativeDailyVolume : TradeBarIndicator { private readonly SortedDictionary _relativeData; private readonly Dictionary _currentData; private int _previousDay; private int _days; private int _period; /// /// Gets a flag indicating when the indicator is ready and fully initialized /// public override bool IsReady => _days >= _period; /// /// Initializes a new instance of the RelativeDailyVolume class using the specified period /// /// The period over which to perform the computation public RelativeDailyVolume(int period = 2) : this($"RDV({period})", period) { } /// /// Creates a new RelativeDailyVolume indicator with the specified period /// /// The name of this indicator /// The period of this indicator public RelativeDailyVolume(string name, int period) : base(name) { _relativeData = new SortedDictionary(); _currentData = new Dictionary(); _period = period; _previousDay = -1; // No calendar day can be -1, thus default is not a calendar day _days = -1; // Will increment by one after first TradeBar, then will increment by one every new day } /// /// Computes the next value for this indicator from the given state. /// /// The input value to this indicator on this time step /// A a value for this indicator protected override decimal ComputeNextValue(TradeBar input) { if (input.Time.Day != _previousDay) { var cumulativeVolume = 0.0m; foreach (var pair in _currentData) { var timeBar = pair.Key.TimeOfDay; SimpleMovingAverage daysAverage; cumulativeVolume += pair.Value; if (!_relativeData.TryGetValue(timeBar, out daysAverage)) { daysAverage = _relativeData[timeBar] = new SimpleMovingAverage(_period); } daysAverage.Update(pair.Key, cumulativeVolume); } _currentData.Clear(); _previousDay = input.Time.Day; _days += 1; // _days is starting from -1, to reach IsReady => _days == WarmUpPeriod; also means WarmUpPeriod+1 } _currentData[input.Time] = input.Volume; if (!IsReady) { return 0; } var currentTimeBar = input.Time.TimeOfDay; var denominator = 0.0m; SimpleMovingAverage currentAverage; if (_relativeData.TryGetValue(currentTimeBar, out currentAverage)) { denominator = currentAverage.Current.Value; } else { // If there is no historical data for the current time, get most recent historical data // This may come into play for crypto assets or a circuit breaker event var relativeDataKeys = _relativeData.Keys.ToList(); for (int i = 1; i < relativeDataKeys.Count; i++) { if (relativeDataKeys[i] > currentTimeBar) { denominator = _relativeData[relativeDataKeys[i - 1]].Current.Value; } } } if (denominator == 0) { return 0; } var relativeDailyVolume = _currentData.Values.Sum() / denominator; return relativeDailyVolume; } /// /// Resets this indicator to its initial state /// public override void Reset() { _relativeData.Clear(); _currentData.Clear(); _previousDay = -1; _days = -1; base.Reset(); } } }