I'm new to QuantConnect/Lean. I'm posting this code here for two reasons:

1) If I did it right, it might be useful to somebody.

2) If I did it wrong, I would like to know where I went wrong and how I can improve it, especially if I have coded any bugs.

It's supposed to indicate the change in volume (in percentage terms) traded of a stock by comparing a slow and a fast moving averages. As such, it shouldn't rocket up or down exponentially, but oscilate up and down as it compares recent average volume with long-term average volume.

using QuantConnect.Data.Market; using System; namespace QuantConnect.Indicators { /// <summary> /// This indicator computes the Percentage Volume Ocillator (PVO) /// PVO = ([Fast moving average of volume] - [Slow moving average of volume]) / [Slow moving average of volume] /// </summary> public class PercentageVolumeOscillator : TradeBarIndicator, IIndicatorWarmUpPeriodProvider { private readonly SimpleMovingAverage _slowMovingAverageVolume; private readonly SimpleMovingAverage _fastMovingAverageVolume; /// <summary> /// Creates a new PercentageVolumeOscillator indicator using the specified periods /// </summary> public PercentageVolumeOscillator( string name, int fastMovingAveragePeriods = 5, int slowMovingAveragePeriods = 20) // Try 14 and 28 for Resolution.Daily and longer intervals : base(name) { _slowMovingAverageVolume = new SimpleMovingAverage($"{name}_SlowMovingAverageVolume", slowMovingAveragePeriods); _fastMovingAverageVolume = new SimpleMovingAverage($"{name}_FastMovingAverageVolume", fastMovingAveragePeriods); } /// <summary> /// Creates a new PercentageVolumeOscillator indicator using the specified periods /// </summary> public PercentageVolumeOscillator(int fastMovingAveragePeriods, int slowMovingAveragePeriods) : this($"{nameof(PercentageVolumeOscillator)}({fastMovingAveragePeriods},{slowMovingAveragePeriods})", fastMovingAveragePeriods, slowMovingAveragePeriods) { } /// <summary> /// Gets a flag indicating when this indicator is ready and fully initialized /// </summary> public override bool IsReady => _slowMovingAverageVolume.IsReady && _fastMovingAverageVolume.IsReady; /// <summary> /// Resets this indicator to its initial state /// </summary> public override void Reset() { _slowMovingAverageVolume.Reset(); _fastMovingAverageVolume.Reset(); base.Reset(); } /// <summary> /// Required period, in data points, for the indicator to be ready and fully initialized. /// </summary> public int WarmUpPeriod => _slowMovingAverageVolume.WarmUpPeriod; /// <summary> /// Computes the next value of this indicator from the given state /// </summary> /// <param name="input">The input given to the indicator</param> /// <returns>A new value for this indicator</returns> protected override decimal ComputeNextValue(TradeBar input) { UpdateIndicators(input); if (_slowMovingAverageVolume == 0) return 0; // To avoid a divide-by-zero error var difference = (_fastMovingAverageVolume - _slowMovingAverageVolume); var percentageChange = difference / _slowMovingAverageVolume * 100; return percentageChange; } private void UpdateIndicators(TradeBar input) { _slowMovingAverageVolume.Update(input.Time, input.Volume); _fastMovingAverageVolume.Update(input.Time, input.Volume); } } }

To use it, inside the constructor of the private class SymbolData that lives in an AlphaModel class,

PercentageVolumeOscillator = new PercentageVolumeOscillator( $"{nameof(PercentageVolumeOscillator)}({Symbol},{fastMovingAveragePeriods},{slowMovingAveragePeriods})", fastMovingAveragePeriods, slowMovingAveragePeriods); algorithm.RegisterIndicator(Symbol, PercentageVolumeOscillator, resolution);

If the current value of the oscillator is positive, the current trend is gaining momentum. If the current value of the oscillator is negative, the current trend is losing momentum.

Author

Benjamin Charlton

April 2021