#region Using declarations using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Windows.Media; using System.Xml.Serialization; #endregion namespace NinjaTrader.NinjaScript.Indicators { public class ThreeCandleReversal : Indicator { public class Pattern { public double UltimateLevel { get; set; } public double PenultimateLevel { get; set; } public int StartBarIndex { get; set; } public int EndBarIndex { get; set; } public bool IsSwingHigh { get; set; } public bool IsSwingLow { get; set; } } [XmlIgnore] public Series DetectedPatterns; private List detectedPatterns = new List(); protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Credit to @BestForexMethod."; Name = "3CR"; Calculate = Calculate.OnPriceChange; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; DrawHorizontalGridLines = true; DrawVerticalGridLines = true; PaintPriceMarkers = true; ScaleJustification = ScaleJustification.Right; IsSuspendedWhileInactive = true; BarLookback = 5; BullishLevelStroke = new Stroke(Brushes.LimeGreen, DashStyleHelper.Solid, 2); BearishLevelStroke = new Stroke(Brushes.Red, DashStyleHelper.Solid, 2); // For use when backtesting in order to limit the number of patterns processed. MaxPatterns = 0; } if (State == State.DataLoaded) { DetectedPatterns = new Series(this); } else if (State == State.Historical) { SetZOrder(-1); // Display behind bars on chart. } } protected override void OnBarUpdate() { if (CurrentBar < BarLookback + 1) return; for (int i = detectedPatterns.Count - 1; i >= 0; i--) { var pattern = detectedPatterns[i]; if (pattern.IsSwingHigh && Closes[0][0] > pattern.UltimateLevel && pattern.EndBarIndex == 0) { detectedPatterns.RemoveAt(i); } else if (pattern.IsSwingLow && Closes[0][0] < pattern.UltimateLevel && pattern.EndBarIndex == 0) { detectedPatterns.RemoveAt(i); } } DetectedPatterns[0] = null; int candleIndex = (State == State.Realtime) ? 1 : 0; bool isSwingHigh = true, isSwingLow = true; for (int i = (candleIndex + 1); i <= (BarLookback + 1); i++) { if (Highs[0][candleIndex] <= Highs[0][i]) isSwingHigh = false; if (Lows[0][candleIndex] >= Lows[0][i]) isSwingLow = false; } // Skip inside bars and get the proper penultimate candle. int penultimateIndex = candleIndex + 1; while (Highs[0][penultimateIndex] <= Highs[0][penultimateIndex + 1] && Lows[0][penultimateIndex] >= Lows[0][penultimateIndex + 1]) { penultimateIndex++; } if (isSwingHigh) { detectedPatterns.Add(new Pattern { UltimateLevel = Highs[0][candleIndex], PenultimateLevel = Lows[0][penultimateIndex], StartBarIndex = CurrentBar - penultimateIndex, IsSwingHigh = true }); } else if (isSwingLow) { detectedPatterns.Add(new Pattern { UltimateLevel = Lows[0][candleIndex], PenultimateLevel = Highs[0][penultimateIndex], StartBarIndex = CurrentBar - penultimateIndex, IsSwingLow = true }); } for (int i = detectedPatterns.Count - 1; i >= 0; i--) { var pattern = detectedPatterns[i]; // Check for completed 3CRs. if ((pattern.IsSwingHigh && Closes[0][candleIndex] < pattern.PenultimateLevel && pattern.EndBarIndex == 0) || (pattern.IsSwingLow && Closes[0][candleIndex] > pattern.PenultimateLevel && pattern.EndBarIndex == 0)) { pattern.EndBarIndex = CurrentBar - candleIndex; DetectedPatterns[0] = pattern; } // Check for invalidated 3CRs. if ((pattern.IsSwingHigh && Highs[0][candleIndex] > pattern.UltimateLevel && pattern.EndBarIndex == 0) || (pattern.IsSwingLow && Lows[0][candleIndex] < pattern.UltimateLevel && pattern.EndBarIndex == 0)) { detectedPatterns.RemoveAt(i); } } if (MaxPatterns > 0 && detectedPatterns.Count > MaxPatterns) detectedPatterns.RemoveAt(0); } protected override void OnRender(ChartControl chartControl, ChartScale chartScale) { base.OnRender(chartControl, chartScale); foreach (var pattern in detectedPatterns.ToArray()) { int startX = chartControl.GetXByBarIndex(ChartBars, pattern.StartBarIndex); int endX = pattern.EndBarIndex == 0 ? ChartPanel.X + ChartPanel.W : chartControl.GetXByBarIndex(ChartBars, pattern.EndBarIndex); var levelToDraw = pattern.PenultimateLevel; Stroke currentStroke = pattern.IsSwingHigh ? BearishLevelStroke : BullishLevelStroke; DrawHorizontalLine(startX, endX, chartScale.GetYByValue(levelToDraw), currentStroke); } } private void DrawHorizontalLine(int startX, int endX, int y, Stroke stroke) { SharpDX.Direct2D1.Brush dxBrush = stroke.Brush.ToDxBrush(RenderTarget); RenderTarget.DrawLine( new SharpDX.Vector2(startX, y), new SharpDX.Vector2(endX, y), dxBrush, stroke.Width, stroke.StrokeStyle ); dxBrush.Dispose(); } public override string DisplayName { get { return Name; } } [Range(1, int.MaxValue), NinjaScriptProperty] [Display(Name = "Bar Lookback", Description = "Number of bars to compare for detecting swing highs / lows", Order = 1, GroupName = "3CR")] public int BarLookback { get; set; } [XmlIgnore] [Display(Name = "Bullish Level", Description = "Stroke for bullish level drawn on chart", Order = 2, GroupName = "3CR")] public Stroke BullishLevelStroke { get; set; } [XmlIgnore] [Display(Name = "Bearish Level", Description = "Stroke for bearish level drawn on chart", Order = 3, GroupName = "3CR")] public Stroke BearishLevelStroke { get; set; } [Browsable(false)] [Range(0, int.MaxValue)] public int MaxPatterns { get; set; } } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private ThreeCandleReversal[] cacheThreeCandleReversal; public ThreeCandleReversal ThreeCandleReversal(int barLookback) { return ThreeCandleReversal(Input, barLookback); } public ThreeCandleReversal ThreeCandleReversal(ISeries input, int barLookback) { if (cacheThreeCandleReversal != null) for (int idx = 0; idx < cacheThreeCandleReversal.Length; idx++) if (cacheThreeCandleReversal[idx] != null && cacheThreeCandleReversal[idx].BarLookback == barLookback && cacheThreeCandleReversal[idx].EqualsInput(input)) return cacheThreeCandleReversal[idx]; return CacheIndicator(new ThreeCandleReversal(){ BarLookback = barLookback }, input, ref cacheThreeCandleReversal); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.ThreeCandleReversal ThreeCandleReversal(int barLookback) { return indicator.ThreeCandleReversal(Input, barLookback); } public Indicators.ThreeCandleReversal ThreeCandleReversal(ISeries input , int barLookback) { return indicator.ThreeCandleReversal(input, barLookback); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.ThreeCandleReversal ThreeCandleReversal(int barLookback) { return indicator.ThreeCandleReversal(Input, barLookback); } public Indicators.ThreeCandleReversal ThreeCandleReversal(ISeries input , int barLookback) { return indicator.ThreeCandleReversal(input, barLookback); } } } #endregion