#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 { [CategoryOrder("Sonic R Dragon", 1)] [CategoryOrder("Plots", 2)] public class SonicRDragon : Indicator { private EMA closeEma; private EMA highEma; private EMA lowEma; protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Inspired by the Sonic R System created by sonicdeejay on the Forex Factory forums"; Name = "Sonic R Dragon"; Calculate = Calculate.OnPriceChange; IsOverlay = true; DisplayInDataBox = true; DrawOnPricePanel = true; PaintPriceMarkers = true; ScaleJustification = ScaleJustification.Right; IsSuspendedWhileInactive = true; Period = 34; RegionColor = Brushes.Yellow; RegionOpacity = 0.1; AddPlot(new Stroke(Brushes.Transparent, DashStyleHelper.Solid, 3), PlotStyle.Line, "Close EMA"); AddPlot(new Stroke(Brushes.Transparent, DashStyleHelper.Solid, 3), PlotStyle.Line, "High EMA"); AddPlot(new Stroke(Brushes.Transparent, DashStyleHelper.Solid, 3), PlotStyle.Line, "Low EMA"); } else if (State == State.DataLoaded) { closeEma = EMA(Close, Period); highEma = EMA(High, Period); lowEma = EMA(Low, Period); } else if (State == State.Historical) { SetZOrder(-1); // Display behind bars on chart. } } protected override void OnBarUpdate() { CloseEMA[0] = closeEma[0]; HighEMA[0] = highEma[0]; LowEMA[0] = lowEma[0]; } protected override void OnRender(ChartControl chartControl, ChartScale chartScale) { base.OnRender(chartControl, chartScale); SharpDX.Direct2D1.AntialiasMode previousAntialiasMode = RenderTarget.AntialiasMode; RenderTarget.AntialiasMode = SharpDX.Direct2D1.AntialiasMode.PerPrimitive; // Code for drawing the region on the chart was taken from the indicator mentioned in the following link: // https://forum.ninjatrader.com/forum/ninjatrader-8/indicator-development/1127244-best-way-to-draw-region-between-plots-in-indicator DrawRegionBetweenSeries(chartScale, lowEma.Value, highEma.Value, 0); RenderTarget.AntialiasMode = previousAntialiasMode; } private class SharpDXFigure { public SharpDX.Vector2[] Points; public Brush Color; public SharpDXFigure(SharpDX.Vector2[] points, Brush color) { Points = points; Color = color; } } private SharpDX.Vector2 FindIntersection(SharpDX.Vector2 p1, SharpDX.Vector2 p2, SharpDX.Vector2 p3, SharpDX.Vector2 p4) { // Get the segments' parameters. float deltaXFirstLine = p2.X - p1.X; float deltaYFirstLine = p2.Y - p1.Y; float deltaXSecondLine = p4.X - p3.X; float deltaYSecondLine = p4.Y - p3.Y; float denominator = (deltaYFirstLine * deltaXSecondLine - deltaXFirstLine * deltaYSecondLine); // Undefined for parallel lines (denominator = 0). float t1 = ((p1.X - p3.X) * deltaYSecondLine + (p3.Y - p1.Y) * deltaXSecondLine) / denominator; // Find the point of intersection. return new SharpDX.Vector2(p1.X + deltaXFirstLine * t1, p1.Y + deltaYFirstLine * t1); } private void DrawFigure(SharpDXFigure figure) { SharpDX.Direct2D1.PathGeometry geometry = new SharpDX.Direct2D1.PathGeometry(Core.Globals.D2DFactory); SharpDX.Direct2D1.GeometrySink sink = geometry.Open(); sink.BeginFigure(figure.Points[0], new SharpDX.Direct2D1.FigureBegin()); for (int i = 0; i < figure.Points.Length; i++) sink.AddLine(figure.Points[i]); sink.AddLine(figure.Points[0]); sink.EndFigure(SharpDX.Direct2D1.FigureEnd.Closed); sink.Close(); SharpDX.Direct2D1.Brush fillBrush = figure.Color.ToDxBrush(RenderTarget); fillBrush.Opacity = (float)RegionOpacity; RenderTarget.FillGeometry(geometry, fillBrush); geometry.Dispose(); fillBrush.Dispose(); sink.Dispose(); } private void DrawRegionBetweenSeries(ChartScale chartScale, Series firstSeries, Series secondSeries, int displacement) { List SeriesAPoints = new List(); List SeriesBPoints = new List(); List tmpPoints = new List(); List SharpDXFigures = new List(); // Convert SeriesA and SeriesB to points int start = ChartBars.FromIndex - displacement * 2 > 0 ? ChartBars.FromIndex - displacement * 2 : 0; int end = ChartBars.ToIndex; float x0 = (float)ChartControl.GetXByBarIndex(ChartBars, 0); float x1 = (float)ChartControl.GetXByBarIndex(ChartBars, 1); if (ChartControl.Properties.EquidistantBarSpacing) for (int barIndex = start; barIndex <= end; barIndex++) { if (firstSeries.IsValidDataPointAt(barIndex)) { SeriesAPoints.Add(new SharpDX.Vector2((float)ChartControl.GetXByBarIndex(ChartBars, barIndex + displacement), (float)chartScale.GetYByValue(firstSeries.GetValueAt(barIndex)))); SeriesBPoints.Add(new SharpDX.Vector2((float)ChartControl.GetXByBarIndex(ChartBars, barIndex + displacement), (float)chartScale.GetYByValue(secondSeries.GetValueAt(barIndex)))); } } else for (int barIndex = start; barIndex <= end; barIndex++) { if (firstSeries.IsValidDataPointAt(barIndex)) { SeriesAPoints.Add(new SharpDX.Vector2((float)ChartControl.GetXByBarIndex(ChartBars, barIndex) + displacement * (x1 - x0), (float)chartScale.GetYByValue(firstSeries.GetValueAt(barIndex)))); SeriesBPoints.Add(new SharpDX.Vector2((float)ChartControl.GetXByBarIndex(ChartBars, barIndex) + displacement * (x1 - x0), (float)chartScale.GetYByValue(secondSeries.GetValueAt(barIndex)))); } } int lastCross = 0; bool isTouching = false; bool colorNeeded = true; for (int i = 0; i < SeriesAPoints.Count; i++) { if (colorNeeded) { colorNeeded = false; // Set initial color or wait until we need to start a shape if (SeriesAPoints[i].Y < SeriesBPoints[i].Y) { } else if (SeriesAPoints[i].Y > SeriesBPoints[i].Y) { } else { colorNeeded = true; lastCross = i; } if (!colorNeeded) tmpPoints.Add(SeriesAPoints[i]); continue; } // Check if SeriesA and SeriesB meet or have crossed to loop back and close figure if ((SeriesAPoints[i].Y == SeriesBPoints[i].Y && isTouching == false) || (SeriesAPoints[i].Y > SeriesBPoints[i].Y && SeriesAPoints[i - 1].Y < SeriesBPoints[i - 1].Y) || (SeriesBPoints[i].Y > SeriesAPoints[i].Y && SeriesBPoints[i - 1].Y < SeriesAPoints[i - 1].Y)) { // reset isTouching isTouching = false; // Set the endpoint SharpDX.Vector2 endpoint = (SeriesAPoints[i].Y != SeriesBPoints[i].Y) ? FindIntersection(SeriesAPoints[i - 1], SeriesAPoints[i], SeriesBPoints[i - 1], SeriesBPoints[i]) : SeriesAPoints[i]; tmpPoints.Add(endpoint); // Loop back and add SeriesBPoints for (int j = i - 1; j >= lastCross; j--) tmpPoints.Add(SeriesBPoints[j]); // Create figure SharpDXFigure figure = new SharpDXFigure(tmpPoints.ToArray(), RegionColor); SharpDXFigures.Add(figure); // Clear Points tmpPoints.Clear(); // Start new figure if we crossed, otherwise we will wait until we need a new figure if (SeriesAPoints[i].Y != SeriesBPoints[i].Y) { tmpPoints.Add(SeriesBPoints[i]); tmpPoints.Add(endpoint); tmpPoints.Add(SeriesAPoints[i]); } else isTouching = true; // Set last cross lastCross = i; } // Check if we are at the end of our rendering pass to loop back to loop back and close figure else if (i == SeriesAPoints.Count - 1) { tmpPoints.Add(SeriesAPoints[i]); // Loop back and add SeriesBPoints for (int j = i; j >= lastCross; j--) tmpPoints.Add(SeriesBPoints[j]); // Create figure SharpDXFigure figure = new SharpDXFigure(tmpPoints.ToArray(), RegionColor); SharpDXFigures.Add(figure); // Clear Points tmpPoints.Clear(); break; } // Figure does not need to be closed. Add more points or open a new figure if we were touching else if (SeriesAPoints[i].Y != SeriesBPoints[i].Y) { if (isTouching == true) { tmpPoints.Add(SeriesAPoints[i - 1]); lastCross = i - 1; } tmpPoints.Add(SeriesAPoints[i]); isTouching = false; } } // Draw figures foreach (SharpDXFigure figure in SharpDXFigures) DrawFigure(figure); } public override string DisplayName { get { return Name; } } [Browsable(false)] [XmlIgnore] public Series CloseEMA { get { return Values[0]; } } [Browsable(false)] [XmlIgnore] public Series HighEMA { get { return Values[1]; } } [Browsable(false)] [XmlIgnore] public Series LowEMA { get { return Values[2]; } } #region Properties [NinjaScriptProperty] [Range(1, int.MaxValue)] [Display(Name = "Period", Description = "Period for the EMA", Order = 1, GroupName = "Sonic R Dragon")] public int Period { get; set; } [XmlIgnore] [Display(Name = "Region Color", Description = "Color of the region between the EMAs of highs and lows", Order = 2, GroupName = "Sonic R Dragon")] public Brush RegionColor { get; set; } [Browsable(false)] public string RegionColorSerializable { get { return Serialize.BrushToString(RegionColor); } set { RegionColor = Serialize.StringToBrush(value); } } [Range(0, 1)] [Display(Name = "Region Opacity", Description = "Opacity of the region", Order = 3, GroupName = "Sonic R Dragon")] public double RegionOpacity { get; set; } #endregion } } #region NinjaScript generated code. Neither change nor remove. namespace NinjaTrader.NinjaScript.Indicators { public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private SonicRDragon[] cacheSonicRDragon; public SonicRDragon SonicRDragon(int period) { return SonicRDragon(Input, period); } public SonicRDragon SonicRDragon(ISeries input, int period) { if (cacheSonicRDragon != null) for (int idx = 0; idx < cacheSonicRDragon.Length; idx++) if (cacheSonicRDragon[idx] != null && cacheSonicRDragon[idx].Period == period && cacheSonicRDragon[idx].EqualsInput(input)) return cacheSonicRDragon[idx]; return CacheIndicator(new SonicRDragon(){ Period = period }, input, ref cacheSonicRDragon); } } } namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { public Indicators.SonicRDragon SonicRDragon(int period) { return indicator.SonicRDragon(Input, period); } public Indicators.SonicRDragon SonicRDragon(ISeries input , int period) { return indicator.SonicRDragon(input, period); } } } namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { public Indicators.SonicRDragon SonicRDragon(int period) { return indicator.SonicRDragon(Input, period); } public Indicators.SonicRDragon SonicRDragon(ISeries input , int period) { return indicator.SonicRDragon(input, period); } } } #endregion