#region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Xml.Serialization; using NinjaTrader.Cbi; using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; using NinjaTrader.Gui.SuperDom; using NinjaTrader.Gui.Tools; using NinjaTrader.Data; using NinjaTrader.NinjaScript; using NinjaTrader.Core.FloatingPoint; using NinjaTrader.NinjaScript.DrawingTools; using System.Globalization; using NinjaTrader.NinjaScript.Indicators; #endregion //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators { public class FairValueGap { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public double StartPrice { get; set; } public double EndPrice { get; set; } } [TypeConverter("NinjaTrader.NinjaScript.Indicators.FairValueGapsPropertyConverter")] public class FairValueGaps : Indicator { public const int CHART_BARS = 0; public const int OTHER_BARS = 1; private List fairValueGaps; protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"ICT Fair Value Gap (FVG)"; Name = "Fair Value Gaps"; Calculate = Calculate.OnPriceChange; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; DrawHorizontalGridLines = true; DrawVerticalGridLines = true; PaintPriceMarkers = true; ScaleJustification = ScaleJustification.Right; IsSuspendedWhileInactive = true; MinimumGap = 1.0; GapBars = CHART_BARS; BarType = BarsPeriodType.Minute; Period = 1; GapUpColor = Brushes.LimeGreen; GapUpOpacity = 25; GapDownColor = Brushes.Red; GapDownOpacity = 25; } else if (State == State.Configure) { fairValueGaps = new List(); if (GapBars != CHART_BARS) { AddDataSeries(Instrument.FullName, new BarsPeriod { BarsPeriodType = BarType, Value = Period }, Bars.TradingHours.Name); } } else if (State == State.Historical) { SetZOrder(-1); } } protected override void OnBarUpdate() { if (CurrentBar < 4) return; if (GapBars == BarsInProgress && IsFirstTickOfBar) { double downGap = Lows[GapBars][3] - Highs[GapBars][1]; if (downGap > MinimumGap) { fairValueGaps.Add(new FairValueGap { StartTime = Times[GapBars][3], StartPrice = Lows[GapBars][3], EndPrice = Highs[GapBars][1] }); } double upGap = Lows[GapBars][1] - Highs[GapBars][3]; if (upGap > MinimumGap) { fairValueGaps.Add(new FairValueGap { StartTime = Times[GapBars][3], StartPrice = Highs[GapBars][3], EndPrice = Lows[GapBars][1] }); } } var unfilledGaps = from fairValueGap in fairValueGaps where fairValueGap.EndTime == default(DateTime) select fairValueGap; foreach (FairValueGap unfilledGap in unfilledGaps) { if (unfilledGap.EndPrice < unfilledGap.StartPrice && Highs[BarsInProgress][0] >= unfilledGap.StartPrice) unfilledGap.EndTime = Times[BarsInProgress][0]; else if (unfilledGap.EndPrice > unfilledGap.StartPrice && Lows[BarsInProgress][0] <= unfilledGap.StartPrice) unfilledGap.EndTime = Times[BarsInProgress][0]; } } protected override void OnRender(ChartControl chartControl, ChartScale chartScale) { base.OnRender(chartControl, chartScale); SharpDX.Direct2D1.Brush fairValueGapUpBrush = GapUpColor.ToDxBrush(RenderTarget); fairValueGapUpBrush.Opacity = GapUpOpacity / 100f; SharpDX.Direct2D1.Brush fairValueGapDownBrush = GapDownColor.ToDxBrush(RenderTarget); fairValueGapDownBrush.Opacity = GapDownOpacity / 100f; // TODO: Only render gaps that would actually be visible on the screen. foreach (FairValueGap fairValueGap in fairValueGaps) { int fairValueGapStartX = chartControl.GetXByTime(fairValueGap.StartTime); int fairValueGapEndX; if (fairValueGap.EndTime != default(DateTime)) fairValueGapEndX = chartControl.GetXByTime(fairValueGap.EndTime); else fairValueGapEndX = ChartPanel.X + ChartPanel.W; int fairValueGapStartY = chartScale.GetYByValue(Math.Max(fairValueGap.StartPrice, fairValueGap.EndPrice)); int fairValueGapEndY = chartScale.GetYByValue(Math.Min(fairValueGap.StartPrice, fairValueGap.EndPrice)); SharpDX.RectangleF fairValueGapRectangle = new SharpDX.RectangleF(fairValueGapStartX, fairValueGapStartY, fairValueGapEndX - fairValueGapStartX, fairValueGapEndY - fairValueGapStartY); RenderTarget.FillRectangle(fairValueGapRectangle, fairValueGap.EndPrice > fairValueGap.StartPrice ? fairValueGapUpBrush : fairValueGapDownBrush); } fairValueGapUpBrush.Dispose(); fairValueGapDownBrush.Dispose(); } public override string DisplayName { get { return Name; } } #region Properties [NinjaScriptProperty] [Range(1, double.MaxValue)] [Display(Name = "Minimum Gap Size", Description = "Minimum size of gaps to consider (in points)", Order = 1, GroupName = "Fair Value Gaps")] public double MinimumGap { get; set; } [TypeConverter(typeof(GapBarsConverter))] [PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")] [RefreshProperties(RefreshProperties.All)] [Display(Name = "Detect Gaps On", Description = "The bars used to detect gaps", Order = 2, GroupName = "Fair Value Gaps")] public int GapBars { get; set; } [TypeConverter(typeof(BarsPeriodTypeConverter))] [PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")] [Display(Name = "Bar Type", Description = "Type of bars on which to detect gaps", Order = 3, GroupName = "Fair Value Gaps")] public BarsPeriodType BarType { get; set; } [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Period", Description = "Period of the bars used to detect gaps", Order = 4, GroupName = "Fair Value Gaps")] public int Period { get; set; } [XmlIgnore] [NinjaScriptProperty] [Display(Name = "Gap Up", Description = "Color of the area of upward gaps", Order = 5, GroupName = "Fair Value Gaps")] public Brush GapUpColor { get; set; } [Browsable(false)] public string GapUpColorSerialization { get { return Serialize.BrushToString(GapUpColor); } set { GapUpColor = Serialize.StringToBrush(value); } } [NinjaScriptProperty] [Range(0, 100)] [Display(Name = "Gap Up Opacity (%)", Description = "Opacity of the area of upward gaps", Order = 6, GroupName = "Fair Value Gaps")] public int GapUpOpacity { get; set; } [XmlIgnore] [NinjaScriptProperty] [Display(Name = "Gap Down", Description = "Color of the area of downward gaps", Order = 7, GroupName = "Fair Value Gaps")] public Brush GapDownColor { get; set; } [Browsable(false)] public string GapDownColorSerialization { get { return Serialize.BrushToString(GapDownColor); } set { GapDownColor = Serialize.StringToBrush(value); } } [NinjaScriptProperty] [Range(0, 100)] [Display(Name = "Gap Down Opacity (%)", Description = "Opacity of the area of downward gaps", Order = 8, GroupName = "Fair Value Gaps")] public int GapDownOpacity { get; set; } #endregion } public class FairValueGapsPropertyConverter : IndicatorBaseConverter { public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object component, Attribute[] attrs) { FairValueGaps indicator = component as FairValueGaps; PropertyDescriptorCollection properties = base.GetPropertiesSupported(context) ? base.GetProperties(context, component, attrs) : TypeDescriptor.GetProperties(component, attrs); if (indicator == null || properties == null) return properties; PropertyDescriptor barType = properties["BarType"]; PropertyDescriptor period = properties["Period"]; properties.Remove(barType); properties.Remove(period); if (indicator.GapBars == FairValueGaps.OTHER_BARS) { properties.Add(barType); properties.Add(period); } return properties; } public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } } } public class GapBarsConverter : TypeConverter { private const string CHART = "Chart Bars"; private const string OTHER = "Other"; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { List values = new List() { CHART, OTHER }; return new StandardValuesCollection(values); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { switch (value.ToString()) { case CHART: return FairValueGaps.CHART_BARS; case OTHER: return FairValueGaps.OTHER_BARS; } return FairValueGaps.CHART_BARS; } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { switch ((int)value) { case FairValueGaps.CHART_BARS: return CHART; case FairValueGaps.OTHER_BARS: return OTHER; } 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 BarsPeriodTypeConverter : TypeConverter { private const string MINUTE = "Minute"; private const string SECOND = "Second"; private const string DAY = "Day"; private const string WEEK = "Week"; private const string MONTH = "Month"; private const string YEAR = "Year"; private const string RANGE = "Range"; private const string TICK = "Tick"; private const string VOLUME = "Volume"; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { List values = new List() { MINUTE, SECOND, DAY, WEEK, MONTH, YEAR, RANGE, TICK, VOLUME }; return new StandardValuesCollection(values); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { switch (value.ToString()) { case MINUTE: return BarsPeriodType.Minute; case SECOND: return BarsPeriodType.Second; case DAY: return BarsPeriodType.Day; case WEEK: return BarsPeriodType.Week; case MONTH: return BarsPeriodType.Month; case YEAR: return BarsPeriodType.Year; case RANGE: return BarsPeriodType.Range; case TICK: return BarsPeriodType.Tick; case VOLUME: return BarsPeriodType.Volume; } return BarsPeriodType.Minute; } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { BarsPeriodType enumValue = (BarsPeriodType)Enum.Parse(typeof(BarsPeriodType), value.ToString()); switch (enumValue) { case BarsPeriodType.Minute: return MINUTE; case BarsPeriodType.Second: return SECOND; case BarsPeriodType.Day: return DAY; case BarsPeriodType.Week: return WEEK; case BarsPeriodType.Month: return MONTH; case BarsPeriodType.Year: return YEAR; case BarsPeriodType.Range: return RANGE; case BarsPeriodType.Tick: return TICK; case BarsPeriodType.Volume: return VOLUME; } return MINUTE; } 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 FairValueGaps[] cacheFairValueGaps; public FairValueGaps FairValueGaps(double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { return FairValueGaps(Input, minimumGap, period, gapUpColor, gapUpOpacity, gapDownColor, gapDownOpacity); } public FairValueGaps FairValueGaps(ISeries input, double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { if (cacheFairValueGaps != null) for (int idx = 0; idx < cacheFairValueGaps.Length; idx++) if (cacheFairValueGaps[idx] != null && cacheFairValueGaps[idx].MinimumGap == minimumGap && cacheFairValueGaps[idx].Period == period && cacheFairValueGaps[idx].GapUpColor == gapUpColor && cacheFairValueGaps[idx].GapUpOpacity == gapUpOpacity && cacheFairValueGaps[idx].GapDownColor == gapDownColor && cacheFairValueGaps[idx].GapDownOpacity == gapDownOpacity && cacheFairValueGaps[idx].EqualsInput(input)) return cacheFairValueGaps[idx]; return CacheIndicator(new FairValueGaps(){ MinimumGap = minimumGap, Period = period, GapUpColor = gapUpColor, GapUpOpacity = gapUpOpacity, GapDownColor = gapDownColor, GapDownOpacity = gapDownOpacity }, input, ref cacheFairValueGaps); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.FairValueGaps FairValueGaps(double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { return indicator.FairValueGaps(Input, minimumGap, period, gapUpColor, gapUpOpacity, gapDownColor, gapDownOpacity); } public Indicators.FairValueGaps FairValueGaps(ISeries input , double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { return indicator.FairValueGaps(input, minimumGap, period, gapUpColor, gapUpOpacity, gapDownColor, gapDownOpacity); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.FairValueGaps FairValueGaps(double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { return indicator.FairValueGaps(Input, minimumGap, period, gapUpColor, gapUpOpacity, gapDownColor, gapDownOpacity); } public Indicators.FairValueGaps FairValueGaps(ISeries input , double minimumGap, int period, Brush gapUpColor, int gapUpOpacity, Brush gapDownColor, int gapDownOpacity) { return indicator.FairValueGaps(input, minimumGap, period, gapUpColor, gapUpOpacity, gapDownColor, gapDownOpacity); } } } #endregion