ninjatrader/indicators/SonicRDragon.cs

383 lines
14 KiB
C#

#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<double> firstSeries, Series<double> secondSeries, int displacement)
{
List<SharpDX.Vector2> SeriesAPoints = new List<SharpDX.Vector2>();
List<SharpDX.Vector2> SeriesBPoints = new List<SharpDX.Vector2>();
List<SharpDX.Vector2> tmpPoints = new List<SharpDX.Vector2>();
List<SharpDXFigure> SharpDXFigures = new List<SharpDXFigure>();
// 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<double> CloseEMA
{
get { return Values[0]; }
}
[Browsable(false)]
[XmlIgnore]
public Series<double> HighEMA
{
get { return Values[1]; }
}
[Browsable(false)]
[XmlIgnore]
public Series<double> 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<double> 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<SonicRDragon>(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<double> 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<double> input , int period)
{
return indicator.SonicRDragon(input, period);
}
}
}
#endregion