From 78c9332976d70d83ee6b3a2ec8c11f999d8b67a9 Mon Sep 17 00:00:00 2001 From: moshferatu Date: Tue, 25 Jun 2024 11:32:22 -0700 Subject: [PATCH] Add custom VWAP indicator --- indicators/VWAP.cs | 331 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 indicators/VWAP.cs diff --git a/indicators/VWAP.cs b/indicators/VWAP.cs new file mode 100644 index 0000000..d5f61fa --- /dev/null +++ b/indicators/VWAP.cs @@ -0,0 +1,331 @@ +#region Using declarations +using NinjaTrader.Cbi; +using NinjaTrader.Data; +using NinjaTrader.Gui; +using NinjaTrader.NinjaScript.MarketAnalyzerColumns; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Windows.Media; +#endregion + +namespace NinjaTrader.NinjaScript.Indicators +{ + public class VWAP : Indicator + { + private static int CHART_BARS = 0; + private static int TICK_BARS = 1; + + private double TotalVolume; + private double VolumeWeightedPrice; + + private double TotalVolumeTick; + private double VolumeWeightedPriceTick; + private double VWAPTick; + + protected override void OnStateChange() + { + if (State == State.SetDefaults) + { + Description = "Volume Weighted Average Price (VWAP)"; + Name = "VWAP"; + Calculate = Calculate.OnBarClose; + IsOverlay = true; + DisplayInDataBox = true; + DrawOnPricePanel = true; + ScaleJustification = Gui.Chart.ScaleJustification.Right; + IsSuspendedWhileInactive = true; + BarsRequiredToPlot = 0; + Precision = VWAPPrecision.Chart; + Price = VWAPPrice.OHLC; + AddPlot(new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3), PlotStyle.Line, "VWAP"); + } + else if (State == State.Configure) + { + ClearOutputWindow(); + + if (Precision == VWAPPrecision.Tick) + { + AddDataSeries(Instrument.FullName, new BarsPeriod { + BarsPeriodType = BarsPeriodType.Tick, Value = 1 }, Bars.TradingHours.Name); + } + } + else if (State == State.DataLoaded) + { + TotalVolume = 0; + VolumeWeightedPrice = 0; + + TotalVolumeTick = 0; + VolumeWeightedPriceTick = 0; + VWAPTick = 0; + } + } + + private double GetPrice(int bars) + { + switch (Price) + { + case VWAPPrice.Open: + return Opens[bars][0]; + case VWAPPrice.High: + return Highs[bars][0]; + case VWAPPrice.Low: + return Lows[bars][0]; + case VWAPPrice.Close: + return Closes[bars][0]; + case VWAPPrice.HL: + return (Highs[bars][0] + Lows[bars][0]) / 2; + case VWAPPrice.HLC: + return (Highs[bars][0] + Lows[bars][0] + Closes[bars][0]) / 3; + case VWAPPrice.OHLC: + default: + return (Opens[bars][0] + Highs[bars][0] + Lows[bars][0] + Closes[bars][0]) / 4; + } + } + + protected override void OnBarUpdate() + { + if (CHART_BARS == BarsInProgress) + { + if (Precision == VWAPPrecision.Tick) + { + Value[0] = VWAPTick; + } + else if (Calculate == Calculate.OnBarClose || IsFirstTickOfBar) + { + TotalVolume += Volume[0]; + VolumeWeightedPrice += Volume[0] * GetPrice(CHART_BARS); + Value[0] = VolumeWeightedPrice / TotalVolume; + } + else + { + Value[0] = (VolumeWeightedPrice + (Volume[0] * GetPrice(CHART_BARS))) / (TotalVolume + Volume[0]); + } + } + else if (TICK_BARS == BarsInProgress) + { + TotalVolumeTick += Volumes[TICK_BARS][0]; + VolumeWeightedPriceTick += Volumes[TICK_BARS][0] * GetPrice(TICK_BARS); + VWAPTick = VolumeWeightedPriceTick / TotalVolumeTick; + } + } + + public override string DisplayName + { + get { return Name; } + } + + #region Properties + [TypeConverter(typeof(VWAPPrecisionConverter))] + [PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")] + [Display(Name = "Precision", Description = "Precision with which VWAP is calculated", Order = 1, GroupName = "VWAP")] + public VWAPPrecision Precision + { get; set; } + + [TypeConverter(typeof(VWAPPriceConverter))] + [PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")] + [Display(Name = "Average Price", Description = "How the average price of the bar is calculated", Order = 2, GroupName = "VWAP")] + public VWAPPrice Price + { get; set; } + #endregion + } +} + +public enum VWAPPrecision +{ + Chart, + Tick +} + +public enum VWAPPrice +{ + Open, + High, + Low, + Close, + HL, + HLC, + OHLC +} + +public class VWAPPrecisionConverter : TypeConverter +{ + private const string CHART = "Chart Period"; + private const string TICK = "Tick"; + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + List values = new List() { CHART, TICK }; + return new StandardValuesCollection(values); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + switch (value.ToString()) + { + case CHART: + return VWAPPrecision.Chart; + case TICK: + return VWAPPrecision.Tick; + } + return VWAPPrecision.Chart; + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + VWAPPrecision enumValue = (VWAPPrecision)Enum.Parse(typeof(VWAPPrecision), value.ToString()); + switch (enumValue) + { + case VWAPPrecision.Chart: + return CHART; + case VWAPPrecision.Tick: + return TICK; + } + return CHART; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { return true; } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { return true; } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { return true; } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { return true; } +} + +public class VWAPPriceConverter : TypeConverter +{ + private const string OPEN = "Open"; + private const string HIGH = "High"; + private const string LOW = "Low"; + private const string CLOSE = "Close"; + private const string HIGH_LOW = "(High + Low) / 2"; + private const string HIGH_LOW_CLOSE = "(High + Low + Close) / 3"; + private const string OPEN_HIGH_LOW_CLOSE = "(Open + High + Low + Close) / 4"; + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + List values = new List() { OPEN, HIGH, LOW, CLOSE, HIGH_LOW, HIGH_LOW_CLOSE, OPEN_HIGH_LOW_CLOSE }; + return new StandardValuesCollection(values); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + switch (value.ToString()) + { + case OPEN: + return VWAPPrice.Open; + case HIGH: + return VWAPPrice.High; + case LOW: + return VWAPPrice.Low; + case CLOSE: + return VWAPPrice.Close; + case HIGH_LOW: + return VWAPPrice.HL; + case HIGH_LOW_CLOSE: + return VWAPPrice.HLC; + case OPEN_HIGH_LOW_CLOSE: + return VWAPPrice.OHLC; + } + return VWAPPrice.OHLC; + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + VWAPPrice enumValue = (VWAPPrice)Enum.Parse(typeof(VWAPPrice), value.ToString()); + switch (enumValue) + { + case VWAPPrice.Open: + return OPEN; + case VWAPPrice.High: + return HIGH; + case VWAPPrice.Low: + return LOW; + case VWAPPrice.Close: + return CLOSE; + case VWAPPrice.HL: + return HIGH_LOW; + case VWAPPrice.HLC: + return HIGH_LOW_CLOSE; + case VWAPPrice.OHLC: + return OPEN_HIGH_LOW_CLOSE; + } + return OPEN_HIGH_LOW_CLOSE; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { return true; } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { return true; } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { return true; } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { return true; } +} + +#region NinjaScript generated code. Neither change nor remove. + +namespace NinjaTrader.NinjaScript.Indicators +{ + public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase + { + private VWAP[] cacheVWAPIndicator; + public VWAP VWAPIndicator() + { + return VWAPIndicator(Input); + } + + public VWAP VWAPIndicator(ISeries input) + { + if (cacheVWAPIndicator != null) + for (int idx = 0; idx < cacheVWAPIndicator.Length; idx++) + if (cacheVWAPIndicator[idx] != null && cacheVWAPIndicator[idx].EqualsInput(input)) + return cacheVWAPIndicator[idx]; + return CacheIndicator(new VWAP(), input, ref cacheVWAPIndicator); + } + } +} + +namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns +{ + public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase + { + public Indicators.VWAP VWAPIndicator() + { + return indicator.VWAPIndicator(Input); + } + + public Indicators.VWAP VWAPIndicator(ISeries input ) + { + return indicator.VWAPIndicator(input); + } + } +} + +namespace NinjaTrader.NinjaScript.Strategies +{ + public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase + { + public Indicators.VWAP VWAPIndicator() + { + return indicator.VWAPIndicator(Input); + } + + public Indicators.VWAP VWAPIndicator(ISeries input ) + { + return indicator.VWAPIndicator(input); + } + } +} + +#endregion