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.
Vladimir
Benjamin Charlton,
Here is my simple implementation of Percentage Volume Oscillator in Python.
volumes = self.History(self.stock , MA_S, Resolution.Daily)['volume'].unstack(level = 0).dropna() pvo = float(100*(volumes[:MA_F].mean()/volumes[:MA_S].mean() - 1))
Vladimir
Tere was a typo in my previous post.
Here is corrected version.
v = self.History(self.stock,MA_S,Resolution.Daily)['volume'].unstack(level = 0).dropna() pvo = float(100*(v[-MA_F:].mean()/v[-MA_S:].mean() - 1))
Benjamin Charlton
Thanks for your interest, Vladimir!
I don't speak a word of Python but it's great to have another implementation!
Vladimir
Benjamin Charlton,
Here's another way to get the Percentage Volume Oscillator (PVO) using the QC MACD indicator.
Derek Melchin
Hi Benjamin,
Thanks for sharing this custom indicator with the community! For those looking to use this, see the attached backtest for a working example.
Hi Vladimir,
Thanks for translating the PercentageVolumeOscillator to python. To improve its efficiency, consider adding a RollingWindow to avoid the daily History calls. In addition, we can convert this to a PythonIndicator object by following the example in this related thread.
Best,
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Vladimir
Derek Melchin,
My late version doesn't use daily History calls.
How can I call it OnEndOfDay () or Schedule.On ()?
Benjamin Charlton
Thanks, Derek and Vladimir!
Derek Melchin
Hi Vladimir,
We can remove the daily history calls from the algorithm above that uses OnEndOfDay by manually warming up a RollingWindow in `Initialize`. See the attached backtest for reference. Alternatively, we can also use `SetWarmUp` as demonstrated in your latest version above.
Best,
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Benjamin Charlton
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!