NinjaTrader - ZigZag Labels
Jun 28, 2023
This modified ZigZag indicator plots the rotation text above/below the swing high/low.
#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.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
{
/// <summary>
/// The ZigZag indicator shows trend lines filtering out changes below a defined level.
/// </summary>
public class ZigZagLabels : Indicator
{
private Series<double> zigZagHighZigZags;
private Series<double> zigZagLowZigZags;
private Series<double> zigZagHighSeries;
private Series<double> zigZagLowSeries;
private double currentZigZagHigh;
private double currentZigZagLow;
private int lastSwingIdx;
private double lastSwingPrice;
private int startIndex;
private int trendDir;
private string swingHighName;
private string swingLowName;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = NinjaTrader.Custom.Resource.NinjaScriptIndicatorDescriptionZigZag;
Name = "ZigZagLabels";
DeviationType = DeviationType.Points;
DeviationValue = 0.5;
DisplayInDataBox = false;
DrawOnPricePanel = false;
IsSuspendedWhileInactive = true;
IsOverlay = true;
PaintPriceMarkers = false;
UseHighLow = false;
AddPlot(Brushes.DodgerBlue, NinjaTrader.Custom.Resource.NinjaScriptIndicatorNameZigZag);
DisplayInDataBox = false;
PaintPriceMarkers = false;
}
else if (State == State.Configure)
{
currentZigZagHigh = 0;
currentZigZagLow = 0;
lastSwingIdx = -1;
lastSwingPrice = 0.0;
trendDir = 0; // 1 = trend up, -1 = trend down, init = 0
startIndex = int.MinValue;
}
else if (State == State.DataLoaded)
{
zigZagHighZigZags = new Series<double>(this, MaximumBarsLookBack.Infinite);
zigZagLowZigZags = new Series<double>(this, MaximumBarsLookBack.Infinite);
zigZagHighSeries = new Series<double>(this, MaximumBarsLookBack.Infinite);
zigZagLowSeries = new Series<double>(this, MaximumBarsLookBack.Infinite);
}
}
// Returns the number of bars ago a zig zag low occurred. Returns a value of -1 if a zig zag low is not found within the look back period.
public int LowBar(int barsAgo, int instance, int lookBackPeriod)
{
if (instance < 1)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZagLowBarInstanceGreaterEqual, GetType().Name, instance));
if (barsAgo < 0)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZigLowBarBarsAgoGreaterEqual, GetType().Name, barsAgo));
if (barsAgo >= Count)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZagLowBarBarsAgoOutOfRange, GetType().Name, (Count - 1), barsAgo));
Update();
for (int idx = CurrentBar - barsAgo - 1; idx >= CurrentBar - barsAgo - 1 - lookBackPeriod; idx--)
{
if (idx < 0)
return -1;
if (idx >= zigZagLowZigZags.Count)
continue;
if (!zigZagLowZigZags.IsValidDataPointAt(idx))
continue;
if (instance == 1) // 1-based, < to be save
return CurrentBar - idx;
instance--;
}
return -1;
}
// Returns the number of bars ago a zig zag high occurred. Returns a value of -1 if a zig zag high is not found within the look back period.
public int HighBar(int barsAgo, int instance, int lookBackPeriod)
{
if (instance < 1)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZagHighBarInstanceGreaterEqual, GetType().Name, instance));
if (barsAgo < 0)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZigHighBarBarsAgoGreaterEqual, GetType().Name, barsAgo));
if (barsAgo >= Count)
throw new Exception(string.Format(NinjaTrader.Custom.Resource.ZigZagHighBarBarsAgoOutOfRange, GetType().Name, (Count - 1), barsAgo));
Update();
for (int idx = CurrentBar - barsAgo - 1; idx >= CurrentBar - barsAgo - 1 - lookBackPeriod; idx--)
{
if (idx < 0)
return -1;
if (idx >= zigZagHighZigZags.Count)
continue;
if (!zigZagHighZigZags.IsValidDataPointAt(idx))
continue;
if (instance <= 1) // 1-based, < to be save
return CurrentBar - idx;
instance--;
}
return -1;
}
protected override void OnBarUpdate()
{
if (CurrentBar < 2) // Need at least 3 bars to calculate Low/High
{
zigZagHighSeries[0] = 0;
zigZagLowSeries[0] = 0;
return;
}
// Initialization
if (lastSwingPrice == 0.0)
lastSwingPrice = Input[0];
ISeries<double> highSeries = High;
ISeries<double> lowSeries = Low;
if (!UseHighLow)
{
highSeries = Input;
lowSeries = Input;
}
// Calculation always for 1-bar ago !
bool isSwingHigh = highSeries[1].ApproxCompare(highSeries[0]) >= 0
&& highSeries[1].ApproxCompare(highSeries[2]) >= 0;
bool isSwingLow = lowSeries[1].ApproxCompare(lowSeries[0]) <= 0
&& lowSeries[1].ApproxCompare(lowSeries[2]) <= 0;
bool isOverHighDeviation = (DeviationType == DeviationType.Percent && IsPriceGreater(highSeries[1], (lastSwingPrice * (1.0 + DeviationValue / 100.0))))
|| (DeviationType == DeviationType.Points && IsPriceGreater(highSeries[1], lastSwingPrice + DeviationValue));
bool isOverLowDeviation = (DeviationType == DeviationType.Percent && IsPriceGreater(lastSwingPrice * (1.0 - DeviationValue / 100.0), lowSeries[1]))
|| (DeviationType == DeviationType.Points && IsPriceGreater(lastSwingPrice - DeviationValue, lowSeries[1]));
double saveValue = 0.0;
bool addHigh = false;
bool addLow = false;
bool updateHigh = false;
bool updateLow = false;
if (!isSwingHigh && !isSwingLow)
{
zigZagHighSeries[0] = currentZigZagHigh;
zigZagLowSeries[0] = currentZigZagLow;
return;
}
if (trendDir <= 0 && isSwingHigh && isOverHighDeviation)
{
saveValue = highSeries[1];
addHigh = true;
trendDir = 1;
}
else if (trendDir >= 0 && isSwingLow && isOverLowDeviation)
{
saveValue = lowSeries[1];
addLow = true;
trendDir = -1;
}
else if (trendDir == 1 && isSwingHigh && IsPriceGreater(highSeries[1], lastSwingPrice))
{
saveValue = highSeries[1];
updateHigh = true;
}
else if (trendDir == -1 && isSwingLow && IsPriceGreater(lastSwingPrice, lowSeries[1]))
{
saveValue = lowSeries[1];
updateLow = true;
}
if (addHigh || addLow || updateHigh || updateLow)
{
if (updateHigh && lastSwingIdx >= 0)
{
zigZagHighZigZags.Reset(CurrentBar - lastSwingIdx);
Value.Reset(CurrentBar - lastSwingIdx);
}
else if (updateLow && lastSwingIdx >= 0)
{
zigZagLowZigZags.Reset(CurrentBar - lastSwingIdx);
Value.Reset(CurrentBar - lastSwingIdx);
}
if (addHigh || updateHigh)
{
zigZagHighZigZags[1] = saveValue;
currentZigZagHigh = saveValue;
zigZagHighSeries[1] = currentZigZagHigh;
Value[1] = currentZigZagHigh;
}
else if (addLow || updateLow)
{
zigZagLowZigZags[1] = saveValue;
currentZigZagLow = saveValue;
zigZagLowSeries[1] = currentZigZagLow;
Value[1] = currentZigZagLow;
}
lastSwingIdx = CurrentBar - 1;
lastSwingPrice = saveValue;
}
zigZagHighSeries[0] = currentZigZagHigh;
zigZagLowSeries[0] = currentZigZagLow;
if (addHigh || updateHigh)
{
string text = string.Format("{0}", lastSwingPrice - currentZigZagLow);
if (DeviationType == DeviationType.Percent)
text = string.Format("{0,0:N2}%", (lastSwingPrice / currentZigZagLow - 1) * 100);
if (addHigh)
swingHighName = string.Format("{0} ZZ", CurrentBar);
Draw.Text(this, swingHighName, text , 0, High[0] + 5 * TickSize, Brushes.Green);
}
else if (addLow || updateLow)
{
string text = string.Format("{0}", lastSwingPrice - currentZigZagHigh);
if (DeviationType == DeviationType.Percent)
text = string.Format("{0,0:N2}%", (lastSwingPrice / currentZigZagHigh - 1) * 100);
if (addLow)
swingLowName = string.Format("{0} ZZ", CurrentBar);
Draw.Text(this, swingLowName, text , 0, Low[0] - 5 * TickSize, Brushes.Red);
}
if (startIndex == int.MinValue && (zigZagHighZigZags.IsValidDataPoint(1) && zigZagHighZigZags[1] != zigZagHighZigZags[2] || zigZagLowZigZags.IsValidDataPoint(1) && zigZagLowZigZags[1] != zigZagLowZigZags[2]))
startIndex = CurrentBar - (Calculate == Calculate.OnBarClose ? 2 : 1);
}
#region Properties
/// <summary>
/// Gets the ZigZag high points.
/// </summary>
[NinjaScriptProperty]
[Display(ResourceType = typeof(Custom.Resource), Name = "DeviationType", GroupName = "NinjaScriptParameters", Order = 0)]
public DeviationType DeviationType
{ get; set; }
[Range(0, int.MaxValue), NinjaScriptProperty]
[Display(ResourceType = typeof(Custom.Resource), Name = "DeviationValue", GroupName = "NinjaScriptParameters", Order = 1)]
public double DeviationValue
{ get; set; }
[NinjaScriptProperty]
[Display(ResourceType = typeof(Custom.Resource), Name = "UseHighLow", GroupName = "NinjaScriptParameters", Order = 2)]
public bool UseHighLow
{ get; set; }
[Browsable(false)]
[XmlIgnore()]
public Series<double> ZigZagHigh
{
get
{
Update();
return zigZagHighSeries;
}
}
/// <summary>
/// Gets the ZigZag low points.
/// </summary>
[Browsable(false)]
[XmlIgnore()]
public Series<double> ZigZagLow
{
get
{
Update();
return zigZagLowSeries;
}
}
#endregion
#region Miscellaneous
private bool IsPriceGreater(double a, double b)
{
return a.ApproxCompare(b) > 0;
}
public override void OnCalculateMinMax()
{
MinValue = double.MaxValue;
MaxValue = double.MinValue;
if (BarsArray[0] == null || ChartBars == null || startIndex == int.MinValue)
return;
for (int seriesCount = 0; seriesCount < Values.Length; seriesCount++)
{
for (int idx = ChartBars.FromIndex - Displacement; idx <= ChartBars.ToIndex + Displacement; idx++)
{
if (idx < 0 || idx > Bars.Count - 1 - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 1 : 0))
continue;
if (zigZagHighZigZags.IsValidDataPointAt(idx))
MaxValue = Math.Max(MaxValue, zigZagHighZigZags.GetValueAt(idx));
if (zigZagLowZigZags.IsValidDataPointAt(idx))
MinValue = Math.Min(MinValue, zigZagLowZigZags.GetValueAt(idx));
}
}
}
protected override Point[] OnGetSelectionPoints(ChartControl chartControl, ChartScale chartScale)
{
if (!IsSelected || Count == 0 || Plots[0].Brush.IsTransparent() || startIndex == int.MinValue)
return new System.Windows.Point[0];
List<System.Windows.Point> points = new List<System.Windows.Point>();
int lastIndex = Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? ChartBars.ToIndex - 1 : ChartBars.ToIndex - 2;
for (int i = Math.Max(0, ChartBars.FromIndex - Displacement); i <= Math.Max(lastIndex, Math.Min(Bars.Count - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 2 : 1), lastIndex - Displacement)); i++)
{
int x = (chartControl.BarSpacingType == BarSpacingType.TimeBased || chartControl.BarSpacingType == BarSpacingType.EquidistantMulti && i + Displacement >= ChartBars.Count
? chartControl.GetXByTime(ChartBars.GetTimeByBarIdx(chartControl, i + Displacement))
: chartControl.GetXByBarIndex(ChartBars, i + Displacement));
if (Value.IsValidDataPointAt(i))
points.Add(new System.Windows.Point(x, chartScale.GetYByValue(Value.GetValueAt(i))));
}
return points.ToArray();
}
protected override void OnRender(Gui.Chart.ChartControl chartControl, Gui.Chart.ChartScale chartScale)
{
if (Bars == null || chartControl == null || startIndex == int.MinValue)
return;
IsValidDataPointAt(Bars.Count - 1 - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 1 : 0)); // Make sure indicator is calculated until last (existing) bar
int preDiff = 1;
for (int i = ChartBars.FromIndex - 1; i >= 0; i--)
{
if (i - Displacement < startIndex || i - Displacement > Bars.Count - 1 - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 1 : 0))
break;
bool isHigh = zigZagHighZigZags.IsValidDataPointAt(i - Displacement);
bool isLow = zigZagLowZigZags.IsValidDataPointAt(i - Displacement);
if (isHigh || isLow)
break;
preDiff++;
}
preDiff -= (Displacement < 0 ? Displacement : 0 - Displacement);
int postDiff = 0;
for (int i = ChartBars.ToIndex; i <= zigZagHighZigZags.Count; i++)
{
if (i - Displacement < startIndex || i - Displacement > Bars.Count - 1 - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 1 : 0))
break;
bool isHigh = zigZagHighZigZags.IsValidDataPointAt(i - Displacement);
bool isLow = zigZagLowZigZags.IsValidDataPointAt(i - Displacement);
if (isHigh || isLow)
break;
postDiff++;
}
postDiff += (Displacement < 0 ? 0 - Displacement : Displacement);
int lastIdx = -1;
double lastValue = -1;
SharpDX.Direct2D1.PathGeometry g = null;
SharpDX.Direct2D1.GeometrySink sink = null;
for (int idx = ChartBars.FromIndex - preDiff; idx <= ChartBars.ToIndex + postDiff; idx++)
{
if (idx < startIndex || idx > Bars.Count - (Calculate == NinjaTrader.NinjaScript.Calculate.OnBarClose ? 2 : 1) || idx < Math.Max(BarsRequiredToPlot - Displacement, Displacement))
continue;
bool isHigh = zigZagHighZigZags.IsValidDataPointAt(idx);
bool isLow = zigZagLowZigZags.IsValidDataPointAt(idx);
if (!isHigh && !isLow)
continue;
double value = isHigh ? zigZagHighZigZags.GetValueAt(idx) : zigZagLowZigZags.GetValueAt(idx);
if (lastIdx >= startIndex)
{
float x1 = (chartControl.BarSpacingType == BarSpacingType.TimeBased || chartControl.BarSpacingType == BarSpacingType.EquidistantMulti && idx + Displacement >= ChartBars.Count
? chartControl.GetXByTime(ChartBars.GetTimeByBarIdx(chartControl, idx + Displacement))
: chartControl.GetXByBarIndex(ChartBars, idx + Displacement));
float y1 = chartScale.GetYByValue(value);
if (sink == null)
{
float x0 = (chartControl.BarSpacingType == BarSpacingType.TimeBased || chartControl.BarSpacingType == BarSpacingType.EquidistantMulti && lastIdx + Displacement >= ChartBars.Count
? chartControl.GetXByTime(ChartBars.GetTimeByBarIdx(chartControl, lastIdx + Displacement))
: chartControl.GetXByBarIndex(ChartBars, lastIdx + Displacement));
float y0 = chartScale.GetYByValue(lastValue);
g = new SharpDX.Direct2D1.PathGeometry(Core.Globals.D2DFactory);
sink = g.Open();
sink.BeginFigure(new SharpDX.Vector2(x0, y0), SharpDX.Direct2D1.FigureBegin.Hollow);
}
sink.AddLine(new SharpDX.Vector2(x1, y1));
}
// Save as previous point
lastIdx = idx;
lastValue = value;
}
if (sink != null)
{
sink.EndFigure(SharpDX.Direct2D1.FigureEnd.Open);
sink.Close();
}
if (g != null)
{
var oldAntiAliasMode = RenderTarget.AntialiasMode;
RenderTarget.AntialiasMode = SharpDX.Direct2D1.AntialiasMode.PerPrimitive;
RenderTarget.DrawGeometry(g, Plots[0].BrushDX, Plots[0].Width, Plots[0].StrokeStyle);
RenderTarget.AntialiasMode = oldAntiAliasMode;
g.Dispose();
RemoveDrawObject("NinjaScriptInfo");
}
else
Draw.TextFixed(this, "NinjaScriptInfo", NinjaTrader.Custom.Resource.ZigZagDeviationValueError, TextPosition.BottomRight);
}
#endregion
}
}
#region NinjaScript generated code. Neither change nor remove.
namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private ZigZagLabels[] cacheZigZagLabels;
public ZigZagLabels ZigZagLabels(DeviationType deviationType, double deviationValue, bool useHighLow)
{
return ZigZagLabels(Input, deviationType, deviationValue, useHighLow);
}
public ZigZagLabels ZigZagLabels(ISeries<double> input, DeviationType deviationType, double deviationValue, bool useHighLow)
{
if (cacheZigZagLabels != null)
for (int idx = 0; idx < cacheZigZagLabels.Length; idx++)
if (cacheZigZagLabels[idx] != null && cacheZigZagLabels[idx].DeviationType == deviationType && cacheZigZagLabels[idx].DeviationValue == deviationValue && cacheZigZagLabels[idx].UseHighLow == useHighLow && cacheZigZagLabels[idx].EqualsInput(input))
return cacheZigZagLabels[idx];
return CacheIndicator<ZigZagLabels>(new ZigZagLabels(){ DeviationType = deviationType, DeviationValue = deviationValue, UseHighLow = useHighLow }, input, ref cacheZigZagLabels);
}
}
}
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.ZigZagLabels ZigZagLabels(DeviationType deviationType, double deviationValue, bool useHighLow)
{
return indicator.ZigZagLabels(Input, deviationType, deviationValue, useHighLow);
}
public Indicators.ZigZagLabels ZigZagLabels(ISeries<double> input , DeviationType deviationType, double deviationValue, bool useHighLow)
{
return indicator.ZigZagLabels(input, deviationType, deviationValue, useHighLow);
}
}
}
namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.ZigZagLabels ZigZagLabels(DeviationType deviationType, double deviationValue, bool useHighLow)
{
return indicator.ZigZagLabels(Input, deviationType, deviationValue, useHighLow);
}
public Indicators.ZigZagLabels ZigZagLabels(ISeries<double> input , DeviationType deviationType, double deviationValue, bool useHighLow)
{
return indicator.ZigZagLabels(input, deviationType, deviationValue, useHighLow);
}
}
}
#endregion