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.