383 lines
15 KiB
C#
383 lines
15 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
|
||
|
{
|
||
|
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; }
|
||
|
|
||
|
[NinjaScriptProperty]
|
||
|
[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); }
|
||
|
}
|
||
|
|
||
|
[NinjaScriptProperty]
|
||
|
[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, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
return SonicRDragon(Input, period, regionColor, regionOpacity);
|
||
|
}
|
||
|
|
||
|
public SonicRDragon SonicRDragon(ISeries<double> input, int period, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
if (cacheSonicRDragon != null)
|
||
|
for (int idx = 0; idx < cacheSonicRDragon.Length; idx++)
|
||
|
if (cacheSonicRDragon[idx] != null && cacheSonicRDragon[idx].Period == period && cacheSonicRDragon[idx].RegionColor == regionColor && cacheSonicRDragon[idx].RegionOpacity == regionOpacity && cacheSonicRDragon[idx].EqualsInput(input))
|
||
|
return cacheSonicRDragon[idx];
|
||
|
return CacheIndicator<SonicRDragon>(new SonicRDragon(){ Period = period, RegionColor = regionColor, RegionOpacity = regionOpacity }, input, ref cacheSonicRDragon);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
|
||
|
{
|
||
|
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
|
||
|
{
|
||
|
public Indicators.SonicRDragon SonicRDragon(int period, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
return indicator.SonicRDragon(Input, period, regionColor, regionOpacity);
|
||
|
}
|
||
|
|
||
|
public Indicators.SonicRDragon SonicRDragon(ISeries<double> input , int period, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
return indicator.SonicRDragon(input, period, regionColor, regionOpacity);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace NinjaTrader.NinjaScript.Strategies
|
||
|
{
|
||
|
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
|
||
|
{
|
||
|
public Indicators.SonicRDragon SonicRDragon(int period, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
return indicator.SonicRDragon(Input, period, regionColor, regionOpacity);
|
||
|
}
|
||
|
|
||
|
public Indicators.SonicRDragon SonicRDragon(ISeries<double> input , int period, Brush regionColor, double regionOpacity)
|
||
|
{
|
||
|
return indicator.SonicRDragon(input, period, regionColor, regionOpacity);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|