#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 FractalsIndicator : Indicator { // v1 = initial version (8-JUN-2025) // v2 = updates (29-JUN-2025) #region Variables private string codeUpdated = "Last updated: 29-JUN-2025"; private string codeVers = "v2"; private string codeAuthor = "Author: XX"; private string codeFor = "For: Reece"; private string codeOrder = "Order Ref: # 10278"; private string codeProductName = "Product Name: FractalsIndicator"; // Fractal detection variables private double bullFractal; private double bearFractal; #endregion #region Config public override string DisplayName { get { return Name; } } #endregion protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Fractals Indicator - Identifies bullish and bearish fractal patterns"; Name = "Fractals Indicator"; Calculate = Calculate.OnBarClose; IsOverlay = true; versionInfo = codeVers + " " + codeUpdated; AddPlot(Brushes.Transparent, "FractalHigh"); AddPlot(Brushes.Transparent, "FractalLow"); ShowTransparentPlotsInDataBox = true; // Default fractal periods FractalPeriods = 1; } else if (State == State.DataLoaded) { PrintSettings(); } } protected override void OnBarUpdate() { if (CurrentBar < BarsRequiredToPlot) return; // Calculate minimum bars required for fractal detection int minBarsRequired = (FractalPeriods * 2) + 1; if (CurrentBar < minBarsRequired) return; // Detect bullish fractal (pivot high) bullFractal = GetPivotHigh(FractalPeriods, FractalPeriods); if (!Double.IsNaN(bullFractal)) { // Draw bull fractal marker above the bar Draw.TriangleUp(this, "BullFractal" + CurrentBar, true, FractalPeriods, bullFractal + (TickSize * 5), Brushes.Green); FractalHigh[0] = bullFractal; } // Detect bearish fractal (pivot low) bearFractal = GetPivotLow(FractalPeriods, FractalPeriods); if (!Double.IsNaN(bearFractal)) { // Draw bear fractal marker below the bar Draw.TriangleDown(this, "BearFractal" + CurrentBar, true, FractalPeriods, bearFractal - (TickSize * 5), Brushes.Red); FractalLow[0] = bearFractal; } } #region Fractal Detection Methods // Equivalent to PineScript pivothigh() function private double GetPivotHigh(int leftBars, int rightBars) { if (CurrentBar < leftBars + rightBars) return Double.NaN; int pivotIndex = rightBars; double pivotHigh = High[pivotIndex]; // Check left side bars for (int i = 1; i <= leftBars; i++) { if (High[pivotIndex + i] >= pivotHigh) return Double.NaN; } // Check right side bars for (int i = 1; i <= rightBars; i++) { if (High[pivotIndex - i] >= pivotHigh) return Double.NaN; } return pivotHigh; } // Equivalent to PineScript pivotlow() function private double GetPivotLow(int leftBars, int rightBars) { if (CurrentBar < leftBars + rightBars) return Double.NaN; int pivotIndex = rightBars; double pivotLow = Low[pivotIndex]; // Check left side bars for (int i = 1; i <= leftBars; i++) { if (Low[pivotIndex + i] <= pivotLow) return Double.NaN; } // Check right side bars for (int i = 1; i <= rightBars; i++) { if (Low[pivotIndex - i] <= pivotLow) return Double.NaN; } return pivotLow; } #endregion #region Properties [Browsable(false)] [XmlIgnore] public Series FractalHigh { get { return Values[0]; } } [Browsable(false)] [XmlIgnore] public Series FractalLow { get { return Values[1]; } } [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Fractal Periods", Description = "Number of bars on each side for fractal detection", Order = 1, GroupName = "Fractal Settings")] public int FractalPeriods { get; set; } [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; } #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("Show Output: " + showOutput); PM("Version: " + versionInfo); PM("Fractal Periods: " + FractalPeriods); 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; } public string GetFontInfo(SimpleFont font) { if (font != null) { string fontName = font.Family.ToString(); double fontSize = font.Size; bool isBold = font.Bold; bool isItalic = font.Italic; string fontDetails = fontName + ", Font Size: " + fontSize + ", Is Bold: " + isBold + ", Is Italic: " + isItalic; return fontDetails; } else { return "Font is null."; } } #endregion } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private FractalsIndicator[] cacheFractalsIndicator; public FractalsIndicator FractalsIndicator(int fractalPeriods, bool showOutput, string versionInfo) { return FractalsIndicator(Input, fractalPeriods, showOutput, versionInfo); } public FractalsIndicator FractalsIndicator(ISeries input, int fractalPeriods, bool showOutput, string versionInfo) { if (cacheFractalsIndicator != null) for (int idx = 0; idx < cacheFractalsIndicator.Length; idx++) if (cacheFractalsIndicator[idx] != null && cacheFractalsIndicator[idx].FractalPeriods == fractalPeriods && cacheFractalsIndicator[idx].showOutput == showOutput && cacheFractalsIndicator[idx].versionInfo == versionInfo && cacheFractalsIndicator[idx].EqualsInput(input)) return cacheFractalsIndicator[idx]; return CacheIndicator(new FractalsIndicator(){ FractalPeriods = fractalPeriods, showOutput = showOutput, versionInfo = versionInfo }, input, ref cacheFractalsIndicator); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.FractalsIndicator FractalsIndicator(int fractalPeriods, bool showOutput, string versionInfo) { return indicator.FractalsIndicator(Input, fractalPeriods, showOutput, versionInfo); } public Indicators.FractalsIndicator FractalsIndicator(ISeries input , int fractalPeriods, bool showOutput, string versionInfo) { return indicator.FractalsIndicator(input, fractalPeriods, showOutput, versionInfo); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.FractalsIndicator FractalsIndicator(int fractalPeriods, bool showOutput, string versionInfo) { return indicator.FractalsIndicator(Input, fractalPeriods, showOutput, versionInfo); } public Indicators.FractalsIndicator FractalsIndicator(ISeries input , int fractalPeriods, bool showOutput, string versionInfo) { return indicator.FractalsIndicator(input, fractalPeriods, showOutput, versionInfo); } } } #endregion