#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; #endregion //This namespace holds Indicators in this folder and is required. Do not change it. namespace NinjaTrader.NinjaScript.Indicators { public class FVGData { public string Tag { get; set; } public double TopPrice { get; set; } public double BottomPrice { get; set; } public Rectangle DrawingRect { get; set; } } public class FairValueGapFVGIndicator : Indicator { // v1 = initial version (8-JUN-2025) #region Version Info private string codeUpdated = "Last updated: 8-JUN-2025"; private string codeVers = "v1"; private string codeAuthor = "Author: XX"; private string codeFor = "For: Reece"; private string codeOrder = "Order Ref: # 10278"; private string codeProductName = "Product Name: FairValueGapFVGIndicator"; public override string DisplayName { get { return Name; } } #endregion protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Fair Value Gap (FVG) Indicator - Identifies and plots bullish and bearish fair value gaps"; Name = "Fair Value Gap (FVG) Indicator"; versionInfo = codeVers + " " + codeUpdated; Calculate = Calculate.OnBarClose; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; DrawHorizontalGridLines = true; DrawVerticalGridLines = true; PaintPriceMarkers = true; ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right; IsSuspendedWhileInactive = true; // FVG Settings PlotFVG = true; PlotStructureBreakingFVG = true; FvgBullColor = Brushes.Black; FvgBearColor = Brushes.Black; FvgStructBreakingColor = Brushes.Blue; FvgMaxBoxSet = 10; FilterMitFVG = false; MitFVGColor = Brushes.Gray; AddPlot(new Stroke(Brushes.Transparent), PlotStyle.Dot, "BullFvgTop"); AddPlot(new Stroke(Brushes.Transparent), PlotStyle.Dot, "BullFvgBottom"); AddPlot(new Stroke(Brushes.Transparent), PlotStyle.Dot, "BearFvgTop"); AddPlot(new Stroke(Brushes.Transparent), PlotStyle.Dot, "BearFvgBottom"); ShowTransparentPlotsInDataBox = true; } else if (State == State.DataLoaded) { PrintSettings(); } } protected override void OnBarUpdate() { // Initialize plots to 0, indicating no new FVG on this bar BullFvgTop[0] = 0; BullFvgBottom[0] = 0; BearFvgTop[0] = 0; BearFvgBottom[0] = 0; if (CurrentBar < 2) return; double top = GetRecentHigh(); double bottom = GetRecentLow(); // Check for Bullish FVG if (IsFvgUp()) { // --- ADDED: Set the plot values --- BullFvgTop[0] = Low[0]; BullFvgBottom[0] = High[2]; // --- ALL YOUR EXISTING LOGIC IS PRESERVED --- bool isStructureBreaking = PlotStructureBreakingFVG && Close[1] > top && Low[1] < top && High[2] < top && Low[0] > top; string tag = (isStructureBreaking ? "BullStructFVG" : "BullFVG") + CurrentBar; FVGData fvgData = new FVGData { Tag = tag, TopPrice = Low[0], BottomPrice = High[2] }; if ((isStructureBreaking || PlotFVG) && ChartControl != null) { Brush color = isStructureBreaking ? FvgStructBreakingColor : FvgBullColor; Rectangle fvgBox = Draw.Rectangle(this, tag, 2, fvgData.TopPrice, 0, fvgData.BottomPrice, color); fvgBox.IsLocked = true; fvgData.DrawingRect = fvgBox; } bullFVGBoxes.Add(fvgData); if (bullFVGBoxes.Count > FvgMaxBoxSet) { FVGData oldData = bullFVGBoxes[0]; if (oldData.DrawingRect != null) { RemoveDrawObject(oldData.DrawingRect.Tag); } bullFVGBoxes.RemoveAt(0); } } // Check for Bearish FVG if (IsFvgDown()) { // --- ADDED: Set the plot values --- BearFvgTop[0] = High[0]; BearFvgBottom[0] = Low[2]; // --- ALL YOUR EXISTING LOGIC IS PRESERVED --- bool isStructureBreaking = PlotStructureBreakingFVG && Close[1] < bottom && High[1] > bottom && Low[2] > bottom && High[0] < bottom; string tag = (isStructureBreaking ? "BearStructFVG" : "BearFVG") + CurrentBar; FVGData fvgData = new FVGData { Tag = tag, TopPrice = High[0], BottomPrice = Low[2] }; if ((isStructureBreaking || PlotFVG) && ChartControl != null) { Brush color = isStructureBreaking ? FvgStructBreakingColor : FvgBearColor; Rectangle fvgBox = Draw.Rectangle(this, tag, 2, fvgData.BottomPrice, 0, fvgData.TopPrice, color); fvgBox.IsLocked = true; fvgData.DrawingRect = fvgBox; } bearFVGBoxes.Add(fvgData); if (bearFVGBoxes.Count > FvgMaxBoxSet) { FVGData oldData = bearFVGBoxes[0]; if (oldData.DrawingRect != null) { RemoveDrawObject(oldData.DrawingRect.Tag); } bearFVGBoxes.RemoveAt(0); } } if (FilterMitFVG) { ControlFVGBoxes(); } } // Check for Bullish FVG - low[0] > high[2] private bool IsFvgUp() { return Low[0] > High[2]; } // Check for Bearish FVG - high[0] < low[2] private bool IsFvgDown() { return High[0] < Low[2]; } // Get recent high for pivot calculation (simplified) private double GetRecentHigh() { double recentHigh = High[0]; for (int i = 1; i < Math.Min(20, CurrentBar); i++) { if (High[i] > recentHigh) recentHigh = High[i]; } return recentHigh; } // Get recent low for pivot calculation (simplified) private double GetRecentLow() { double recentLow = Low[0]; for (int i = 1; i < Math.Min(20, CurrentBar); i++) { if (Low[i] < recentLow) recentLow = Low[i]; } return recentLow; } // Control FVG boxes for mitigation private void ControlFVGBoxes() { // Check bullish FVG boxes for mitigation foreach (FVGData fvgData in bullFVGBoxes) { // Check if there is a drawing object to modify and if it has been mitigated if (fvgData.DrawingRect != null && ((High[0] > fvgData.BottomPrice && Low[0] < fvgData.BottomPrice) || (High[0] > fvgData.TopPrice && Low[0] < fvgData.TopPrice))) { fvgData.DrawingRect.AreaBrush = MitFVGColor; fvgData.DrawingRect.OutlineStroke = new Stroke(MitFVGColor); } } // Check bearish FVG boxes for mitigation foreach (FVGData fvgData in bearFVGBoxes) { // Check if there is a drawing object to modify and if it has been mitigated if (fvgData.DrawingRect != null && ((High[0] > fvgData.BottomPrice && Low[0] < fvgData.BottomPrice) || (High[0] > fvgData.TopPrice && Low[0] < fvgData.TopPrice))) { fvgData.DrawingRect.AreaBrush = MitFVGColor; fvgData.DrawingRect.OutlineStroke = new Stroke(MitFVGColor); } } } #region Properties [Browsable(false)] [XmlIgnore] public Series BullFvgTop { get { return Values[0]; } } [Browsable(false)] [XmlIgnore] public Series BullFvgBottom { get { return Values[1]; } } [Browsable(false)] [XmlIgnore] public Series BearFvgTop { get { return Values[2]; } } [Browsable(false)] [XmlIgnore] public Series BearFvgBottom { get { return Values[3]; } } [Browsable(false)] [XmlIgnore] public List bullFVGBoxes = new List(); [Browsable(false)] [XmlIgnore] public List bearFVGBoxes = new List(); [NinjaScriptProperty] [Display(Name = "NinjaScript Output", Description = "", Order = 10, GroupName = "Settings")] public bool showOutput { get; set; } [NinjaScriptProperty] [ReadOnly(true)] [Display(Name = "Version:", Description = "", Order = 30, GroupName = "Settings")] public string versionInfo { get; set; } [NinjaScriptProperty] [Display(Name = "Plot FVG", Description = "Enable/disable FVG plotting", Order = 1, GroupName = "Fair Value Gaps")] public bool PlotFVG { get; set; } [NinjaScriptProperty] [Display(Name = "Plot Structure Breaking FVG", Description = "Enable/disable structure breaking FVG plotting", Order = 2, GroupName = "Fair Value Gaps")] public bool PlotStructureBreakingFVG { get; set; } [NinjaScriptProperty] [XmlIgnore] [Display(Name = "Bullish FVG Color", Description = "Color for bullish FVG", Order = 3, GroupName = "Fair Value Gaps")] public Brush FvgBullColor { get; set; } [Browsable(false)] public string FvgBullColorSerializable { get { return Serialize.BrushToString(FvgBullColor); } set { FvgBullColor = Serialize.StringToBrush(value); } } [NinjaScriptProperty] [XmlIgnore] [Display(Name = "Bearish FVG Color", Description = "Color for bearish FVG", Order = 4, GroupName = "Fair Value Gaps")] public Brush FvgBearColor { get; set; } [Browsable(false)] public string FvgBearColorSerializable { get { return Serialize.BrushToString(FvgBearColor); } set { FvgBearColor = Serialize.StringToBrush(value); } } [NinjaScriptProperty] [XmlIgnore] [Display(Name = "Structure Breaking FVG Color", Description = "Color for structure breaking FVG", Order = 5, GroupName = "Fair Value Gaps")] public Brush FvgStructBreakingColor { get; set; } [Browsable(false)] public string FvgStructBreakingColorSerializable { get { return Serialize.BrushToString(FvgStructBreakingColor); } set { FvgStructBreakingColor = Serialize.StringToBrush(value); } } [NinjaScriptProperty] [Range(1, 100)] [Display(Name = "Maximum FVG Boxes", Description = "Maximum number of FVG boxes to display", Order = 6, GroupName = "Fair Value Gaps")] public int FvgMaxBoxSet { get; set; } [NinjaScriptProperty] [Display(Name = "Filter Mitigated FVG", Description = "Enable custom color for mitigated FVG", Order = 7, GroupName = "Fair Value Gaps")] public bool FilterMitFVG { get; set; } [NinjaScriptProperty] [XmlIgnore] [Display(Name = "Mitigated FVG Color", Description = "Color for mitigated FVG", Order = 8, GroupName = "Fair Value Gaps")] public Brush MitFVGColor { get; set; } [Browsable(false)] public string MitFVGColorSerializable { get { return Serialize.BrushToString(MitFVGColor); } set { MitFVGColor = Serialize.StringToBrush(value); } } #endregion #region Helpers private void PrintSettings() { PM("#########################################################"); PM(codeAuthor); PM(codeFor); PM(codeOrder); PM(codeProductName); PM(codeUpdated); PM(codeVers); PM("#########################################################"); PM("Indicator enabled: " + DateTime.Now); PM("Indicator name: " + this.Name); PM("Indicator Settings..."); PM("Chart: " + this.ChartBars); PM("Chart: " + Bars.ToChartString()); PM("Instrument: " + Instrument.FullName); PM("Default Trading Hours: " + Instrument.MasterInstrument.TradingHours); PM("Chart Trading Hours: " + TradingHours.Name); PM("Timeframe: " + this.BarsPeriod); PM("Bars Period Type Name: " + BarsPeriod.BarsPeriodTypeName + " Type #" + BarsPeriod.BarsPeriodTypeSerialize + " Value: " + BarsPeriod.Value + " Value 2: " + BarsPeriod.Value2); PM("Tick Replay: " + IsTickReplays[0]); PM("Bars on chart: " + Count); PM("Bars - Start Date: " + Bars.FromDate.ToString("yyyy-MM-dd")); PM("Bars - End Date: " + Bars.ToDate.ToString("yyyy-MM-dd")); PM("Timezone: " + Core.Globals.GeneralOptions.TimeZoneInfo); PM("Install folder: " + NinjaTrader.Core.Globals.InstallDir); PM("User directory: " + NinjaTrader.Core.Globals.UserDataDir); PM("Machine ID: " + Cbi.License.MachineId); PM("---------------------------------------------------------"); PM("Setup..."); PM("Calculate: " + Calculate); PM("Label: " + this.DisplayName); PM("Maximum bars look back: " + MaximumBarsLookBack); PM("BarsRequiredToPlot: " + BarsRequiredToPlot); PM("---------------------------------------------------------"); PM("Settings - NinjaScript Output: " + showOutput); PM("Settings - Version: " + versionInfo); PM("Fair Value Gaps - Plot FVG: " + PlotFVG); PM("Fair Value Gaps - Plot Structure Breaking FVG: " + PlotStructureBreakingFVG); PM("Fair Value Gaps - Bullish FVG Color: " + GetBrushName(FvgBullColor)); PM("Fair Value Gaps - Bearish FVG Color: " + GetBrushName(FvgBearColor)); PM("Fair Value Gaps - Structure Breaking FVG Color: " + GetBrushName(FvgStructBreakingColor)); PM("Fair Value Gaps - Maximum FVG Boxes: " + FvgMaxBoxSet); PM("Fair Value Gaps - Filter Mitigated FVG: " + FilterMitFVG); PM("Fair Value Gaps - Mitigated FVG Color: " + GetBrushName(MitFVGColor)); PM("---------------------------------------------------------"); } private void PM(string msg, [System.Runtime.CompilerServices.CallerMemberName] string sourceMethod = "") { if (!showOutput) return; DateTime timestamp = DateTime.Now; try { if (Bars != null && CurrentBar >= 0) { timestamp = Time[0]; } } catch { /* Fallback to DateTime.Now */ } Print(string.Format("[{0} - {1} {2}] {3:yyyy-MM-dd HH:mm:ss} - {4}", Name, Bars != null ? Bars.ToChartString() : "No Bars", sourceMethod, timestamp, msg)); } public string GetBrushName(Brush brush) { SolidColorBrush solidColorBrush = brush as SolidColorBrush; if (solidColorBrush == null) return "Not a SolidColorBrush"; string colorString = solidColorBrush.Color.ToString(); foreach (var colorProperty in typeof(Colors).GetProperties()) { if (colorProperty.PropertyType == typeof(Color)) { Color color = (Color)colorProperty.GetValue(null); if (color.ToString() == colorString) { return colorProperty.Name; } } } // No matching named color found, return the original color string return colorString; } #endregion } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private FairValueGapFVGIndicator[] cacheFairValueGapFVGIndicator; public FairValueGapFVGIndicator FairValueGapFVGIndicator(bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { return FairValueGapFVGIndicator(Input, showOutput, versionInfo, plotFVG, plotStructureBreakingFVG, fvgBullColor, fvgBearColor, fvgStructBreakingColor, fvgMaxBoxSet, filterMitFVG, mitFVGColor); } public FairValueGapFVGIndicator FairValueGapFVGIndicator(ISeries input, bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { if (cacheFairValueGapFVGIndicator != null) for (int idx = 0; idx < cacheFairValueGapFVGIndicator.Length; idx++) if (cacheFairValueGapFVGIndicator[idx] != null && cacheFairValueGapFVGIndicator[idx].showOutput == showOutput && cacheFairValueGapFVGIndicator[idx].versionInfo == versionInfo && cacheFairValueGapFVGIndicator[idx].PlotFVG == plotFVG && cacheFairValueGapFVGIndicator[idx].PlotStructureBreakingFVG == plotStructureBreakingFVG && cacheFairValueGapFVGIndicator[idx].FvgBullColor == fvgBullColor && cacheFairValueGapFVGIndicator[idx].FvgBearColor == fvgBearColor && cacheFairValueGapFVGIndicator[idx].FvgStructBreakingColor == fvgStructBreakingColor && cacheFairValueGapFVGIndicator[idx].FvgMaxBoxSet == fvgMaxBoxSet && cacheFairValueGapFVGIndicator[idx].FilterMitFVG == filterMitFVG && cacheFairValueGapFVGIndicator[idx].MitFVGColor == mitFVGColor && cacheFairValueGapFVGIndicator[idx].EqualsInput(input)) return cacheFairValueGapFVGIndicator[idx]; return CacheIndicator(new FairValueGapFVGIndicator(){ showOutput = showOutput, versionInfo = versionInfo, PlotFVG = plotFVG, PlotStructureBreakingFVG = plotStructureBreakingFVG, FvgBullColor = fvgBullColor, FvgBearColor = fvgBearColor, FvgStructBreakingColor = fvgStructBreakingColor, FvgMaxBoxSet = fvgMaxBoxSet, FilterMitFVG = filterMitFVG, MitFVGColor = mitFVGColor }, input, ref cacheFairValueGapFVGIndicator); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.FairValueGapFVGIndicator FairValueGapFVGIndicator(bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { return indicator.FairValueGapFVGIndicator(Input, showOutput, versionInfo, plotFVG, plotStructureBreakingFVG, fvgBullColor, fvgBearColor, fvgStructBreakingColor, fvgMaxBoxSet, filterMitFVG, mitFVGColor); } public Indicators.FairValueGapFVGIndicator FairValueGapFVGIndicator(ISeries input , bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { return indicator.FairValueGapFVGIndicator(input, showOutput, versionInfo, plotFVG, plotStructureBreakingFVG, fvgBullColor, fvgBearColor, fvgStructBreakingColor, fvgMaxBoxSet, filterMitFVG, mitFVGColor); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.FairValueGapFVGIndicator FairValueGapFVGIndicator(bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { return indicator.FairValueGapFVGIndicator(Input, showOutput, versionInfo, plotFVG, plotStructureBreakingFVG, fvgBullColor, fvgBearColor, fvgStructBreakingColor, fvgMaxBoxSet, filterMitFVG, mitFVGColor); } public Indicators.FairValueGapFVGIndicator FairValueGapFVGIndicator(ISeries input , bool showOutput, string versionInfo, bool plotFVG, bool plotStructureBreakingFVG, Brush fvgBullColor, Brush fvgBearColor, Brush fvgStructBreakingColor, int fvgMaxBoxSet, bool filterMitFVG, Brush mitFVGColor) { return indicator.FairValueGapFVGIndicator(input, showOutput, versionInfo, plotFVG, plotStructureBreakingFVG, fvgBullColor, fvgBearColor, fvgStructBreakingColor, fvgMaxBoxSet, filterMitFVG, mitFVGColor); } } } #endregion