using System; using QuantConnect.Data; using QuantConnect.Data.Market; namespace QuantConnect.Indicators { /// /// Defines the canonical intraday VWAP indicator /// public class IntradayVwap : IndicatorBase { private DateTime _lastDate; private decimal _sumOfVolume; private decimal _sumOfPriceTimesVolume; /// /// Gets a flag indicating when this indicator is ready and fully initialized /// public override bool IsReady => _sumOfVolume > 0; /// /// Initializes a new instance of the class /// /// The name of the indicator public IntradayVwap(string name) : base(name) { } /// /// Computes the new VWAP /// protected override IndicatorResult ValidateAndComputeNextValue(BaseData input) { decimal volume, averagePrice; if (!TryGetVolumeAndAveragePrice(input, out volume, out averagePrice)) { return new IndicatorResult(0, IndicatorStatus.InvalidInput); } // reset vwap on daily boundaries if (_lastDate != input.EndTime.Date) { _sumOfVolume = 0m; _sumOfPriceTimesVolume = 0m; _lastDate = input.EndTime.Date; } // running totals for Σ PiVi / Σ Vi _sumOfVolume += volume; _sumOfPriceTimesVolume += averagePrice * volume; if (_sumOfVolume == 0m) { // if we have no trade volume then use the current price as VWAP return input.Value; } return _sumOfPriceTimesVolume / _sumOfVolume; } /// /// Computes the next value of this indicator from the given state. /// NOTE: This must be overriden since it's abstract in the base, but /// will never be invoked since we've override the validate method above. /// /// The input given to the indicator /// A new value for this indicator protected override decimal ComputeNextValue(BaseData input) { throw new NotImplementedException($"{nameof(IntradayVwap)}.{nameof(ComputeNextValue)} should never be invoked."); } /// /// Determines the volume and price to be used for the current input in the VWAP computation /// protected bool TryGetVolumeAndAveragePrice(BaseData input, out decimal volume, out decimal averagePrice) { var tick = input as Tick; if (tick?.TickType == TickType.Trade) { volume = tick.Quantity; averagePrice = tick.LastPrice; return true; } var tradeBar = input as TradeBar; if (tradeBar?.IsFillForward == false) { volume = tradeBar.Volume; averagePrice = (tradeBar.High + tradeBar.Low + tradeBar.Close) / 3m; return true; } volume = 0; averagePrice = 0; return false; } } }