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.